a very good jj gui
0
fork

Configure Feed

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

feat: add shadcn/ui setup with base components

Add components.json configuration and ui components:
- Button, Card, Badge, Input, Label, Textarea
- Select, Combobox, Dropdown Menu, Alert Dialog
- Resizable panels, Scroll Area, Separator, Tooltip
- Field, Input Group helper components
- Utility functions in lib/utils.ts

+1692
+24
apps/desktop/components.json
··· 1 + { 2 + "$schema": "https://ui.shadcn.com/schema.json", 3 + "style": "base-lyra", 4 + "rsc": false, 5 + "tsx": true, 6 + "tailwind": { 7 + "config": "", 8 + "css": "src/styles/index.css", 9 + "baseColor": "stone", 10 + "cssVariables": true, 11 + "prefix": "" 12 + }, 13 + "iconLibrary": "lucide", 14 + "aliases": { 15 + "components": "@/components", 16 + "utils": "@/lib/utils", 17 + "ui": "@/components/ui", 18 + "lib": "@/lib", 19 + "hooks": "@/hooks" 20 + }, 21 + "menuColor": "default", 22 + "menuAccent": "subtle", 23 + "registries": {} 24 + }
+160
apps/desktop/src/components/ui/alert-dialog.tsx
··· 1 + import * as React from "react"; 2 + import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + import { Button } from "@/components/ui/button"; 6 + 7 + function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { 8 + return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />; 9 + } 10 + 11 + function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { 12 + return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />; 13 + } 14 + 15 + function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { 16 + return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />; 17 + } 18 + 19 + function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { 20 + return ( 21 + <AlertDialogPrimitive.Backdrop 22 + data-slot="alert-dialog-overlay" 23 + className={cn( 24 + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", 25 + className, 26 + )} 27 + {...props} 28 + /> 29 + ); 30 + } 31 + 32 + function AlertDialogContent({ 33 + className, 34 + size = "default", 35 + ...props 36 + }: AlertDialogPrimitive.Popup.Props & { 37 + size?: "default" | "sm"; 38 + }) { 39 + return ( 40 + <AlertDialogPortal> 41 + <AlertDialogOverlay /> 42 + <AlertDialogPrimitive.Popup 43 + data-slot="alert-dialog-content" 44 + data-size={size} 45 + className={cn( 46 + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-4 rounded-none p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", 47 + className, 48 + )} 49 + {...props} 50 + /> 51 + </AlertDialogPortal> 52 + ); 53 + } 54 + 55 + function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { 56 + return ( 57 + <div 58 + data-slot="alert-dialog-header" 59 + className={cn( 60 + "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", 61 + className, 62 + )} 63 + {...props} 64 + /> 65 + ); 66 + } 67 + 68 + function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) { 69 + return ( 70 + <div 71 + data-slot="alert-dialog-footer" 72 + className={cn( 73 + "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end", 74 + className, 75 + )} 76 + {...props} 77 + /> 78 + ); 79 + } 80 + 81 + function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) { 82 + return ( 83 + <div 84 + data-slot="alert-dialog-media" 85 + className={cn( 86 + "bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-none sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", 87 + className, 88 + )} 89 + {...props} 90 + /> 91 + ); 92 + } 93 + 94 + function AlertDialogTitle({ 95 + className, 96 + ...props 97 + }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { 98 + return ( 99 + <AlertDialogPrimitive.Title 100 + data-slot="alert-dialog-title" 101 + className={cn( 102 + "text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", 103 + className, 104 + )} 105 + {...props} 106 + /> 107 + ); 108 + } 109 + 110 + function AlertDialogDescription({ 111 + className, 112 + ...props 113 + }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) { 114 + return ( 115 + <AlertDialogPrimitive.Description 116 + data-slot="alert-dialog-description" 117 + className={cn( 118 + "text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", 119 + className, 120 + )} 121 + {...props} 122 + /> 123 + ); 124 + } 125 + 126 + function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>) { 127 + return <Button data-slot="alert-dialog-action" className={cn(className)} {...props} />; 128 + } 129 + 130 + function AlertDialogCancel({ 131 + className, 132 + variant = "outline", 133 + size = "default", 134 + ...props 135 + }: AlertDialogPrimitive.Close.Props & 136 + Pick<React.ComponentProps<typeof Button>, "variant" | "size">) { 137 + return ( 138 + <AlertDialogPrimitive.Close 139 + data-slot="alert-dialog-cancel" 140 + className={cn(className)} 141 + render={<Button variant={variant} size={size} />} 142 + {...props} 143 + /> 144 + ); 145 + } 146 + 147 + export { 148 + AlertDialog, 149 + AlertDialogAction, 150 + AlertDialogCancel, 151 + AlertDialogContent, 152 + AlertDialogDescription, 153 + AlertDialogFooter, 154 + AlertDialogHeader, 155 + AlertDialogMedia, 156 + AlertDialogOverlay, 157 + AlertDialogPortal, 158 + AlertDialogTitle, 159 + AlertDialogTrigger, 160 + };
+49
apps/desktop/src/components/ui/badge.tsx
··· 1 + import { mergeProps } from "@base-ui/react/merge-props"; 2 + import { useRender } from "@base-ui/react/use-render"; 3 + import { cva, type VariantProps } from "class-variance-authority"; 4 + 5 + import { cn } from "@/lib/utils"; 6 + 7 + const badgeVariants = cva( 8 + "h-5 gap-1 rounded-none border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge", 9 + { 10 + variants: { 11 + variant: { 12 + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", 13 + secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", 14 + destructive: 15 + "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20", 16 + outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", 17 + ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", 18 + link: "text-primary underline-offset-4 hover:underline", 19 + }, 20 + }, 21 + defaultVariants: { 22 + variant: "default", 23 + }, 24 + }, 25 + ); 26 + 27 + function Badge({ 28 + className, 29 + variant = "default", 30 + render, 31 + ...props 32 + }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) { 33 + return useRender({ 34 + defaultTagName: "span", 35 + props: mergeProps<"span">( 36 + { 37 + className: cn(badgeVariants({ className, variant })), 38 + }, 39 + props, 40 + ), 41 + render, 42 + state: { 43 + slot: "badge", 44 + variant, 45 + }, 46 + }); 47 + } 48 + 49 + export { Badge, badgeVariants };
+59
apps/desktop/src/components/ui/button.tsx
··· 1 + import * as React from "react"; 2 + import { Button as ButtonPrimitive } from "@base-ui/react/button"; 3 + import { cva, type VariantProps } from "class-variance-authority"; 4 + 5 + import { cn } from "@/lib/utils"; 6 + 7 + const buttonVariants = cva( 8 + "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none", 9 + { 10 + variants: { 11 + variant: { 12 + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", 13 + outline: 14 + "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground", 15 + secondary: 16 + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", 17 + ghost: 18 + "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground", 19 + destructive: 20 + "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30", 21 + link: "text-primary underline-offset-4 hover:underline", 22 + }, 23 + size: { 24 + default: 25 + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", 26 + xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", 27 + sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", 28 + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", 29 + icon: "size-8", 30 + "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3", 31 + "icon-sm": "size-7 rounded-none", 32 + "icon-lg": "size-9", 33 + }, 34 + }, 35 + defaultVariants: { 36 + variant: "default", 37 + size: "default", 38 + }, 39 + }, 40 + ); 41 + 42 + const Button = React.forwardRef< 43 + React.ElementRef<typeof ButtonPrimitive>, 44 + ButtonPrimitive.Props & VariantProps<typeof buttonVariants> 45 + >(function Button( 46 + { className, variant = "default", size = "default", ...props }, 47 + ref, 48 + ) { 49 + return ( 50 + <ButtonPrimitive 51 + ref={ref} 52 + data-slot="button" 53 + className={cn(buttonVariants({ variant, size, className }))} 54 + {...props} 55 + /> 56 + ); 57 + }); 58 + 59 + export { Button, buttonVariants };
+89
apps/desktop/src/components/ui/card.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + function Card({ 6 + className, 7 + size = "default", 8 + ...props 9 + }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { 10 + return ( 11 + <div 12 + data-slot="card" 13 + data-size={size} 14 + className={cn( 15 + "ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col", 16 + className, 17 + )} 18 + {...props} 19 + /> 20 + ); 21 + } 22 + 23 + function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 24 + return ( 25 + <div 26 + data-slot="card-header" 27 + className={cn( 28 + "gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]", 29 + className, 30 + )} 31 + {...props} 32 + /> 33 + ); 34 + } 35 + 36 + function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 37 + return ( 38 + <div 39 + data-slot="card-title" 40 + className={cn("text-sm font-medium group-data-[size=sm]/card:text-sm", className)} 41 + {...props} 42 + /> 43 + ); 44 + } 45 + 46 + function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 47 + return ( 48 + <div 49 + data-slot="card-description" 50 + className={cn("text-muted-foreground text-xs/relaxed", className)} 51 + {...props} 52 + /> 53 + ); 54 + } 55 + 56 + function CardAction({ className, ...props }: React.ComponentProps<"div">) { 57 + return ( 58 + <div 59 + data-slot="card-action" 60 + className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} 61 + {...props} 62 + /> 63 + ); 64 + } 65 + 66 + function CardContent({ className, ...props }: React.ComponentProps<"div">) { 67 + return ( 68 + <div 69 + data-slot="card-content" 70 + className={cn("px-4 group-data-[size=sm]/card:px-3", className)} 71 + {...props} 72 + /> 73 + ); 74 + } 75 + 76 + function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 77 + return ( 78 + <div 79 + data-slot="card-footer" 80 + className={cn( 81 + "rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", 82 + className, 83 + )} 84 + {...props} 85 + /> 86 + ); 87 + } 88 + 89 + export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
+273
apps/desktop/src/components/ui/combobox.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { Combobox as ComboboxPrimitive } from "@base-ui/react"; 5 + 6 + import { cn } from "@/lib/utils"; 7 + import { Button } from "@/components/ui/button"; 8 + import { 9 + InputGroup, 10 + InputGroupAddon, 11 + InputGroupButton, 12 + InputGroupInput, 13 + } from "@/components/ui/input-group"; 14 + import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"; 15 + 16 + const Combobox = ComboboxPrimitive.Root; 17 + 18 + function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { 19 + return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />; 20 + } 21 + 22 + function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) { 23 + return ( 24 + <ComboboxPrimitive.Trigger 25 + data-slot="combobox-trigger" 26 + className={cn("[&_svg:not([class*='size-'])]:size-4", className)} 27 + {...props} 28 + > 29 + {children} 30 + <ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" /> 31 + </ComboboxPrimitive.Trigger> 32 + ); 33 + } 34 + 35 + function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { 36 + return ( 37 + <ComboboxPrimitive.Clear 38 + data-slot="combobox-clear" 39 + render={<InputGroupButton variant="ghost" size="icon-xs" />} 40 + className={cn(className)} 41 + {...props} 42 + > 43 + <XIcon className="pointer-events-none" /> 44 + </ComboboxPrimitive.Clear> 45 + ); 46 + } 47 + 48 + function ComboboxInput({ 49 + className, 50 + children, 51 + disabled = false, 52 + showTrigger = true, 53 + showClear = false, 54 + ...props 55 + }: ComboboxPrimitive.Input.Props & { 56 + showTrigger?: boolean; 57 + showClear?: boolean; 58 + }) { 59 + return ( 60 + <InputGroup className={cn("w-auto", className)}> 61 + <ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} /> 62 + <InputGroupAddon align="inline-end"> 63 + {showTrigger && ( 64 + <InputGroupButton 65 + size="icon-xs" 66 + variant="ghost" 67 + render={<ComboboxTrigger />} 68 + data-slot="input-group-button" 69 + className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent" 70 + disabled={disabled} 71 + /> 72 + )} 73 + {showClear && <ComboboxClear disabled={disabled} />} 74 + </InputGroupAddon> 75 + {children} 76 + </InputGroup> 77 + ); 78 + } 79 + 80 + function ComboboxContent({ 81 + className, 82 + side = "bottom", 83 + sideOffset = 6, 84 + align = "start", 85 + alignOffset = 0, 86 + anchor, 87 + ...props 88 + }: ComboboxPrimitive.Popup.Props & 89 + Pick< 90 + ComboboxPrimitive.Positioner.Props, 91 + "side" | "align" | "sideOffset" | "alignOffset" | "anchor" 92 + >) { 93 + return ( 94 + <ComboboxPrimitive.Portal> 95 + <ComboboxPrimitive.Positioner 96 + side={side} 97 + sideOffset={sideOffset} 98 + align={align} 99 + alignOffset={alignOffset} 100 + anchor={anchor} 101 + className="isolate z-50" 102 + > 103 + <ComboboxPrimitive.Popup 104 + data-slot="combobox-content" 105 + data-chips={!!anchor} 106 + className={cn( 107 + "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 max-h-72 min-w-36 overflow-hidden rounded-none shadow-md ring-1 duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)", 108 + className, 109 + )} 110 + {...props} 111 + /> 112 + </ComboboxPrimitive.Positioner> 113 + </ComboboxPrimitive.Portal> 114 + ); 115 + } 116 + 117 + function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { 118 + return ( 119 + <ComboboxPrimitive.List 120 + data-slot="combobox-list" 121 + className={cn( 122 + "no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto data-empty:p-0 overflow-y-auto overscroll-contain", 123 + className, 124 + )} 125 + {...props} 126 + /> 127 + ); 128 + } 129 + 130 + function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) { 131 + return ( 132 + <ComboboxPrimitive.Item 133 + data-slot="combobox-item" 134 + className={cn( 135 + "data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", 136 + className, 137 + )} 138 + {...props} 139 + > 140 + {children} 141 + <ComboboxPrimitive.ItemIndicator 142 + render={ 143 + <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" /> 144 + } 145 + > 146 + <CheckIcon className="pointer-events-none" /> 147 + </ComboboxPrimitive.ItemIndicator> 148 + </ComboboxPrimitive.Item> 149 + ); 150 + } 151 + 152 + function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { 153 + return ( 154 + <ComboboxPrimitive.Group data-slot="combobox-group" className={cn(className)} {...props} /> 155 + ); 156 + } 157 + 158 + function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) { 159 + return ( 160 + <ComboboxPrimitive.GroupLabel 161 + data-slot="combobox-label" 162 + className={cn("text-muted-foreground px-2 py-2 text-xs", className)} 163 + {...props} 164 + /> 165 + ); 166 + } 167 + 168 + function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { 169 + return <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />; 170 + } 171 + 172 + function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { 173 + return ( 174 + <ComboboxPrimitive.Empty 175 + data-slot="combobox-empty" 176 + className={cn( 177 + "text-muted-foreground hidden w-full justify-center py-2 text-center text-xs group-data-empty/combobox-content:flex", 178 + className, 179 + )} 180 + {...props} 181 + /> 182 + ); 183 + } 184 + 185 + function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) { 186 + return ( 187 + <ComboboxPrimitive.Separator 188 + data-slot="combobox-separator" 189 + className={cn("bg-border -mx-1 h-px", className)} 190 + {...props} 191 + /> 192 + ); 193 + } 194 + 195 + function ComboboxChips({ 196 + className, 197 + ...props 198 + }: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props) { 199 + return ( 200 + <ComboboxPrimitive.Chips 201 + data-slot="combobox-chips" 202 + className={cn( 203 + "dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-8 flex-wrap items-center gap-1 rounded-none border bg-transparent bg-clip-padding px-2.5 py-1 text-xs transition-colors focus-within:ring-1 has-aria-invalid:ring-1 has-data-[slot=combobox-chip]:px-1", 204 + className, 205 + )} 206 + {...props} 207 + /> 208 + ); 209 + } 210 + 211 + function ComboboxChip({ 212 + className, 213 + children, 214 + showRemove = true, 215 + ...props 216 + }: ComboboxPrimitive.Chip.Props & { 217 + showRemove?: boolean; 218 + }) { 219 + return ( 220 + <ComboboxPrimitive.Chip 221 + data-slot="combobox-chip" 222 + className={cn( 223 + "bg-muted text-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-none px-1.5 text-xs font-medium whitespace-nowrap has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50", 224 + className, 225 + )} 226 + {...props} 227 + > 228 + {children} 229 + {showRemove && ( 230 + <ComboboxPrimitive.ChipRemove 231 + render={<Button variant="ghost" size="icon-xs" />} 232 + className="-ml-1 opacity-50 hover:opacity-100" 233 + data-slot="combobox-chip-remove" 234 + > 235 + <XIcon className="pointer-events-none" /> 236 + </ComboboxPrimitive.ChipRemove> 237 + )} 238 + </ComboboxPrimitive.Chip> 239 + ); 240 + } 241 + 242 + function ComboboxChipsInput({ className, ...props }: ComboboxPrimitive.Input.Props) { 243 + return ( 244 + <ComboboxPrimitive.Input 245 + data-slot="combobox-chip-input" 246 + className={cn("min-w-16 flex-1 outline-none", className)} 247 + {...props} 248 + /> 249 + ); 250 + } 251 + 252 + function useComboboxAnchor() { 253 + return React.useRef<HTMLDivElement | null>(null); 254 + } 255 + 256 + export { 257 + Combobox, 258 + ComboboxInput, 259 + ComboboxContent, 260 + ComboboxList, 261 + ComboboxItem, 262 + ComboboxGroup, 263 + ComboboxLabel, 264 + ComboboxCollection, 265 + ComboboxEmpty, 266 + ComboboxSeparator, 267 + ComboboxChips, 268 + ComboboxChip, 269 + ComboboxChipsInput, 270 + ComboboxTrigger, 271 + ComboboxValue, 272 + useComboboxAnchor, 273 + };
+241
apps/desktop/src/components/ui/dropdown-menu.tsx
··· 1 + import * as React from "react"; 2 + import { Menu as MenuPrimitive } from "@base-ui/react/menu"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + import { ChevronRightIcon, CheckIcon } from "lucide-react"; 6 + 7 + function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { 8 + return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />; 9 + } 10 + 11 + function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) { 12 + return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />; 13 + } 14 + 15 + function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) { 16 + return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />; 17 + } 18 + 19 + function DropdownMenuContent({ 20 + align = "start", 21 + alignOffset = 0, 22 + side = "bottom", 23 + sideOffset = 4, 24 + className, 25 + ...props 26 + }: MenuPrimitive.Popup.Props & 27 + Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) { 28 + return ( 29 + <MenuPrimitive.Portal> 30 + <MenuPrimitive.Positioner 31 + className="isolate z-50 outline-none" 32 + align={align} 33 + alignOffset={alignOffset} 34 + side={side} 35 + sideOffset={sideOffset} 36 + > 37 + <MenuPrimitive.Popup 38 + data-slot="dropdown-menu-content" 39 + className={cn( 40 + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden", 41 + className, 42 + )} 43 + {...props} 44 + /> 45 + </MenuPrimitive.Positioner> 46 + </MenuPrimitive.Portal> 47 + ); 48 + } 49 + 50 + function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) { 51 + return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />; 52 + } 53 + 54 + function DropdownMenuLabel({ 55 + className, 56 + inset, 57 + ...props 58 + }: MenuPrimitive.GroupLabel.Props & { 59 + inset?: boolean; 60 + }) { 61 + return ( 62 + <MenuPrimitive.GroupLabel 63 + data-slot="dropdown-menu-label" 64 + data-inset={inset} 65 + className={cn("text-muted-foreground px-2 py-2 text-xs data-[inset]:pl-8", className)} 66 + {...props} 67 + /> 68 + ); 69 + } 70 + 71 + function DropdownMenuItem({ 72 + className, 73 + inset, 74 + variant = "default", 75 + ...props 76 + }: MenuPrimitive.Item.Props & { 77 + inset?: boolean; 78 + variant?: "default" | "destructive"; 79 + }) { 80 + return ( 81 + <MenuPrimitive.Item 82 + data-slot="dropdown-menu-item" 83 + data-inset={inset} 84 + data-variant={variant} 85 + className={cn( 86 + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0", 87 + className, 88 + )} 89 + {...props} 90 + /> 91 + ); 92 + } 93 + 94 + function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) { 95 + return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />; 96 + } 97 + 98 + function DropdownMenuSubTrigger({ 99 + className, 100 + inset, 101 + children, 102 + ...props 103 + }: MenuPrimitive.SubmenuTrigger.Props & { 104 + inset?: boolean; 105 + }) { 106 + return ( 107 + <MenuPrimitive.SubmenuTrigger 108 + data-slot="dropdown-menu-sub-trigger" 109 + data-inset={inset} 110 + className={cn( 111 + "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0", 112 + className, 113 + )} 114 + {...props} 115 + > 116 + {children} 117 + <ChevronRightIcon className="ml-auto" /> 118 + </MenuPrimitive.SubmenuTrigger> 119 + ); 120 + } 121 + 122 + function DropdownMenuSubContent({ 123 + align = "start", 124 + alignOffset = -3, 125 + side = "right", 126 + sideOffset = 0, 127 + className, 128 + ...props 129 + }: React.ComponentProps<typeof DropdownMenuContent>) { 130 + return ( 131 + <DropdownMenuContent 132 + data-slot="dropdown-menu-sub-content" 133 + className={cn( 134 + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-none shadow-lg ring-1 duration-100 w-auto", 135 + className, 136 + )} 137 + align={align} 138 + alignOffset={alignOffset} 139 + side={side} 140 + sideOffset={sideOffset} 141 + {...props} 142 + /> 143 + ); 144 + } 145 + 146 + function DropdownMenuCheckboxItem({ 147 + className, 148 + children, 149 + checked, 150 + ...props 151 + }: MenuPrimitive.CheckboxItem.Props) { 152 + return ( 153 + <MenuPrimitive.CheckboxItem 154 + data-slot="dropdown-menu-checkbox-item" 155 + className={cn( 156 + "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", 157 + className, 158 + )} 159 + checked={checked} 160 + {...props} 161 + > 162 + <span 163 + className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none" 164 + data-slot="dropdown-menu-checkbox-item-indicator" 165 + > 166 + <MenuPrimitive.CheckboxItemIndicator> 167 + <CheckIcon /> 168 + </MenuPrimitive.CheckboxItemIndicator> 169 + </span> 170 + {children} 171 + </MenuPrimitive.CheckboxItem> 172 + ); 173 + } 174 + 175 + function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) { 176 + return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />; 177 + } 178 + 179 + function DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) { 180 + return ( 181 + <MenuPrimitive.RadioItem 182 + data-slot="dropdown-menu-radio-item" 183 + className={cn( 184 + "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", 185 + className, 186 + )} 187 + {...props} 188 + > 189 + <span 190 + className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none" 191 + data-slot="dropdown-menu-radio-item-indicator" 192 + > 193 + <MenuPrimitive.RadioItemIndicator> 194 + <CheckIcon /> 195 + </MenuPrimitive.RadioItemIndicator> 196 + </span> 197 + {children} 198 + </MenuPrimitive.RadioItem> 199 + ); 200 + } 201 + 202 + function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) { 203 + return ( 204 + <MenuPrimitive.Separator 205 + data-slot="dropdown-menu-separator" 206 + className={cn("bg-border -mx-1 h-px", className)} 207 + {...props} 208 + /> 209 + ); 210 + } 211 + 212 + function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { 213 + return ( 214 + <span 215 + data-slot="dropdown-menu-shortcut" 216 + className={cn( 217 + "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest", 218 + className, 219 + )} 220 + {...props} 221 + /> 222 + ); 223 + } 224 + 225 + export { 226 + DropdownMenu, 227 + DropdownMenuPortal, 228 + DropdownMenuTrigger, 229 + DropdownMenuContent, 230 + DropdownMenuGroup, 231 + DropdownMenuLabel, 232 + DropdownMenuItem, 233 + DropdownMenuCheckboxItem, 234 + DropdownMenuRadioGroup, 235 + DropdownMenuRadioItem, 236 + DropdownMenuSeparator, 237 + DropdownMenuShortcut, 238 + DropdownMenuSub, 239 + DropdownMenuSubTrigger, 240 + DropdownMenuSubContent, 241 + };
+222
apps/desktop/src/components/ui/field.tsx
··· 1 + import { useMemo } from "react"; 2 + import { cva, type VariantProps } from "class-variance-authority"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + import { Label } from "@/components/ui/label"; 6 + import { Separator } from "@/components/ui/separator"; 7 + 8 + function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { 9 + return ( 10 + <fieldset 11 + data-slot="field-set" 12 + className={cn( 13 + "gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col", 14 + className, 15 + )} 16 + {...props} 17 + /> 18 + ); 19 + } 20 + 21 + function FieldLegend({ 22 + className, 23 + variant = "legend", 24 + ...props 25 + }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { 26 + return ( 27 + <legend 28 + data-slot="field-legend" 29 + data-variant={variant} 30 + className={cn( 31 + "mb-2.5 font-medium data-[variant=label]:text-xs data-[variant=legend]:text-sm", 32 + className, 33 + )} 34 + {...props} 35 + /> 36 + ); 37 + } 38 + 39 + function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { 40 + return ( 41 + <div 42 + data-slot="field-group" 43 + className={cn( 44 + "gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col", 45 + className, 46 + )} 47 + {...props} 48 + /> 49 + ); 50 + } 51 + 52 + const fieldVariants = cva("data-[invalid=true]:text-destructive gap-2 group/field flex w-full", { 53 + variants: { 54 + orientation: { 55 + vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto", 56 + horizontal: 57 + "flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", 58 + responsive: 59 + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", 60 + }, 61 + }, 62 + defaultVariants: { 63 + orientation: "vertical", 64 + }, 65 + }); 66 + 67 + function Field({ 68 + className, 69 + orientation = "vertical", 70 + ...props 71 + }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) { 72 + return ( 73 + <div 74 + role="group" 75 + data-slot="field" 76 + data-orientation={orientation} 77 + className={cn(fieldVariants({ orientation }), className)} 78 + {...props} 79 + /> 80 + ); 81 + } 82 + 83 + function FieldContent({ className, ...props }: React.ComponentProps<"div">) { 84 + return ( 85 + <div 86 + data-slot="field-content" 87 + className={cn("gap-0.5 group/field-content flex flex-1 flex-col leading-snug", className)} 88 + {...props} 89 + /> 90 + ); 91 + } 92 + 93 + function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) { 94 + return ( 95 + <Label 96 + data-slot="field-label" 97 + className={cn( 98 + "has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-2 group/field-label peer/field-label flex w-fit leading-snug", 99 + "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", 100 + className, 101 + )} 102 + {...props} 103 + /> 104 + ); 105 + } 106 + 107 + function FieldTitle({ className, ...props }: React.ComponentProps<"div">) { 108 + return ( 109 + <div 110 + data-slot="field-label" 111 + className={cn( 112 + "gap-2 text-xs/relaxed group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug", 113 + className, 114 + )} 115 + {...props} 116 + /> 117 + ); 118 + } 119 + 120 + function FieldDescription({ className, ...props }: React.ComponentProps<"p">) { 121 + return ( 122 + <p 123 + data-slot="field-description" 124 + className={cn( 125 + "text-muted-foreground text-left text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance", 126 + "last:mt-0 nth-last-2:-mt-1", 127 + "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", 128 + className, 129 + )} 130 + {...props} 131 + /> 132 + ); 133 + } 134 + 135 + function FieldSeparator({ 136 + children, 137 + className, 138 + ...props 139 + }: React.ComponentProps<"div"> & { 140 + children?: React.ReactNode; 141 + }) { 142 + return ( 143 + <div 144 + data-slot="field-separator" 145 + data-content={!!children} 146 + className={cn( 147 + "-my-2 h-5 text-xs group-data-[variant=outline]/field-group:-mb-2 relative", 148 + className, 149 + )} 150 + {...props} 151 + > 152 + <Separator className="absolute inset-0 top-1/2" /> 153 + {children && ( 154 + <span 155 + className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit" 156 + data-slot="field-separator-content" 157 + > 158 + {children} 159 + </span> 160 + )} 161 + </div> 162 + ); 163 + } 164 + 165 + function FieldError({ 166 + className, 167 + children, 168 + errors, 169 + ...props 170 + }: React.ComponentProps<"div"> & { 171 + errors?: Array<{ message?: string } | undefined>; 172 + }) { 173 + const content = useMemo(() => { 174 + if (children) { 175 + return children; 176 + } 177 + 178 + if (!errors?.length) { 179 + return null; 180 + } 181 + 182 + const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()]; 183 + 184 + if (uniqueErrors?.length == 1) { 185 + return uniqueErrors[0]?.message; 186 + } 187 + 188 + return ( 189 + <ul className="ml-4 flex list-disc flex-col gap-1"> 190 + {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)} 191 + </ul> 192 + ); 193 + }, [children, errors]); 194 + 195 + if (!content) { 196 + return null; 197 + } 198 + 199 + return ( 200 + <div 201 + role="alert" 202 + data-slot="field-error" 203 + className={cn("text-destructive text-xs font-normal", className)} 204 + {...props} 205 + > 206 + {content} 207 + </div> 208 + ); 209 + } 210 + 211 + export { 212 + Field, 213 + FieldLabel, 214 + FieldDescription, 215 + FieldError, 216 + FieldGroup, 217 + FieldLegend, 218 + FieldSeparator, 219 + FieldSet, 220 + FieldContent, 221 + FieldTitle, 222 + };
+146
apps/desktop/src/components/ui/input-group.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { cva, type VariantProps } from "class-variance-authority"; 5 + 6 + import { cn } from "@/lib/utils"; 7 + import { Button } from "@/components/ui/button"; 8 + import { Input } from "@/components/ui/input"; 9 + import { Textarea } from "@/components/ui/textarea"; 10 + 11 + function InputGroup({ className, ...props }: React.ComponentProps<"div">) { 12 + return ( 13 + <div 14 + data-slot="input-group" 15 + role="group" 16 + className={cn( 17 + "border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 h-8 rounded-none border transition-colors has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-1 has-[[data-slot][aria-invalid=true]]:ring-1 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto", 18 + className, 19 + )} 20 + {...props} 21 + /> 22 + ); 23 + } 24 + 25 + const inputGroupAddonVariants = cva( 26 + "text-muted-foreground h-auto gap-2 py-1.5 text-xs font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-none [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none", 27 + { 28 + variants: { 29 + align: { 30 + "inline-start": "pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first", 31 + "inline-end": "pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last", 32 + "block-start": 33 + "px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start", 34 + "block-end": 35 + "px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start", 36 + }, 37 + }, 38 + defaultVariants: { 39 + align: "inline-start", 40 + }, 41 + }, 42 + ); 43 + 44 + function InputGroupAddon({ 45 + className, 46 + align = "inline-start", 47 + ...props 48 + }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) { 49 + return ( 50 + <div 51 + role="group" 52 + data-slot="input-group-addon" 53 + data-align={align} 54 + className={cn(inputGroupAddonVariants({ align }), className)} 55 + onClick={(e) => { 56 + if ((e.target as HTMLElement).closest("button")) { 57 + return; 58 + } 59 + e.currentTarget.parentElement?.querySelector("input")?.focus(); 60 + }} 61 + {...props} 62 + /> 63 + ); 64 + } 65 + 66 + const inputGroupButtonVariants = cva("gap-2 text-xs shadow-none flex items-center", { 67 + variants: { 68 + size: { 69 + xs: "h-6 gap-1 rounded-none px-1.5 [&>svg:not([class*='size-'])]:size-3.5", 70 + sm: "", 71 + "icon-xs": "size-6 rounded-none p-0 has-[>svg]:p-0", 72 + "icon-sm": "size-8 p-0 has-[>svg]:p-0", 73 + }, 74 + }, 75 + defaultVariants: { 76 + size: "xs", 77 + }, 78 + }); 79 + 80 + function InputGroupButton({ 81 + className, 82 + type = "button", 83 + variant = "ghost", 84 + size = "xs", 85 + ...props 86 + }: Omit<React.ComponentProps<typeof Button>, "size" | "type"> & 87 + VariantProps<typeof inputGroupButtonVariants> & { 88 + type?: "button" | "submit" | "reset"; 89 + }) { 90 + return ( 91 + <Button 92 + type={type} 93 + data-size={size} 94 + variant={variant} 95 + className={cn(inputGroupButtonVariants({ size }), className)} 96 + {...props} 97 + /> 98 + ); 99 + } 100 + 101 + function InputGroupText({ className, ...props }: React.ComponentProps<"span">) { 102 + return ( 103 + <span 104 + className={cn( 105 + "text-muted-foreground gap-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none", 106 + className, 107 + )} 108 + {...props} 109 + /> 110 + ); 111 + } 112 + 113 + function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) { 114 + return ( 115 + <Input 116 + data-slot="input-group-control" 117 + className={cn( 118 + "rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent flex-1", 119 + className, 120 + )} 121 + {...props} 122 + /> 123 + ); 124 + } 125 + 126 + function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) { 127 + return ( 128 + <Textarea 129 + data-slot="input-group-control" 130 + className={cn( 131 + "rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent flex-1 resize-none", 132 + className, 133 + )} 134 + {...props} 135 + /> 136 + ); 137 + } 138 + 139 + export { 140 + InputGroup, 141 + InputGroupAddon, 142 + InputGroupButton, 143 + InputGroupText, 144 + InputGroupInput, 145 + InputGroupTextarea, 146 + };
+20
apps/desktop/src/components/ui/input.tsx
··· 1 + import * as React from "react"; 2 + import { Input as InputPrimitive } from "@base-ui/react/input"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + 6 + function Input({ className, type, ...props }: React.ComponentProps<"input">) { 7 + return ( 8 + <InputPrimitive 9 + type={type} 10 + data-slot="input" 11 + className={cn( 12 + "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", 13 + className, 14 + )} 15 + {...props} 16 + /> 17 + ); 18 + } 19 + 20 + export { Input };
+20
apps/desktop/src/components/ui/label.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + 5 + import { cn } from "@/lib/utils"; 6 + 7 + function Label({ className, ...props }: React.ComponentProps<"label">) { 8 + return ( 9 + <label 10 + data-slot="label" 11 + className={cn( 12 + "gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed", 13 + className, 14 + )} 15 + {...props} 16 + /> 17 + ); 18 + } 19 + 20 + export { Label };
+41
apps/desktop/src/components/ui/resizable.tsx
··· 1 + import * as React from "react"; 2 + import { Group, Panel, Separator } from "react-resizable-panels"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + 6 + function ResizablePanelGroup({ className, ...props }: React.ComponentProps<typeof Group>) { 7 + return ( 8 + <Group 9 + data-slot="resizable-panel-group" 10 + className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)} 11 + {...props} 12 + /> 13 + ); 14 + } 15 + 16 + function ResizablePanel({ ...props }: React.ComponentProps<typeof Panel>) { 17 + return <Panel data-slot="resizable-panel" {...props} />; 18 + } 19 + 20 + function ResizableHandle({ 21 + withHandle, 22 + className, 23 + ...props 24 + }: React.ComponentProps<typeof Separator> & { 25 + withHandle?: boolean; 26 + }) { 27 + return ( 28 + <Separator 29 + data-slot="resizable-handle" 30 + className={cn( 31 + "bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90", 32 + className, 33 + )} 34 + {...props} 35 + > 36 + {withHandle && <div className="bg-border h-6 w-1 rounded-none z-10 flex shrink-0" />} 37 + </Separator> 38 + ); 39 + } 40 + 41 + export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
+50
apps/desktop/src/components/ui/scroll-area.tsx
··· 1 + "use client"; 2 + 3 + import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; 4 + 5 + import { cn } from "@/lib/utils"; 6 + 7 + function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) { 8 + return ( 9 + <ScrollAreaPrimitive.Root 10 + data-slot="scroll-area" 11 + className={cn("relative", className)} 12 + {...props} 13 + > 14 + <ScrollAreaPrimitive.Viewport 15 + data-slot="scroll-area-viewport" 16 + className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" 17 + > 18 + {children} 19 + </ScrollAreaPrimitive.Viewport> 20 + <ScrollBar /> 21 + <ScrollAreaPrimitive.Corner /> 22 + </ScrollAreaPrimitive.Root> 23 + ); 24 + } 25 + 26 + function ScrollBar({ 27 + className, 28 + orientation = "vertical", 29 + ...props 30 + }: ScrollAreaPrimitive.Scrollbar.Props) { 31 + return ( 32 + <ScrollAreaPrimitive.Scrollbar 33 + data-slot="scroll-area-scrollbar" 34 + data-orientation={orientation} 35 + orientation={orientation} 36 + className={cn( 37 + "data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none", 38 + className, 39 + )} 40 + {...props} 41 + > 42 + <ScrollAreaPrimitive.Thumb 43 + data-slot="scroll-area-thumb" 44 + className="rounded-none bg-border relative flex-1" 45 + /> 46 + </ScrollAreaPrimitive.Scrollbar> 47 + ); 48 + } 49 + 50 + export { ScrollArea, ScrollBar };
+189
apps/desktop/src/components/ui/select.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { Select as SelectPrimitive } from "@base-ui/react/select"; 5 + 6 + import { cn } from "@/lib/utils"; 7 + import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"; 8 + 9 + const Select = SelectPrimitive.Root; 10 + 11 + function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { 12 + return ( 13 + <SelectPrimitive.Group 14 + data-slot="select-group" 15 + className={cn("scroll-my-1", className)} 16 + {...props} 17 + /> 18 + ); 19 + } 20 + 21 + function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { 22 + return ( 23 + <SelectPrimitive.Value 24 + data-slot="select-value" 25 + className={cn("flex flex-1 text-left", className)} 26 + {...props} 27 + /> 28 + ); 29 + } 30 + 31 + function SelectTrigger({ 32 + className, 33 + size = "default", 34 + children, 35 + ...props 36 + }: SelectPrimitive.Trigger.Props & { 37 + size?: "sm" | "default"; 38 + }) { 39 + return ( 40 + <SelectPrimitive.Trigger 41 + data-slot="select-trigger" 42 + data-size={size} 43 + className={cn( 44 + "border-input data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-none border bg-transparent py-2 pr-2 pl-2.5 text-xs transition-colors select-none focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0", 45 + className, 46 + )} 47 + {...props} 48 + > 49 + {children} 50 + <SelectPrimitive.Icon 51 + render={<ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />} 52 + /> 53 + </SelectPrimitive.Trigger> 54 + ); 55 + } 56 + 57 + function SelectContent({ 58 + className, 59 + children, 60 + side = "bottom", 61 + sideOffset = 4, 62 + align = "center", 63 + alignOffset = 0, 64 + alignItemWithTrigger = true, 65 + ...props 66 + }: SelectPrimitive.Popup.Props & 67 + Pick< 68 + SelectPrimitive.Positioner.Props, 69 + "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" 70 + >) { 71 + return ( 72 + <SelectPrimitive.Portal> 73 + <SelectPrimitive.Positioner 74 + side={side} 75 + sideOffset={sideOffset} 76 + align={align} 77 + alignOffset={alignOffset} 78 + alignItemWithTrigger={alignItemWithTrigger} 79 + className="isolate z-50" 80 + > 81 + <SelectPrimitive.Popup 82 + data-slot="select-content" 83 + className={cn( 84 + "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 min-w-36 rounded-none shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto", 85 + className, 86 + )} 87 + {...props} 88 + > 89 + <SelectScrollUpButton /> 90 + <SelectPrimitive.List>{children}</SelectPrimitive.List> 91 + <SelectScrollDownButton /> 92 + </SelectPrimitive.Popup> 93 + </SelectPrimitive.Positioner> 94 + </SelectPrimitive.Portal> 95 + ); 96 + } 97 + 98 + function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) { 99 + return ( 100 + <SelectPrimitive.GroupLabel 101 + data-slot="select-label" 102 + className={cn("text-muted-foreground px-2 py-2 text-xs", className)} 103 + {...props} 104 + /> 105 + ); 106 + } 107 + 108 + function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) { 109 + return ( 110 + <SelectPrimitive.Item 111 + data-slot="select-item" 112 + className={cn( 113 + "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", 114 + className, 115 + )} 116 + {...props} 117 + > 118 + <SelectPrimitive.ItemText className="flex flex-1 gap-2 shrink-0 whitespace-nowrap"> 119 + {children} 120 + </SelectPrimitive.ItemText> 121 + <SelectPrimitive.ItemIndicator 122 + render={ 123 + <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" /> 124 + } 125 + > 126 + <CheckIcon className="pointer-events-none" /> 127 + </SelectPrimitive.ItemIndicator> 128 + </SelectPrimitive.Item> 129 + ); 130 + } 131 + 132 + function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) { 133 + return ( 134 + <SelectPrimitive.Separator 135 + data-slot="select-separator" 136 + className={cn("bg-border -mx-1 h-px pointer-events-none", className)} 137 + {...props} 138 + /> 139 + ); 140 + } 141 + 142 + function SelectScrollUpButton({ 143 + className, 144 + ...props 145 + }: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) { 146 + return ( 147 + <SelectPrimitive.ScrollUpArrow 148 + data-slot="select-scroll-up-button" 149 + className={cn( 150 + "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full", 151 + className, 152 + )} 153 + {...props} 154 + > 155 + <ChevronUpIcon /> 156 + </SelectPrimitive.ScrollUpArrow> 157 + ); 158 + } 159 + 160 + function SelectScrollDownButton({ 161 + className, 162 + ...props 163 + }: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) { 164 + return ( 165 + <SelectPrimitive.ScrollDownArrow 166 + data-slot="select-scroll-down-button" 167 + className={cn( 168 + "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full", 169 + className, 170 + )} 171 + {...props} 172 + > 173 + <ChevronDownIcon /> 174 + </SelectPrimitive.ScrollDownArrow> 175 + ); 176 + } 177 + 178 + export { 179 + Select, 180 + SelectContent, 181 + SelectGroup, 182 + SelectItem, 183 + SelectLabel, 184 + SelectScrollDownButton, 185 + SelectScrollUpButton, 186 + SelectSeparator, 187 + SelectTrigger, 188 + SelectValue, 189 + };
+19
apps/desktop/src/components/ui/separator.tsx
··· 1 + import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) { 6 + return ( 7 + <SeparatorPrimitive 8 + data-slot="separator" 9 + orientation={orientation} 10 + className={cn( 11 + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch", 12 + className, 13 + )} 14 + {...props} 15 + /> 16 + ); 17 + } 18 + 19 + export { Separator };
+18
apps/desktop/src/components/ui/textarea.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 + return ( 7 + <textarea 8 + data-slot="textarea" 9 + className={cn( 10 + "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-none border bg-transparent px-2.5 py-2 text-xs transition-colors focus-visible:ring-1 aria-invalid:ring-1 md:text-xs placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50", 11 + className, 12 + )} 13 + {...props} 14 + /> 15 + ); 16 + } 17 + 18 + export { Textarea };
+66
apps/desktop/src/components/ui/tooltip.tsx
··· 1 + import * as React from "react"; 2 + import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + 6 + function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) { 7 + return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />; 8 + } 9 + 10 + function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { 11 + return ( 12 + <TooltipProvider> 13 + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> 14 + </TooltipProvider> 15 + ); 16 + } 17 + 18 + function TooltipTrigger({ 19 + children, 20 + ...props 21 + }: TooltipPrimitive.Trigger.Props) { 22 + return ( 23 + <TooltipPrimitive.Trigger 24 + data-slot="tooltip-trigger" 25 + render={children as React.ReactElement} 26 + {...props} 27 + /> 28 + ); 29 + } 30 + 31 + function TooltipContent({ 32 + className, 33 + side = "top", 34 + sideOffset = 4, 35 + align = "center", 36 + alignOffset = 0, 37 + children, 38 + ...props 39 + }: TooltipPrimitive.Popup.Props & 40 + Pick<TooltipPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) { 41 + return ( 42 + <TooltipPrimitive.Portal> 43 + <TooltipPrimitive.Positioner 44 + align={align} 45 + alignOffset={alignOffset} 46 + side={side} 47 + sideOffset={sideOffset} 48 + className="isolate z-50" 49 + > 50 + <TooltipPrimitive.Popup 51 + data-slot="tooltip-content" 52 + className={cn( 53 + "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-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 rounded-none px-3 py-1.5 text-xs bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)", 54 + className, 55 + )} 56 + {...props} 57 + > 58 + {children} 59 + <TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" /> 60 + </TooltipPrimitive.Popup> 61 + </TooltipPrimitive.Positioner> 62 + </TooltipPrimitive.Portal> 63 + ); 64 + } 65 + 66 + export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+6
apps/desktop/src/lib/utils.ts
··· 1 + import { clsx, type ClassValue } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]) { 5 + return twMerge(clsx(inputs)); 6 + }