refactor(ui): re-pull label, textarea, separator and skeleton from the registry

These four primitives still wrapped their body in React.forwardRef, which
the dashboard has not needed since it moved to React 19: a function
component receives ref as an ordinary prop and the existing {...props}
spread already hands it to the DOM node.

Re-pulling each from base-vega drops the wrapper and its displayName.
These four were picked because the ref plumbing is their only divergence
from current upstream, so the class strings, data-slot values and exports
are untouched and nothing renders differently. The other seven primitives
that still carry forwardRef have also drifted on their class strings, so
re-pulling them would ship a visual change alongside the cleanup and they
are left alone here.

Textarea is the one with real ref call sites, roughly seventeen of them
through react-hook-form's field.ref, and ref-forwarding.test.tsx did not
cover it. Add that case next to the Label, Separator and Skeleton ones
already there.
This commit is contained in:
Yuneng Jiang 2026-08-25 22:56:59 -07:00
parent e300822483
commit d2aea2d4e7
No known key found for this signature in database
5 changed files with 22 additions and 27 deletions

View file

@ -4,10 +4,9 @@ import * as React from "react";
import { cn } from "@/lib/cva.config";
const Label = React.forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<"label">>(
({ className, ...props }, ref) => (
function Label({ className, ...props }: React.ComponentProps<"label">) {
return (
<label
ref={ref}
data-slot="label"
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",
@ -15,8 +14,7 @@ const Label = React.forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<
)}
{...props}
/>
),
);
Label.displayName = "Label";
);
}
export { Label };

View file

@ -11,6 +11,7 @@ import { Label } from "./label";
import { Separator } from "./separator";
import { Skeleton } from "./skeleton";
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "./table";
import { Textarea } from "./textarea";
import { UiLoadingSpinner } from "./ui-loading-spinner";
describe("ui primitives forward refs to their DOM node", () => {
@ -50,6 +51,12 @@ describe("ui primitives forward refs to their DOM node", () => {
expect(ref.current).toBeInstanceOf(HTMLDivElement);
});
it("Textarea", () => {
const ref = React.createRef<HTMLTextAreaElement>();
render(<Textarea ref={ref} />);
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
});
it("UiLoadingSpinner", () => {
const ref = React.createRef<SVGSVGElement>();
render(<UiLoadingSpinner ref={ref} />);

View file

@ -1,14 +1,12 @@
"use client";
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
import * as React from "react";
import { cn } from "@/lib/cva.config";
const Separator = React.forwardRef<React.ComponentRef<typeof SeparatorPrimitive>, SeparatorPrimitive.Props>(
({ className, orientation = "horizontal", ...props }, ref) => (
function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
return (
<SeparatorPrimitive
ref={ref}
data-slot="separator"
orientation={orientation}
className={cn(
@ -17,8 +15,7 @@ const Separator = React.forwardRef<React.ComponentRef<typeof SeparatorPrimitive>
)}
{...props}
/>
),
);
Separator.displayName = "Separator";
);
}
export { Separator };

View file

@ -1,12 +1,7 @@
import * as React from "react";
import { cn } from "@/lib/cva.config";
const Skeleton = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ className, ...props }, ref) => (
<div ref={ref} data-slot="skeleton" className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />
),
);
Skeleton.displayName = "Skeleton";
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="skeleton" className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />;
}
export { Skeleton };

View file

@ -2,10 +2,9 @@ import * as React from "react";
import { cn } from "@/lib/cva.config";
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
({ className, ...props }, ref) => (
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
ref={ref}
data-slot="textarea"
className={cn(
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
@ -13,8 +12,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"tex
)}
{...props}
/>
),
);
Textarea.displayName = "Textarea";
);
}
export { Textarea };