fix: update sidebar structure to match shadcn spec (#591)

This commit is contained in:
Jeremy 2024-12-04 18:20:35 +03:00 committed by GitHub
parent b609dca828
commit 2e479ccce7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 95 additions and 125 deletions

View file

@ -50,10 +50,10 @@
--chart-5: 27 87% 67%; --chart-5: 27 87% 67%;
--radius: 0.5rem; --radius: 0.5rem;
--sidebar-background: 0 0% 98%; --sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 10% 3.9%; --sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%; --sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%; --sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 5.9% 94%; --sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%; --sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%; --sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%; --sidebar-ring: 217.2 91.2% 59.8%;

View file

@ -17,8 +17,8 @@ import {
SidebarMenu, SidebarMenu,
useSidebar, useSidebar,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import { BetterTooltip } from '@/components/ui/tooltip';
import Link from 'next/link'; import Link from 'next/link';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
export function AppSidebar({ user }: { user: User | undefined }) { export function AppSidebar({ user }: { user: User | undefined }) {
const router = useRouter(); const router = useRouter();
@ -40,37 +40,30 @@ export function AppSidebar({ user }: { user: User | undefined }) {
Chatbot Chatbot
</span> </span>
</Link> </Link>
<BetterTooltip content="New Chat" align="start"> <Tooltip>
<Button <TooltipTrigger asChild>
variant="ghost" <Button
type="button" variant="ghost"
className="p-2 h-fit" type="button"
onClick={() => { className="p-2 h-fit"
setOpenMobile(false); onClick={() => {
router.push('/'); setOpenMobile(false);
router.refresh(); router.push('/');
}} router.refresh();
> }}
<PlusIcon /> >
</Button> <PlusIcon />
</BetterTooltip> </Button>
</TooltipTrigger>
<TooltipContent align="end">New Chat</TooltipContent>
</Tooltip>
</div> </div>
</SidebarMenu> </SidebarMenu>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<SidebarGroup className="-mx-2"> <SidebarHistory user={user} />
<SidebarHistory user={user} />
</SidebarGroup>
</SidebarContent> </SidebarContent>
<SidebarFooter className="gap-0 -mx-2"> <SidebarFooter>{user && <SidebarUserNav user={user} />}</SidebarFooter>
{user && (
<SidebarGroup>
<SidebarGroupContent>
<SidebarUserNav user={user} />
</SidebarGroupContent>
</SidebarGroup>
)}
</SidebarFooter>
</Sidebar> </Sidebar>
); );
} }

View file

@ -7,10 +7,10 @@ import { useWindowSize } from 'usehooks-ts';
import { ModelSelector } from '@/components/model-selector'; import { ModelSelector } from '@/components/model-selector';
import { SidebarToggle } from '@/components/sidebar-toggle'; import { SidebarToggle } from '@/components/sidebar-toggle';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { BetterTooltip } from '@/components/ui/tooltip';
import { PlusIcon, VercelIcon } from './icons'; import { PlusIcon, VercelIcon } from './icons';
import { useSidebar } from './ui/sidebar'; import { useSidebar } from './ui/sidebar';
import { memo } from 'react'; import { memo } from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
function PureChatHeader({ selectedModelId }: { selectedModelId: string }) { function PureChatHeader({ selectedModelId }: { selectedModelId: string }) {
const router = useRouter(); const router = useRouter();
@ -22,19 +22,22 @@ function PureChatHeader({ selectedModelId }: { selectedModelId: string }) {
<header className="flex sticky top-0 bg-background py-1.5 items-center px-2 md:px-2 gap-2"> <header className="flex sticky top-0 bg-background py-1.5 items-center px-2 md:px-2 gap-2">
<SidebarToggle /> <SidebarToggle />
{(!open || windowWidth < 768) && ( {(!open || windowWidth < 768) && (
<BetterTooltip content="New Chat"> <Tooltip>
<Button <TooltipTrigger asChild>
variant="outline" <Button
className="order-2 md:order-1 md:px-2 px-2 md:h-fit ml-auto md:ml-0" variant="outline"
onClick={() => { className="order-2 md:order-1 md:px-2 px-2 md:h-fit ml-auto md:ml-0"
router.push('/'); onClick={() => {
router.refresh(); router.push('/');
}} router.refresh();
> }}
<PlusIcon /> >
<span className="md:sr-only">New Chat</span> <PlusIcon />
</Button> <span className="md:sr-only">New Chat</span>
</BetterTooltip> </Button>
</TooltipTrigger>
<TooltipContent>New Chat</TooltipContent>
</Tooltip>
)} )}
<ModelSelector <ModelSelector
selectedModelId={selectedModelId} selectedModelId={selectedModelId}

View file

@ -1,7 +1,11 @@
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import { type SidebarTrigger, useSidebar } from '@/components/ui/sidebar'; import { type SidebarTrigger, useSidebar } from '@/components/ui/sidebar';
import { BetterTooltip } from '@/components/ui/tooltip'; import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { SidebarLeftIcon } from './icons'; import { SidebarLeftIcon } from './icons';
import { Button } from './ui/button'; import { Button } from './ui/button';
@ -12,14 +16,17 @@ export function SidebarToggle({
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
return ( return (
<BetterTooltip content="Toggle Sidebar" align="start"> <Tooltip>
<Button <TooltipTrigger asChild>
onClick={toggleSidebar} <Button
variant="outline" onClick={toggleSidebar}
className="md:px-2 md:h-fit" variant="outline"
> className="md:px-2 md:h-fit"
<SidebarLeftIcon size={16} /> >
</Button> <SidebarLeftIcon size={16} />
</BetterTooltip> </Button>
</TooltipTrigger>
<TooltipContent align="start">Toggle Sidebar</TooltipContent>
</Tooltip>
); );
} }

View file

@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const buttonVariants = cva( const buttonVariants = cva(
'inline-flex items-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 'inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {

View file

@ -30,10 +30,10 @@ const CardHeader = React.forwardRef<
CardHeader.displayName = 'CardHeader'; CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLParagraphElement, HTMLDivElement,
React.HTMLAttributes<HTMLHeadingElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h3 <div
ref={ref} ref={ref}
className={cn( className={cn(
'text-2xl font-semibold leading-none tracking-tight', 'text-2xl font-semibold leading-none tracking-tight',
@ -45,10 +45,10 @@ const CardTitle = React.forwardRef<
CardTitle.displayName = 'CardTitle'; CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLDivElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<p <div
ref={ref} ref={ref}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-sm text-muted-foreground', className)}
{...props} {...props}

View file

@ -27,14 +27,14 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent', 'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)); ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-lg border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className, className,
)} )}
{...props} {...props}
@ -83,7 +83,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex gap-2 cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
className, className,
)} )}

View file

@ -2,16 +2,13 @@ import * as React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export interface InputProps const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
className={cn( className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className, className,
)} )}
ref={ref} ref={ref}

