{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-field",
  "title": "Number Field",
  "description": "A number-field component.",
  "dependencies": [
    "class-variance-authority",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "@cubby-ui/button"
  ],
  "files": [
    {
      "path": "registry/default/number-field/number-field.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { NumberField as BaseNumberField } from \"@base-ui/react/number-field\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n  ChevronDownIcon,\n  ChevronUpIcon,\n  MinusSignIcon,\n  PlusSignIcon,\n} from \"@hugeicons/core-free-icons\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/registry/default/button/button\";\n\n// A field-colored shell, like Button Group's. The input and steppers fill it\n// with a 2px transparent-border inset (even at fractional display scales).\n// Paint corners are the shell's minus its border and the 2px.\nconst numberFieldGroupVariants = cva(\n  [\n    \"relative flex h-(--number-field-h) w-fit items-stretch rounded-(--number-field-radius) border bg-clip-padding\",\n    \"[--number-field-item-radius:max(0px,calc(var(--number-field-radius)-3px))]\",\n    // Focus and invalid rings\n    \"outline-0 outline-offset-0 outline-transparent outline-solid transition-[outline-width,outline-offset,outline-color] duration-100 ease-out\",\n    \"has-[[data-slot=number-field-input]:focus-visible]:outline-ring/50 has-[[data-slot=number-field-input]:focus-visible]:outline-2 has-[[data-slot=number-field-input]:focus-visible]:outline-offset-2\",\n    \"has-[[data-slot=number-field-input][aria-invalid=true]]:outline-destructive/50 has-[[data-slot=number-field-input][aria-invalid=true]]:outline-2 has-[[data-slot=number-field-input][aria-invalid=true]]:outline-offset-2\",\n    // Disabled: dims once here; the steppers' own disabled opacity is\n    // cancelled so it doesn't stack. A stepper alone at min or max still dims.\n    \"data-disabled:opacity-60 data-disabled:[&_button]:opacity-100\",\n  ],\n  {\n    variants: {\n      // Input's surfaces\n      variant: {\n        default: \"bg-input\",\n        elevated: \"bg-input-elevated\",\n      },\n      // Input's heights (one step taller below sm); corners step with them.\n      size: {\n        default:\n          \"[--number-field-h:--spacing(10)] sm:not-pointer-coarse:[--number-field-h:--spacing(9)] [--number-field-radius:var(--radius)]\",\n        sm: \"[--number-field-h:--spacing(9)] sm:not-pointer-coarse:[--number-field-h:--spacing(8)] [--number-field-radius:calc(var(--radius)-2px)]\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\n// Ghost Button recipe, sized by the shell. Hover and press are the label\n// color at 10% / 14%, as in Button Group.\nconst stepperClasses = cn(\n  buttonVariants({ variant: \"ghost\", size: null }),\n  \"aspect-square h-auto self-stretch border-2 border-transparent text-sm rounded-(--number-field-item-radius) [--btn-bg-hover:color-mix(in_oklab,currentColor_10%,transparent)] [--btn-bg-active:color-mix(in_oklab,currentColor_14%,transparent)]\",\n);\n\ntype NumberFieldProps = BaseNumberField.Root.Props;\n\nfunction NumberField({ className, ...props }: NumberFieldProps) {\n  return (\n    <BaseNumberField.Root\n      data-slot=\"number-field\"\n      className={cn(\"flex flex-col items-start gap-1.5\", className)}\n      {...props}\n    />\n  );\n}\n\ntype NumberFieldGroupProps = BaseNumberField.Group.Props &\n  VariantProps<typeof numberFieldGroupVariants>;\n\nfunction NumberFieldGroup({\n  className,\n  variant,\n  size,\n  ...props\n}: NumberFieldGroupProps) {\n  return (\n    <BaseNumberField.Group\n      data-slot=\"number-field-group\"\n      data-variant={variant ?? \"default\"}\n      data-size={size ?? \"default\"}\n      className={cn(numberFieldGroupVariants({ variant, size }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction NumberFieldInput({\n  className,\n  ...props\n}: BaseNumberField.Input.Props) {\n  return (\n    <BaseNumberField.Input\n      data-slot=\"number-field-input\"\n      className={cn(\n        \"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground\",\n        \"w-16 min-w-0 self-stretch border-2 border-transparent bg-transparent px-1 text-center text-base font-normal tabular-nums outline-none sm:not-pointer-coarse:text-sm\",\n        // Inline scrub prefix. Set on the input to keep `:has()` cheap.\n        \"[[data-slot=number-field-group]:has(>[data-slot=number-field-scrub-area])>&]:text-left\",\n        // Dividers: two background layers on the input's edges. Each shows\n        // only beside a stepper and collapses while that stepper is lit.\n        \"bg-[linear-gradient(var(--border),var(--border)),linear-gradient(var(--border),var(--border))] [background-size:1px_var(--number-field-divider-s,0px),1px_var(--number-field-divider-e,0px)] [background-position:left_center,right_center] bg-no-repeat [background-origin:border-box] transition-[background-size] duration-100 ease-out motion-reduce:transition-none\",\n        \"has-[+[data-slot=number-field-increment]]:[--number-field-divider-e:calc(100%_-_12px)] has-[+[data-slot=number-field-stepper]]:[--number-field-divider-e:calc(100%_-_12px)] [[data-slot=number-field-decrement]+&]:[--number-field-divider-s:calc(100%_-_12px)]\",\n        // Hover is gated to real hover: a raw :hover sticks after a tap.\n        \"has-[+[data-slot=number-field-increment]:active]:[--number-field-divider-e:0px] has-[+[data-slot=number-field-stepper]:active]:[--number-field-divider-e:0px] [[data-slot=number-field-decrement]:active+&]:[--number-field-divider-s:0px]\",\n        \"[@media(hover:hover)]:has-[+[data-slot=number-field-increment]:hover]:[--number-field-divider-e:0px] [@media(hover:hover)]:has-[+[data-slot=number-field-stepper]:hover]:[--number-field-divider-e:0px] [@media(hover:hover)]:[[data-slot=number-field-decrement]:hover+&]:[--number-field-divider-s:0px]\",\n        \"disabled:pointer-events-none disabled:cursor-not-allowed\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction NumberFieldDecrement({\n  className,\n  children,\n  ...props\n}: BaseNumberField.Decrement.Props) {\n  return (\n    <BaseNumberField.Decrement\n      data-slot=\"number-field-decrement\"\n      aria-label=\"Decrease\"\n      className={cn(stepperClasses, className)}\n      {...props}\n    >\n      {children ?? <HugeiconsIcon icon={MinusSignIcon} strokeWidth={2} />}\n    </BaseNumberField.Decrement>\n  );\n}\n\nfunction NumberFieldIncrement({\n  className,\n  children,\n  ...props\n}: BaseNumberField.Increment.Props) {\n  return (\n    <BaseNumberField.Increment\n      data-slot=\"number-field-increment\"\n      aria-label=\"Increase\"\n      className={cn(stepperClasses, className)}\n      {...props}\n    >\n      {children ?? <HugeiconsIcon icon={PlusSignIcon} strokeWidth={2} />}\n    </BaseNumberField.Increment>\n  );\n}\n\n// Appends a consumer className, including Base UI's function-of-state form.\nfunction withClassName<State>(\n  base: string,\n  className: string | ((state: State) => string | undefined) | undefined,\n): string | ((state: State) => string) {\n  if (typeof className === \"function\") {\n    return (state) => cn(base, className(state));\n  }\n  return cn(base, className);\n}\n\ntype NumberFieldStepperProps = React.ComponentProps<\"div\"> & {\n  /** Props for the increment half, e.g. a localized `aria-label`. */\n  incrementProps?: BaseNumberField.Increment.Props;\n  /** Props for the decrement half, e.g. a localized `aria-label`. */\n  decrementProps?: BaseNumberField.Decrement.Props;\n};\n\n// Stacked chevrons, like a native spinner. Only the corner inside the\n// shell's corner follows it; the rest stay tight.\nfunction NumberFieldStepper({\n  className,\n  incrementProps,\n  decrementProps,\n  ...props\n}: NumberFieldStepperProps) {\n  const stepClassName = cn(\n    stepperClasses,\n    \"aspect-auto w-7 flex-1 [&_svg:not([class*='size-'])]:size-3.5\",\n  );\n\n  return (\n    <div\n      data-slot=\"number-field-stepper\"\n      className={cn(\"flex flex-col self-stretch\", className)}\n      {...props}\n    >\n      <BaseNumberField.Increment\n        data-slot=\"number-field-increment\"\n        aria-label=\"Increase\"\n        {...incrementProps}\n        className={withClassName(\n          cn(\n            stepClassName,\n            \"rounded-[3px] rounded-se-(--number-field-item-radius) border-b\",\n          ),\n          incrementProps?.className,\n        )}\n      >\n        <HugeiconsIcon icon={ChevronUpIcon} strokeWidth={2} />\n      </BaseNumberField.Increment>\n      <BaseNumberField.Decrement\n        data-slot=\"number-field-decrement\"\n        aria-label=\"Decrease\"\n        {...decrementProps}\n        className={withClassName(\n          cn(\n            stepClassName,\n            \"rounded-[3px] rounded-ee-(--number-field-item-radius) border-t\",\n          ),\n          decrementProps?.className,\n        )}\n      >\n        <HugeiconsIcon icon={ChevronDownIcon} strokeWidth={2} />\n      </BaseNumberField.Decrement>\n    </div>\n  );\n}\n\n// Wraps a label outside the group, or sits inside it as a quiet prefix.\nfunction NumberFieldScrubArea({\n  className,\n  ...props\n}: BaseNumberField.ScrubArea.Props) {\n  return (\n    <BaseNumberField.ScrubArea\n      data-slot=\"number-field-scrub-area\"\n      className={cn(\n        // A <label> otherwise resets the cursor over its text.\n        \"cursor-ew-resize select-none [&_*]:cursor-[inherit]\",\n        // As a plain span it takes the body line height, leaving space under\n        // the label.\n        \"flex w-fit items-center\",\n        \"in-data-[slot=number-field-group]:text-muted-foreground in-data-[slot=number-field-group]:hover:text-foreground in-data-[slot=number-field-group]:data-scrubbing:text-foreground in-data-[slot=number-field-group]:flex in-data-[slot=number-field-group]:items-center in-data-[slot=number-field-group]:ps-2.5 in-data-[slot=number-field-group]:pe-1 in-data-[slot=number-field-group]:text-sm in-data-[slot=number-field-group]:font-medium in-data-[slot=number-field-group]:transition-colors in-data-[slot=number-field-group]:duration-100\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n// Replaces the locked pointer while scrubbing. Defaults to a two-way arrow.\nfunction NumberFieldScrubAreaCursor({\n  className,\n  children,\n  ...props\n}: BaseNumberField.ScrubAreaCursor.Props) {\n  return (\n    <BaseNumberField.ScrubAreaCursor\n      data-slot=\"number-field-scrub-area-cursor\"\n      className={cn(\"drop-shadow-[0_1px_1px_oklch(0_0_0/0.4)]\", className)}\n      {...props}\n    >\n      {children ?? (\n        <svg\n          width=\"26\"\n          height=\"14\"\n          viewBox=\"0 0 26 14\"\n          fill=\"black\"\n          stroke=\"white\"\n          aria-hidden\n        >\n          <path d=\"M19.5 5.5L6.5 5.52V2L1 7l5.5 5-.01-3.5H19.5V12L25 7l-5.5-5v3.5Z\" />\n        </svg>\n      )}\n    </BaseNumberField.ScrubAreaCursor>\n  );\n}\n\nexport {\n  NumberField,\n  NumberFieldGroup,\n  NumberFieldInput,\n  NumberFieldIncrement,\n  NumberFieldDecrement,\n  NumberFieldStepper,\n  NumberFieldScrubArea,\n  NumberFieldScrubAreaCursor,\n  numberFieldGroupVariants,\n};\n\nexport type {\n  NumberFieldProps,\n  NumberFieldGroupProps,\n  NumberFieldStepperProps,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/number-field.tsx"
    }
  ],
  "type": "registry:ui"
}