fix: fix table horizontal scroll and collapsed sidebar

This commit is contained in:
2025-05-01 20:49:42 +08:00
parent ac7d1efb8d
commit 5645645c5f
19 changed files with 849 additions and 134 deletions

View File

@@ -0,0 +1,32 @@
import { Card, CardContent, CardHeader } from './ui/card';
import { Skeleton } from './ui/skeleton';
export function DetailCardSkeleton() {
return (
<div className="container mx-auto max-w-4xl py-6">
<div className="space-y-6">
<div className="flex items-center gap-4">
<Skeleton className="h-8 w-8" />
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-64" />
</div>
</div>
<Card>
<CardHeader>
<Skeleton className="h-6 w-32" />
<Skeleton className="h-4 w-48" />
</CardHeader>
<CardContent className="space-y-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-10 w-full" />
</div>
))}
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -7,6 +7,14 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { ProLink, type ProLinkProps } from '@/components/ui/pro-link';
import {
SidebarGroup,
SidebarGroupLabel,
@@ -16,9 +24,9 @@ import {
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from '@/components/ui/sidebar';
import { useMatches } from '@tanstack/react-router';
import { ProLink, type ProLinkProps } from '../ui/pro-link';
export interface NavMainItem {
link?: ProLinkProps;
@@ -38,6 +46,7 @@ export function NavMain({
groups: NavMainGroup[];
}) {
const matches = useMatches();
const { state } = useSidebar();
const isMenuMatch = (link: ProLinkProps | undefined) => {
const linkTo = link?.to;
@@ -50,13 +59,84 @@ export function NavMain({
const renderSidebarMenuItemButton = (item: NavMainItem) => {
return (
<>
{item.icon && <item.icon />}
<span>{item.title}</span>
{item.icon && <item.icon className="h-4 w-4" aria-hidden="true" />}
<span className="truncate">{item.title}</span>
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]:rotate-90" />
</>
);
};
const renderCollapsedSubMenu = (item: NavMainItem) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
tooltip={item.title}
isActive={isMenuMatch(item.link)}
aria-label={`${item.title} (expandable)`}
>
{item.icon && <item.icon className="h-4 w-4" aria-hidden="true" />}
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="right" align="start" className="min-w-48">
<DropdownMenuItem asChild>
<span className="font-medium">{item.title}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{item.children?.map((subItem, index) => (
<DropdownMenuItem key={index} asChild>
<ProLink {...subItem.link}>{subItem.title}</ProLink>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};
const renderExpandedSubMenu = (item: NavMainItem, itemIndex: number) => {
return (
<Collapsible
key={itemIndex}
asChild
className="group/collapsible"
defaultOpen={isMenuMatch(item.link)}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton
tooltip={item.title}
aria-label={`${item.title} (expandable)`}
>
{renderSidebarMenuItemButton(item)}
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{(item.children || []).map((subItem, subItemIndex) => {
return (
<SidebarMenuSubItem key={subItemIndex}>
<SidebarMenuSubButton
asChild
isActive={isMenuMatch(subItem.link)}
>
<ProLink
{...subItem.link}
activeProps={{ className: '' }}
aria-label={`${subItem.title}`}
>
<span>{subItem.title}</span>
</ProLink>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
);
};
return groups.map((group, groupIndex) => {
return (
<SidebarGroup key={groupIndex}>
@@ -66,51 +146,21 @@ export function NavMain({
if (!item.children?.length) {
return (
<SidebarMenuItem key={itemIndex}>
<SidebarMenuButton asChild tooltip={item.title}>
<ProLink {...item.link}>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={isMenuMatch(item.link)}
>
<ProLink {...item.link} tabIndex={0}>
{renderSidebarMenuItemButton(item)}
</ProLink>
</SidebarMenuButton>
</SidebarMenuItem>
);
}
return (
<Collapsible
key={itemIndex}
asChild
className="group/collapsible"
defaultOpen={isMenuMatch(item.link)}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{renderSidebarMenuItemButton(item)}
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{(item.children || []).map((subItem, subItemIndex) => {
return (
<SidebarMenuSubItem key={subItemIndex}>
<SidebarMenuSubButton
asChild
isActive={isMenuMatch(subItem.link)}
>
<ProLink
{...subItem.link}
activeProps={{ className: '' }}
>
<span>{subItem.title}</span>
</ProLink>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
);
return state === 'collapsed'
? renderCollapsedSubMenu(item)
: renderExpandedSubMenu(item, itemIndex);
})}
</SidebarMenu>
</SidebarGroup>

View File

@@ -0,0 +1,18 @@
import { memo } from "react";
import { Card, CardContent } from "./card";
export interface DetailEmptyViewProps {
message: string;
}
export const DetailEmptyView = memo(({ message }: DetailEmptyViewProps) => {
return (
<div className="container mx-auto py-6 max-w-4xl">
<Card>
<CardContent className="flex items-center justify-center h-32">
<p className="text-muted-foreground">{message ?? "No data"}</p>
</CardContent>
</Card>
</div>
);
});

View File

@@ -25,8 +25,6 @@ import {
import { useIsMobile } from "@/presentation/hooks/use-mobile";
import { cn } from "@/presentation/utils";
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
@@ -81,10 +79,6 @@ function SidebarProvider({
} else {
_setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
// TODO: FIX THIS
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
[setOpenProp, open]
);
@@ -310,7 +304,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"bg-background relative flex w-full flex-1 flex-col",
"bg-background relative flex w-full flex-1 min-w-0 flex-col",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className
)}