Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mispositioned active link indicator #6880

Merged
merged 3 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 16 additions & 40 deletions src/Components/Common/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,61 +55,36 @@ const StatelessSidebar = ({
const { dashboard_url } = useConfig();

const indicatorRef = useRef<HTMLDivElement>(null);
const activeLinkRef = useRef<HTMLAnchorElement>(null);
const [lastIndicatorPosition, setLastIndicatorPosition] = useState(0);
const [isOverflowVisible, setOverflowVisisble] = useState(false);

useEffect(() => {
if (!indicatorRef.current) return;
const index = NavItems.findIndex((item) => item.to === activeLink);
const navItemCount = NavItems.length + 2; // +2 for notification and dashboard
if (index !== -1) {
// Haha math go brrrrrrrrr

const e = indicatorRef.current;

const itemHeight = 44;
const bottomItemOffset = 2;

const indexDifference = index - lastIndicatorPosition;
e.style.display = "block";

if (indexDifference > 0) {
e.style.top = lastIndicatorPosition * itemHeight + 16 + "px";
e.style.bottom = "auto";
const itemHeight = activeLinkRef.current?.clientHeight || 0;
if (lastIndicatorPosition > index) {
e.style.top = `${itemHeight * (index + 0.37)}px`;
setTimeout(() => {
e.style.bottom = `${itemHeight * (navItemCount - 0.63 - index)}px`;
}, 50);
} else {
e.style.bottom =
itemHeight * (NavItems.length + bottomItemOffset) -
lastIndicatorPosition * itemHeight -
28 +
"px";
e.style.top = "auto";
e.style.bottom = `${itemHeight * (navItemCount - 0.63 - index)}px`;
setTimeout(() => {
e.style.top = `${itemHeight * (index + 0.37)}px`;
}, 50);
}

const variableHeight = Math.min(
Math.abs(indexDifference) * itemHeight,
70
);

e.style.height = `${variableHeight}px`;
setTimeout(() => {
if (!e) return;
if (indexDifference > 0) {
e.style.top = index * itemHeight + 16 + "px";
e.style.bottom = "auto";
} else {
e.style.bottom =
itemHeight * (NavItems.length + bottomItemOffset) -
index * itemHeight -
28 +
"px";
e.style.top = "auto";
}
e.style.height = "0.75rem";
setLastIndicatorPosition(index);
}, 300);
setLastIndicatorPosition(index);
} else {
indicatorRef.current.style.display = "none";
}
}, [activeLink]);
}, [activeLink, lastIndicatorPosition]);

const handleOverflow = (value: boolean) => {
setOverflowVisisble(value);
};
Expand Down Expand Up @@ -147,6 +122,7 @@ const StatelessSidebar = ({
{NavItems.map((i) => {
return (
<Item
ref={i.to === activeLink ? activeLinkRef : undefined}
key={i.text}
{...i}
icon={<CareIcon className={`${i.icon} h-5`} />}
Expand Down
126 changes: 69 additions & 57 deletions src/Components/Common/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { Link } from "raviger";
import { useTranslation } from "react-i18next";
import CareIcon from "../../../CAREUI/icons/CareIcon";
import useAppHistory from "../../../Common/hooks/useAppHistory";
import React, { forwardRef, Ref } from "react";

export type SidebarIcon = React.ReactNode;

type SidebarItemProps = {
ref?: React.Ref<HTMLAnchorElement>;
text: string;
icon: SidebarIcon;
external?: true | undefined;
Expand All @@ -14,74 +16,84 @@ type SidebarItemProps = {
handleOverflow?: any;
} & ({ to: string; do?: undefined } | { to?: string; do: () => void });

type SidebarItemBaseProps = SidebarItemProps & { shrinked?: boolean };
const SidebarItemBase = ({
shrinked,
external,
...props
}: SidebarItemBaseProps) => {
const { t } = useTranslation();
const { resetHistory } = useAppHistory();
type SidebarItemBaseProps = SidebarItemProps & {
shrinked?: boolean;
ref: Ref<HTMLAnchorElement>;
};

const SidebarItemBase = forwardRef(
(
{ shrinked, external, ...props }: SidebarItemBaseProps,
ref: Ref<HTMLAnchorElement>
) => {
const { t } = useTranslation();
const { resetHistory } = useAppHistory();

return (
<Link
className={`tooltip relative ml-1 mr-3 h-full min-h-[40px] flex-1 cursor-pointer rounded-lg text-white transition-all duration-200 ease-in-out md:h-11 md:flex-none
return (
<Link
ref={ref}
className={`tooltip relative ml-1 mr-3 h-full min-h-[40px] flex-1 cursor-pointer rounded-lg text-white transition-all duration-200 ease-in-out md:h-11 md:flex-none
${
props.selected
? "bg-primary-900 font-bold"
: "bg-primary-800 font-normal hover:bg-primary-700"
}`}
target={external && "_blank"}
rel={external && "noreferrer"}
href={props.to ?? ""}
onClick={props.do ?? resetHistory}
onMouseEnter={() => {
props.handleOverflow(true);
}}
onMouseLeave={() => {
props.handleOverflow(false);
}}
>
<span className={`tooltip-text tooltip-right ${!shrinked && "hidden"}`}>
{t(props.text)}
</span>
<div
className={`flex h-full items-center ${
shrinked ? "justify-center" : "justify-start pl-5 pr-4"
} transition-all duration-200 ease-in-out`}
target={external && "_blank"}
rel={external && "noreferrer"}
href={props.to ?? ""}
onClick={props.do ?? resetHistory}
onMouseEnter={() => {
props.handleOverflow(true);
}}
onMouseLeave={() => {
props.handleOverflow(false);
}}
>
<div className="flex-none text-lg">{props.icon}</div>
<span
className={`${
shrinked ? "hidden" : "grow"
} flex w-full items-center pl-4 text-sm tracking-wide`}
>
<span className={`tooltip-text tooltip-right ${!shrinked && "hidden"}`}>
{t(props.text)}
</span>
{external && !shrinked && (
<CareIcon className="care-l-external-link-alt text-lg" />
)}
</div>

{!!props.badgeCount && (
<span
className={`absolute flex items-center justify-center bg-primary-500 font-semibold text-white ${
shrinked
? "right-3 top-0.5 h-4 w-5 rounded-md text-[9px]"
: "inset-y-0 right-4 my-auto h-6 rounded-md px-2 text-xs"
} z-10 animate-pulse transition-all duration-200 ease-in-out`}
<div
className={`flex h-full items-center ${
shrinked ? "justify-center" : "justify-start pl-5 pr-4"
} transition-all duration-200 ease-in-out`}
>
{props.badgeCount > 9 ? "9+" : props.badgeCount}
</span>
)}
</Link>
);
};
<div className="flex-none text-lg">{props.icon}</div>
<span
className={`${
shrinked ? "hidden" : "grow"
} flex w-full items-center pl-4 text-sm tracking-wide`}
>
{t(props.text)}
</span>
{external && !shrinked && (
<CareIcon className="care-l-external-link-alt text-lg" />
)}
</div>

{!!props.badgeCount && (
<span
className={`absolute flex items-center justify-center bg-primary-500 font-semibold text-white ${
shrinked
? "right-3 top-0.5 h-4 w-5 rounded-md text-[9px]"
: "inset-y-0 right-4 my-auto h-6 rounded-md px-2 text-xs"
} z-10 animate-pulse transition-all duration-200 ease-in-out`}
>
{props.badgeCount > 9 ? "9+" : props.badgeCount}
</span>
)}
</Link>
);
}
);

export const SidebarItem = (props: SidebarItemProps) => (
<SidebarItemBase {...props} />
export const SidebarItem = forwardRef(
(props: SidebarItemProps, ref: Ref<HTMLAnchorElement>) => (
<SidebarItemBase {...props} ref={ref} />
)
);

export const ShrinkedSidebarItem = (props: SidebarItemProps) => (
<SidebarItemBase shrinked {...props} />
export const ShrinkedSidebarItem = forwardRef(
(props: SidebarItemProps, ref: Ref<HTMLAnchorElement>) => (
<SidebarItemBase shrinked ref={ref} {...props} />
)
);
Loading