View file

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import { Slot } from '@radix-ui/react-slot'; import { Slot } from '@radix-ui/react-slot';
import { type VariantProps, cva } from 'class-variance-authority'; import { VariantProps, cva } from 'class-variance-authority';
import { PanelLeft } from 'lucide-react'; import { PanelLeft } from 'lucide-react';
import { useIsMobile } from '@/hooks/use-mobile'; import { useIsMobile } from '@/hooks/use-mobile';
@ -10,12 +10,7 @@ import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { import { Sheet, SheetContent } from '@/components/ui/sheet';
Sheet,
SheetContent,
SheetDescription,
SheetTitle,
} from '@/components/ui/sheet';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { import {
Tooltip, Tooltip,
@ -157,7 +152,6 @@ const SidebarProvider = React.forwardRef<
'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar', 'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar',
className, className,
)} )}
data-state={state}
ref={ref} ref={ref}
{...props} {...props}
> >
@ -209,10 +203,6 @@ const Sidebar = React.forwardRef<
if (isMobile) { if (isMobile) {
return ( return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetTitle className="sr-only">Sidebar</SheetTitle>
<SheetDescription className="sr-only">
Mobile sidebar
</SheetDescription>
<SheetContent <SheetContent
data-sidebar="sidebar" data-sidebar="sidebar"
data-mobile="true" data-mobile="true"
@ -280,7 +270,7 @@ Sidebar.displayName = 'Sidebar';
const SidebarTrigger = React.forwardRef< const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>, React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ onClick, ...props }, ref) => { >(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
return ( return (
@ -289,13 +279,14 @@ const SidebarTrigger = React.forwardRef<
data-sidebar="trigger" data-sidebar="trigger"
variant="ghost" variant="ghost"
size="icon" size="icon"
className={cn('h-7 w-7', className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event); onClick?.(event);
toggleSidebar(); toggleSidebar();
}} }}
{...props} {...props}
> >
<PanelLeft className="size-5" /> <PanelLeft />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
); );
@ -681,12 +672,12 @@ const SidebarMenuSkeleton = React.forwardRef<
> >
{showIcon && ( {showIcon && (
<Skeleton <Skeleton
className="size-4 rounded-md bg-sidebar-accent-foreground/10" className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon" data-sidebar="menu-skeleton-icon"
/> />
)} )}
<Skeleton <Skeleton
className="h-4 flex-1 max-w-[--skeleton-width] bg-sidebar-accent-foreground/10" className="h-4 flex-1 max-w-[--skeleton-width]"
data-sidebar="menu-skeleton-text" data-sidebar="menu-skeleton-text"
style={ style={
{ {

View file

@ -2,23 +2,21 @@ import * as React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export interface TextareaProps const Textarea = React.forwardRef<
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} HTMLTextAreaElement,
React.ComponentProps<'textarea'>
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( >(({ className, ...props }, ref) => {
({ className, ...props }, ref) => { return (
return ( <textarea
<textarea className={cn(
className={cn( 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className,
className, )}
)} ref={ref}
ref={ref} {...props}
{...props} />
/> );
); });
},
);
Textarea.displayName = 'Textarea'; Textarea.displayName = 'Textarea';
export { Textarea }; export { Textarea };

View file

@ -28,22 +28,3 @@ const TooltipContent = React.forwardRef<
TooltipContent.displayName = TooltipPrimitive.Content.displayName; TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export const BetterTooltip = ({
content,
children,
align = 'center',
...props
}: React.ComponentPropsWithoutRef<typeof Tooltip> & {
content: JSX.Element | string;
align?: 'center' | 'end' | 'start';
}) => {
return (
<TooltipProvider delayDuration={0}>
<Tooltip {...props}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent align={align}>{content}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

View file

@ -21,13 +21,13 @@
"@ai-sdk/openai": "1.0.6", "@ai-sdk/openai": "1.0.6",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2", "@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.3",
"@radix-ui/react-visually-hidden": "^1.1.0", "@radix-ui/react-visually-hidden": "^1.1.0",
"@vercel/analytics": "^1.3.1", "@vercel/analytics": "^1.3.1",
"@vercel/blob": "^0.24.1", "@vercel/blob": "^0.24.1",

4
pnpm-lock.yaml generated
View file

@ -18,7 +18,7 @@ importers:
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
'@radix-ui/react-dropdown-menu': '@radix-ui/react-dropdown-menu':
specifier: ^2.1.1 specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
'@radix-ui/react-icons': '@radix-ui/react-icons':
specifier: ^1.3.0 specifier: ^1.3.0
@ -36,7 +36,7 @@ importers:
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021) version: 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.1.2 specifier: ^1.1.3
version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
'@radix-ui/react-visually-hidden': '@radix-ui/react-visually-hidden':
specifier: ^1.1.0 specifier: ^1.1.0