Prefer specific active navigation links

This commit is contained in:
2026-05-29 06:29:22 +07:00
parent 3a52d29f8c
commit 4c5b62dd70
2 changed files with 22 additions and 22 deletions

View File

@ -19,15 +19,19 @@ function matchesPath(currentPath: string, href: string) {
return currentPath === href || currentPath.startsWith(`${href}/`); return currentPath === href || currentPath.startsWith(`${href}/`);
} }
function findActiveHref(currentPath: string, items: Array<{ href: string }>) {
return items
.filter((item) => matchesPath(currentPath, item.href))
.sort((a, b) => b.href.length - a.href.length)[0]?.href;
}
export function MobileNav({ pathname, userRole }: MobileNavProps) { export function MobileNav({ pathname, userRole }: MobileNavProps) {
const { dict } = useLocale(); const { dict } = useLocale();
const navigation = useMemo(() => getNavigationForRole(userRole as AppRole), [userRole]); const navigation = useMemo(() => getNavigationForRole(userRole as AppRole), [userRole]);
const buildOpenGroups = () => { const buildOpenGroups = () => {
const grouped = navigation.filter(isNavGroup); const grouped = navigation.filter(isNavGroup);
return grouped.reduce<Record<string, boolean>>((acc, item) => { return grouped.reduce<Record<string, boolean>>((acc, item) => {
const isGroupActive = item.children.some( const isGroupActive = Boolean(findActiveHref(pathname, item.children));
(child) => matchesPath(pathname, child.href)
);
acc[item.key] = isGroupActive; acc[item.key] = isGroupActive;
return acc; return acc;
}, {}); }, {});
@ -39,9 +43,7 @@ export function MobileNav({ pathname, userRole }: MobileNavProps) {
setOpenGroups((current) => { setOpenGroups((current) => {
const next = { ...current }; const next = { ...current };
navigation.filter(isNavGroup).forEach((item) => { navigation.filter(isNavGroup).forEach((item) => {
const isGroupActive = item.children.some( const isGroupActive = Boolean(findActiveHref(pathname, item.children));
(child) => matchesPath(pathname, child.href)
);
if (isGroupActive) { if (isGroupActive) {
next[item.key] = true; next[item.key] = true;
} }
@ -55,9 +57,8 @@ export function MobileNav({ pathname, userRole }: MobileNavProps) {
<div className="space-y-2 pb-1"> <div className="space-y-2 pb-1">
{navigation.map((item) => { {navigation.map((item) => {
if (isNavGroup(item)) { if (isNavGroup(item)) {
const isGroupActive = item.children.some( const activeChildHref = findActiveHref(pathname, item.children);
(child) => matchesPath(pathname, child.href) const isGroupActive = Boolean(activeChildHref);
);
const isOpen = openGroups[item.key] ?? isGroupActive; const isOpen = openGroups[item.key] ?? isGroupActive;
return ( return (
<div key={item.key} className="rounded border border-line/70 bg-slate-50"> <div key={item.key} className="rounded border border-line/70 bg-slate-50">
@ -83,8 +84,7 @@ export function MobileNav({ pathname, userRole }: MobileNavProps) {
{isOpen ? ( {isOpen ? (
<div className="grid gap-2 border-t border-line/70 bg-white p-2"> <div className="grid gap-2 border-t border-line/70 bg-white p-2">
{item.children.map((child) => { {item.children.map((child) => {
const isActive = const isActive = activeChildHref === child.href;
matchesPath(pathname, child.href);
return ( return (
<Link <Link
key={child.href} key={child.href}

View File

@ -20,15 +20,19 @@ function matchesPath(currentPath: string, href: string) {
return currentPath === href || currentPath.startsWith(`${href}/`); return currentPath === href || currentPath.startsWith(`${href}/`);
} }
function findActiveHref(currentPath: string, items: Array<{ href: string }>) {
return items
.filter((item) => matchesPath(currentPath, item.href))
.sort((a, b) => b.href.length - a.href.length)[0]?.href;
}
export function Sidebar({ pathname, user }: SidebarProps) { export function Sidebar({ pathname, user }: SidebarProps) {
const { dict } = useLocale(); const { dict } = useLocale();
const navigation = useMemo(() => getNavigationForRole(user.role as never), [user.role]); const navigation = useMemo(() => getNavigationForRole(user.role as never), [user.role]);
const buildOpenGroups = () => { const buildOpenGroups = () => {
const grouped = navigation.filter(isNavGroup); const grouped = navigation.filter(isNavGroup);
return grouped.reduce<Record<string, boolean>>((acc, item) => { return grouped.reduce<Record<string, boolean>>((acc, item) => {
const isGroupActive = item.children.some( const isGroupActive = Boolean(findActiveHref(pathname, item.children));
(child) => matchesPath(pathname, child.href)
);
acc[item.key] = isGroupActive; acc[item.key] = isGroupActive;
return acc; return acc;
}, {}); }, {});
@ -40,9 +44,7 @@ export function Sidebar({ pathname, user }: SidebarProps) {
setOpenGroups((current) => { setOpenGroups((current) => {
const next = { ...current }; const next = { ...current };
navigation.filter(isNavGroup).forEach((item) => { navigation.filter(isNavGroup).forEach((item) => {
const isGroupActive = item.children.some( const isGroupActive = Boolean(findActiveHref(pathname, item.children));
(child) => matchesPath(pathname, child.href)
);
if (isGroupActive) { if (isGroupActive) {
next[item.key] = true; next[item.key] = true;
} }
@ -70,9 +72,8 @@ export function Sidebar({ pathname, user }: SidebarProps) {
<nav className="space-y-1 pr-1"> <nav className="space-y-1 pr-1">
{navigation.map((item) => { {navigation.map((item) => {
if (isNavGroup(item)) { if (isNavGroup(item)) {
const isGroupActive = item.children.some( const activeChildHref = findActiveHref(pathname, item.children);
(child) => matchesPath(pathname, child.href) const isGroupActive = Boolean(activeChildHref);
);
const isOpen = openGroups[item.key] ?? isGroupActive; const isOpen = openGroups[item.key] ?? isGroupActive;
return ( return (
@ -108,8 +109,7 @@ export function Sidebar({ pathname, user }: SidebarProps) {
{isOpen ? ( {isOpen ? (
<div className="mt-1 space-y-1 pl-3"> <div className="mt-1 space-y-1 pl-3">
{item.children.map((child) => { {item.children.map((child) => {
const isActive = const isActive = activeChildHref === child.href;
matchesPath(pathname, child.href);
return ( return (
<Link <Link