Migrate UI primitives from Radix to Base UI.
Local Build and Deploy / deploy (push) Successful in 54s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 18:31:07 +02:00
1 parent 90cff7e5d5
commit 25f01b61b3
18 files changed
+510 -846

No files matched your search

+1 -11
View File
@@ -21,23 +21,13 @@
"jobs:worker": "tsx scripts/jobs-worker.ts" "jobs:worker": "tsx scripts/jobs-worker.ts"
}, },
"dependencies": { "dependencies": {
"@base-ui/react": "^1.6.0",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^5.4.0", "@hookform/resolvers": "^5.4.0",
"@prisma/adapter-mariadb": "^7.8.0", "@prisma/adapter-mariadb": "^7.8.0",
"@prisma/client": "^7.8.0", "@prisma/client": "^7.8.0",
"@radix-ui/react-checkbox": "^1.3.7",
"@radix-ui/react-context-menu": "^2.3.3",
"@radix-ui/react-dialog": "^1.1.19",
"@radix-ui/react-dropdown-menu": "^2.1.20",
"@radix-ui/react-label": "^2.1.11",
"@radix-ui/react-popover": "^1.1.19",
"@radix-ui/react-select": "^2.3.3",
"@radix-ui/react-slot": "^1.3.0",
"@radix-ui/react-switch": "^1.3.3",
"@radix-ui/react-tabs": "^1.1.17",
"@radix-ui/react-tooltip": "^1.2.12",
"@tanstack/react-virtual": "^3.14.5", "@tanstack/react-virtual": "^3.14.5",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
+73 -565
View File
File diff suppressed because it is too large. Load diff
@@ -364,8 +364,8 @@ export function FurniClient({ room, items = [], canEdit }: Props) {
<Select <Select
value={typeFilter} value={typeFilter}
onValueChange={(v: "all" | "floor" | "wall") => { onValueChange={(v) => {
setTypeFilter(v); setTypeFilter(v as "all" | "floor" | "wall");
setPage(1); setPage(1);
}} }}
> >
@@ -25,17 +25,14 @@ export function CatalogManagerDialog() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen} disablePointerDismissal>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" className="gap-2"> <Button variant="outline" className="gap-2">
<LayoutGrid className="h-4 w-4" /> <LayoutGrid className="h-4 w-4" />
Visual Manager Visual Manager
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent className="max-w-full w-[98vw] h-[95vh] max-h-[95vh] p-0 flex flex-col gap-0 sm:max-w-[98vw]">
className="max-w-full w-[98vw] h-[95vh] max-h-[95vh] p-0 flex flex-col gap-0 sm:max-w-[98vw]"
onInteractOutside={(e) => e.preventDefault()}
>
<CatalogManagerProvider> <CatalogManagerProvider>
<CatalogManagerInner open={open} onOpenChange={setOpen} /> <CatalogManagerInner open={open} onOpenChange={setOpen} />
</CatalogManagerProvider> </CatalogManagerProvider>
+31 -13
View File
@@ -1,6 +1,7 @@
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 { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "@radix-ui/react-slot"; import { isValidElement, type ReactElement, type ReactNode } from "react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -30,19 +31,36 @@ function Badge({
className, className,
variant = "default", variant = "default",
asChild = false, asChild = false,
render,
children,
...props ...props
}: React.ComponentProps<"span"> & }: useRender.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) { VariantProps<typeof badgeVariants> & {
const Comp = asChild ? Slot : "span"; asChild?: boolean;
}) {
let resolvedRender = render;
let resolvedChildren: ReactNode = children;
return ( if (!resolvedRender && asChild && isValidElement(children)) {
<Comp resolvedRender = children as ReactElement;
data-slot="badge" resolvedChildren = (children.props as { children?: ReactNode }).children;
data-variant={variant} }
className={cn(badgeVariants({ variant }), className)}
{...props} return useRender({
/> defaultTagName: "span",
); props: mergeProps<"span">(
{
className: cn(badgeVariants({ variant }), className),
children: resolvedChildren,
},
props,
),
render: resolvedRender,
state: {
slot: "badge",
variant,
},
});
} }
export { Badge, badgeVariants }; export { Badge, badgeVariants };
+11 -5
View File
@@ -1,7 +1,8 @@
import { Button as ButtonPrimitive } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "@radix-ui/react-slot";
import type * as React from "react"; import type * as React from "react";
import { resolveAsChild } from "@/lib/as-child";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
@@ -43,21 +44,26 @@ function Button({
variant = "default", variant = "default",
size = "default", size = "default",
asChild = false, asChild = false,
children,
render,
...props ...props
}: React.ComponentProps<"button"> & }: ButtonPrimitive.Props &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean; asChild?: boolean;
}) { }) {
const Comp = asChild ? Slot : "button"; const resolved = resolveAsChild(asChild, children, render);
return ( return (
<Comp <ButtonPrimitive
data-slot="button" data-slot="button"
data-variant={variant} data-variant={variant}
data-size={size} data-size={size}
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
render={resolved.render}
{...props} {...props}
/> >
{resolved.children}
</ButtonPrimitive>
); );
} }
+3 -7
View File
@@ -1,20 +1,16 @@
"use client"; "use client";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Checkbox({ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return ( return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
className={cn( className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className, className,
)} )}
{...props} {...props}
+5 -1
View File
@@ -35,11 +35,15 @@ function CommandDialog({
className, className,
showCloseButton = true, showCloseButton = true,
...props ...props
}: React.ComponentProps<typeof Dialog> & { }: {
title?: string; title?: string;
description?: string; description?: string;
className?: string; className?: string;
showCloseButton?: boolean; showCloseButton?: boolean;
children?: React.ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
}) { }) {
return ( return (
<Dialog {...props}> <Dialog {...props}>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2 -6
View File
@@ -1,16 +1,12 @@
"use client"; "use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import type * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Label({ function Label({ className, ...props }: React.ComponentProps<"label">) {
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return ( return (
<LabelPrimitive.Root <label
data-slot="label" data-slot="label"
className={cn( className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -5
View File
@@ -1,7 +1,6 @@
"use client"; "use client";
import * as SwitchPrimitive from "@radix-ui/react-switch"; import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -9,7 +8,7 @@ function Switch({
className, className,
size = "default", size = "default",
...props ...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & { }: SwitchPrimitive.Root.Props & {
size?: "sm" | "default"; size?: "sm" | "default";
}) { }) {
return ( return (
@@ -17,7 +16,7 @@ function Switch({
data-slot="switch" data-slot="switch"
data-size={size} data-size={size}
className={cn( className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6", "peer data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-unchecked:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
className, className,
)} )}
{...props} {...props}
@@ -25,7 +24,7 @@ function Switch({
<SwitchPrimitive.Thumb <SwitchPrimitive.Thumb
data-slot="switch-thumb" data-slot="switch-thumb"
className={cn( className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0", "bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:translate-x-[calc(100%-2px)] data-unchecked:translate-x-0",
)} )}
/> />
</SwitchPrimitive.Root> </SwitchPrimitive.Root>
+11 -19
View File
@@ -1,8 +1,7 @@
"use client"; "use client";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -10,7 +9,7 @@ function Tabs({
className, className,
orientation = "horizontal", orientation = "horizontal",
...props ...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) { }: TabsPrimitive.Root.Props) {
return ( return (
<TabsPrimitive.Root <TabsPrimitive.Root
data-slot="tabs" data-slot="tabs"
@@ -44,8 +43,7 @@ function TabsList({
className, className,
variant = "default", variant = "default",
...props ...props
}: React.ComponentProps<typeof TabsPrimitive.List> & }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
VariantProps<typeof tabsListVariants>) {
return ( return (
<TabsPrimitive.List <TabsPrimitive.List
data-slot="tabs-list" data-slot="tabs-list"
@@ -56,18 +54,15 @@ function TabsList({
); );
} }
function TabsTrigger({ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return ( return (
<TabsPrimitive.Trigger <TabsPrimitive.Tab
data-slot="tabs-trigger" data-slot="tabs-trigger"
className={cn( className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground", "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className, className,
)} )}
{...props} {...props}
@@ -75,12 +70,9 @@ function TabsTrigger({
); );
} }
function TabsContent({ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return ( return (
<TabsPrimitive.Content <TabsPrimitive.Panel
data-slot="tabs-content" data-slot="tabs-content"
className={cn("flex-1 outline-none", className)} className={cn("flex-1 outline-none", className)}
{...props} {...props}
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
import { isValidElement, type ReactNode } from "react";
/**
* Map Radix-style `asChild` onto Base UI's `render` prop.
*/
export function resolveAsChild(
asChild: boolean | undefined,
children: ReactNode,
render?: any,
): {
render?: any;
children: ReactNode;
} {
if (render != null) return { render, children };
if (asChild && isValidElement(children)) {
return {
render: children,
children: (children.props as { children?: ReactNode }).children,
};
}
return { children };
}