{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-field",
  "type": "registry:ui",
  "title": "Number Field",
  "description": "Numeric input with stepper buttons and optional drag-to-change behavior.",
  "dependencies": [
    "@base-ui/react@^1.7.0"
  ],
  "registryDependencies": [
    "https://control-ui.dev/r/core.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/field-knobs.ts",
      "target": "@components/control-ui/knob-contracts/field-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/field.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const fieldKnobs = [\n  \"--cui-field-radius\",\n  \"--cui-field-background\",\n  \"--cui-field-foreground\",\n  \"--cui-field-border-color\",\n  \"--cui-field-border-width\",\n  \"--cui-field-shadow\",\n  \"--cui-field-backdrop-filter\",\n  \"--cui-field-focus-border-color\",\n] as const;\nexport type FieldKnobStyle = Partial<Record<(typeof fieldKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/field-parts.css",
      "target": "@components/control-ui/styles/recipes/field-parts.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"field\"][data-field-kind=\"input-group\"][data-slot=\"addon\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"trigger\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"][data-slot=\"clear\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"native-select\"][data-slot=\"icon\"]) {\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"input-group\"][data-slot=\"input\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"phone-input\"][data-slot=\"input\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"input\"]) {\n    outline: none;\n    background: transparent;\n    color: var(--foreground);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"input-group\"][data-slot=\"input\"])::placeholder,\n  :where([data-control-family=\"field\"][data-field-kind=\"phone-input\"][data-slot=\"input\"])::placeholder,\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"input\"])::placeholder {\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"trigger\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"][data-slot=\"clear\"]) {\n    border-radius: var(--radius-control);\n    outline: none;\n    transition-property: color, box-shadow;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-standard);\n  }\n\n  @media (hover: hover) {\n    :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"trigger\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"][data-slot=\"clear\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"]:hover) {\n      color: var(--foreground);\n    }\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"trigger\"]:disabled),\n  :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"][data-slot=\"clear\"]:disabled) {\n    cursor: not-allowed;\n    opacity: 0.45;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"icon\"]) {\n    transition-property: rotate;\n    transition-duration: var(--duration-base);\n    transition-timing-function: var(--ease-emphasized);\n  }\n\n  :where(\n    [data-control-family=\"field\"][data-field-kind=\"combobox\"][data-popup-open]\n      :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"icon\"])\n  ) {\n    rotate: 180deg;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"]) {\n    border-inline-end: 1px solid var(--border);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"]) {\n    border-inline-start: 1px solid var(--border);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"]),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"]) {\n    outline: none;\n    transition-property: color, background-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-standard);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"]:active),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"]:active) {\n    background: oklch(from var(--foreground) l c h / 0.06);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"decrement\"][data-disabled]),\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"increment\"][data-disabled]) {\n    color: oklch(from var(--muted-foreground) l c h / 0.5);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"scrub-cursor\"]) {\n    color: var(--foreground);\n    filter: drop-shadow(0 1px 1px oklch(0 0 0 / 0.05));\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"input-otp\"][data-slot=\"separator\"]) {\n    border-radius: 9999px;\n    background: var(--border);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"title\"]) {\n    color: var(--foreground);\n    font-size: var(--text-label);\n    font-weight: 500;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"label\"]) {\n    color: var(--foreground);\n    font-size: var(--text-label);\n    font-weight: 500;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"label\"][data-disabled]) {\n    opacity: 0.5;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"description\"]) {\n    color: var(--muted-foreground);\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"error\"]) {\n    color: var(--destructive-text);\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"separator\"]) {\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"separator-content\"]) {\n    background-color: var(--background);\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"set\"]) {\n    border-width: 0px;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"field\"][data-slot=\"legend\"]) {\n    color: var(--foreground);\n    font-size: var(--text-label);\n    font-weight: 600;\n  }\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/field.css",
      "target": "@components/control-ui/styles/recipes/field.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"field\"][data-control=\"true\"]) {\n    --cui-field-radius: var(--radius-control);\n    --cui-field-background: oklch(from var(--card) l c h / 0.72);\n    --cui-field-foreground: var(--foreground);\n    --cui-field-border-color: var(--border);\n    --cui-field-border-width: 1px;\n    --cui-field-shadow: var(--shadow-sm);\n    --cui-field-backdrop-filter: blur(0px);\n    --cui-field-focus-border-color: var(--border);\n    border: var(--cui-field-border-width) solid var(--cui-field-border-color);\n    border-radius: var(--cui-field-radius);\n    outline: none;\n    background: var(--cui-field-background);\n    box-shadow: var(--cui-field-shadow);\n    color: var(--cui-field-foreground);\n    backdrop-filter: var(--cui-field-backdrop-filter);\n  }\n\n  :where(\n    [data-control-family=\"field\"][data-control=\"true\"]:not([data-control-family=\"field\"][data-field-kind=\"autocomplete\"]):not(\n      [data-control-family=\"field\"][data-field-kind=\"combobox\"]\n    )\n  ) {\n    transition-property: color, background-color, border-color, box-shadow, opacity, backdrop-filter;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-standard);\n  }\n\n  :where([data-control-family=\"field\"][data-control=\"true\"])::placeholder {\n    color: var(--muted-foreground);\n  }\n\n  @media (hover: hover) {\n    :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"combobox\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"native-select\"]:hover),\n    :where([data-control-family=\"field\"][data-field-kind=\"number-field\"]:hover) {\n      background: oklch(from var(--foreground) l c h / 0.05);\n    }\n  }\n\n  :where([data-control-family=\"field\"][data-control=\"true\"]:is(:focus-visible, :focus-within)) {\n    border-color: var(--cui-field-focus-border-color);\n  }\n\n  :where([data-control-family=\"field\"][data-control=\"true\"][data-invalid]),\n  :where([data-control-family=\"field\"][data-control=\"true\"][aria-invalid=\"true\"]) {\n    box-shadow:\n      0 0 0 2px var(--destructive),\n      var(--cui-field-shadow);\n  }\n\n  :where([data-control-family=\"field\"][data-control=\"true\"]:disabled),\n  :where([data-control-family=\"field\"][data-control=\"true\"][data-disabled]) {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"input-otp\"][data-slot=\"slot\"]) {\n    text-align: center;\n    font-size: var(--text-body);\n    font-weight: var(--font-weight-medium);\n    font-variant-numeric: tabular-nums;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"combobox\"][data-slot=\"input\"]) {\n    font-weight: var(--font-weight-medium);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"native-select\"][data-slot=\"root\"]) {\n    font-weight: var(--font-weight-medium);\n    appearance: none;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"drag-icon\"]) {\n    color: var(--foreground);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"input\"]) {\n    text-align: center;\n    font-weight: var(--font-weight-medium);\n    font-variant-numeric: tabular-nums;\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"number-field\"][data-slot=\"group\"]) {\n    font-weight: var(--font-weight-medium);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"textarea\"][data-slot=\"root\"]) {\n    font-size: var(--text-body);\n  }\n\n  :where([data-control-family=\"field\"][data-field-kind=\"autocomplete\"][data-slot=\"input\"]) {\n    font-weight: var(--font-weight-medium);\n  }\n}\n\n@property --cui-field-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-field-background {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-field-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-field-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-field-border-width {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-field-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-field-backdrop-filter {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-field-focus-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/number-field.tsx",
      "target": "@components/control-ui/ui/number-field.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { NumberField as NumberFieldPrimitive } from \"@base-ui/react/number-field\";\nimport type { ComponentProps, CSSProperties, ReactNode, Ref } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { ControlSize } from \"@/components/control-ui/control-variants\";\nimport { controlSize } from \"@/components/control-ui/control-variants\";\nimport type { FieldKnobStyle } from \"@/components/control-ui/knob-contracts/field-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\n\nexport type NumberFieldChangeReason =\n  | \"input-change\"\n  | \"input-clear\"\n  | \"input-blur\"\n  | \"input-paste\"\n  | \"keyboard\"\n  | \"increment-press\"\n  | \"decrement-press\"\n  | \"wheel\"\n  | \"scrub\"\n  | \"none\";\n\nexport type NumberFieldChangeEventDetails = {\n  reason: NumberFieldChangeReason;\n  event: Event;\n  cancel?: () => void;\n  allowPropagation?: () => void;\n  isCanceled?: boolean;\n  isPropagationAllowed?: boolean;\n  trigger?: Element | undefined;\n};\n\nexport type NumberFieldProps = {\n  size?: ControlSize;\n  value?: number | null;\n  defaultValue?: number;\n  onValueChange?: (value: number | null, eventDetails: NumberFieldChangeEventDetails) => void;\n  onValueCommitted?: (value: number | null, eventDetails: NumberFieldChangeEventDetails) => void;\n  min?: number;\n  max?: number;\n  step?: number;\n  smallStep?: number;\n  largeStep?: number;\n  snapOnStep?: boolean;\n  allowOutOfRange?: boolean;\n  allowWheelScrub?: boolean;\n  format?: Intl.NumberFormatOptions;\n  locale?: Intl.LocalesArgument;\n  disabled?: boolean;\n  readOnly?: boolean;\n  required?: boolean;\n  name?: string;\n  form?: string;\n  id?: string;\n  inputRef?: Ref<HTMLInputElement>;\n  className?: string;\n  style?: CSSProperties & FieldKnobStyle;\n  children?: ReactNode;\n};\n\nexport type NumberFieldGroupProps = Omit<ComponentProps<\"div\">, \"style\"> & { style?: CSSProperties & FieldKnobStyle };\n\nexport type NumberFieldInputProps = Omit<ComponentProps<\"input\">, \"style\"> & { style?: CSSProperties & FieldKnobStyle };\n\nexport type NumberFieldIncrementProps = ComponentProps<\"button\"> & { nativeButton?: boolean } & { style?: CSSProperties & FieldKnobStyle };\n\nexport type NumberFieldDecrementProps = ComponentProps<\"button\"> & { nativeButton?: boolean } & { style?: CSSProperties & FieldKnobStyle };\n\nexport type NumberFieldScrubAreaProps = ComponentProps<\"span\"> & {\n  direction?: \"horizontal\" | \"vertical\";\n  pixelSensitivity?: number;\n  teleportDistance?: number;\n} & { style?: CSSProperties & FieldKnobStyle };\n\n// Group carries surface and Input stays transparent, so stepper and field fuse into one joined segment.\n// `size` lives on Root and reaches Group through context, so one prop resizes whole unit.\nconst NumberFieldSizeContext = createContext<ControlSize>(\"md\");\n\nexport function NumberField({ size = \"md\", value, defaultValue, children, ...props }: NumberFieldProps) {\n  return (\n    <NumberFieldSizeContext.Provider value={size}>\n      {/* explicit, never spread — Base UI decides controlled-ness from value !== undefined on first render */}\n      <NumberFieldPrimitive.Root value={value} defaultValue={defaultValue} {...props}>\n        {children}\n      </NumberFieldPrimitive.Root>\n    </NumberFieldSizeContext.Provider>\n  );\n}\n\nexport function NumberFieldGroup({ className, children, ...props }: NumberFieldGroupProps) {\n  const size = useContext(NumberFieldSizeContext);\n  return (\n    <NumberFieldPrimitive.Group\n      data-control-ui=\"number-field\"\n      data-field-kind=\"number-field\"\n      data-slot=\"group\"\n      data-control=\"true\"\n      data-control-family=\"field\"\n      data-size={size}\n      className={cn(\"inline-flex items-stretch overflow-hidden\", controlSize({ size }), \"gap-0 px-0\", className)}\n      {...props}\n    >\n      {children}\n    </NumberFieldPrimitive.Group>\n  );\n}\n\nexport function NumberFieldInput({ className, ...props }: NumberFieldInputProps) {\n  return (\n    <NumberFieldPrimitive.Input\n      data-control-ui=\"number-field\"\n      data-control-family=\"field\"\n      data-field-kind=\"number-field\"\n      data-slot=\"input\"\n      className={cn(\"h-full min-w-0 flex-1 px-2 disabled:cursor-not-allowed\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function NumberFieldDecrement({ className, children, ...props }: NumberFieldDecrementProps) {\n  return (\n    <NumberFieldPrimitive.Decrement\n      data-control-ui=\"number-field\"\n      data-control-family=\"field\"\n      data-field-kind=\"number-field\"\n      data-slot=\"decrement\"\n      aria-label=\"Decrease\"\n      className={cn(\"flex aspect-square h-full shrink-0 cursor-pointer select-none items-center justify-center\", className)}\n      {...props}\n    >\n      {children ?? <MinusIcon />}\n    </NumberFieldPrimitive.Decrement>\n  );\n}\n\nexport function NumberFieldIncrement({ className, children, ...props }: NumberFieldIncrementProps) {\n  return (\n    <NumberFieldPrimitive.Increment\n      data-control-ui=\"number-field\"\n      data-control-family=\"field\"\n      data-field-kind=\"number-field\"\n      data-slot=\"increment\"\n      aria-label=\"Increase\"\n      className={cn(\"flex aspect-square h-full shrink-0 cursor-pointer select-none items-center justify-center\", className)}\n      {...props}\n    >\n      {children ?? <PlusIcon />}\n    </NumberFieldPrimitive.Increment>\n  );\n}\n\n// optional drag-to-change affordance, usually wrapped around field's label\nexport function NumberFieldScrubArea({ className, children, ...props }: NumberFieldScrubAreaProps) {\n  return (\n    <NumberFieldPrimitive.ScrubArea\n      data-control-ui=\"number-field\"\n      data-control-family=\"field\"\n      data-field-kind=\"number-field\"\n      data-slot=\"scrub-area\"\n      className={cn(\"cursor-ew-resize select-none\", className)}\n      {...props}\n    >\n      {children}\n      <NumberFieldPrimitive.ScrubAreaCursor\n        data-control-ui=\"number-field\"\n        data-control-family=\"field\"\n        data-field-kind=\"number-field\"\n        data-slot=\"scrub-cursor\"\n      >\n        <CursorGrowIcon />\n      </NumberFieldPrimitive.ScrubAreaCursor>\n    </NumberFieldPrimitive.ScrubArea>\n  );\n}\n\nfunction MinusIcon() {\n  return (\n    <svg\n      viewBox=\"0 0 16 16\"\n      className=\"size-3.5\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M3 8h10\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon() {\n  return (\n    <svg\n      viewBox=\"0 0 16 16\"\n      className=\"size-3.5\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M8 3v10M3 8h10\" />\n    </svg>\n  );\n}\n\nfunction CursorGrowIcon() {\n  return (\n    <svg\n      data-control-ui=\"number-field\"\n      data-field-kind=\"number-field\"\n      data-control-family=\"field\"\n      data-slot=\"drag-icon\"\n      width=\"26\"\n      height=\"14\"\n      viewBox=\"0 0 24 14\"\n      fill=\"currentColor\"\n      className=\"block\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M19.5 5.5 6.5 5.52V2L1 7l5.5 5V8.5h13V12L25 7l-5.5-5v3.5Z\" />\n    </svg>\n  );\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/field.css\"": {},
    "@import \"../components/control-ui/styles/recipes/field-parts.css\"": {}
  },
  "meta": {}
}
