{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"switch","type":"registry:ui","title":"Switch","description":"Boolean toggle switch with controlled state, colors, labels and form support.","package":"react-fancy","dependencies":["tailwind-merge"],"registryDependencies":[],"files":[{"path":"components\/fancy\/switch\/Switch.colors.ts","content":"\/\/ AUTO-GENERATED by scripts\/gen-switch-colors \u2014 do not edit by hand.\n\/\/ Literal Tailwind class strings for every color in the v4 palette so the\n\/\/ scanner can detect them in the published dist (template-built names are\n\/\/ invisible to Tailwind's JIT). Regenerate after changing the palette.\nimport type { Color } from \"..\/lib\/utils\/types\";\n\nexport const switchTrack: Record<Color, string> = {\n  \/\/ The host's colour, from CSS variables -- no dark: variant, because a\n  \/\/ host that themes per mode scopes the variables themselves.\n  brand: \"bg-brand\",\n  slate: \"bg-slate-500\",\n  gray: \"bg-gray-500\",\n  zinc: \"bg-zinc-500\",\n  neutral: \"bg-neutral-500\",\n  stone: \"bg-stone-500\",\n  red: \"bg-red-500\",\n  orange: \"bg-orange-500\",\n  amber: \"bg-amber-500\",\n  yellow: \"bg-yellow-500\",\n  lime: \"bg-lime-500\",\n  green: \"bg-green-500\",\n  emerald: \"bg-emerald-500\",\n  teal: \"bg-teal-500\",\n  cyan: \"bg-cyan-500\",\n  sky: \"bg-sky-500\",\n  blue: \"bg-blue-500\",\n  indigo: \"bg-indigo-500\",\n  violet: \"bg-violet-500\",\n  purple: \"bg-purple-500\",\n  fuchsia: \"bg-fuchsia-500\",\n  pink: \"bg-pink-500\",\n  rose: \"bg-rose-500\",\n};\n","type":"registry:ui","target":"components\/fancy\/switch\/Switch.colors.ts"},{"path":"components\/fancy\/switch\/Switch.tsx","content":"import { forwardRef, useId } from \"react\";\nimport { cn } from \"..\/lib\/utils\/cn\";\nimport { useControllableState } from \"..\/lib\/hooks\/use-controllable-state\";\nimport { useFieldMode } from \"..\/lib\/components\/inputs\/mode\/FieldMode.context\";\nimport { useInlineEdit } from \"..\/lib\/components\/inputs\/mode\/useInlineEdit\";\nimport { dirtyRingClasses } from \"..\/lib\/components\/inputs\/inputs.utils\";\nimport type { SwitchProps } from \".\/Switch.types\";\nimport { switchTrack } from \".\/Switch.colors\";\n\nexport const Switch = forwardRef<HTMLButtonElement, SwitchProps>(\n  (\n    {\n      size = \"md\",\n      dirty,\n      error,\n      label,\n      labelHidden,\n      description,\n      required,\n      disabled,\n      className,\n      id,\n      name,\n      checked: controlledChecked,\n      defaultChecked = false,\n      onCheckedChange,\n      color = \"blue\",\n      mode,\n      ...rest\n    },\n    ref,\n  ) => {\n    \/\/ `aria-*` names and describes the CONTROL, so it goes on the role=\"switch\"\n    \/\/ button; everything else (className, style, data-*, handlers) stays on the\n    \/\/ wrapper, where it has always gone. On the wrapper, an `aria-label` named a\n    \/\/ div and left the switch itself unnamed.\n    const wrapperProps: Record<string, unknown> = {};\n    const controlAria: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(rest)) {\n      if (key.startsWith(\"aria-\")) controlAria[key] = value;\n      else wrapperProps[key] = value;\n    }\n\n    const autoId = useId();\n    const switchId = id ?? autoId;\n    const resolvedMode = useFieldMode(mode);\n    const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);\n    const [checked, setChecked] = useControllableState(\n      controlledChecked,\n      defaultChecked,\n      onCheckedChange,\n    );\n\n    const trackSizes = {\n      xs: \"h-3.5 w-6\",\n      sm: \"h-4 w-7\",\n      md: \"h-5 w-9\",\n      lg: \"h-6 w-11\",\n      xl: \"h-7 w-[52px]\",\n    }[size];\n\n    const knobSizes = {\n      xs: \"h-2.5 w-2.5\",\n      sm: \"h-3 w-3\",\n      md: \"h-4 w-4\",\n      lg: \"h-5 w-5\",\n      xl: \"h-6 w-6\",\n    }[size];\n\n    const translateOn = {\n      xs: \"translate-x-2.5\",\n      sm: \"translate-x-3\",\n      md: \"translate-x-4\",\n      lg: \"translate-x-5\",\n      xl: \"translate-x-6\",\n    }[size];\n\n    return (\n      <div\n        {...wrapperProps}\n        \/\/ View mode has no switch to name, so its aria-* stay on the wrapper.\n        {...(showControl ? undefined : controlAria)}\n        data-react-fancy-switch=\"\" className={cn(\"flex items-start gap-2\", className)}>\n        {!showControl ? (\n          <span\n            data-react-fancy-display=\"\"\n            data-mode=\"view\"\n            role={interactive ? \"button\" : undefined}\n            tabIndex={interactive ? 0 : undefined}\n            title={interactive ? \"Click to edit\" : undefined}\n            onClick={interactive ? enterEdit : undefined}\n            onKeyDown={\n              interactive\n                ? (e) => {\n                    if (e.key === \"Enter\" || e.key === \" \") {\n                      e.preventDefault();\n                      enterEdit();\n                    }\n                  }\n                : undefined\n            }\n            className={cn(\n              \"text-sm font-medium text-zinc-700 dark:text-zinc-200\",\n              interactive &&\n                \"cursor-pointer rounded-md outline-none transition-colors hover:text-zinc-900 focus-visible:ring-2 focus-visible:ring-blue-500\/40 dark:hover:text-white\",\n            )}\n          >\n            {checked ? \"On\" : \"Off\"}\n          <\/span>\n        ) : (\n        <button\n          {...controlAria}\n          ref={ref}\n          id={switchId}\n          type=\"button\"\n          role=\"switch\"\n          aria-checked={checked}\n          disabled={disabled}\n          autoFocus={interactive}\n          onBlur={() => {\n            if (interactive) exitEdit();\n          }}\n          onClick={() => setChecked(!checked)}\n          className={cn(\n            \"relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500\/40 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n            trackSizes,\n            \/\/ The unchecked track is the `border` role (\"tracks\"). The dark\n            \/\/ fallback stays zinc-600, today's colour, not the role's zinc-700\n            \/\/ default \u2014 set nothing, see no change.\n            checked\n              ? switchTrack[color]\n              : \"bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-600))]\",\n            dirtyRingClasses(dirty),\n            error && \"ring-2 ring-red-500\/50\",\n          )}\n        >\n          <span\n            className={cn(\n              \"pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform\",\n              knobSizes,\n              checked ? translateOn : \"translate-x-0\",\n            )}\n          \/>\n        <\/button>\n        )}\n        {name && (\n          <input type=\"hidden\" name={name} value={checked ? \"1\" : \"0\"} \/>\n        )}\n        {(label || description) && (\n          <div className=\"flex flex-col\">\n            {label && (\n              <label\n                htmlFor={switchId}\n                className={cn(\n                  \"cursor-pointer text-sm text-zinc-700 dark:text-zinc-100\",\n                  disabled && \"cursor-not-allowed opacity-50\",\n                  \/\/ Not drawn, still the accessible name (it labels the button).\n                  labelHidden && \"sr-only\",\n                )}\n              >\n                {label}\n                {required && <span className=\"ml-0.5 text-red-500\">*<\/span>}\n              <\/label>\n            )}\n            {description && (\n              <span className=\"text-xs text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\">\n                {description}\n              <\/span>\n            )}\n            {error && <span className=\"text-xs text-red-500\">{error}<\/span>}\n          <\/div>\n        )}\n      <\/div>\n    );\n  },\n);\n\nSwitch.displayName = \"Switch\";\n","type":"registry:ui","target":"components\/fancy\/switch\/Switch.tsx"},{"path":"components\/fancy\/switch\/Switch.types.ts","content":"import type { HTMLAttributes } from \"react\";\nimport type { InputBaseProps } from \"..\/lib\/components\/inputs\/inputs.types\";\nimport type { Color } from \"..\/lib\/utils\/types\";\n\nexport interface SwitchProps\n  extends Omit<InputBaseProps, \"label\">,\n    Omit<HTMLAttributes<HTMLDivElement>, \"color\" | \"defaultChecked\" | \"onChange\" | \"id\"> {\n  checked?: boolean;\n  defaultChecked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  color?: Color;\n  label?: string;\n}\n","type":"registry:ui","target":"components\/fancy\/switch\/Switch.types.ts"},{"path":"components\/fancy\/switch\/index.ts","content":"export { Switch } from \".\/Switch\";\nexport type { SwitchProps } from \".\/Switch.types\";\n","type":"registry:ui","target":"components\/fancy\/switch\/index.ts"},{"path":"components\/fancy\/lib\/utils\/types.ts","content":"import type { ElementType } from \"react\";\n\nexport type Size = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n\/**\n * What a navigation primitive owes a router.\n *\n * Every nav surface here \u2014 `Navbar.Item`, `Sidebar.Item`, `Menu.Item`,\n * `MobileMenu.Item`, `Breadcrumbs.Item` \u2014 used to hardcode a plain `<a href>`.\n * That is a FULL PAGE LOAD in every client-routed app, and the only way around\n * it was to nest a router `<Link>` inside the item: an anchor inside an anchor,\n * which is invalid HTML and the same nested-anchor shape that already cost this\n * suite an SSR hydration bug.\n *\n * `as` is the seam {@link ButtonProps.as} already had. It is router-agnostic on\n * purpose \u2014 one prop serves Inertia, TanStack Router, React Router and Next\n * rather than a package per router:\n *\n * ```tsx\n * \/\/ href-based routers (Inertia, Next)\n * <Navbar.Item as={Link} href=\"\/dashboard\">Dashboard<\/Navbar.Item>\n *\n * \/\/ to-based routers (TanStack Router, React Router) \u2014 `to` reaches the Link\n * \/\/ through the forwarded rest props\n * <Navbar.Item as={Link} to=\"\/dashboard\">Dashboard<\/Navbar.Item>\n * ```\n *\n * Link mode engages on `href` OR `as`, because a `to`-based router never passes\n * an `href` and would otherwise silently render the non-interactive fallback.\n *\/\nexport interface NavigableProps {\n  \/** Target URL. Renders link mode with a plain `<a>` unless `as` is given. *\/\n  href?: string;\n  \/**\n   * Link-mode element override \u2014 the component to render INSTEAD of the plain\n   * `<a>`, e.g. a router's `<Link>` so navigation happens client-side. Receives\n   * `className`, the item's `data-*` handles, and every unrecognized prop, so\n   * router-specific props (`to`, `params`, `preload`, `search`) pass straight\n   * through. Ignored in button mode.\n   *\/\n  as?: ElementType;\n}\n\n\/**\n * The full Tailwind v4 named color palette \u2014 every default hue, including all\n * five gray families \u2014 plus `brand`, the host's own colour. Components that\n * accept a `color` should type it as this so the whole palette is available.\n *\n * `brand` is not a hue. It resolves through three CSS variables the host sets\n * once, rather than through a Tailwind scale:\n *\n *   `--color-brand`, `--color-brand-contrast`, `--color-brand-hover`\n *\n * Three rather than one because a generated hover assumes \"same hue, darker\",\n * and a brand palette need not work that way \u2014 navy whose hover is gold cannot\n * be derived from navy by any amount of arithmetic. The host states it.\n *\/\nexport type Color =\n  \/\/ the host's own colour, resolved from CSS variables (see styles.css)\n  | \"brand\"\n  \/\/ grays\n  | \"slate\"\n  | \"gray\"\n  | \"zinc\"\n  | \"neutral\"\n  | \"stone\"\n  \/\/ hues\n  | \"red\"\n  | \"orange\"\n  | \"amber\"\n  | \"yellow\"\n  | \"lime\"\n  | \"green\"\n  | \"emerald\"\n  | \"teal\"\n  | \"cyan\"\n  | \"sky\"\n  | \"blue\"\n  | \"indigo\"\n  | \"violet\"\n  | \"purple\"\n  | \"fuchsia\"\n  | \"pink\"\n  | \"rose\";\n\n\/** Every value of {@link Color}, for building exhaustive class maps. *\/\nexport const COLORS = [\n  \"brand\",\n  \"slate\", \"gray\", \"zinc\", \"neutral\", \"stone\",\n  \"red\", \"orange\", \"amber\", \"yellow\", \"lime\", \"green\", \"emerald\", \"teal\",\n  \"cyan\", \"sky\", \"blue\", \"indigo\", \"violet\", \"purple\", \"fuchsia\", \"pink\", \"rose\",\n] as const satisfies readonly Color[];\n\nexport type Variant = \"solid\" | \"outline\" | \"ghost\" | \"soft\";\n\n\/** Button accepts the full Tailwind v4 palette (alias of {@link Color}). *\/\nexport type ButtonColor = Color;\n\n\/**\n * @deprecated Renamed to {@link ButtonColor}. `ActionColor` remains as an alias\n * for backward compatibility and will be removed in a future major version.\n *\/\nexport type ActionColor = ButtonColor;\n\nexport type Placement =\n  | \"top\"\n  | \"bottom\"\n  | \"left\"\n  | \"right\"\n  | \"top-start\"\n  | \"top-end\"\n  | \"bottom-start\"\n  | \"bottom-end\";\n","type":"registry:lib","target":"components\/fancy\/lib\/utils\/types.ts"},{"path":"components\/fancy\/lib\/utils\/cn.ts","content":"import { twMerge } from \"tailwind-merge\";\n\n\/**\n * A class value: a string, a number, a conditional map, an array of any of\n * those, or nothing. Mirrors what `clsx` accepted, because every component in\n * this package already passes these shapes.\n *\/\nexport type ClassValue =\n  | string\n  | number\n  | bigint\n  | null\n  | undefined\n  | false\n  | ClassDictionary\n  | ClassValue[];\n\ntype ClassDictionary = Record<string, unknown>;\n\n\/**\n * Flatten class values into a space-separated string.\n *\n * This was `clsx` until 2026-08-19. It is roughly twenty lines of logic, it\n * shipped as a RUNTIME dependency to every consumer of this package, and its\n * last commit was 801 days old \u2014 so it fell to the rule that third-party code\n * must be both approved and actively maintained. Owning it costs less than\n * carrying it.\n *\n * `tailwind-merge` deliberately stays: deciding which of two conflicting\n * Tailwind utilities wins requires modelling the entire utility space, which is\n * emphatically not twenty lines, and it is actively maintained.\n *\n * Falsy values are dropped \u2014 including `0` and `\"\"`, which is what `clsx` did\n * and what `tests\/cn.test.ts` pins. A truthy number is kept, because digits are\n * legal class names. `bigint` is admitted because clsx's signature did and\n * the compiler proved it reachable from two call sites here.\n *\/\nfunction flatten(value: ClassValue): string {\n  if (!value) return \"\";\n\n  if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"bigint\") {\n    return String(value);\n  }\n\n  if (Array.isArray(value)) {\n    let out = \"\";\n    for (const item of value) {\n      const part = flatten(item);\n      if (part) out = out ? `${out} ${part}` : part;\n    }\n    return out;\n  }\n\n  let out = \"\";\n  for (const key in value) {\n    if (value[key]) out = out ? `${out} ${key}` : key;\n  }\n  return out;\n}\n\nexport function cn(...inputs: ClassValue[]): string {\n  return twMerge(flatten(inputs));\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/utils\/cn.ts"},{"path":"components\/fancy\/lib\/hooks\/use-controllable-state.ts","content":"import { useCallback, useRef, useState } from \"react\";\n\n\/**\n * One state slot that works controlled or uncontrolled.\n *\n * ---------------------------------------------------------------------------\n * Why the value is tracked in a ref (fixed 5.32.0)\n * ---------------------------------------------------------------------------\n *\n * This resolved a functional update against the RENDER CLOSURE's `value` and then\n * handed React an already-computed value:\n *\n *     const nextValue = typeof next === \"function\" ? next(value) : next;\n *     setUncontrolledValue(nextValue);\n *\n * So `setValue(fn)` twice in one batch had both calls compute from the same stale\n * `value`, and **the first update was silently lost.** React's own `setState`\n * updater form does not have that problem precisely because React resolves it\n * against the latest state; passing a pre-computed value gives that up.\n *\n * Found by a hook test that paste-held two items inside one `act()` and got one.\n * It is not an exotic case \u2014 any caller that loops, or handles two events in a\n * batch, hits it, and the symptom is a dropped update with nothing to indicate one\n * happened.\n *\n * The fix cannot be \"pass the updater to React\", because `onChange` has to be\n * called with the resulting value and an updater must stay pure \u2014 React may invoke\n * it twice (StrictMode), which would fire `onChange` twice. So the previous value\n * comes from a ref that is re-synced from the authoritative `value` on every render\n * and advanced on every set. Within a batch the ref carries intent; after a render\n * the real value wins.\n *\n * A side effect worth having: `setValue` no longer depends on `value`, so it is\n * stable for the life of the component.\n *\/\nexport function useControllableState<T>(\n  controlledValue: T | undefined,\n  defaultValue: T,\n  onChange?: (value: T) => void,\n): [T, (value: T | ((prev: T) => T)) => void] {\n  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n  const isControlled = controlledValue !== undefined;\n  const value = isControlled ? controlledValue : uncontrolledValue;\n\n  const onChangeRef = useRef(onChange);\n  onChangeRef.current = onChange;\n\n  \/\/ Re-synced every render from whichever side owns the value. A controlled parent\n  \/\/ that ignores `onChange` therefore wins on the next render rather than being\n  \/\/ overridden by what this hook optimistically recorded.\n  const valueRef = useRef(value);\n  valueRef.current = value;\n\n  const isControlledRef = useRef(isControlled);\n  isControlledRef.current = isControlled;\n\n  const setValue = useCallback((next: T | ((prev: T) => T)) => {\n    const nextValue =\n      typeof next === \"function\"\n        ? (next as (prev: T) => T)(valueRef.current)\n        : next;\n\n    \/\/ Advance the ref BEFORE notifying, so a second call in the same batch builds\n    \/\/ on this one instead of on what the last render saw.\n    valueRef.current = nextValue;\n\n    if (!isControlledRef.current) {\n      setUncontrolledValue(nextValue);\n    }\n\n    onChangeRef.current?.(nextValue);\n  }, []);\n\n  return [value, setValue];\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/hooks\/use-controllable-state.ts"},{"path":"components\/fancy\/lib\/components\/inputs\/mode\/FieldMode.context.ts","content":"import { createContext, useContext } from \"react\";\nimport type { FieldMode } from \"..\/inputs.types\";\n\nexport interface FieldModeContextValue {\n  mode: FieldMode;\n}\n\nexport const FieldModeContext = createContext<FieldModeContextValue | null>(null);\n\n\/**\n * Resolve the effective mode for an input. Precedence: an explicit prop wins,\n * then the nearest `<Form>` \/ `<FormProvider>` context, then `\"edit\"`.\n *\n * Deliberately tolerant (returns `\"edit\"` outside any provider) \u2014 unlike a slot\n * guard \u2014 so every input works standalone with zero context and existing call\n * sites behave identically.\n *\/\nexport function useFieldMode(explicit?: FieldMode): FieldMode {\n  const ctx = useContext(FieldModeContext);\n  return explicit ?? ctx?.mode ?? \"edit\";\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/components\/inputs\/mode\/FieldMode.context.ts"},{"path":"components\/fancy\/lib\/components\/inputs\/mode\/useInlineEdit.ts","content":"import { useCallback, useState } from \"react\";\nimport type { FieldMode } from \"..\/inputs.types\";\n\nexport interface InlineEdit {\n  \/** Render the editable control (`true`) vs. the read-only value display (`false`). *\/\n  showControl: boolean;\n  \/** Whether view mode is interactive (click-to-edit) \u2014 i.e. `view` and not disabled. *\/\n  interactive: boolean;\n  \/** Enter edit mode (no-op unless interactive). *\/\n  enterEdit: () => void;\n  \/** Leave edit mode, back to the value display. *\/\n  exitEdit: () => void;\n}\n\n\/**\n * Inline click-to-edit for `mode=\"view\"`. The bound value shows as clean static\n * text; clicking it swaps in the focused control; blur returns to the display\n * (the control's `onChange` \/ `onBlur` fire as usual, so a host can persist on\n * commit). `mode=\"edit\"` is always the control; a **disabled** view stays static\n * text (no click-to-edit).\n *\n * This is the value of \"view mode\": tidy read-only surfaces where each field\n * becomes editable only when the user reaches for it.\n *\/\nexport function useInlineEdit(mode: FieldMode, disabled?: boolean): InlineEdit {\n  const [editing, setEditing] = useState(false);\n  const interactive = mode === \"view\" && !disabled;\n  const showControl = mode !== \"view\" || (interactive && editing);\n  const enterEdit = useCallback(() => {\n    if (interactive) setEditing(true);\n  }, [interactive]);\n  const exitEdit = useCallback(() => setEditing(false), []);\n  return { showControl, interactive, enterEdit, exitEdit };\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/components\/inputs\/mode\/useInlineEdit.ts"},{"path":"components\/fancy\/lib\/components\/inputs\/inputs.utils.ts","content":"import type { Size } from \"..\/..\/utils\/types\";\nimport type { InputOption } from \".\/inputs.types\";\n\nexport const inputSizeClasses: Record<Size, string> = {\n  xs: \"px-2 py-1 text-xs rounded\",\n  sm: \"px-2.5 py-1.5 text-sm rounded-md\",\n  md: \"px-3 py-2 text-sm rounded-lg\",\n  lg: \"px-4 py-2.5 text-base rounded-lg\",\n  xl: \"px-5 py-3 text-lg rounded-xl\",\n};\n\nexport const labelSizeClasses: Record<Size, string> = {\n  xs: \"text-xs\",\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n};\n\nexport function dirtyClasses(dirty?: boolean): string {\n  return dirty ? \"border-l-[3px] border-l-amber-400\" : \"\";\n}\n\nexport function dirtyRingClasses(dirty?: boolean): string {\n  return dirty ? \"ring-2 ring-amber-400\/50\" : \"\";\n}\n\nexport function errorClasses(error?: string): string {\n  return error ? \"border-red-500 focus:ring-red-500\" : \"\";\n}\n\nexport const inputBaseClasses =\n  \"border border-[color:var(--fancy-border-strong,var(--color-zinc-300))] bg-[color:var(--fancy-input,var(--color-white))] text-[color:var(--fancy-text,var(--color-zinc-900))] placeholder:text-[color:var(--fancy-text-faint,var(--color-zinc-400))] transition-[border-color,box-shadow] duration-150 focus:outline-none focus:ring-2 focus:ring-[color:var(--fancy-focus,var(--color-blue-500))]\/40 focus:border-[color:var(--fancy-focus,var(--color-blue-500))] disabled:opacity-50 disabled:cursor-not-allowed dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-input-dark,#1e1e24)] dark:text-[color:var(--fancy-text-dark,var(--color-zinc-100))] dark:placeholder:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))] dark:focus:border-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:focus:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]\/20\";\n\nexport function resolveOption<V = string>(\n  option: InputOption<V>,\n): { value: V; label: string; disabled?: boolean; description?: string } {\n  if (typeof option === \"string\") {\n    return { value: option as unknown as V, label: option };\n  }\n  return option;\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/components\/inputs\/inputs.utils.ts"},{"path":"components\/fancy\/lib\/components\/inputs\/inputs.types.ts","content":"import type { ReactNode } from \"react\";\nimport type { Size } from \"..\/..\/utils\/types\";\n\nexport type InputOption<V = string> =\n  | string\n  | { value: V; label: string; disabled?: boolean; description?: string };\n\nexport type InputOptionGroup<V = string> = {\n  label: string;\n  options: InputOption<V>[];\n};\n\n\/**\n * Edit vs. read-only display. In `\"view\"` mode an input renders the bound value\n * as static text instead of an interactive control \u2014 the React\/Inertia analog of\n * Livewire's public\/bindable properties. Declared here (rather than in `mode\/`)\n * to keep `InputBaseProps` free of an import cycle. Resolved per input via\n * {@link useFieldMode}: explicit prop \u2192 `<Form>`\/`<FormProvider>` context \u2192 `\"edit\"`.\n *\/\nexport type FieldMode = \"edit\" | \"view\";\n\nexport interface InputBaseProps {\n  size?: Size;\n  dirty?: boolean;\n  error?: string;\n  label?: string;\n  \/**\n   * Keep the label for assistive tech, but do not draw it.\n   *\n   * For a control whose options already say what it does \u2014 a filter toolbar's\n   * segmented switches, say \u2014 where a visible label is noise but an unnamed\n   * control is a bug. Without this the only choices were an unnamed control or\n   * a label the design does not have, and the silent one is the one that ships.\n   *\/\n  labelHidden?: boolean;\n  description?: string;\n  required?: boolean;\n  disabled?: boolean;\n  className?: string;\n  id?: string;\n  name?: string;\n  \/** `\"edit\"` (default) renders the control; `\"view\"` renders the value as text. Overrides the `<Form mode>` context. *\/\n  mode?: FieldMode;\n}\n\nexport type AffixPosition = \"inside\" | \"outside\";\n\nexport interface InputAffixProps {\n  prefix?: ReactNode;\n  suffix?: ReactNode;\n  prefixPosition?: AffixPosition;\n  suffixPosition?: AffixPosition;\n}\n","type":"registry:lib","target":"components\/fancy\/lib\/components\/inputs\/inputs.types.ts"}]}