Skip to content

Commit

Permalink
Fix mispositioned active link indicator (#6880)
Browse files Browse the repository at this point in the history
* Fix mispositioned active link indicator

* Make indicator height 26% of link element height.

* Extract 'navItemCount' as variable
  • Loading branch information
Omkar76 authored Dec 28, 2023
1 parent cfe0011 commit 988e383
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 97 deletions.
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} />
)
);

0 comments on commit 988e383

Please sign in to comment.