feat(ui): improve collapsed sidebar and status action icons
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { ColumnDef, SortingState } from '@tanstack/react-table';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Edit, RotateCcw } from 'lucide-react';
|
||||
import { Edit, ShieldCheck, ShieldX } from 'lucide-react';
|
||||
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
@@ -58,7 +58,12 @@ export function RoleTable({
|
||||
},
|
||||
{
|
||||
label: (role) => (role.effective_status ? 'Deactivate' : 'Activate'),
|
||||
icon: <RotateCcw className="size-4" />,
|
||||
icon: (role) =>
|
||||
role.effective_status ? (
|
||||
<ShieldX className="size-4 text-destructive" />
|
||||
) : (
|
||||
<ShieldCheck className="size-4 text-emerald-600" />
|
||||
),
|
||||
permission: PERMISSIONS.ROLE.DELETE,
|
||||
disabled: () => isStatusPending,
|
||||
onClick: onToggleStatus,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ColumnDef, SortingState } from '@tanstack/react-table';
|
||||
import { Edit, RotateCcw } from 'lucide-react';
|
||||
import { Edit, UserCheck, UserX } from 'lucide-react';
|
||||
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
@@ -59,7 +59,12 @@ export function UserTable({
|
||||
{
|
||||
label: (user) =>
|
||||
user.effective_status === 'active' ? 'Deactivate' : 'Activate',
|
||||
icon: <RotateCcw className="size-4" />,
|
||||
icon: (user) =>
|
||||
user.effective_status === 'active' ? (
|
||||
<UserX className="size-4 text-destructive" />
|
||||
) : (
|
||||
<UserCheck className="size-4 text-emerald-600" />
|
||||
),
|
||||
permission: PERMISSIONS.USER.DELETE,
|
||||
disabled: (user) =>
|
||||
pendingUserId === user.id || user.effective_status === 'pending',
|
||||
|
||||
@@ -14,6 +14,14 @@ import {
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -124,6 +132,7 @@ function SidebarNavItem({
|
||||
item: MenuItem;
|
||||
pathname: string;
|
||||
}) {
|
||||
const { isMobile, state } = useSidebar();
|
||||
const isActive = isRouteActive(pathname, item.path);
|
||||
const isChildActive =
|
||||
item.children?.some((child) => isRouteActive(pathname, child.path)) ??
|
||||
@@ -136,6 +145,52 @@ function SidebarNavItem({
|
||||
}, [isChildActive]);
|
||||
|
||||
if (hasChildren) {
|
||||
if (state === 'collapsed' && !isMobile) {
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
isActive={isChildActive}
|
||||
aria-label={item.title}
|
||||
>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
<ChevronRight className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="right"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="min-w-48"
|
||||
>
|
||||
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{item.children?.map((child) =>
|
||||
child.path ? (
|
||||
<DropdownMenuItem
|
||||
key={child.title}
|
||||
asChild
|
||||
className={
|
||||
isRouteActive(pathname, child.path)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Link href={child.path}>
|
||||
<child.icon />
|
||||
<span>{child.title}</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
) : null,
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
asChild
|
||||
|
||||
@@ -41,7 +41,7 @@ export type ColumnAlign = 'left' | 'right';
|
||||
|
||||
export interface DataTableAction<TData> {
|
||||
label: string | ((item: TData) => string);
|
||||
icon: React.ReactNode;
|
||||
icon: React.ReactNode | ((item: TData) => React.ReactNode);
|
||||
onClick: (item: TData) => void;
|
||||
permission?: PermissionInput;
|
||||
disabled?: (item: TData) => boolean;
|
||||
@@ -163,6 +163,10 @@ function DataTableContent<TData, TValue>({
|
||||
typeof action.label === 'function'
|
||||
? action.label(item)
|
||||
: action.label;
|
||||
const icon =
|
||||
typeof action.icon === 'function'
|
||||
? action.icon(item)
|
||||
: action.icon;
|
||||
|
||||
return (
|
||||
<TableActionButton
|
||||
@@ -176,7 +180,7 @@ function DataTableContent<TData, TValue>({
|
||||
action.onClick(item);
|
||||
}}
|
||||
>
|
||||
{action.icon}
|
||||
{icon}
|
||||
</TableActionButton>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user