50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
|
|
|
import { Switch as SubframeSwitch } from "~/ui/components/Switch"
|
|
import { cn } from "~/lib/utils"
|
|
|
|
// Subframe migration (strangler shim):
|
|
// Keeps the original shadcn/Radix `Switch` API (`React.ComponentProps<typeof
|
|
// SwitchPrimitive.Root>`) but renders the Subframe design-system Switch behind it.
|
|
//
|
|
// Pitfall handled here: Subframe's Switch is ALWAYS controlled (it defaults `checked`
|
|
// to false and has no uncontrolled/`defaultChecked` path). Since this primitive is often
|
|
// used uncontrolled (`defaultChecked`, no `checked`), the shim owns the checked state with
|
|
// its own `useState` and only defers to the caller when `checked` is supplied — preserving
|
|
// both the controlled and uncontrolled public behavior.
|
|
function Switch({
|
|
className,
|
|
checked,
|
|
defaultChecked,
|
|
onCheckedChange,
|
|
...props
|
|
}: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>) {
|
|
// ref intentionally excluded: Radix Switch.Root's ref targets a <button> while Subframe's
|
|
// Switch ref targets a <div>; forwarding would be a type/element mismatch.
|
|
const isControlled = checked !== undefined
|
|
const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)
|
|
|
|
const currentChecked = isControlled ? checked : internalChecked
|
|
|
|
const handleCheckedChange = (next: boolean) => {
|
|
if (!isControlled) {
|
|
setInternalChecked(next)
|
|
}
|
|
onCheckedChange?.(next)
|
|
}
|
|
|
|
return (
|
|
<SubframeSwitch
|
|
{...props}
|
|
checked={currentChecked}
|
|
onCheckedChange={handleCheckedChange}
|
|
className={cn(className)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export { Switch }
|