fix: fix table horizontal scroll and collapsed sidebar
This commit is contained in:
32
apps/webui/src/components/detail-card-skeleton.tsx
Normal file
32
apps/webui/src/components/detail-card-skeleton.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
18
apps/webui/src/components/ui/detail-empty-view.tsx
Normal file
18
apps/webui/src/components/ui/detail-empty-view.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user