From 01b546cc816c4fb6b7389e5122b7802d7e724a2b Mon Sep 17 00:00:00 2001 From: 苏向夜 Date: Sun, 22 Feb 2026 19:47:13 +0800 Subject: refactor(shadcn): use base lyra instead --- packages/ui-new/src/components/ui/badge.tsx | 48 ++++---- packages/ui-new/src/components/ui/button.tsx | 44 +++---- packages/ui-new/src/components/ui/card.tsx | 25 ++-- packages/ui-new/src/components/ui/checkbox.tsx | 15 +-- packages/ui-new/src/components/ui/dialog.tsx | 80 +++++++----- packages/ui-new/src/components/ui/input.tsx | 7 +- packages/ui-new/src/components/ui/label.tsx | 13 +- packages/ui-new/src/components/ui/scroll-area.tsx | 23 ++-- packages/ui-new/src/components/ui/select.tsx | 143 ++++++++++++---------- packages/ui-new/src/components/ui/separator.tsx | 11 +- packages/ui-new/src/components/ui/sonner.tsx | 5 + packages/ui-new/src/components/ui/switch.tsx | 17 +-- packages/ui-new/src/components/ui/tabs.tsx | 62 ++++++---- packages/ui-new/src/components/ui/textarea.tsx | 2 +- 14 files changed, 268 insertions(+), 227 deletions(-) (limited to 'packages/ui-new/src/components/ui') diff --git a/packages/ui-new/src/components/ui/badge.tsx b/packages/ui-new/src/components/ui/badge.tsx index ccfa4e7..425ab9e 100644 --- a/packages/ui-new/src/components/ui/badge.tsx +++ b/packages/ui-new/src/components/ui/badge.tsx @@ -1,22 +1,24 @@ -import { Slot } from "@radix-ui/react-slot"; +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; import { cn } from "@/lib/utils"; const badgeVariants = cva( - "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>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-[color,box-shadow] overflow-hidden", + "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 overflow-hidden group/badge", { variants: { variant: { - default: - "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", secondary: - "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: - "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "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", outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", }, }, defaultVariants: { @@ -27,20 +29,24 @@ const badgeVariants = cva( function Badge({ className, - variant, - asChild = false, + variant = "default", + render, ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span"; - - return ( - - ); +}: useRender.ComponentProps<"span"> & VariantProps) { + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">( + { + className: cn(badgeVariants({ variant }), className), + }, + props, + ), + render, + state: { + slot: "badge", + variant, + }, + }); } export { Badge, badgeVariants }; diff --git a/packages/ui-new/src/components/ui/button.tsx b/packages/ui-new/src/components/ui/button.tsx index be181b0..7dee494 100644 --- a/packages/ui-new/src/components/ui/button.tsx +++ b/packages/ui-new/src/components/ui/button.tsx @@ -1,32 +1,34 @@ -import { Slot } from "@radix-ui/react-slot"; +import { Button as ButtonPrimitive } from "@base-ui/react/button"; import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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", + "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", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "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", secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground", + destructive: + "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", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + 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", + 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", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + icon: "size-8", + "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-7 rounded-none", + "icon-lg": "size-9", }, }, defaultVariants: { @@ -40,19 +42,11 @@ function Button({ className, variant = "default", size = "default", - asChild = false, ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean; - }) { - const Comp = asChild ? Slot : "button"; - +}: ButtonPrimitive.Props & VariantProps) { return ( - diff --git a/packages/ui-new/src/components/ui/card.tsx b/packages/ui-new/src/components/ui/card.tsx index cc1ff8a..b7084a0 100644 --- a/packages/ui-new/src/components/ui/card.tsx +++ b/packages/ui-new/src/components/ui/card.tsx @@ -2,12 +2,17 @@ import type * as React from "react"; import { cn } from "@/lib/utils"; -function Card({ className, ...props }: React.ComponentProps<"div">) { +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { return (
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", className, )} {...props} @@ -20,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
) { return (
); @@ -42,7 +50,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return (
); @@ -65,7 +73,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { return (
); @@ -75,7 +83,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); diff --git a/packages/ui-new/src/components/ui/checkbox.tsx b/packages/ui-new/src/components/ui/checkbox.tsx index e771797..9f22cea 100644 --- a/packages/ui-new/src/components/ui/checkbox.tsx +++ b/packages/ui-new/src/components/ui/checkbox.tsx @@ -1,29 +1,24 @@ "use client"; -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"; import { CheckIcon } from "lucide-react"; -import type * as React from "react"; - import { cn } from "@/lib/utils"; -function Checkbox({ - className, - ...props -}: React.ComponentProps) { +function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) { return ( - + ); diff --git a/packages/ui-new/src/components/ui/dialog.tsx b/packages/ui-new/src/components/ui/dialog.tsx index fc2261a..033b47c 100644 --- a/packages/ui-new/src/components/ui/dialog.tsx +++ b/packages/ui-new/src/components/ui/dialog.tsx @@ -1,42 +1,36 @@ -import * as DialogPrimitive from "@radix-ui/react-dialog"; +"use client"; + +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; import { XIcon } from "lucide-react"; import type * as React from "react"; - +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -function Dialog({ - ...props -}: React.ComponentProps) { +function Dialog({ ...props }: DialogPrimitive.Root.Props) { return ; } -function DialogTrigger({ - ...props -}: React.ComponentProps) { +function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) { return ; } -function DialogPortal({ - ...props -}: React.ComponentProps) { +function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) { return ; } -function DialogClose({ - ...props -}: React.ComponentProps) { +function DialogClose({ ...props }: DialogPrimitive.Close.Props) { return ; } function DialogOverlay({ className, ...props -}: React.ComponentProps) { +}: DialogPrimitive.Backdrop.Props) { return ( - & { +}: DialogPrimitive.Popup.Props & { showCloseButton?: boolean; }) { return ( - + - + } > Close )} - + ); } @@ -82,13 +82,20 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { return (
); } -function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean; +}) { return (
) { className, )} {...props} - /> + > + {children} + {showCloseButton && ( + }> + Close + + )} +
); } -function DialogTitle({ - className, - ...props -}: React.ComponentProps) { +function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { return ( ); @@ -117,11 +128,14 @@ function DialogTitle({ function DialogDescription({ className, ...props -}: React.ComponentProps) { +}: DialogPrimitive.Description.Props) { return ( ); diff --git a/packages/ui-new/src/components/ui/input.tsx b/packages/ui-new/src/components/ui/input.tsx index 73ea867..bb0390a 100644 --- a/packages/ui-new/src/components/ui/input.tsx +++ b/packages/ui-new/src/components/ui/input.tsx @@ -1,16 +1,15 @@ +import { Input as InputPrimitive } from "@base-ui/react/input"; import type * as React from "react"; import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( - ) { +function Label({ className, ...props }: React.ComponentProps<"label">) { return ( - ) { +}: ScrollAreaPrimitive.Root.Props) { return ( ) { +}: ScrollAreaPrimitive.Scrollbar.Props) { return ( - - - + ); } diff --git a/packages/ui-new/src/components/ui/select.tsx b/packages/ui-new/src/components/ui/select.tsx index c611948..210adba 100644 --- a/packages/ui-new/src/components/ui/select.tsx +++ b/packages/ui-new/src/components/ui/select.tsx @@ -1,25 +1,28 @@ -import * as SelectPrimitive from "@radix-ui/react-select"; +import { Select as SelectPrimitive } from "@base-ui/react/select"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import type * as React from "react"; - import { cn } from "@/lib/utils"; -function Select({ - ...props -}: React.ComponentProps) { - return ; -} +const Select = SelectPrimitive.Root; -function SelectGroup({ - ...props -}: React.ComponentProps) { - return ; +function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { + return ( + + ); } -function SelectValue({ - ...props -}: React.ComponentProps) { - return ; +function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { + return ( + + ); } function SelectTrigger({ @@ -27,7 +30,7 @@ function SelectTrigger({ size = "default", children, ...props -}: React.ComponentProps & { +}: SelectPrimitive.Trigger.Props & { size?: "sm" | "default"; }) { return ( @@ -35,15 +38,17 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground 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:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "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]: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", className, )} {...props} > {children} - - - + + } + /> ); } @@ -51,36 +56,41 @@ function SelectTrigger({ function SelectContent({ className, children, - position = "item-aligned", + side = "bottom", + sideOffset = 4, align = "center", + alignOffset = 0, + alignItemWithTrigger = true, ...props -}: React.ComponentProps) { +}: SelectPrimitive.Popup.Props & + Pick< + SelectPrimitive.Positioner.Props, + "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" + >) { return ( - - - - {children} - - - + + {children} + + + ); } @@ -88,11 +98,11 @@ function SelectContent({ function SelectLabel({ className, ...props -}: React.ComponentProps) { +}: SelectPrimitive.GroupLabel.Props) { return ( - ); @@ -102,25 +112,26 @@ function SelectItem({ className, children, ...props -}: React.ComponentProps) { +}: SelectPrimitive.Item.Props) { return ( - + {children} + + + } > - - - - - {children} + + ); } @@ -128,11 +139,11 @@ function SelectItem({ function SelectSeparator({ className, ...props -}: React.ComponentProps) { +}: SelectPrimitive.Separator.Props) { return ( ); @@ -141,36 +152,36 @@ function SelectSeparator({ function SelectScrollUpButton({ className, ...props -}: React.ComponentProps) { +}: React.ComponentProps) { return ( - - - + + ); } function SelectScrollDownButton({ className, ...props -}: React.ComponentProps) { +}: React.ComponentProps) { return ( - - - + + ); } diff --git a/packages/ui-new/src/components/ui/separator.tsx b/packages/ui-new/src/components/ui/separator.tsx index 50733e0..e91a862 100644 --- a/packages/ui-new/src/components/ui/separator.tsx +++ b/packages/ui-new/src/components/ui/separator.tsx @@ -1,23 +1,20 @@ "use client"; -import * as SeparatorPrimitive from "@radix-ui/react-separator"; -import type * as React from "react"; +import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"; import { cn } from "@/lib/utils"; function Separator({ className, orientation = "horizontal", - decorative = true, ...props -}: React.ComponentProps) { +}: SeparatorPrimitive.Props) { return ( - { "--border-radius": "var(--radius)", } as React.CSSProperties } + toastOptions={{ + classNames: { + toast: "cn-toast", + }, + }} {...props} /> ); diff --git a/packages/ui-new/src/components/ui/switch.tsx b/packages/ui-new/src/components/ui/switch.tsx index 14b3b5b..fef14e3 100644 --- a/packages/ui-new/src/components/ui/switch.tsx +++ b/packages/ui-new/src/components/ui/switch.tsx @@ -1,26 +1,29 @@ -import * as SwitchPrimitive from "@radix-ui/react-switch"; -import type * as React from "react"; +"use client"; + +import { Switch as SwitchPrimitive } from "@base-ui/react/switch"; import { cn } from "@/lib/utils"; function Switch({ className, + size = "default", ...props -}: React.ComponentProps) { +}: SwitchPrimitive.Root.Props & { + size?: "sm" | "default"; +}) { return ( ); diff --git a/packages/ui-new/src/components/ui/tabs.tsx b/packages/ui-new/src/components/ui/tabs.tsx index 2da77f2..6349f40 100644 --- a/packages/ui-new/src/components/ui/tabs.tsx +++ b/packages/ui-new/src/components/ui/tabs.tsx @@ -1,48 +1,65 @@ -"use client"; - -import * as TabsPrimitive from "@radix-ui/react-tabs"; -import type * as React from "react"; +import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"; +import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; function Tabs({ className, + orientation = "horizontal", ...props -}: React.ComponentProps) { +}: TabsPrimitive.Root.Props) { return ( ); } +const tabsListVariants = cva( + "rounded-none p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col", + { + variants: { + variant: { + default: "bg-muted", + line: "gap-1 bg-transparent", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + function TabsList({ className, + variant = "default", ...props -}: React.ComponentProps) { +}: TabsPrimitive.List.Props & VariantProps) { return ( ); } -function TabsTrigger({ - className, - ...props -}: React.ComponentProps) { +function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) { return ( - ) { +function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) { return ( - ); } -export { Tabs, TabsList, TabsTrigger, TabsContent }; +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }; diff --git a/packages/ui-new/src/components/ui/textarea.tsx b/packages/ui-new/src/components/ui/textarea.tsx index 4f6221b..3c3e5d0 100644 --- a/packages/ui-new/src/components/ui/textarea.tsx +++ b/packages/ui-new/src/components/ui/textarea.tsx @@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {