kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo

refactor(ui): remove Radix Slot and use Base UI render composition

Andrej f6778ed4 6723296a

+164 -28
+20 -5
apps/web/src/components/ui/button.tsx
··· 3 3 import { mergeProps } from "@base-ui/react/merge-props"; 4 4 import { useRender } from "@base-ui/react/use-render"; 5 5 import { cva, type VariantProps } from "class-variance-authority"; 6 - import type * as React from "react"; 6 + import * as React from "react"; 7 7 8 8 import { cn } from "@/lib/cn"; 9 9 ··· 33 33 default: 34 34 "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-primary/90", 35 35 destructive: 36 - "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive shadow-destructive/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-destructive/90", 36 + "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-destructive/90", 37 37 "destructive-outline": 38 38 "border-input bg-popover not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4", 39 39 ghost: ··· 49 49 ); 50 50 51 51 interface ButtonProps extends useRender.ComponentProps<"button"> { 52 + asChild?: boolean; 52 53 variant?: VariantProps<typeof buttonVariants>["variant"]; 53 54 size?: VariantProps<typeof buttonVariants>["size"]; 54 55 } 55 56 56 - function Button({ className, variant, size, render, ...props }: ButtonProps) { 57 + function Button({ 58 + asChild = false, 59 + className, 60 + children, 61 + variant, 62 + size, 63 + render, 64 + ...props 65 + }: ButtonProps) { 57 66 const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] = 58 67 render ? undefined : "button"; 59 68 ··· 63 72 type: typeValue, 64 73 }; 65 74 75 + const resolvedRender = 76 + asChild && React.isValidElement(children) ? children : render; 77 + 66 78 return useRender({ 67 79 defaultTagName: "button", 68 - props: mergeProps<"button">(defaultProps, props), 69 - render, 80 + props: mergeProps<"button">(defaultProps, { 81 + ...props, 82 + children: asChild ? undefined : children, 83 + }), 84 + render: resolvedRender, 70 85 }); 71 86 } 72 87
+11 -3
apps/web/src/components/ui/collapsible.tsx
··· 1 1 "use client"; 2 2 3 3 import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"; 4 + import * as React from "react"; 4 5 5 6 import { cn } from "@/lib/cn"; 6 7 ··· 9 10 } 10 11 11 12 function CollapsibleTrigger({ 13 + asChild = false, 12 14 className, 15 + children, 13 16 render, 14 17 nativeButton, 15 18 ...props 16 - }: CollapsiblePrimitive.Trigger.Props) { 19 + }: CollapsiblePrimitive.Trigger.Props & { asChild?: boolean }) { 20 + const resolvedRender = 21 + asChild && React.isValidElement(children) ? children : render; 22 + 17 23 return ( 18 24 <CollapsiblePrimitive.Trigger 19 25 className={cn("cursor-pointer", className)} 20 26 data-slot="collapsible-trigger" 21 27 nativeButton={render ? false : nativeButton} 22 - render={render} 28 + render={resolvedRender} 23 29 {...props} 24 - /> 30 + > 31 + {asChild ? undefined : children} 32 + </CollapsiblePrimitive.Trigger> 25 33 ); 26 34 } 27 35
+51 -6
apps/web/src/components/ui/dialog.tsx
··· 2 2 3 3 import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; 4 4 import { XIcon } from "lucide-react"; 5 + import * as React from "react"; 5 6 import { Button } from "@/components/ui/button"; 6 7 import { ScrollArea } from "@/components/ui/scroll-area"; 7 8 import { cn } from "@/lib/cn"; ··· 12 13 13 14 const DialogPortal = DialogPrimitive.Portal; 14 15 15 - function DialogTrigger(props: DialogPrimitive.Trigger.Props) { 16 - return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; 16 + function DialogTrigger({ 17 + asChild = false, 18 + children, 19 + render, 20 + ...props 21 + }: DialogPrimitive.Trigger.Props & { asChild?: boolean }) { 22 + const resolvedRender = 23 + asChild && React.isValidElement(children) ? children : render; 24 + 25 + return ( 26 + <DialogPrimitive.Trigger 27 + data-slot="dialog-trigger" 28 + render={resolvedRender} 29 + {...props} 30 + > 31 + {asChild ? undefined : children} 32 + </DialogPrimitive.Trigger> 33 + ); 17 34 } 18 35 19 - function DialogClose(props: DialogPrimitive.Close.Props) { 20 - return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; 36 + function DialogClose({ 37 + asChild = false, 38 + children, 39 + render, 40 + ...props 41 + }: DialogPrimitive.Close.Props & { asChild?: boolean }) { 42 + const resolvedRender = 43 + asChild && React.isValidElement(children) ? children : render; 44 + 45 + return ( 46 + <DialogPrimitive.Close 47 + data-slot="dialog-close" 48 + render={resolvedRender} 49 + {...props} 50 + > 51 + {asChild ? undefined : children} 52 + </DialogPrimitive.Close> 53 + ); 21 54 } 22 55 23 56 function DialogBackdrop({ ··· 132 165 ); 133 166 } 134 167 135 - function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { 168 + function DialogTitle({ 169 + asChild = false, 170 + className, 171 + children, 172 + render, 173 + ...props 174 + }: DialogPrimitive.Title.Props & { asChild?: boolean }) { 175 + const resolvedRender = 176 + asChild && React.isValidElement(children) ? children : render; 177 + 136 178 return ( 137 179 <DialogPrimitive.Title 138 180 className={cn( ··· 140 182 className, 141 183 )} 142 184 data-slot="dialog-title" 185 + render={resolvedRender} 143 186 {...props} 144 - /> 187 + > 188 + {asChild ? undefined : children} 189 + </DialogPrimitive.Title> 145 190 ); 146 191 } 147 192
+11 -2
apps/web/src/components/ui/kbd.tsx
··· 29 29 keys, 30 30 className, 31 31 description, 32 + separator = "+", 32 33 }: { 33 34 keys: string[]; 34 35 className?: string; 35 36 description?: string; 37 + separator?: string; 36 38 }) { 37 39 return ( 38 40 <KbdGroup aria-label={description} className={className}> 39 - {keys.map((key) => ( 40 - <Kbd key={key}>{key}</Kbd> 41 + {keys.map((key, index) => ( 42 + <span key={key} className="inline-flex items-center gap-1"> 43 + <Kbd>{key}</Kbd> 44 + {separator && index < keys.length - 1 ? ( 45 + <span className="text-muted-foreground/72 text-xs"> 46 + {separator} 47 + </span> 48 + ) : null} 49 + </span> 41 50 ))} 42 51 </KbdGroup> 43 52 );
+9 -3
apps/web/src/components/ui/menu.tsx
··· 2 2 3 3 import { Menu as MenuPrimitive } from "@base-ui/react/menu"; 4 4 import { ChevronRightIcon } from "lucide-react"; 5 - import type * as React from "react"; 5 + import * as React from "react"; 6 6 7 7 import { cn } from "@/lib/cn"; 8 8 ··· 13 13 const MenuPortal = MenuPrimitive.Portal; 14 14 15 15 function MenuTrigger({ 16 + asChild = false, 16 17 className, 17 18 children, 19 + render, 18 20 ...props 19 - }: MenuPrimitive.Trigger.Props) { 21 + }: MenuPrimitive.Trigger.Props & { asChild?: boolean }) { 22 + const resolvedRender = 23 + asChild && React.isValidElement(children) ? children : render; 24 + 20 25 return ( 21 26 <MenuPrimitive.Trigger 22 27 className={className} 23 28 data-slot="menu-trigger" 29 + render={resolvedRender} 24 30 {...props} 25 31 > 26 - {children} 32 + {asChild ? undefined : children} 27 33 </MenuPrimitive.Trigger> 28 34 ); 29 35 }
+11 -4
apps/web/src/components/ui/popover.tsx
··· 1 1 "use client"; 2 2 3 3 import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; 4 + import * as React from "react"; 4 5 5 6 import { cn } from "@/lib/cn"; 6 7 ··· 9 10 const Popover = PopoverPrimitive.Root; 10 11 11 12 function PopoverTrigger({ 13 + asChild = false, 12 14 className, 13 15 children, 16 + render, 14 17 ...props 15 - }: PopoverPrimitive.Trigger.Props) { 18 + }: PopoverPrimitive.Trigger.Props & { asChild?: boolean }) { 19 + const resolvedRender = 20 + asChild && React.isValidElement(children) ? children : render; 21 + 16 22 return ( 17 23 <PopoverPrimitive.Trigger 18 24 className={className} 19 25 data-slot="popover-trigger" 26 + render={resolvedRender} 20 27 {...props} 21 28 > 22 - {children} 29 + {asChild ? undefined : children} 23 30 </PopoverPrimitive.Trigger> 24 31 ); 25 32 } ··· 65 72 > 66 73 <PopoverPrimitive.Viewport 67 74 className={cn( 68 - "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) py-4 [--viewport-inline-padding:--spacing(4)] has-data-[slot=calendar]:p-2 data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity", 75 + "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) py-0.5 [--viewport-inline-padding:--spacing(1)] has-data-[slot=calendar]:p-2 data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity", 69 76 tooltipStyle 70 - ? "py-1 [--viewport-inline-padding:--spacing(2)]" 77 + ? "py-0.5 [--viewport-inline-padding:--spacing(1)]" 71 78 : "not-data-transitioning:overflow-y-auto", 72 79 )} 73 80 data-slot="popover-viewport"
+32 -3
apps/web/src/components/ui/preview-card.tsx
··· 1 1 "use client"; 2 2 3 3 import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"; 4 + import * as React from "react"; 4 5 5 6 import { cn } from "@/lib/cn"; 6 7 7 - const PreviewCard = PreviewCardPrimitive.Root; 8 + function PreviewCard({ 9 + closeDelay, 10 + openDelay, 11 + ...props 12 + }: PreviewCardPrimitive.Root.Props & { 13 + openDelay?: number; 14 + closeDelay?: number; 15 + }) { 16 + void openDelay; 17 + void closeDelay; 18 + return <PreviewCardPrimitive.Root {...props} />; 19 + } 20 + 21 + function PreviewCardTrigger({ 22 + asChild = false, 23 + children, 24 + render, 25 + ...props 26 + }: PreviewCardPrimitive.Trigger.Props & { asChild?: boolean }) { 27 + const resolvedRender = 28 + asChild && React.isValidElement(children) ? children : render; 8 29 9 - function PreviewCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) { 10 30 return ( 11 - <PreviewCardPrimitive.Trigger data-slot="preview-card-trigger" {...props} /> 31 + <PreviewCardPrimitive.Trigger 32 + data-slot="preview-card-trigger" 33 + render={resolvedRender} 34 + {...props} 35 + > 36 + {asChild ? undefined : children} 37 + </PreviewCardPrimitive.Trigger> 12 38 ); 13 39 } 14 40 ··· 16 42 className, 17 43 children, 18 44 align = "center", 45 + side = "top", 19 46 sideOffset = 4, 20 47 anchor, 21 48 ...props 22 49 }: PreviewCardPrimitive.Popup.Props & { 23 50 align?: PreviewCardPrimitive.Positioner.Props["align"]; 51 + side?: PreviewCardPrimitive.Positioner.Props["side"]; 24 52 sideOffset?: PreviewCardPrimitive.Positioner.Props["sideOffset"]; 25 53 anchor?: PreviewCardPrimitive.Positioner.Props["anchor"]; 26 54 }) { ··· 31 59 anchor={anchor} 32 60 className="z-50" 33 61 data-slot="preview-card-positioner" 62 + side={side} 34 63 sideOffset={sideOffset} 35 64 > 36 65 <PreviewCardPrimitive.Popup
+19 -2
apps/web/src/components/ui/tooltip.tsx
··· 1 1 "use client"; 2 2 3 3 import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; 4 + import * as React from "react"; 4 5 5 6 import { cn } from "@/lib/cn"; 6 7 ··· 10 11 11 12 const Tooltip = TooltipPrimitive.Root; 12 13 13 - function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) { 14 - return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; 14 + function TooltipTrigger({ 15 + asChild = false, 16 + children, 17 + render, 18 + ...props 19 + }: TooltipPrimitive.Trigger.Props & { asChild?: boolean }) { 20 + const resolvedRender = 21 + asChild && React.isValidElement(children) ? children : render; 22 + 23 + return ( 24 + <TooltipPrimitive.Trigger 25 + data-slot="tooltip-trigger" 26 + render={resolvedRender} 27 + {...props} 28 + > 29 + {asChild ? undefined : children} 30 + </TooltipPrimitive.Trigger> 31 + ); 15 32 } 16 33 17 34 function TooltipPopup({