mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
fix(admin): nav bar incorrectly active state (#7870)
This commit is contained in:
@@ -91,7 +91,12 @@ export const router = _createBrowserRouter(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
lazy: () => import('./modules/settings'),
|
children: [
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
lazy: () => import('./modules/settings'),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const AccordionTrigger = React.forwardRef<
|
|||||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<AccordionPrimitive.Header className="flex">
|
<AccordionPrimitive.Header className="flex w-full">
|
||||||
<AccordionPrimitive.Trigger
|
<AccordionPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import {
|
|||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from '@affine/admin/components/ui/accordion';
|
} from '@affine/admin/components/ui/accordion';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { useNav } from './context';
|
|
||||||
|
|
||||||
export const CollapsibleItem = ({
|
export const CollapsibleItem = ({
|
||||||
items,
|
items,
|
||||||
@@ -18,7 +16,9 @@ export const CollapsibleItem = ({
|
|||||||
items: string[];
|
items: string[];
|
||||||
changeModule?: (module: string) => void;
|
changeModule?: (module: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { activeSubTab, setActiveSubTab } = useNav();
|
const location = useLocation();
|
||||||
|
const activeSubTab = location.hash.slice(1);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
const targetElement = document.getElementById(id);
|
const targetElement = document.getElementById(id);
|
||||||
@@ -29,36 +29,45 @@ export const CollapsibleItem = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
changeModule?.(title);
|
changeModule?.(title);
|
||||||
setActiveSubTab(id);
|
|
||||||
},
|
},
|
||||||
[changeModule, setActiveSubTab, title]
|
[changeModule, title]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="multiple" className="w-full ">
|
<Accordion type="multiple" className="w-full ">
|
||||||
<AccordionItem value="item-1" className="border-b-0">
|
<AccordionItem value="item-1" className="border-b-0">
|
||||||
<Link to={`/admin/settings#${title}`}>
|
<NavLink
|
||||||
|
to={`/admin/settings/${title}`}
|
||||||
|
className={({ isActive }) => {
|
||||||
|
return isActive
|
||||||
|
? 'w-full bg-zinc-100 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'
|
||||||
|
: '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
<AccordionTrigger
|
<AccordionTrigger
|
||||||
onClick={() => handleClick(title)}
|
onClick={() => handleClick(title)}
|
||||||
className={`py-2 px-3 rounded ${activeSubTab === title ? 'bg-zinc-100' : ''}`}
|
className={`py-2 px-3 rounded`}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
</Link>
|
</NavLink>
|
||||||
<AccordionContent className=" flex flex-col gap-2">
|
<AccordionContent className=" flex flex-col gap-2 py-1">
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<Link
|
<NavLink
|
||||||
key={index}
|
key={index}
|
||||||
to={`/admin/settings#${item}`}
|
to={`/admin/settings/${title}#${item}`}
|
||||||
className="px-3 overflow-hidden"
|
className={({ isActive }) => {
|
||||||
|
return isActive && activeSubTab === item
|
||||||
|
? `transition-all overflow-hidden w-full bg-zinc-100 inline-flex items-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50`
|
||||||
|
: '';
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AccordionContent
|
<AccordionContent
|
||||||
onClick={() => handleClick(item)}
|
onClick={() => handleClick(item)}
|
||||||
className={`py-1 px-2 rounded text-ellipsis whitespace-nowrap overflow-hidden ${activeSubTab === item ? 'bg-zinc-100' : ''}`}
|
className={`py-1 px-2 rounded text-ellipsis whitespace-nowrap overflow-hidden`}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</Link>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
@@ -8,54 +8,38 @@ import { buttonVariants } from '@affine/admin/components/ui/button';
|
|||||||
import { cn } from '@affine/admin/utils';
|
import { cn } from '@affine/admin/utils';
|
||||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||||
import { ClipboardListIcon, SettingsIcon, UsersIcon } from 'lucide-react';
|
import { ClipboardListIcon, SettingsIcon, UsersIcon } from 'lucide-react';
|
||||||
import { useEffect } from 'react';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { useGetServerRuntimeConfig } from '../settings/use-get-server-runtime-config';
|
import { useGetServerRuntimeConfig } from '../settings/use-get-server-runtime-config';
|
||||||
import { CollapsibleItem } from './collapsible-item';
|
import { CollapsibleItem } from './collapsible-item';
|
||||||
import { useNav } from './context';
|
import { useNav } from './context';
|
||||||
import { UserDropdown } from './user-dropdown';
|
import { UserDropdown } from './user-dropdown';
|
||||||
|
|
||||||
const TabsMap: { [key: string]: string } = {
|
|
||||||
accounts: 'Accounts',
|
|
||||||
ai: 'AI',
|
|
||||||
config: 'Config',
|
|
||||||
settings: 'Settings',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Nav() {
|
export function Nav() {
|
||||||
const { moduleList } = useGetServerRuntimeConfig();
|
const { moduleList } = useGetServerRuntimeConfig();
|
||||||
const { activeTab, setActiveTab, setCurrentModule } = useNav();
|
const { setCurrentModule } = useNav();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const path = window.location.pathname;
|
|
||||||
for (const key in TabsMap) {
|
|
||||||
if (path.includes(key)) {
|
|
||||||
setActiveTab(TabsMap[key]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [setActiveTab]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 py-2 justify-between flex-grow overflow-hidden">
|
<div className="flex flex-col gap-4 py-2 justify-between flex-grow overflow-hidden">
|
||||||
<nav className="flex flex-col gap-1 px-2 flex-grow overflow-hidden">
|
<nav className="flex flex-col gap-1 px-2 flex-grow overflow-hidden">
|
||||||
<Link
|
<NavLink
|
||||||
to={'/admin/accounts'}
|
to={'/admin/accounts'}
|
||||||
className={cn(
|
className={({ isActive }) =>
|
||||||
buttonVariants({
|
cn(
|
||||||
variant: activeTab === 'Accounts' ? 'default' : 'ghost',
|
buttonVariants({
|
||||||
size: 'sm',
|
variant: isActive ? 'default' : 'ghost',
|
||||||
}),
|
size: 'sm',
|
||||||
activeTab === 'Accounts' &&
|
}),
|
||||||
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
isActive &&
|
||||||
'justify-start',
|
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
||||||
'flex-none'
|
'justify-start',
|
||||||
)}
|
'flex-none'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<UsersIcon className="mr-2 h-4 w-4" />
|
<UsersIcon className="mr-2 h-4 w-4" />
|
||||||
Accounts
|
Accounts
|
||||||
</Link>
|
</NavLink>
|
||||||
{/* <Link
|
{/* <Link
|
||||||
to={'/admin/ai'}
|
to={'/admin/ai'}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -72,48 +56,55 @@ export function Nav() {
|
|||||||
<CpuIcon className="mr-2 h-4 w-4" />
|
<CpuIcon className="mr-2 h-4 w-4" />
|
||||||
AI
|
AI
|
||||||
</Link> */}
|
</Link> */}
|
||||||
<Link
|
<NavLink
|
||||||
to={'/admin/config'}
|
to={'/admin/config'}
|
||||||
className={cn(
|
className={({ isActive }) =>
|
||||||
buttonVariants({
|
cn(
|
||||||
variant: activeTab === 'Config' ? 'default' : 'ghost',
|
buttonVariants({
|
||||||
size: 'sm',
|
variant: isActive ? 'default' : 'ghost',
|
||||||
}),
|
size: 'sm',
|
||||||
activeTab === 'Config' &&
|
}),
|
||||||
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
isActive &&
|
||||||
'justify-start',
|
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
||||||
'flex-none'
|
'justify-start',
|
||||||
)}
|
'flex-none'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ClipboardListIcon className="mr-2 h-4 w-4" />
|
<ClipboardListIcon className="mr-2 h-4 w-4" />
|
||||||
Config
|
Config
|
||||||
</Link>
|
</NavLink>
|
||||||
|
|
||||||
<Accordion type="multiple" className="w-full h-full overflow-hidden">
|
<Accordion type="multiple" className="w-full h-full overflow-hidden">
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="item-1"
|
value="item-1"
|
||||||
className="border-b-0 h-full flex flex-col gap-1"
|
className="border-b-0 h-full flex flex-col gap-1 w-full"
|
||||||
>
|
>
|
||||||
<Link to={'/admin/settings'}>
|
<NavLink
|
||||||
<AccordionTrigger
|
to={'/admin/settings'}
|
||||||
className={cn(
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: activeTab === 'Settings' ? 'default' : 'ghost',
|
variant: isActive ? 'default' : 'ghost',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
}),
|
}),
|
||||||
|
isActive &&
|
||||||
activeTab === 'Settings' &&
|
|
||||||
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
||||||
'justify-between',
|
'justify-start',
|
||||||
'hover:no-underline'
|
'flex-none',
|
||||||
)}
|
'w-full'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AccordionTrigger
|
||||||
|
className={'flex items-center justify-between w-full'}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<SettingsIcon className="mr-2 h-4 w-4" />
|
<SettingsIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
</Link>
|
</NavLink>
|
||||||
|
|
||||||
<AccordionContent className="h-full overflow-hidden w-full">
|
<AccordionContent className="h-full overflow-hidden w-full">
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
|
|||||||
Reference in New Issue
Block a user