{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"input","type":"registry:ui","title":"Input","description":"A single-line text field with resting, focus, error and disabled looks, built on a native input","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["forms"],"registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/input/Input.tsx","content":"\"use client\";\n\nimport { forwardRef, useState } from \"react\";\nimport type { ChangeEvent, ComponentPropsWithoutRef, HTMLInputAutoCompleteAttribute } from \"react\";\nimport { cn } from \"../../utils\";\nimport { useField } from \"../../internals/field\";\nimport { mergeIds } from \"../../internals/merge-ids\";\nimport \"./input.css\";\n\nexport interface InputOwnProps {\n\t/**\n\t * Current value. Controlled when supplied: pair it with `onValueChange`,\n\t * the React counterpart of the Svelte source's `bind:value`.\n\t */\n\tvalue?: string;\n\t/** Initial value when uncontrolled (no `value` prop given). */\n\tdefaultValue?: string;\n\t/** Called with the new value on every input event. */\n\tonValueChange?: (value: string) => void;\n\t/** Native input type. */\n\ttype?: \"text\" | \"email\" | \"url\" | \"tel\" | \"password\" | \"search\" | \"number\";\n\t/** Shown while the field is empty. */\n\tplaceholder?: string;\n\t/** Blocks focus and typing; excluded from form submission. Overridden by a surrounding FormField. */\n\tdisabled?: boolean;\n\t/** Blocks typing but stays focusable and is still submitted, unlike `disabled`. */\n\treadonly?: boolean;\n\t/** Native `required`. Overridden by a surrounding FormField. */\n\trequired?: boolean;\n\t/** Drives the error border and `aria-invalid`. Overridden by a surrounding FormField. */\n\tinvalid?: boolean;\n\t/** Element id. Overridden by a surrounding FormField's own `controlId`. */\n\tid?: string;\n\t/** Native `name`, read on form submission. */\n\tname?: string;\n\t/** Native `autocomplete` hint — the real token set the DOM accepts, not a bare string. */\n\tautocomplete?: HTMLInputAutoCompleteAttribute;\n\t/** Accessible name — for a control with no visible Label next to it. */\n\tlabel?: string;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\n/**\n * Own props plus every native `<input>` attribute and listener; the rest are\n * spread onto the inner `<input>` (see the component README for the rules).\n */\nexport interface InputProps\n\textends Omit<ComponentPropsWithoutRef<\"input\">, keyof InputOwnProps>, InputOwnProps {}\n\n/**\n * A styled native `<input>` with full FormField integration.\n *\n * The element reference arrives through the ref channel (the Svelte source\n * declares `ref = $bindable(null)`), pointing at the native `<input>`.\n */\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n\t(\n\t\t{\n\t\t\tvalue: valueProp,\n\t\t\tdefaultValue = \"\",\n\t\t\tonValueChange,\n\t\t\ttype = \"text\",\n\t\t\tplaceholder,\n\t\t\tdisabled = false,\n\t\t\treadonly = false,\n\t\t\trequired = false,\n\t\t\tinvalid = false,\n\t\t\tid,\n\t\t\tname,\n\t\t\tautocomplete,\n\t\t\tlabel,\n\t\t\tclassName,\n\t\t\tautoComplete: autoCompleteAttr,\n\t\t\tonChange,\n\t\t\t\"aria-describedby\": consumerDescribedBy,\n\t\t\t\"aria-label\": consumerAriaLabel,\n\t\t\t\"aria-invalid\": consumerAriaInvalid,\n\t\t\treadOnly: readOnlyAttr,\n\t\t\t...rest\n\t\t},\n\t\tref\n\t) => {\n\t\t// The Svelte side's `value = $bindable(\"\")` becomes the standard\n\t\t// controlled/uncontrolled split: uncontrolled by default, controlled\n\t\t// the moment a `value` prop is passed.\n\t\tconst isControlled = valueProp !== undefined;\n\t\tconst [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n\t\tconst value = isControlled ? valueProp : uncontrolledValue;\n\n\t\t// Undefined outside a FormField — every fallback below then uses this\n\t\t// component's own props instead of the context, so the control works\n\t\t// standalone exactly as it does wrapped.\n\t\tconst field = useField();\n\n\t\tconst effectiveId = field?.controlId ?? id;\n\t\tconst effectiveDisabled = field?.disabled ?? disabled;\n\t\tconst effectiveRequired = field?.required ?? required;\n\t\tconst effectiveInvalid = field?.invalid ?? invalid;\n\n\t\t// A consumer's own description id rides along with the FormField's, never\n\t\t// replacing it.\n\t\tconst describedBy = mergeIds(field?.describedBy, consumerDescribedBy);\n\n\t\tconst classes = cn(\n\t\t\t\"ft-input w-full rounded-[8px] border border-input bg-background px-[12px] py-[9px] text-[13px] text-foreground transition-colors\",\n\t\t\t\"placeholder:text-muted-foreground\",\n\t\t\t\"focus-visible:outline-none\",\n\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\teffectiveInvalid && \"border-destructive/50\",\n\t\t\tclassName\n\t\t);\n\n\t\t// The single place `value` changes. A native `disabled` input never fires\n\t\t// `input` from real typing, but a synthetic dispatch walks straight past\n\t\t// that guard the same way a synthetic click does on a button — so the\n\t\t// early return is repeated here rather than trusted to the attribute\n\t\t// alone. Putting the DOM's value back keeps a disabled control's visible\n\t\t// text from drifting away from the app's own model.\n\t\tfunction handleInput(event: ChangeEvent<HTMLInputElement>) {\n\t\t\tif (effectiveDisabled) {\n\t\t\t\tevent.currentTarget.value = value;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst next = event.currentTarget.value;\n\t\t\tif (!isControlled) setUncontrolledValue(next);\n\t\t\tonValueChange?.(next);\n\t\t\tonChange?.(event);\n\t\t}\n\n\t\treturn (\n\t\t\t<input\n\t\t\t\t{...rest}\n\t\t\t\tref={ref}\n\t\t\t\ttype={type}\n\t\t\t\tplaceholder={placeholder}\n\t\t\t\tname={name}\n\t\t\t\tautoComplete={autocomplete ?? autoCompleteAttr}\n\t\t\t\tid={effectiveId}\n\t\t\t\tvalue={value}\n\t\t\t\tdisabled={effectiveDisabled}\n\t\t\t\treadOnly={readonly || readOnlyAttr}\n\t\t\t\trequired={effectiveRequired}\n\t\t\t\taria-invalid={effectiveInvalid ? \"true\" : consumerAriaInvalid}\n\t\t\t\taria-describedby={describedBy}\n\t\t\t\taria-label={label ?? consumerAriaLabel}\n\t\t\t\tclassName={classes}\n\t\t\t\tonChange={handleInput}\n\t\t\t/>\n\t\t);\n\t}\n);\n\nInput.displayName = \"Input\";\n","type":"registry:ui","target":"@ui/fancy/components/input/Input.tsx"},{"path":"react/src/components/input/index.ts","content":"\"use client\";\n\nexport { Input } from \"./Input\";\nexport type { InputProps } from \"./Input\";\n","type":"registry:ui","target":"@ui/fancy/components/input/index.ts"},{"path":"react/src/components/input/input.css","content":"/*\n * The brand accent has no semantic token, so it is declared locally with a\n * light-dark() fallback — the same shape Toggle and Button use for their\n * own accent ring. The 3px halo is the mockup's focus state exactly;\n * the resting/error/disabled looks are plain utility classes above.\n */\n.ft-input:focus-visible {\n\t--ft-input-accent: var(\n\t\t--ft-accent,\n\t\tlight-dark(oklch(0.5432 0.2528 300.22), oklch(0.604 0.2606 301.75))\n\t);\n\tborder-color: var(--ft-input-accent);\n\tbox-shadow: 0 0 0 3px color-mix(in oklab, var(--ft-input-accent) 25%, transparent);\n}\n","type":"registry:ui","target":"@ui/fancy/components/input/input.css"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/input?framework=react"}}