{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"number-field","title":"Number Field","description":"Base UI-backed numeric entry and stepping.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/label"],"files":[{"path":"packages/ui/src/components/number-field/number-field.tsx","content":"\"use client\";\n\nimport { NumberField as NumberFieldPrimitive } from \"@base-ui/react/number-field\";\nimport { MinusIcon, PlusIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n  type SiteplaneControlSize,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport {\n  formControlSurfaceClassNames,\n  formControlTextClassNames,\n  type FormControlSurface,\n  type FormControlTextSize,\n  surfaceClassNames,\n} from \"../../system/styles/surface\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport { KitButton } from \"../button/button-internal\";\nimport { Label } from \"../label\";\n\nimport \"./number-field.css\";\n\nexport const NumberFieldContext: React.Context<{\n  fieldId: string;\n  size: SiteplaneControlSize;\n  textSize?: FormControlTextSize | undefined;\n  variant: FormControlSurface;\n} | null> = React.createContext<{\n  fieldId: string;\n  size: SiteplaneControlSize;\n  textSize?: FormControlTextSize | undefined;\n  variant: FormControlSurface;\n} | null>(null);\n\nexport function NumberField({\n  id,\n  className,\n  emphasized = false,\n  size,\n  textSize,\n  variant = \"default\",\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.Root.Props> & {\n  /** A semibold value one text step larger, for a number that is the content. */\n  emphasized?: boolean | undefined;\n  size?: SiteplaneControlSize | undefined;\n  textSize?: FormControlTextSize | undefined;\n  variant?: FormControlSurface | undefined;\n}): React.ReactElement {\n  const generatedId = React.useId();\n  const fieldId = id ?? generatedId;\n  const resolvedSize = useResolvedControlSize(size);\n  // The value follows the field text scale, as Input does: 14 px from sm to\n  // lg and 16 px at xl. textSize=\"base\" sets 16 px at every size.\n  // An emphasized value is one step larger and semibold: 16 px at sm, 18 px\n  // at md and lg and 20 px at xl, whatever the textSize (design-rework.css).\n  const resolvedTextSize =\n    textSize ?? (resolvedSize === \"xl\" ? \"base\" : undefined);\n\n  return (\n    <NumberFieldContext.Provider\n      value={{\n        fieldId,\n        size: resolvedSize,\n        textSize: resolvedTextSize,\n        variant,\n      }}\n    >\n      <NumberFieldPrimitive.Root\n        className={cn(\n          // Compact by default; number-field.css derives the width from the\n          // control size. A width utility in className replaces it.\n          slotClass(\"number-field\", props),\n          \"flex w-(--number-field-width) max-w-full flex-col items-start gap-2\",\n          className,\n        )}\n        data-emphasized={emphasized || undefined}\n        data-size={resolvedSize}\n        data-slot=\"number-field\"\n        data-text-size={resolvedTextSize}\n        data-variant={variant}\n        id={fieldId}\n        {...props}\n      />\n    </NumberFieldContext.Provider>\n  );\n}\n\nexport function NumberFieldGroup({\n  className,\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.Group.Props>): React.ReactElement {\n  const context = React.useContext(NumberFieldContext);\n  return (\n    <NumberFieldPrimitive.Group\n      className={cn(\n        slotClass(\"number-field-group\", props),\n        surfaceClassNames.formControl,\n        formControlSurfaceClassNames[context?.variant ?? \"default\"],\n        \"justify-between [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        context?.textSize && formControlTextClassNames[context.textSize],\n        className,\n      )}\n      data-slot=\"number-field-group\"\n      {...props}\n    />\n  );\n}\n\nexport function NumberFieldDecrement({\n  className,\n  render,\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.Decrement.Props>): React.ReactElement {\n  const context = React.useContext(NumberFieldContext);\n  return (\n    <NumberFieldPrimitive.Decrement\n      className={cn(\n        slotClass(\"number-field-decrement\", props, render),\n        className,\n      )}\n      data-profile=\"joined-start\"\n      data-slot=\"number-field-decrement\"\n      render={\n        render ?? (\n          <KitButton\n            profile=\"joined-start\"\n            shape=\"square\"\n            size={context?.size}\n            variant=\"ghost\"\n          />\n        )\n      }\n      {...props}\n    >\n      <MinusIcon />\n    </NumberFieldPrimitive.Decrement>\n  );\n}\n\nexport function NumberFieldIncrement({\n  className,\n  render,\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.Increment.Props>): React.ReactElement {\n  const context = React.useContext(NumberFieldContext);\n  return (\n    <NumberFieldPrimitive.Increment\n      className={cn(\n        slotClass(\"number-field-increment\", props, render),\n        className,\n      )}\n      data-profile=\"joined-end\"\n      data-slot=\"number-field-increment\"\n      render={\n        render ?? (\n          <KitButton\n            profile=\"joined-end\"\n            shape=\"square\"\n            size={context?.size}\n            variant=\"ghost\"\n          />\n        )\n      }\n      {...props}\n    >\n      <PlusIcon />\n    </NumberFieldPrimitive.Increment>\n  );\n}\n\nexport function NumberFieldInput({\n  className,\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.Input.Props>): React.ReactElement {\n  return (\n    <NumberFieldPrimitive.Input\n      className={cn(\n        slotClass(\"number-field-input\", props),\n        \"w-full min-w-0 grow bg-transparent in-data-[size=sm]:px-[calc(--spacing(2.5)-1px)] in-data-[size=xl]:px-[calc(--spacing(4)-1px)] px-[calc(--spacing(3)-1px)] text-center tabular-nums leading-7 in-data-[size=sm]:leading-6 outline-none\",\n        className,\n      )}\n      data-slot=\"number-field-input\"\n      {...props}\n    />\n  );\n}\n\nexport function NumberFieldScrubArea({\n  className,\n  label,\n  ...props\n}: WithStringClassName<NumberFieldPrimitive.ScrubArea.Props> & {\n  label: string;\n}): React.ReactElement {\n  const context = React.useContext(NumberFieldContext);\n  if (!context)\n    throw new Error(\n      \"NumberFieldScrubArea must be used within a NumberField component for accessibility.\",\n    );\n  return (\n    <NumberFieldPrimitive.ScrubArea\n      className={cn(\n        slotClass(\"number-field-scrub-area\", props),\n        \"flex cursor-ew-resize data-disabled:cursor-not-allowed\",\n        className,\n      )}\n      data-slot=\"number-field-scrub-area\"\n      {...props}\n    >\n      {/* The label takes the scrub area's cursor and fades with a disabled\n          field (design-rework.css, the shared label rules). */}\n      <Label htmlFor={context.fieldId}>{label}</Label>\n      <NumberFieldPrimitive.ScrubAreaCursor className=\"drop-shadow-[0_1px_1px_--theme(--color-black/50%)] filter\">\n        <CursorGrowIcon />\n      </NumberFieldPrimitive.ScrubAreaCursor>\n    </NumberFieldPrimitive.ScrubArea>\n  );\n}\n\nexport function CursorGrowIcon(\n  props: React.ComponentProps<\"svg\">,\n): React.ReactElement {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"black\"\n      height=\"14\"\n      stroke=\"white\"\n      viewBox=\"0 0 24 14\"\n      width=\"26\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z\" />\n    </svg>\n  );\n}\n\nexport { NumberFieldPrimitive };\n","type":"registry:ui","target":"~/src/siteplane/components/number-field/number-field.tsx"},{"path":"packages/ui/src/components/number-field/number-field.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* A Number Field is compact by default: two square step buttons and a 7rem\n   * value field, room for a formatted amount such as $1,250.00. The control\n   * sizes are the Input scale (28, 32, 36 and 48 px) and a step button is as\n   * wide as its control is tall, so the value field keeps its width at every\n   * size. The root reads --number-field-width; a width utility in className\n   * replaces it. */\n  .sp-number-field {\n    --number-field-control: var(--sp-rw-control-md);\n    --number-field-width: calc(var(--number-field-control) * 2 + 7rem);\n  }\n\n  .sp-number-field[data-size=\"sm\"] {\n    --number-field-control: var(--sp-rw-control-sm);\n  }\n\n  .sp-number-field[data-size=\"lg\"] {\n    --number-field-control: var(--sp-rw-control-lg);\n  }\n\n  .sp-number-field[data-size=\"xl\"] {\n    --number-field-control: var(--sp-rw-control-xl);\n  }\n\n  .sp-number-field-value-window {\n    position: absolute;\n    inset: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    overflow: hidden;\n    pointer-events: none;\n    color: var(--foreground);\n    font-variant-numeric: tabular-nums;\n    text-align: center;\n  }\n\n  .sp-number-field-value {\n    position: absolute;\n    inset: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    line-height: inherit;\n    will-change: opacity, transform;\n  }\n\n  .sp-number-field-value[data-roll=\"in\"],\n  .sp-number-field-value[data-roll=\"out\"] {\n    animation-duration: var(--motion-duration-base);\n    animation-fill-mode: both;\n    animation-timing-function: var(--motion-ease-standard);\n  }\n\n  .sp-number-field-value-window[data-roll-direction=\"up\"]\n    .sp-number-field-value[data-roll=\"in\"] {\n    animation-name: sp-number-field-roll-in-up;\n  }\n  .sp-number-field-value-window[data-roll-direction=\"up\"]\n    .sp-number-field-value[data-roll=\"out\"] {\n    animation-name: sp-number-field-roll-out-up;\n  }\n  .sp-number-field-value-window[data-roll-direction=\"down\"]\n    .sp-number-field-value[data-roll=\"in\"] {\n    animation-name: sp-number-field-roll-in-down;\n  }\n  .sp-number-field-value-window[data-roll-direction=\"down\"]\n    .sp-number-field-value[data-roll=\"out\"] {\n    animation-name: sp-number-field-roll-out-down;\n  }\n\n  @keyframes sp-number-field-roll-in-up {\n    from {\n      opacity: 0;\n      transform: translateY(100%);\n    }\n    to {\n      opacity: 1;\n      transform: translateY(0);\n    }\n  }\n  @keyframes sp-number-field-roll-out-up {\n    from {\n      opacity: 1;\n      transform: translateY(0);\n    }\n    to {\n      opacity: 0;\n      transform: translateY(-100%);\n    }\n  }\n  @keyframes sp-number-field-roll-in-down {\n    from {\n      opacity: 0;\n      transform: translateY(-100%);\n    }\n    to {\n      opacity: 1;\n      transform: translateY(0);\n    }\n  }\n  @keyframes sp-number-field-roll-out-down {\n    from {\n      opacity: 1;\n      transform: translateY(0);\n    }\n    to {\n      opacity: 0;\n      transform: translateY(100%);\n    }\n  }\n\n  html[data-motion-reduce=\"true\"] .sp-number-field-value[data-roll=\"in\"],\n  html[data-motion-reduce=\"true\"] .sp-number-field-value[data-roll=\"out\"] {\n    animation-duration: 1ms;\n    animation-delay: 0ms;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .sp-number-field-value[data-roll=\"in\"],\n    .sp-number-field-value[data-roll=\"out\"] {\n      animation-duration: 1ms;\n      animation-delay: 0ms;\n    }\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/number-field/number-field.css"},{"path":"packages/ui/src/components/number-field/index.ts","content":"export * from \"./number-field\";\n","type":"registry:ui","target":"~/src/siteplane/components/number-field/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/number-field.md","categories":["Forms & selection"],"meta":{"avoidWhen":["Avoid for a value chosen along a range where precision is not needed; use Slider.","Avoid for digit codes such as a PIN; use OTP Field."],"description":"Base UI-backed numeric entry and stepping.","knownLimitations":[],"providerNotes":[],"releaseStatus":"stable","searchTerms":["number-field","number field","number","field"],"useWhen":["Use for a number that people type, step with the arrow keys or buttons, or scrub, such as a quantity."]}}