WIP. A little custom music server
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: run prettier

+844 -1024
+21 -21
web/src/components/ui/separator.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import * as SeparatorPrimitive from "@radix-ui/react-separator" 3 + import * as React from "react"; 4 + import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 5 6 - import { cn } from "@/lib/utils" 6 + import { cn } from "@/lib/utils"; 7 7 8 8 function Separator({ 9 - className, 10 - orientation = "horizontal", 11 - decorative = true, 12 - ...props 9 + className, 10 + orientation = "horizontal", 11 + decorative = true, 12 + ...props 13 13 }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { 14 - return ( 15 - <SeparatorPrimitive.Root 16 - data-slot="separator" 17 - decorative={decorative} 18 - orientation={orientation} 19 - className={cn( 20 - "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", 21 - className 22 - )} 23 - {...props} 24 - /> 25 - ) 14 + return ( 15 + <SeparatorPrimitive.Root 16 + data-slot="separator" 17 + decorative={decorative} 18 + orientation={orientation} 19 + className={cn( 20 + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", 21 + className, 22 + )} 23 + {...props} 24 + /> 25 + ); 26 26 } 27 27 28 - export { Separator } 28 + export { Separator };
+74 -110
web/src/components/ui/sheet.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import * as SheetPrimitive from "@radix-ui/react-dialog" 5 - import { XIcon } from "lucide-react" 3 + import * as React from "react"; 4 + import * as SheetPrimitive from "@radix-ui/react-dialog"; 5 + import { XIcon } from "lucide-react"; 6 6 7 - import { cn } from "@/lib/utils" 7 + import { cn } from "@/lib/utils"; 8 8 9 9 function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { 10 - return <SheetPrimitive.Root data-slot="sheet" {...props} /> 10 + return <SheetPrimitive.Root data-slot="sheet" {...props} />; 11 11 } 12 12 13 - function SheetTrigger({ 14 - ...props 15 - }: React.ComponentProps<typeof SheetPrimitive.Trigger>) { 16 - return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} /> 13 + function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) { 14 + return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />; 17 15 } 18 16 19 - function SheetClose({ 20 - ...props 21 - }: React.ComponentProps<typeof SheetPrimitive.Close>) { 22 - return <SheetPrimitive.Close data-slot="sheet-close" {...props} /> 17 + function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) { 18 + return <SheetPrimitive.Close data-slot="sheet-close" {...props} />; 23 19 } 24 20 25 - function SheetPortal({ 26 - ...props 27 - }: React.ComponentProps<typeof SheetPrimitive.Portal>) { 28 - return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} /> 21 + function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) { 22 + return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />; 29 23 } 30 24 31 - function SheetOverlay({ 32 - className, 33 - ...props 34 - }: React.ComponentProps<typeof SheetPrimitive.Overlay>) { 35 - return ( 36 - <SheetPrimitive.Overlay 37 - data-slot="sheet-overlay" 38 - className={cn( 39 - "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 40 - className 41 - )} 42 - {...props} 43 - /> 44 - ) 25 + function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) { 26 + return ( 27 + <SheetPrimitive.Overlay 28 + data-slot="sheet-overlay" 29 + className={cn( 30 + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 31 + className, 32 + )} 33 + {...props} 34 + /> 35 + ); 45 36 } 46 37 47 38 function SheetContent({ 48 - className, 49 - children, 50 - side = "right", 51 - ...props 39 + className, 40 + children, 41 + side = "right", 42 + ...props 52 43 }: React.ComponentProps<typeof SheetPrimitive.Content> & { 53 - side?: "top" | "right" | "bottom" | "left" 44 + side?: "top" | "right" | "bottom" | "left"; 54 45 }) { 55 - return ( 56 - <SheetPortal> 57 - <SheetOverlay /> 58 - <SheetPrimitive.Content 59 - data-slot="sheet-content" 60 - className={cn( 61 - "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 62 - side === "right" && 63 - "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", 64 - side === "left" && 65 - "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", 66 - side === "top" && 67 - "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", 68 - side === "bottom" && 69 - "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", 70 - className 71 - )} 72 - {...props} 73 - > 74 - {children} 75 - <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> 76 - <XIcon className="size-4" /> 77 - <span className="sr-only">Close</span> 78 - </SheetPrimitive.Close> 79 - </SheetPrimitive.Content> 80 - </SheetPortal> 81 - ) 46 + return ( 47 + <SheetPortal> 48 + <SheetOverlay /> 49 + <SheetPrimitive.Content 50 + data-slot="sheet-content" 51 + className={cn( 52 + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 53 + side === "right" 54 + && "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", 55 + side === "left" 56 + && "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", 57 + side === "top" 58 + && "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", 59 + side === "bottom" 60 + && "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", 61 + className, 62 + )} 63 + {...props} 64 + > 65 + {children} 66 + <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> 67 + <XIcon className="size-4" /> 68 + <span className="sr-only">Close</span> 69 + </SheetPrimitive.Close> 70 + </SheetPrimitive.Content> 71 + </SheetPortal> 72 + ); 82 73 } 83 74 84 75 function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { 85 - return ( 86 - <div 87 - data-slot="sheet-header" 88 - className={cn("flex flex-col gap-1.5 p-4", className)} 89 - {...props} 90 - /> 91 - ) 76 + return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />; 92 77 } 93 78 94 79 function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { 95 - return ( 96 - <div 97 - data-slot="sheet-footer" 98 - className={cn("mt-auto flex flex-col gap-2 p-4", className)} 99 - {...props} 100 - /> 101 - ) 80 + return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />; 102 81 } 103 82 104 - function SheetTitle({ 105 - className, 106 - ...props 107 - }: React.ComponentProps<typeof SheetPrimitive.Title>) { 108 - return ( 109 - <SheetPrimitive.Title 110 - data-slot="sheet-title" 111 - className={cn("text-foreground font-semibold", className)} 112 - {...props} 113 - /> 114 - ) 83 + function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) { 84 + return ( 85 + <SheetPrimitive.Title 86 + data-slot="sheet-title" 87 + className={cn("text-foreground font-semibold", className)} 88 + {...props} 89 + /> 90 + ); 115 91 } 116 92 117 - function SheetDescription({ 118 - className, 119 - ...props 120 - }: React.ComponentProps<typeof SheetPrimitive.Description>) { 121 - return ( 122 - <SheetPrimitive.Description 123 - data-slot="sheet-description" 124 - className={cn("text-muted-foreground text-sm", className)} 125 - {...props} 126 - /> 127 - ) 93 + function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) { 94 + return ( 95 + <SheetPrimitive.Description 96 + data-slot="sheet-description" 97 + className={cn("text-muted-foreground text-sm", className)} 98 + {...props} 99 + /> 100 + ); 128 101 } 129 102 130 - export { 131 - Sheet, 132 - SheetTrigger, 133 - SheetClose, 134 - SheetContent, 135 - SheetHeader, 136 - SheetFooter, 137 - SheetTitle, 138 - SheetDescription, 139 - } 103 + export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
+578 -705
web/src/components/ui/sidebar.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import { Slot } from "@radix-ui/react-slot" 5 - import { VariantProps, cva } from "class-variance-authority" 6 - import { PanelLeft } from "lucide-react" 3 + import * as React from "react"; 4 + import { Slot } from "@radix-ui/react-slot"; 5 + import { VariantProps, cva } from "class-variance-authority"; 6 + import { PanelLeft } from "lucide-react"; 7 7 8 - import { useIsMobile } from "@/hooks/use-mobile" 9 - import { cn } from "@/lib/utils" 10 - import { Button } from "@/components/ui/button" 11 - import { Input } from "@/components/ui/input" 12 - import { Separator } from "@/components/ui/separator" 13 - import { 14 - Sheet, 15 - SheetContent, 16 - SheetDescription, 17 - SheetHeader, 18 - SheetTitle, 19 - } from "@/components/ui/sheet" 20 - import { Skeleton } from "@/components/ui/skeleton" 21 - import { 22 - Tooltip, 23 - TooltipContent, 24 - TooltipProvider, 25 - TooltipTrigger, 26 - } from "@/components/ui/tooltip" 8 + import { useIsMobile } from "@/hooks/use-mobile"; 9 + import { cn } from "@/lib/utils"; 10 + import { Button } from "@/components/ui/button"; 11 + import { Input } from "@/components/ui/input"; 12 + import { Separator } from "@/components/ui/separator"; 13 + import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; 14 + import { Skeleton } from "@/components/ui/skeleton"; 15 + import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; 27 16 28 - const SIDEBAR_COOKIE_NAME = "sidebar_state" 29 - const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 30 - const SIDEBAR_WIDTH = "16rem" 31 - const SIDEBAR_WIDTH_MOBILE = "18rem" 32 - const SIDEBAR_WIDTH_ICON = "3rem" 33 - const SIDEBAR_KEYBOARD_SHORTCUT = "b" 17 + const SIDEBAR_COOKIE_NAME = "sidebar_state"; 18 + const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; 19 + const SIDEBAR_WIDTH = "16rem"; 20 + const SIDEBAR_WIDTH_MOBILE = "18rem"; 21 + const SIDEBAR_WIDTH_ICON = "3rem"; 22 + const SIDEBAR_KEYBOARD_SHORTCUT = "b"; 34 23 35 24 type SidebarContextProps = { 36 - state: "expanded" | "collapsed" 37 - open: boolean 38 - setOpen: (open: boolean) => void 39 - openMobile: boolean 40 - setOpenMobile: (open: boolean) => void 41 - isMobile: boolean 42 - toggleSidebar: () => void 43 - } 25 + state: "expanded" | "collapsed"; 26 + open: boolean; 27 + setOpen: (open: boolean) => void; 28 + openMobile: boolean; 29 + setOpenMobile: (open: boolean) => void; 30 + isMobile: boolean; 31 + toggleSidebar: () => void; 32 + }; 44 33 45 - const SidebarContext = React.createContext<SidebarContextProps | null>(null) 34 + const SidebarContext = React.createContext<SidebarContextProps | null>(null); 46 35 47 36 function useSidebar() { 48 - const context = React.useContext(SidebarContext) 49 - if (!context) { 50 - throw new Error("useSidebar must be used within a SidebarProvider.") 51 - } 37 + const context = React.useContext(SidebarContext); 38 + if (!context) { 39 + throw new Error("useSidebar must be used within a SidebarProvider."); 40 + } 52 41 53 - return context 42 + return context; 54 43 } 55 44 56 45 const SidebarProvider = React.forwardRef< 57 - HTMLDivElement, 58 - React.ComponentProps<"div"> & { 59 - defaultOpen?: boolean 60 - open?: boolean 61 - onOpenChange?: (open: boolean) => void 62 - } 63 - >( 64 - ( 65 - { 66 - defaultOpen = true, 67 - open: openProp, 68 - onOpenChange: setOpenProp, 69 - className, 70 - style, 71 - children, 72 - ...props 73 - }, 74 - ref 75 - ) => { 76 - const isMobile = useIsMobile() 77 - const [openMobile, setOpenMobile] = React.useState(false) 46 + HTMLDivElement, 47 + React.ComponentProps<"div"> & { 48 + defaultOpen?: boolean; 49 + open?: boolean; 50 + onOpenChange?: (open: boolean) => void; 51 + } 52 + >(({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref) => { 53 + const isMobile = useIsMobile(); 54 + const [openMobile, setOpenMobile] = React.useState(false); 78 55 79 - // This is the internal state of the sidebar. 80 - // We use openProp and setOpenProp for control from outside the component. 81 - const [_open, _setOpen] = React.useState(defaultOpen) 82 - const open = openProp ?? _open 83 - const setOpen = React.useCallback( 84 - (value: boolean | ((value: boolean) => boolean)) => { 85 - const openState = typeof value === "function" ? value(open) : value 86 - if (setOpenProp) { 87 - setOpenProp(openState) 88 - } else { 89 - _setOpen(openState) 90 - } 56 + // This is the internal state of the sidebar. 57 + // We use openProp and setOpenProp for control from outside the component. 58 + const [_open, _setOpen] = React.useState(defaultOpen); 59 + const open = openProp ?? _open; 60 + const setOpen = React.useCallback( 61 + (value: boolean | ((value: boolean) => boolean)) => { 62 + const openState = typeof value === "function" ? value(open) : value; 63 + if (setOpenProp) { 64 + setOpenProp(openState); 65 + } else { 66 + _setOpen(openState); 67 + } 91 68 92 - // This sets the cookie to keep the sidebar state. 93 - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` 94 - }, 95 - [setOpenProp, open] 96 - ) 69 + // This sets the cookie to keep the sidebar state. 70 + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; 71 + }, 72 + [setOpenProp, open], 73 + ); 97 74 98 - // Helper to toggle the sidebar. 99 - const toggleSidebar = React.useCallback(() => { 100 - return isMobile 101 - ? setOpenMobile((open) => !open) 102 - : setOpen((open) => !open) 103 - }, [isMobile, setOpen, setOpenMobile]) 75 + // Helper to toggle the sidebar. 76 + const toggleSidebar = React.useCallback(() => { 77 + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); 78 + }, [isMobile, setOpen, setOpenMobile]); 104 79 105 - // Adds a keyboard shortcut to toggle the sidebar. 106 - React.useEffect(() => { 107 - const handleKeyDown = (event: KeyboardEvent) => { 108 - if ( 109 - event.key === SIDEBAR_KEYBOARD_SHORTCUT && 110 - (event.metaKey || event.ctrlKey) 111 - ) { 112 - event.preventDefault() 113 - toggleSidebar() 114 - } 115 - } 80 + // Adds a keyboard shortcut to toggle the sidebar. 81 + React.useEffect(() => { 82 + const handleKeyDown = (event: KeyboardEvent) => { 83 + if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) { 84 + event.preventDefault(); 85 + toggleSidebar(); 86 + } 87 + }; 116 88 117 - window.addEventListener("keydown", handleKeyDown) 118 - return () => window.removeEventListener("keydown", handleKeyDown) 119 - }, [toggleSidebar]) 89 + window.addEventListener("keydown", handleKeyDown); 90 + return () => window.removeEventListener("keydown", handleKeyDown); 91 + }, [toggleSidebar]); 120 92 121 - // We add a state so that we can do data-state="expanded" or "collapsed". 122 - // This makes it easier to style the sidebar with Tailwind classes. 123 - const state = open ? "expanded" : "collapsed" 93 + // We add a state so that we can do data-state="expanded" or "collapsed". 94 + // This makes it easier to style the sidebar with Tailwind classes. 95 + const state = open ? "expanded" : "collapsed"; 124 96 125 - const contextValue = React.useMemo<SidebarContextProps>( 126 - () => ({ 127 - state, 128 - open, 129 - setOpen, 130 - isMobile, 131 - openMobile, 132 - setOpenMobile, 133 - toggleSidebar, 134 - }), 135 - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] 136 - ) 97 + const contextValue = React.useMemo<SidebarContextProps>( 98 + () => ({ 99 + state, 100 + open, 101 + setOpen, 102 + isMobile, 103 + openMobile, 104 + setOpenMobile, 105 + toggleSidebar, 106 + }), 107 + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], 108 + ); 137 109 138 - return ( 139 - <SidebarContext.Provider value={contextValue}> 140 - <TooltipProvider delayDuration={0}> 141 - <div 142 - style={ 143 - { 144 - "--sidebar-width": SIDEBAR_WIDTH, 145 - "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, 146 - ...style, 147 - } as React.CSSProperties 148 - } 149 - className={cn( 150 - "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar", 151 - className 152 - )} 153 - ref={ref} 154 - {...props} 155 - > 156 - {children} 157 - </div> 158 - </TooltipProvider> 159 - </SidebarContext.Provider> 160 - ) 161 - } 162 - ) 163 - SidebarProvider.displayName = "SidebarProvider" 110 + return ( 111 + <SidebarContext.Provider value={contextValue}> 112 + <TooltipProvider delayDuration={0}> 113 + <div 114 + style={ 115 + { 116 + "--sidebar-width": SIDEBAR_WIDTH, 117 + "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, 118 + ...style, 119 + } as React.CSSProperties 120 + } 121 + className={cn( 122 + "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar", 123 + className, 124 + )} 125 + ref={ref} 126 + {...props} 127 + > 128 + {children} 129 + </div> 130 + </TooltipProvider> 131 + </SidebarContext.Provider> 132 + ); 133 + }); 134 + SidebarProvider.displayName = "SidebarProvider"; 164 135 165 136 const Sidebar = React.forwardRef< 166 - HTMLDivElement, 167 - React.ComponentProps<"div"> & { 168 - side?: "left" | "right" 169 - variant?: "sidebar" | "floating" | "inset" 170 - collapsible?: "offcanvas" | "icon" | "none" 171 - } 172 - >( 173 - ( 174 - { 175 - side = "left", 176 - variant = "sidebar", 177 - collapsible = "offcanvas", 178 - className, 179 - children, 180 - ...props 181 - }, 182 - ref 183 - ) => { 184 - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() 137 + HTMLDivElement, 138 + React.ComponentProps<"div"> & { 139 + side?: "left" | "right"; 140 + variant?: "sidebar" | "floating" | "inset"; 141 + collapsible?: "offcanvas" | "icon" | "none"; 142 + } 143 + >(({ side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }, ref) => { 144 + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); 185 145 186 - if (collapsible === "none") { 187 - return ( 188 - <div 189 - className={cn( 190 - "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground", 191 - className 192 - )} 193 - ref={ref} 194 - {...props} 195 - > 196 - {children} 197 - </div> 198 - ) 199 - } 146 + if (collapsible === "none") { 147 + return ( 148 + <div 149 + className={cn("flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground", className)} 150 + ref={ref} 151 + {...props} 152 + > 153 + {children} 154 + </div> 155 + ); 156 + } 200 157 201 - if (isMobile) { 202 - return ( 203 - <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> 204 - <SheetContent 205 - data-sidebar="sidebar" 206 - data-mobile="true" 207 - className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" 208 - style={ 209 - { 210 - "--sidebar-width": SIDEBAR_WIDTH_MOBILE, 211 - } as React.CSSProperties 212 - } 213 - side={side} 214 - > 215 - <SheetHeader className="sr-only"> 216 - <SheetTitle>Sidebar</SheetTitle> 217 - <SheetDescription>Displays the mobile sidebar.</SheetDescription> 218 - </SheetHeader> 219 - <div className="flex h-full w-full flex-col">{children}</div> 220 - </SheetContent> 221 - </Sheet> 222 - ) 223 - } 158 + if (isMobile) { 159 + return ( 160 + <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> 161 + <SheetContent 162 + data-sidebar="sidebar" 163 + data-mobile="true" 164 + className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" 165 + style={ 166 + { 167 + "--sidebar-width": SIDEBAR_WIDTH_MOBILE, 168 + } as React.CSSProperties 169 + } 170 + side={side} 171 + > 172 + <SheetHeader className="sr-only"> 173 + <SheetTitle>Sidebar</SheetTitle> 174 + <SheetDescription>Displays the mobile sidebar.</SheetDescription> 175 + </SheetHeader> 176 + <div className="flex h-full w-full flex-col">{children}</div> 177 + </SheetContent> 178 + </Sheet> 179 + ); 180 + } 224 181 225 - return ( 226 - <div 227 - ref={ref} 228 - className="group peer hidden text-sidebar-foreground md:block" 229 - data-state={state} 230 - data-collapsible={state === "collapsed" ? collapsible : ""} 231 - data-variant={variant} 232 - data-side={side} 233 - > 234 - {/* This is what handles the sidebar gap on desktop */} 235 - <div 236 - className={cn( 237 - "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", 238 - "group-data-[collapsible=offcanvas]:w-0", 239 - "group-data-[side=right]:rotate-180", 240 - variant === "floating" || variant === "inset" 241 - ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" 242 - : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" 243 - )} 244 - /> 245 - <div 246 - className={cn( 247 - "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", 248 - side === "left" 249 - ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" 250 - : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", 251 - // Adjust the padding for floating and inset variants. 252 - variant === "floating" || variant === "inset" 253 - ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" 254 - : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", 255 - className 256 - )} 257 - {...props} 258 - > 259 - <div 260 - data-sidebar="sidebar" 261 - className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow" 262 - > 263 - {children} 264 - </div> 265 - </div> 266 - </div> 267 - ) 268 - } 269 - ) 270 - Sidebar.displayName = "Sidebar" 182 + return ( 183 + <div 184 + ref={ref} 185 + className="group peer hidden text-sidebar-foreground md:block" 186 + data-state={state} 187 + data-collapsible={state === "collapsed" ? collapsible : ""} 188 + data-variant={variant} 189 + data-side={side} 190 + > 191 + {/* This is what handles the sidebar gap on desktop */} 192 + <div 193 + className={cn( 194 + "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", 195 + "group-data-[collapsible=offcanvas]:w-0", 196 + "group-data-[side=right]:rotate-180", 197 + variant === "floating" || variant === "inset" 198 + ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" 199 + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)", 200 + )} 201 + /> 202 + <div 203 + className={cn( 204 + "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", 205 + side === "left" 206 + ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" 207 + : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", 208 + // Adjust the padding for floating and inset variants. 209 + variant === "floating" || variant === "inset" 210 + ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" 211 + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", 212 + className, 213 + )} 214 + {...props} 215 + > 216 + <div 217 + data-sidebar="sidebar" 218 + className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow" 219 + > 220 + {children} 221 + </div> 222 + </div> 223 + </div> 224 + ); 225 + }); 226 + Sidebar.displayName = "Sidebar"; 271 227 272 - const SidebarTrigger = React.forwardRef< 273 - React.ElementRef<typeof Button>, 274 - React.ComponentProps<typeof Button> 275 - >(({ className, onClick, ...props }, ref) => { 276 - const { toggleSidebar } = useSidebar() 228 + const SidebarTrigger = React.forwardRef<React.ElementRef<typeof Button>, React.ComponentProps<typeof Button>>( 229 + ({ className, onClick, ...props }, ref) => { 230 + const { toggleSidebar } = useSidebar(); 277 231 278 - return ( 279 - <Button 280 - ref={ref} 281 - data-sidebar="trigger" 282 - variant="ghost" 283 - size="icon" 284 - className={cn("h-7 w-7", className)} 285 - onClick={(event) => { 286 - onClick?.(event) 287 - toggleSidebar() 288 - }} 289 - {...props} 290 - > 291 - <PanelLeft /> 292 - <span className="sr-only">Toggle Sidebar</span> 293 - </Button> 294 - ) 295 - }) 296 - SidebarTrigger.displayName = "SidebarTrigger" 232 + return ( 233 + <Button 234 + ref={ref} 235 + data-sidebar="trigger" 236 + variant="ghost" 237 + size="icon" 238 + className={cn("h-7 w-7", className)} 239 + onClick={(event) => { 240 + onClick?.(event); 241 + toggleSidebar(); 242 + }} 243 + {...props} 244 + > 245 + <PanelLeft /> 246 + <span className="sr-only">Toggle Sidebar</span> 247 + </Button> 248 + ); 249 + }, 250 + ); 251 + SidebarTrigger.displayName = "SidebarTrigger"; 297 252 298 - const SidebarRail = React.forwardRef< 299 - HTMLButtonElement, 300 - React.ComponentProps<"button"> 301 - >(({ className, ...props }, ref) => { 302 - const { toggleSidebar } = useSidebar() 253 + const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<"button">>( 254 + ({ className, ...props }, ref) => { 255 + const { toggleSidebar } = useSidebar(); 303 256 304 - return ( 305 - <button 306 - ref={ref} 307 - data-sidebar="rail" 308 - aria-label="Toggle Sidebar" 309 - tabIndex={-1} 310 - onClick={toggleSidebar} 311 - title="Toggle Sidebar" 312 - className={cn( 313 - "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", 314 - "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", 315 - "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", 316 - "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", 317 - "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", 318 - "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", 319 - className 320 - )} 321 - {...props} 322 - /> 323 - ) 324 - }) 325 - SidebarRail.displayName = "SidebarRail" 257 + return ( 258 + <button 259 + ref={ref} 260 + data-sidebar="rail" 261 + aria-label="Toggle Sidebar" 262 + tabIndex={-1} 263 + onClick={toggleSidebar} 264 + title="Toggle Sidebar" 265 + className={cn( 266 + "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", 267 + "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", 268 + "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", 269 + "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", 270 + "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", 271 + "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", 272 + className, 273 + )} 274 + {...props} 275 + /> 276 + ); 277 + }, 278 + ); 279 + SidebarRail.displayName = "SidebarRail"; 326 280 327 - const SidebarInset = React.forwardRef< 328 - HTMLDivElement, 329 - React.ComponentProps<"main"> 330 - >(({ className, ...props }, ref) => { 331 - return ( 332 - <main 333 - ref={ref} 334 - className={cn( 335 - "relative flex w-full flex-1 flex-col bg-background", 336 - "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", 337 - className 338 - )} 339 - {...props} 340 - /> 341 - ) 342 - }) 343 - SidebarInset.displayName = "SidebarInset" 281 + const SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<"main">>(({ className, ...props }, ref) => { 282 + return ( 283 + <main 284 + ref={ref} 285 + className={cn( 286 + "relative flex w-full flex-1 flex-col bg-background", 287 + "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", 288 + className, 289 + )} 290 + {...props} 291 + /> 292 + ); 293 + }); 294 + SidebarInset.displayName = "SidebarInset"; 344 295 345 - const SidebarInput = React.forwardRef< 346 - React.ElementRef<typeof Input>, 347 - React.ComponentProps<typeof Input> 348 - >(({ className, ...props }, ref) => { 349 - return ( 350 - <Input 351 - ref={ref} 352 - data-sidebar="input" 353 - className={cn( 354 - "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", 355 - className 356 - )} 357 - {...props} 358 - /> 359 - ) 360 - }) 361 - SidebarInput.displayName = "SidebarInput" 296 + const SidebarInput = React.forwardRef<React.ElementRef<typeof Input>, React.ComponentProps<typeof Input>>( 297 + ({ className, ...props }, ref) => { 298 + return ( 299 + <Input 300 + ref={ref} 301 + data-sidebar="input" 302 + className={cn( 303 + "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", 304 + className, 305 + )} 306 + {...props} 307 + /> 308 + ); 309 + }, 310 + ); 311 + SidebarInput.displayName = "SidebarInput"; 362 312 363 - const SidebarHeader = React.forwardRef< 364 - HTMLDivElement, 365 - React.ComponentProps<"div"> 366 - >(({ className, ...props }, ref) => { 367 - return ( 368 - <div 369 - ref={ref} 370 - data-sidebar="header" 371 - className={cn("flex flex-col gap-2 p-2", className)} 372 - {...props} 373 - /> 374 - ) 375 - }) 376 - SidebarHeader.displayName = "SidebarHeader" 313 + const SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(({ className, ...props }, ref) => { 314 + return <div ref={ref} data-sidebar="header" className={cn("flex flex-col gap-2 p-2", className)} {...props} />; 315 + }); 316 + SidebarHeader.displayName = "SidebarHeader"; 377 317 378 - const SidebarFooter = React.forwardRef< 379 - HTMLDivElement, 380 - React.ComponentProps<"div"> 381 - >(({ className, ...props }, ref) => { 382 - return ( 383 - <div 384 - ref={ref} 385 - data-sidebar="footer" 386 - className={cn("flex flex-col gap-2 p-2", className)} 387 - {...props} 388 - /> 389 - ) 390 - }) 391 - SidebarFooter.displayName = "SidebarFooter" 318 + const SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(({ className, ...props }, ref) => { 319 + return <div ref={ref} data-sidebar="footer" className={cn("flex flex-col gap-2 p-2", className)} {...props} />; 320 + }); 321 + SidebarFooter.displayName = "SidebarFooter"; 392 322 393 - const SidebarSeparator = React.forwardRef< 394 - React.ElementRef<typeof Separator>, 395 - React.ComponentProps<typeof Separator> 396 - >(({ className, ...props }, ref) => { 397 - return ( 398 - <Separator 399 - ref={ref} 400 - data-sidebar="separator" 401 - className={cn("mx-2 w-auto bg-sidebar-border", className)} 402 - {...props} 403 - /> 404 - ) 405 - }) 406 - SidebarSeparator.displayName = "SidebarSeparator" 323 + const SidebarSeparator = React.forwardRef<React.ElementRef<typeof Separator>, React.ComponentProps<typeof Separator>>( 324 + ({ className, ...props }, ref) => { 325 + return ( 326 + <Separator 327 + ref={ref} 328 + data-sidebar="separator" 329 + className={cn("mx-2 w-auto bg-sidebar-border", className)} 330 + {...props} 331 + /> 332 + ); 333 + }, 334 + ); 335 + SidebarSeparator.displayName = "SidebarSeparator"; 407 336 408 - const SidebarContent = React.forwardRef< 409 - HTMLDivElement, 410 - React.ComponentProps<"div"> 411 - >(({ className, ...props }, ref) => { 412 - return ( 413 - <div 414 - ref={ref} 415 - data-sidebar="content" 416 - className={cn( 417 - "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", 418 - className 419 - )} 420 - {...props} 421 - /> 422 - ) 423 - }) 424 - SidebarContent.displayName = "SidebarContent" 337 + const SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(({ className, ...props }, ref) => { 338 + return ( 339 + <div 340 + ref={ref} 341 + data-sidebar="content" 342 + className={cn( 343 + "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", 344 + className, 345 + )} 346 + {...props} 347 + /> 348 + ); 349 + }); 350 + SidebarContent.displayName = "SidebarContent"; 425 351 426 - const SidebarGroup = React.forwardRef< 427 - HTMLDivElement, 428 - React.ComponentProps<"div"> 429 - >(({ className, ...props }, ref) => { 430 - return ( 431 - <div 432 - ref={ref} 433 - data-sidebar="group" 434 - className={cn("relative flex w-full min-w-0 flex-col p-2", className)} 435 - {...props} 436 - /> 437 - ) 438 - }) 439 - SidebarGroup.displayName = "SidebarGroup" 352 + const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(({ className, ...props }, ref) => { 353 + return ( 354 + <div 355 + ref={ref} 356 + data-sidebar="group" 357 + className={cn("relative flex w-full min-w-0 flex-col p-2", className)} 358 + {...props} 359 + /> 360 + ); 361 + }); 362 + SidebarGroup.displayName = "SidebarGroup"; 440 363 441 - const SidebarGroupLabel = React.forwardRef< 442 - HTMLDivElement, 443 - React.ComponentProps<"div"> & { asChild?: boolean } 444 - >(({ className, asChild = false, ...props }, ref) => { 445 - const Comp = asChild ? Slot : "div" 364 + const SidebarGroupLabel = React.forwardRef<HTMLDivElement, React.ComponentProps<"div"> & { asChild?: boolean }>( 365 + ({ className, asChild = false, ...props }, ref) => { 366 + const Comp = asChild ? Slot : "div"; 446 367 447 - return ( 448 - <Comp 449 - ref={ref} 450 - data-sidebar="group-label" 451 - className={cn( 452 - "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 453 - "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", 454 - className 455 - )} 456 - {...props} 457 - /> 458 - ) 459 - }) 460 - SidebarGroupLabel.displayName = "SidebarGroupLabel" 368 + return ( 369 + <Comp 370 + ref={ref} 371 + data-sidebar="group-label" 372 + className={cn( 373 + "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 374 + "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", 375 + className, 376 + )} 377 + {...props} 378 + /> 379 + ); 380 + }, 381 + ); 382 + SidebarGroupLabel.displayName = "SidebarGroupLabel"; 461 383 462 - const SidebarGroupAction = React.forwardRef< 463 - HTMLButtonElement, 464 - React.ComponentProps<"button"> & { asChild?: boolean } 465 - >(({ className, asChild = false, ...props }, ref) => { 466 - const Comp = asChild ? Slot : "button" 384 + const SidebarGroupAction = React.forwardRef<HTMLButtonElement, React.ComponentProps<"button"> & { asChild?: boolean }>( 385 + ({ className, asChild = false, ...props }, ref) => { 386 + const Comp = asChild ? Slot : "button"; 467 387 468 - return ( 469 - <Comp 470 - ref={ref} 471 - data-sidebar="group-action" 472 - className={cn( 473 - "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 474 - // Increases the hit area of the button on mobile. 475 - "after:absolute after:-inset-2 after:md:hidden", 476 - "group-data-[collapsible=icon]:hidden", 477 - className 478 - )} 479 - {...props} 480 - /> 481 - ) 482 - }) 483 - SidebarGroupAction.displayName = "SidebarGroupAction" 388 + return ( 389 + <Comp 390 + ref={ref} 391 + data-sidebar="group-action" 392 + className={cn( 393 + "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 394 + // Increases the hit area of the button on mobile. 395 + "after:absolute after:-inset-2 after:md:hidden", 396 + "group-data-[collapsible=icon]:hidden", 397 + className, 398 + )} 399 + {...props} 400 + /> 401 + ); 402 + }, 403 + ); 404 + SidebarGroupAction.displayName = "SidebarGroupAction"; 484 405 485 - const SidebarGroupContent = React.forwardRef< 486 - HTMLDivElement, 487 - React.ComponentProps<"div"> 488 - >(({ className, ...props }, ref) => ( 489 - <div 490 - ref={ref} 491 - data-sidebar="group-content" 492 - className={cn("w-full text-sm", className)} 493 - {...props} 494 - /> 495 - )) 496 - SidebarGroupContent.displayName = "SidebarGroupContent" 406 + const SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>( 407 + ({ className, ...props }, ref) => ( 408 + <div ref={ref} data-sidebar="group-content" className={cn("w-full text-sm", className)} {...props} /> 409 + ), 410 + ); 411 + SidebarGroupContent.displayName = "SidebarGroupContent"; 497 412 498 - const SidebarMenu = React.forwardRef< 499 - HTMLUListElement, 500 - React.ComponentProps<"ul"> 501 - >(({ className, ...props }, ref) => ( 502 - <ul 503 - ref={ref} 504 - data-sidebar="menu" 505 - className={cn("flex w-full min-w-0 flex-col gap-1", className)} 506 - {...props} 507 - /> 508 - )) 509 - SidebarMenu.displayName = "SidebarMenu" 413 + const SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(({ className, ...props }, ref) => ( 414 + <ul ref={ref} data-sidebar="menu" className={cn("flex w-full min-w-0 flex-col gap-1", className)} {...props} /> 415 + )); 416 + SidebarMenu.displayName = "SidebarMenu"; 510 417 511 - const SidebarMenuItem = React.forwardRef< 512 - HTMLLIElement, 513 - React.ComponentProps<"li"> 514 - >(({ className, ...props }, ref) => ( 515 - <li 516 - ref={ref} 517 - data-sidebar="menu-item" 518 - className={cn("group/menu-item relative", className)} 519 - {...props} 520 - /> 521 - )) 522 - SidebarMenuItem.displayName = "SidebarMenuItem" 418 + const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(({ className, ...props }, ref) => ( 419 + <li ref={ref} data-sidebar="menu-item" className={cn("group/menu-item relative", className)} {...props} /> 420 + )); 421 + SidebarMenuItem.displayName = "SidebarMenuItem"; 523 422 524 423 const sidebarMenuButtonVariants = cva( 525 - "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", 526 - { 527 - variants: { 528 - variant: { 529 - default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", 530 - outline: 531 - "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", 532 - }, 533 - size: { 534 - default: "h-8 text-sm", 535 - sm: "h-7 text-xs", 536 - lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", 537 - }, 538 - }, 539 - defaultVariants: { 540 - variant: "default", 541 - size: "default", 542 - }, 543 - } 544 - ) 424 + "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", 425 + { 426 + variants: { 427 + variant: { 428 + default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", 429 + outline: 430 + "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", 431 + }, 432 + size: { 433 + default: "h-8 text-sm", 434 + sm: "h-7 text-xs", 435 + lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", 436 + }, 437 + }, 438 + defaultVariants: { 439 + variant: "default", 440 + size: "default", 441 + }, 442 + }, 443 + ); 545 444 546 445 const SidebarMenuButton = React.forwardRef< 547 - HTMLButtonElement, 548 - React.ComponentProps<"button"> & { 549 - asChild?: boolean 550 - isActive?: boolean 551 - tooltip?: string | React.ComponentProps<typeof TooltipContent> 552 - } & VariantProps<typeof sidebarMenuButtonVariants> 553 - >( 554 - ( 555 - { 556 - asChild = false, 557 - isActive = false, 558 - variant = "default", 559 - size = "default", 560 - tooltip, 561 - className, 562 - ...props 563 - }, 564 - ref 565 - ) => { 566 - const Comp = asChild ? Slot : "button" 567 - const { isMobile, state } = useSidebar() 446 + HTMLButtonElement, 447 + React.ComponentProps<"button"> & { 448 + asChild?: boolean; 449 + isActive?: boolean; 450 + tooltip?: string | React.ComponentProps<typeof TooltipContent>; 451 + } & VariantProps<typeof sidebarMenuButtonVariants> 452 + >(({ asChild = false, isActive = false, variant = "default", size = "default", tooltip, className, ...props }, ref) => { 453 + const Comp = asChild ? Slot : "button"; 454 + const { isMobile, state } = useSidebar(); 568 455 569 - const button = ( 570 - <Comp 571 - ref={ref} 572 - data-sidebar="menu-button" 573 - data-size={size} 574 - data-active={isActive} 575 - className={cn(sidebarMenuButtonVariants({ variant, size }), className)} 576 - {...props} 577 - /> 578 - ) 456 + const button = ( 457 + <Comp 458 + ref={ref} 459 + data-sidebar="menu-button" 460 + data-size={size} 461 + data-active={isActive} 462 + className={cn(sidebarMenuButtonVariants({ variant, size }), className)} 463 + {...props} 464 + /> 465 + ); 579 466 580 - if (!tooltip) { 581 - return button 582 - } 467 + if (!tooltip) { 468 + return button; 469 + } 583 470 584 - if (typeof tooltip === "string") { 585 - tooltip = { 586 - children: tooltip, 587 - } 588 - } 471 + if (typeof tooltip === "string") { 472 + tooltip = { 473 + children: tooltip, 474 + }; 475 + } 589 476 590 - return ( 591 - <Tooltip> 592 - <TooltipTrigger asChild>{button}</TooltipTrigger> 593 - <TooltipContent 594 - side="right" 595 - align="center" 596 - hidden={state !== "collapsed" || isMobile} 597 - {...tooltip} 598 - /> 599 - </Tooltip> 600 - ) 601 - } 602 - ) 603 - SidebarMenuButton.displayName = "SidebarMenuButton" 477 + return ( 478 + <Tooltip> 479 + <TooltipTrigger asChild>{button}</TooltipTrigger> 480 + <TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile} {...tooltip} /> 481 + </Tooltip> 482 + ); 483 + }); 484 + SidebarMenuButton.displayName = "SidebarMenuButton"; 604 485 605 486 const SidebarMenuAction = React.forwardRef< 606 - HTMLButtonElement, 607 - React.ComponentProps<"button"> & { 608 - asChild?: boolean 609 - showOnHover?: boolean 610 - } 487 + HTMLButtonElement, 488 + React.ComponentProps<"button"> & { 489 + asChild?: boolean; 490 + showOnHover?: boolean; 491 + } 611 492 >(({ className, asChild = false, showOnHover = false, ...props }, ref) => { 612 - const Comp = asChild ? Slot : "button" 493 + const Comp = asChild ? Slot : "button"; 613 494 614 - return ( 615 - <Comp 616 - ref={ref} 617 - data-sidebar="menu-action" 618 - className={cn( 619 - "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0", 620 - // Increases the hit area of the button on mobile. 621 - "after:absolute after:-inset-2 after:md:hidden", 622 - "peer-data-[size=sm]/menu-button:top-1", 623 - "peer-data-[size=default]/menu-button:top-1.5", 624 - "peer-data-[size=lg]/menu-button:top-2.5", 625 - "group-data-[collapsible=icon]:hidden", 626 - showOnHover && 627 - "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", 628 - className 629 - )} 630 - {...props} 631 - /> 632 - ) 633 - }) 634 - SidebarMenuAction.displayName = "SidebarMenuAction" 495 + return ( 496 + <Comp 497 + ref={ref} 498 + data-sidebar="menu-action" 499 + className={cn( 500 + "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0", 501 + // Increases the hit area of the button on mobile. 502 + "after:absolute after:-inset-2 after:md:hidden", 503 + "peer-data-[size=sm]/menu-button:top-1", 504 + "peer-data-[size=default]/menu-button:top-1.5", 505 + "peer-data-[size=lg]/menu-button:top-2.5", 506 + "group-data-[collapsible=icon]:hidden", 507 + showOnHover 508 + && "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", 509 + className, 510 + )} 511 + {...props} 512 + /> 513 + ); 514 + }); 515 + SidebarMenuAction.displayName = "SidebarMenuAction"; 635 516 636 - const SidebarMenuBadge = React.forwardRef< 637 - HTMLDivElement, 638 - React.ComponentProps<"div"> 639 - >(({ className, ...props }, ref) => ( 640 - <div 641 - ref={ref} 642 - data-sidebar="menu-badge" 643 - className={cn( 644 - "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground", 645 - "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", 646 - "peer-data-[size=sm]/menu-button:top-1", 647 - "peer-data-[size=default]/menu-button:top-1.5", 648 - "peer-data-[size=lg]/menu-button:top-2.5", 649 - "group-data-[collapsible=icon]:hidden", 650 - className 651 - )} 652 - {...props} 653 - /> 654 - )) 655 - SidebarMenuBadge.displayName = "SidebarMenuBadge" 517 + const SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>( 518 + ({ className, ...props }, ref) => ( 519 + <div 520 + ref={ref} 521 + data-sidebar="menu-badge" 522 + className={cn( 523 + "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground", 524 + "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", 525 + "peer-data-[size=sm]/menu-button:top-1", 526 + "peer-data-[size=default]/menu-button:top-1.5", 527 + "peer-data-[size=lg]/menu-button:top-2.5", 528 + "group-data-[collapsible=icon]:hidden", 529 + className, 530 + )} 531 + {...props} 532 + /> 533 + ), 534 + ); 535 + SidebarMenuBadge.displayName = "SidebarMenuBadge"; 656 536 657 537 const SidebarMenuSkeleton = React.forwardRef< 658 - HTMLDivElement, 659 - React.ComponentProps<"div"> & { 660 - showIcon?: boolean 661 - } 538 + HTMLDivElement, 539 + React.ComponentProps<"div"> & { 540 + showIcon?: boolean; 541 + } 662 542 >(({ className, showIcon = false, ...props }, ref) => { 663 - // Random width between 50 to 90%. 664 - const width = React.useMemo(() => { 665 - return `${Math.floor(Math.random() * 40) + 50}%` 666 - }, []) 543 + // Random width between 50 to 90%. 544 + const width = React.useMemo(() => { 545 + return `${Math.floor(Math.random() * 40) + 50}%`; 546 + }, []); 667 547 668 - return ( 669 - <div 670 - ref={ref} 671 - data-sidebar="menu-skeleton" 672 - className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} 673 - {...props} 674 - > 675 - {showIcon && ( 676 - <Skeleton 677 - className="size-4 rounded-md" 678 - data-sidebar="menu-skeleton-icon" 679 - /> 680 - )} 681 - <Skeleton 682 - className="h-4 max-w-(--skeleton-width) flex-1" 683 - data-sidebar="menu-skeleton-text" 684 - style={ 685 - { 686 - "--skeleton-width": width, 687 - } as React.CSSProperties 688 - } 689 - /> 690 - </div> 691 - ) 692 - }) 693 - SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" 548 + return ( 549 + <div 550 + ref={ref} 551 + data-sidebar="menu-skeleton" 552 + className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} 553 + {...props} 554 + > 555 + {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />} 556 + <Skeleton 557 + className="h-4 max-w-(--skeleton-width) flex-1" 558 + data-sidebar="menu-skeleton-text" 559 + style={ 560 + { 561 + "--skeleton-width": width, 562 + } as React.CSSProperties 563 + } 564 + /> 565 + </div> 566 + ); 567 + }); 568 + SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"; 694 569 695 - const SidebarMenuSub = React.forwardRef< 696 - HTMLUListElement, 697 - React.ComponentProps<"ul"> 698 - >(({ className, ...props }, ref) => ( 699 - <ul 700 - ref={ref} 701 - data-sidebar="menu-sub" 702 - className={cn( 703 - "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", 704 - "group-data-[collapsible=icon]:hidden", 705 - className 706 - )} 707 - {...props} 708 - /> 709 - )) 710 - SidebarMenuSub.displayName = "SidebarMenuSub" 570 + const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>( 571 + ({ className, ...props }, ref) => ( 572 + <ul 573 + ref={ref} 574 + data-sidebar="menu-sub" 575 + className={cn( 576 + "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", 577 + "group-data-[collapsible=icon]:hidden", 578 + className, 579 + )} 580 + {...props} 581 + /> 582 + ), 583 + ); 584 + SidebarMenuSub.displayName = "SidebarMenuSub"; 711 585 712 - const SidebarMenuSubItem = React.forwardRef< 713 - HTMLLIElement, 714 - React.ComponentProps<"li"> 715 - >(({ ...props }, ref) => <li ref={ref} {...props} />) 716 - SidebarMenuSubItem.displayName = "SidebarMenuSubItem" 586 + const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(({ ...props }, ref) => ( 587 + <li ref={ref} {...props} /> 588 + )); 589 + SidebarMenuSubItem.displayName = "SidebarMenuSubItem"; 717 590 718 591 const SidebarMenuSubButton = React.forwardRef< 719 - HTMLAnchorElement, 720 - React.ComponentProps<"a"> & { 721 - asChild?: boolean 722 - size?: "sm" | "md" 723 - isActive?: boolean 724 - } 592 + HTMLAnchorElement, 593 + React.ComponentProps<"a"> & { 594 + asChild?: boolean; 595 + size?: "sm" | "md"; 596 + isActive?: boolean; 597 + } 725 598 >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { 726 - const Comp = asChild ? Slot : "a" 599 + const Comp = asChild ? Slot : "a"; 727 600 728 - return ( 729 - <Comp 730 - ref={ref} 731 - data-sidebar="menu-sub-button" 732 - data-size={size} 733 - data-active={isActive} 734 - className={cn( 735 - "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", 736 - "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", 737 - size === "sm" && "text-xs", 738 - size === "md" && "text-sm", 739 - "group-data-[collapsible=icon]:hidden", 740 - className 741 - )} 742 - {...props} 743 - /> 744 - ) 745 - }) 746 - SidebarMenuSubButton.displayName = "SidebarMenuSubButton" 601 + return ( 602 + <Comp 603 + ref={ref} 604 + data-sidebar="menu-sub-button" 605 + data-size={size} 606 + data-active={isActive} 607 + className={cn( 608 + "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", 609 + "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", 610 + size === "sm" && "text-xs", 611 + size === "md" && "text-sm", 612 + "group-data-[collapsible=icon]:hidden", 613 + className, 614 + )} 615 + {...props} 616 + /> 617 + ); 618 + }); 619 + SidebarMenuSubButton.displayName = "SidebarMenuSubButton"; 747 620 748 621 export { 749 - Sidebar, 750 - SidebarContent, 751 - SidebarFooter, 752 - SidebarGroup, 753 - SidebarGroupAction, 754 - SidebarGroupContent, 755 - SidebarGroupLabel, 756 - SidebarHeader, 757 - SidebarInput, 758 - SidebarInset, 759 - SidebarMenu, 760 - SidebarMenuAction, 761 - SidebarMenuBadge, 762 - SidebarMenuButton, 763 - SidebarMenuItem, 764 - SidebarMenuSkeleton, 765 - SidebarMenuSub, 766 - SidebarMenuSubButton, 767 - SidebarMenuSubItem, 768 - SidebarProvider, 769 - SidebarRail, 770 - SidebarSeparator, 771 - SidebarTrigger, 772 - useSidebar, 773 - } 622 + Sidebar, 623 + SidebarContent, 624 + SidebarFooter, 625 + SidebarGroup, 626 + SidebarGroupAction, 627 + SidebarGroupContent, 628 + SidebarGroupLabel, 629 + SidebarHeader, 630 + SidebarInput, 631 + SidebarInset, 632 + SidebarMenu, 633 + SidebarMenuAction, 634 + SidebarMenuBadge, 635 + SidebarMenuButton, 636 + SidebarMenuItem, 637 + SidebarMenuSkeleton, 638 + SidebarMenuSub, 639 + SidebarMenuSubButton, 640 + SidebarMenuSubItem, 641 + SidebarProvider, 642 + SidebarRail, 643 + SidebarSeparator, 644 + SidebarTrigger, 645 + useSidebar, 646 + };
+3 -9
web/src/components/ui/skeleton.tsx
··· 1 - import { cn } from "@/lib/utils" 1 + import { cn } from "@/lib/utils"; 2 2 3 3 function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 - return ( 5 - <div 6 - data-slot="skeleton" 7 - className={cn("bg-accent animate-pulse rounded-md", className)} 8 - {...props} 9 - /> 10 - ) 4 + return <div data-slot="skeleton" className={cn("bg-accent animate-pulse rounded-md", className)} {...props} />; 11 5 } 12 6 13 - export { Skeleton } 7 + export { Skeleton };
+35 -48
web/src/components/ui/tooltip.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import * as TooltipPrimitive from "@radix-ui/react-tooltip" 3 + import * as React from "react"; 4 + import * as TooltipPrimitive from "@radix-ui/react-tooltip"; 5 5 6 - import { cn } from "@/lib/utils" 6 + import { cn } from "@/lib/utils"; 7 7 8 - function TooltipProvider({ 9 - delayDuration = 0, 10 - ...props 11 - }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { 12 - return ( 13 - <TooltipPrimitive.Provider 14 - data-slot="tooltip-provider" 15 - delayDuration={delayDuration} 16 - {...props} 17 - /> 18 - ) 8 + function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { 9 + return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />; 19 10 } 20 11 21 - function Tooltip({ 22 - ...props 23 - }: React.ComponentProps<typeof TooltipPrimitive.Root>) { 24 - return ( 25 - <TooltipProvider> 26 - <TooltipPrimitive.Root data-slot="tooltip" {...props} /> 27 - </TooltipProvider> 28 - ) 12 + function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) { 13 + return ( 14 + <TooltipProvider> 15 + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> 16 + </TooltipProvider> 17 + ); 29 18 } 30 19 31 - function TooltipTrigger({ 32 - ...props 33 - }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { 34 - return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} /> 20 + function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { 21 + return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; 35 22 } 36 23 37 24 function TooltipContent({ 38 - className, 39 - sideOffset = 0, 40 - children, 41 - ...props 25 + className, 26 + sideOffset = 0, 27 + children, 28 + ...props 42 29 }: React.ComponentProps<typeof TooltipPrimitive.Content>) { 43 - return ( 44 - <TooltipPrimitive.Portal> 45 - <TooltipPrimitive.Content 46 - data-slot="tooltip-content" 47 - sideOffset={sideOffset} 48 - className={cn( 49 - "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", 50 - className 51 - )} 52 - {...props} 53 - > 54 - {children} 55 - <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" /> 56 - </TooltipPrimitive.Content> 57 - </TooltipPrimitive.Portal> 58 - ) 30 + return ( 31 + <TooltipPrimitive.Portal> 32 + <TooltipPrimitive.Content 33 + data-slot="tooltip-content" 34 + sideOffset={sideOffset} 35 + className={cn( 36 + "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", 37 + className, 38 + )} 39 + {...props} 40 + > 41 + {children} 42 + <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" /> 43 + </TooltipPrimitive.Content> 44 + </TooltipPrimitive.Portal> 45 + ); 59 46 } 60 47 61 - export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 48 + export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+13 -13
web/src/hooks/use-mobile.ts
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - const MOBILE_BREAKPOINT = 768 3 + const MOBILE_BREAKPOINT = 768; 4 4 5 5 export function useIsMobile() { 6 - const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) 6 + const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined); 7 7 8 - React.useEffect(() => { 9 - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 - const onChange = () => { 11 - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 - } 13 - mql.addEventListener("change", onChange) 14 - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 - return () => mql.removeEventListener("change", onChange) 16 - }, []) 8 + React.useEffect(() => { 9 + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 + const onChange = () => { 11 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 12 + }; 13 + mql.addEventListener("change", onChange); 14 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 15 + return () => mql.removeEventListener("change", onChange); 16 + }, []); 17 17 18 - return !!isMobile 18 + return !!isMobile; 19 19 }
+14 -12
web/src/pages/album/index.tsx
··· 3 3 import { FetchFailedError } from "@/lib/errors"; 4 4 import { Effect, pipe } from "effect"; 5 5 6 - const getAlbums = () => pipe( 7 - Effect.tryPromise({ 8 - try: () => client.albums.get(), 9 - catch: (err) => new FetchFailedError({ 10 - cause: err, 11 - message: "Failed to fetch album" 12 - }) 13 - }), 14 - Effect.map(x => x.data), 15 - Effect.flatMap(Effect.fromNullable) 16 - ) 6 + const getAlbums = () => 7 + pipe( 8 + Effect.tryPromise({ 9 + try: () => client.albums.get(), 10 + catch: (err) => 11 + new FetchFailedError({ 12 + cause: err, 13 + message: "Failed to fetch album", 14 + }), 15 + }), 16 + Effect.map((x) => x.data), 17 + Effect.flatMap(Effect.fromNullable), 18 + ); 17 19 18 20 const AlbumPage = () => 19 21 Effect.gen(function* () { ··· 28 30 </div> 29 31 </div> 30 32 ); 31 - }) 33 + }); 32 34 33 35 export default () => Effect.runPromise(AlbumPage());
+106 -106
web/src/styles.css
··· 4 4 @custom-variant dark (&:is(.dark *)); 5 5 6 6 :root { 7 - --radius: 0.65rem; 8 - --background: oklch(1 0 0); 9 - --foreground: oklch(0.141 0.005 285.823); 10 - --card: oklch(1 0 0); 11 - --card-foreground: oklch(0.141 0.005 285.823); 12 - --popover: oklch(1 0 0); 13 - --popover-foreground: oklch(0.141 0.005 285.823); 14 - --primary: oklch(0.705 0.213 47.604); 15 - --primary-foreground: oklch(0.98 0.016 73.684); 16 - --secondary: oklch(0.967 0.001 286.375); 17 - --secondary-foreground: oklch(0.21 0.006 285.885); 18 - --muted: oklch(0.967 0.001 286.375); 19 - --muted-foreground: oklch(0.552 0.016 285.938); 20 - --accent: oklch(0.967 0.001 286.375); 21 - --accent-foreground: oklch(0.21 0.006 285.885); 22 - --destructive: oklch(0.577 0.245 27.325); 23 - --border: oklch(0.92 0.004 286.32); 24 - --input: oklch(0.92 0.004 286.32); 25 - --ring: oklch(0.705 0.213 47.604); 26 - --chart-1: oklch(0.646 0.222 41.116); 27 - --chart-2: oklch(0.6 0.118 184.704); 28 - --chart-3: oklch(0.398 0.07 227.392); 29 - --chart-4: oklch(0.828 0.189 84.429); 30 - --chart-5: oklch(0.769 0.188 70.08); 31 - --sidebar: oklch(0.985 0 0); 32 - --sidebar-foreground: oklch(0.141 0.005 285.823); 33 - --sidebar-primary: oklch(0.705 0.213 47.604); 34 - --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 35 - --sidebar-accent: oklch(0.967 0.001 286.375); 36 - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); 37 - --sidebar-border: oklch(0.92 0.004 286.32); 38 - --sidebar-ring: oklch(0.705 0.213 47.604); 7 + --radius: 0.65rem; 8 + --background: oklch(1 0 0); 9 + --foreground: oklch(0.141 0.005 285.823); 10 + --card: oklch(1 0 0); 11 + --card-foreground: oklch(0.141 0.005 285.823); 12 + --popover: oklch(1 0 0); 13 + --popover-foreground: oklch(0.141 0.005 285.823); 14 + --primary: oklch(0.705 0.213 47.604); 15 + --primary-foreground: oklch(0.98 0.016 73.684); 16 + --secondary: oklch(0.967 0.001 286.375); 17 + --secondary-foreground: oklch(0.21 0.006 285.885); 18 + --muted: oklch(0.967 0.001 286.375); 19 + --muted-foreground: oklch(0.552 0.016 285.938); 20 + --accent: oklch(0.967 0.001 286.375); 21 + --accent-foreground: oklch(0.21 0.006 285.885); 22 + --destructive: oklch(0.577 0.245 27.325); 23 + --border: oklch(0.92 0.004 286.32); 24 + --input: oklch(0.92 0.004 286.32); 25 + --ring: oklch(0.705 0.213 47.604); 26 + --chart-1: oklch(0.646 0.222 41.116); 27 + --chart-2: oklch(0.6 0.118 184.704); 28 + --chart-3: oklch(0.398 0.07 227.392); 29 + --chart-4: oklch(0.828 0.189 84.429); 30 + --chart-5: oklch(0.769 0.188 70.08); 31 + --sidebar: oklch(0.985 0 0); 32 + --sidebar-foreground: oklch(0.141 0.005 285.823); 33 + --sidebar-primary: oklch(0.705 0.213 47.604); 34 + --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 35 + --sidebar-accent: oklch(0.967 0.001 286.375); 36 + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); 37 + --sidebar-border: oklch(0.92 0.004 286.32); 38 + --sidebar-ring: oklch(0.705 0.213 47.604); 39 39 } 40 40 41 41 .dark { 42 - --background: oklch(0.141 0.005 285.823); 43 - --foreground: oklch(0.985 0 0); 44 - --card: oklch(0.21 0.006 285.885); 45 - --card-foreground: oklch(0.985 0 0); 46 - --popover: oklch(0.21 0.006 285.885); 47 - --popover-foreground: oklch(0.985 0 0); 48 - --primary: oklch(0.646 0.222 41.116); 49 - --primary-foreground: oklch(0.98 0.016 73.684); 50 - --secondary: oklch(0.274 0.006 286.033); 51 - --secondary-foreground: oklch(0.985 0 0); 52 - --muted: oklch(0.274 0.006 286.033); 53 - --muted-foreground: oklch(0.705 0.015 286.067); 54 - --accent: oklch(0.274 0.006 286.033); 55 - --accent-foreground: oklch(0.985 0 0); 56 - --destructive: oklch(0.704 0.191 22.216); 57 - --border: oklch(1 0 0 / 10%); 58 - --input: oklch(1 0 0 / 15%); 59 - --ring: oklch(0.646 0.222 41.116); 60 - --chart-1: oklch(0.488 0.243 264.376); 61 - --chart-2: oklch(0.696 0.17 162.48); 62 - --chart-3: oklch(0.769 0.188 70.08); 63 - --chart-4: oklch(0.627 0.265 303.9); 64 - --chart-5: oklch(0.645 0.246 16.439); 65 - --sidebar: oklch(0.21 0.006 285.885); 66 - --sidebar-foreground: oklch(0.985 0 0); 67 - --sidebar-primary: oklch(0.646 0.222 41.116); 68 - --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 69 - --sidebar-accent: oklch(0.274 0.006 286.033); 70 - --sidebar-accent-foreground: oklch(0.985 0 0); 71 - --sidebar-border: oklch(1 0 0 / 10%); 72 - --sidebar-ring: oklch(0.646 0.222 41.116); 42 + --background: oklch(0.141 0.005 285.823); 43 + --foreground: oklch(0.985 0 0); 44 + --card: oklch(0.21 0.006 285.885); 45 + --card-foreground: oklch(0.985 0 0); 46 + --popover: oklch(0.21 0.006 285.885); 47 + --popover-foreground: oklch(0.985 0 0); 48 + --primary: oklch(0.646 0.222 41.116); 49 + --primary-foreground: oklch(0.98 0.016 73.684); 50 + --secondary: oklch(0.274 0.006 286.033); 51 + --secondary-foreground: oklch(0.985 0 0); 52 + --muted: oklch(0.274 0.006 286.033); 53 + --muted-foreground: oklch(0.705 0.015 286.067); 54 + --accent: oklch(0.274 0.006 286.033); 55 + --accent-foreground: oklch(0.985 0 0); 56 + --destructive: oklch(0.704 0.191 22.216); 57 + --border: oklch(1 0 0 / 10%); 58 + --input: oklch(1 0 0 / 15%); 59 + --ring: oklch(0.646 0.222 41.116); 60 + --chart-1: oklch(0.488 0.243 264.376); 61 + --chart-2: oklch(0.696 0.17 162.48); 62 + --chart-3: oklch(0.769 0.188 70.08); 63 + --chart-4: oklch(0.627 0.265 303.9); 64 + --chart-5: oklch(0.645 0.246 16.439); 65 + --sidebar: oklch(0.21 0.006 285.885); 66 + --sidebar-foreground: oklch(0.985 0 0); 67 + --sidebar-primary: oklch(0.646 0.222 41.116); 68 + --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 69 + --sidebar-accent: oklch(0.274 0.006 286.033); 70 + --sidebar-accent-foreground: oklch(0.985 0 0); 71 + --sidebar-border: oklch(1 0 0 / 10%); 72 + --sidebar-ring: oklch(0.646 0.222 41.116); 73 73 } 74 74 75 75 @theme inline { 76 - --color-background: var(--background); 77 - --color-foreground: var(--foreground); 78 - --color-card: var(--card); 79 - --color-card-foreground: var(--card-foreground); 80 - --color-popover: var(--popover); 81 - --color-popover-foreground: var(--popover-foreground); 82 - --color-primary: var(--primary); 83 - --color-primary-foreground: var(--primary-foreground); 84 - --color-primary-background: var(--primary-foreground); 85 - --color-secondary: var(--secondary); 86 - --color-secondary-foreground: var(--secondary-foreground); 87 - --color-muted: var(--muted); 88 - --color-muted-foreground: var(--muted-foreground); 89 - --color-accent: var(--accent); 90 - --color-accent-foreground: var(--accent-foreground); 91 - --color-destructive: var(--destructive); 92 - --color-destructive-foreground: var(--destructive-foreground); 93 - --color-border: var(--border); 94 - --color-input: var(--input); 95 - --color-ring: var(--ring); 96 - --color-chart-1: var(--chart-1); 97 - --color-chart-2: var(--chart-2); 98 - --color-chart-3: var(--chart-3); 99 - --color-chart-4: var(--chart-4); 100 - --color-chart-5: var(--chart-5); 101 - --radius-sm: calc(var(--radius) - 4px); 102 - --radius-md: calc(var(--radius) - 2px); 103 - --radius-lg: var(--radius); 104 - --radius-xl: calc(var(--radius) + 4px); 105 - --color-sidebar: var(--sidebar); 106 - --color-sidebar-foreground: var(--sidebar-foreground); 107 - --color-sidebar-primary: var(--sidebar-primary); 108 - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 109 - --color-sidebar-accent: var(--sidebar-accent); 110 - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 111 - --color-sidebar-border: var(--sidebar-border); 112 - --color-sidebar-ring: var(--sidebar-ring); 76 + --color-background: var(--background); 77 + --color-foreground: var(--foreground); 78 + --color-card: var(--card); 79 + --color-card-foreground: var(--card-foreground); 80 + --color-popover: var(--popover); 81 + --color-popover-foreground: var(--popover-foreground); 82 + --color-primary: var(--primary); 83 + --color-primary-foreground: var(--primary-foreground); 84 + --color-primary-background: var(--primary-foreground); 85 + --color-secondary: var(--secondary); 86 + --color-secondary-foreground: var(--secondary-foreground); 87 + --color-muted: var(--muted); 88 + --color-muted-foreground: var(--muted-foreground); 89 + --color-accent: var(--accent); 90 + --color-accent-foreground: var(--accent-foreground); 91 + --color-destructive: var(--destructive); 92 + --color-destructive-foreground: var(--destructive-foreground); 93 + --color-border: var(--border); 94 + --color-input: var(--input); 95 + --color-ring: var(--ring); 96 + --color-chart-1: var(--chart-1); 97 + --color-chart-2: var(--chart-2); 98 + --color-chart-3: var(--chart-3); 99 + --color-chart-4: var(--chart-4); 100 + --color-chart-5: var(--chart-5); 101 + --radius-sm: calc(var(--radius) - 4px); 102 + --radius-md: calc(var(--radius) - 2px); 103 + --radius-lg: var(--radius); 104 + --radius-xl: calc(var(--radius) + 4px); 105 + --color-sidebar: var(--sidebar); 106 + --color-sidebar-foreground: var(--sidebar-foreground); 107 + --color-sidebar-primary: var(--sidebar-primary); 108 + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 109 + --color-sidebar-accent: var(--sidebar-accent); 110 + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 111 + --color-sidebar-border: var(--sidebar-border); 112 + --color-sidebar-ring: var(--sidebar-ring); 113 113 } 114 114 115 115 @layer base { 116 - * { 117 - @apply border-border outline-ring/50; 118 - } 119 - body { 120 - @apply bg-background text-foreground; 121 - } 116 + * { 117 + @apply border-border outline-ring/50; 118 + } 119 + body { 120 + @apply bg-background text-foreground; 121 + } 122 122 } 123 123 @font-face { 124 124 font-family: "Geist";