{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "coupon-field",
  "title": "Coupon Field",
  "description": "A compound discount field with rounded, pill, and compact variants.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "input",
    "label",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/coupon-field.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Check, CheckCircle2, CircleAlert, LoaderCircle, X } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type KeyboardEvent,\n  useContext,\n  useEffect,\n  useId,\n  useState,\n} from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const COUPON_FIELD_STATUSES = [\"idle\", \"applying\", \"applied\", \"error\"] as const;\nexport const COUPON_FIELD_VARIANTS = [\"rounded\", \"pill\", \"compact\"] as const;\n\nexport type CouponFieldStatus = (typeof COUPON_FIELD_STATUSES)[number];\nexport type CouponFieldVariant = (typeof COUPON_FIELD_VARIANTS)[number];\n\nexport type CouponFieldProps = Omit<ComponentProps<\"div\">, \"onChange\"> & {\n  variant?: CouponFieldVariant;\n  status?: CouponFieldStatus;\n  appliedCode?: string;\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n  onApply?: (code: string) => void;\n  disabled?: boolean;\n};\n\nfunction couponFieldChrome(variant: CouponFieldVariant) {\n  const compact = variant === \"compact\";\n  const pill = variant === \"pill\";\n\n  return {\n    labelClass: compact ? \"mb-1 text-[11.5px] leading-4\" : \"mb-1.5 text-[12px] leading-4\",\n    controlClass: compact ? \"gap-0.5 p-0.5\" : \"gap-1 p-1\",\n    controlRadius: pill ? \"rounded-full\" : compact ? \"rounded-xl\" : \"rounded-[14px]\",\n    inputClass: compact\n      ? \"h-7 px-2 text-[12.5px] leading-4 md:text-[12.5px] md:leading-4\"\n      : \"h-8 px-2.5 text-[13px] leading-[18px] md:text-[13px] md:leading-[18px]\",\n    applyClass: compact\n      ? \"h-7 min-w-[64px] px-3 text-[12px] leading-4 has-[>svg]:px-3\"\n      : \"h-8 min-w-[76px] px-3.5 text-[12.5px] leading-4 has-[>svg]:px-3.5\",\n    applyRadius: \"rounded-full\",\n    feedbackClass: compact\n      ? \"mt-1.5 min-h-6 gap-1.5 px-0.5 text-[11.5px] leading-4\"\n      : \"mt-2 min-h-7 gap-2 px-1 text-[12px] leading-4\",\n    removeClass: compact\n      ? \"-mt-0.5 h-[22px] px-1.5 has-[>svg]:px-1.5\"\n      : \"-mt-1 h-6 px-2 has-[>svg]:px-2\",\n    iconClass: compact ? \"size-3\" : \"size-3.5\",\n  };\n}\n\ntype CouponFieldContextValue = {\n  status: CouponFieldStatus;\n  appliedCode?: string;\n  draft: string;\n  setDraft: (value: string) => void;\n  apply: () => void;\n  clear: () => void;\n  canApply: boolean;\n  isReplacing: boolean;\n  isApplied: boolean;\n  disabled: boolean;\n  inputId: string;\n  labelId: string;\n  feedbackId: string;\n  chrome: ReturnType<typeof couponFieldChrome>;\n};\n\nconst CouponFieldContext = createContext<CouponFieldContextValue | null>(null);\n\nfunction useCouponField(name: string) {\n  const context = useContext(CouponFieldContext);\n  if (!context) throw new Error(`${name} must be used within CouponField`);\n  return context;\n}\n\nconst couponFieldVariants = cva(\"w-full text-foreground\", {\n  variants: { variant: { rounded: \"\", pill: \"\", compact: \"\" } },\n});\n\nexport function CouponField({\n  variant = \"rounded\",\n  status = \"idle\",\n  appliedCode,\n  defaultValue = \"\",\n  value,\n  onValueChange,\n  onApply,\n  disabled = false,\n  children,\n  className,\n  ...props\n}: CouponFieldProps) {\n  const inputId = useId();\n  const labelId = useId();\n  const feedbackId = useId();\n  const chrome = couponFieldChrome(variant);\n  const [internalValue, setInternalValue] = useState(defaultValue || appliedCode || \"\");\n  const draft = value ?? internalValue;\n  const normalizedDraft = draft.trim();\n  const normalizedAppliedCode = appliedCode?.trim();\n  const isReplacing = Boolean(\n    normalizedAppliedCode && normalizedDraft && normalizedDraft !== normalizedAppliedCode,\n  );\n  const canApply = Boolean(\n    normalizedDraft &&\n      status !== \"applying\" &&\n      !disabled &&\n      normalizedDraft !== normalizedAppliedCode,\n  );\n  const isApplied = Boolean(\n    status === \"applied\" && normalizedAppliedCode && normalizedDraft === normalizedAppliedCode,\n  );\n\n  useEffect(() => {\n    if (value === undefined && status === \"applied\" && appliedCode !== undefined) {\n      setInternalValue(appliedCode);\n    }\n  }, [appliedCode, status, value]);\n\n  const setDraft = (nextValue: string) => {\n    if (value === undefined) setInternalValue(nextValue);\n    onValueChange?.(nextValue);\n  };\n\n  const context: CouponFieldContextValue = {\n    status,\n    appliedCode,\n    draft,\n    setDraft,\n    apply: () => {\n      if (canApply) onApply?.(normalizedDraft);\n    },\n    clear: () => setDraft(\"\"),\n    canApply,\n    isReplacing,\n    isApplied,\n    disabled,\n    inputId,\n    labelId,\n    feedbackId,\n    chrome,\n  };\n\n  return (\n    <CouponFieldContext.Provider value={context}>\n      <div\n        data-slot=\"coupon-field\"\n        data-variant={variant}\n        data-status={status}\n        className={cn(couponFieldVariants({ variant }), disabled && \"opacity-55\", className)}\n        {...props}\n        aria-busy={status === \"applying\"}\n      >\n        {children}\n      </div>\n    </CouponFieldContext.Provider>\n  );\n}\n\nexport type CouponFieldLabelProps = ComponentProps<\"label\">;\n\nexport function CouponFieldLabel({\n  children = \"Coupon code\",\n  className,\n  ...props\n}: CouponFieldLabelProps) {\n  const context = useCouponField(\"CouponFieldLabel\");\n\n  return (\n    <Label\n      data-slot=\"coupon-field-label\"\n      className={cn(\n        \"block font-medium text-muted-foreground select-auto\",\n        context.chrome.labelClass,\n        className,\n      )}\n      {...props}\n      id={context.labelId}\n      htmlFor={context.inputId}\n    >\n      {children}\n    </Label>\n  );\n}\n\nexport type CouponFieldControlProps = ComponentProps<\"div\">;\n\nexport function CouponFieldControl({ children, className, ...props }: CouponFieldControlProps) {\n  const context = useCouponField(\"CouponFieldControl\");\n\n  return (\n    <div\n      data-slot=\"coupon-field-control\"\n      className={cn(\n        \"flex min-w-0 items-center border bg-card transition-[border-color,box-shadow] duration-[120ms] ease-out hover:border-border-strong focus-within:border-border-strong focus-within:shadow-[0_0_0_3px_color-mix(in_oklab,var(--primary)_18%,transparent)] motion-reduce:transition-none\",\n        context.chrome.controlClass,\n        context.chrome.controlRadius,\n        context.status === \"error\"\n          ? \"border-[color-mix(in_oklab,var(--destructive)_70%,var(--border))] hover:border-destructive focus-within:border-destructive\"\n          : context.status === \"applied\"\n            ? \"border-[color-mix(in_oklab,var(--success)_45%,var(--border))]\"\n            : \"border-border\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport type CouponFieldInputProps = Omit<\n  ComponentProps<\"input\">,\n  \"defaultValue\" | \"onChange\" | \"value\"\n>;\n\nexport function CouponFieldInput({\n  className,\n  disabled,\n  onKeyDown,\n  ...props\n}: CouponFieldInputProps) {\n  const context = useCouponField(\"CouponFieldInput\");\n  const isDisabled = context.disabled || disabled;\n\n  const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n    onKeyDown?.(event);\n    if (\n      !event.defaultPrevented &&\n      event.key === \"Enter\" &&\n      !event.nativeEvent.isComposing &&\n      !event.altKey &&\n      !event.ctrlKey &&\n      !event.metaKey &&\n      !event.shiftKey\n    ) {\n      event.preventDefault();\n      context.apply();\n    }\n  };\n\n  return (\n    <Input\n      data-slot=\"coupon-field-input\"\n      className={cn(\n        \"min-w-0 flex-1 rounded-none border-0 bg-transparent py-0 font-medium tracking-[0.04em] uppercase shadow-none outline-none placeholder:font-normal placeholder:tracking-normal placeholder:normal-case placeholder:text-subtle-foreground focus-visible:ring-0 disabled:pointer-events-auto disabled:cursor-not-allowed disabled:opacity-100 dark:bg-transparent\",\n        context.chrome.inputClass,\n        className,\n      )}\n      {...props}\n      id={context.inputId}\n      value={context.draft}\n      onChange={(event) => context.setDraft(event.target.value)}\n      onKeyDown={handleKeyDown}\n      placeholder=\"Enter code\"\n      autoCapitalize=\"characters\"\n      autoComplete=\"off\"\n      spellCheck={false}\n      disabled={isDisabled}\n      aria-invalid={context.status === \"error\"}\n      aria-labelledby={context.labelId}\n      aria-describedby={context.feedbackId}\n    />\n  );\n}\n\nexport type CouponFieldApplyProps = ComponentProps<\"button\">;\n\nexport function CouponFieldApply({\n  children,\n  className,\n  disabled,\n  onClick,\n  ...props\n}: CouponFieldApplyProps) {\n  const context = useCouponField(\"CouponFieldApply\");\n  const isApplying = context.status === \"applying\";\n  const isApplied = context.isApplied;\n  const isDisabled = context.disabled || disabled || !context.canApply;\n  const label = isApplying\n    ? \"Applying…\"\n    : isApplied\n      ? \"Applied\"\n      : context.isReplacing\n        ? \"Replace\"\n        : \"Apply\";\n\n  return (\n    <Button\n      type=\"button\"\n      data-slot=\"coupon-field-apply\"\n      variant={isDisabled && !isApplying && !isApplied ? \"secondary\" : \"default\"}\n      className={cn(\n        \"inline-flex shrink-0 items-center justify-center gap-1.5 py-0 font-medium transition-[scale,background-color,color,filter] duration-[140ms] ease-out-quint focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid disabled:pointer-events-auto motion-reduce:transition-none\",\n        context.chrome.applyClass,\n        context.chrome.applyRadius,\n        isApplying\n          ? \"cursor-progress bg-primary text-primary-foreground opacity-80 hover:bg-primary disabled:opacity-80\"\n          : isApplied\n            ? \"cursor-default bg-success/14 text-success-ink hover:bg-success/14 disabled:opacity-100\"\n            : isDisabled\n              ? \"cursor-not-allowed bg-secondary text-subtle-foreground hover:bg-secondary disabled:opacity-100\"\n              : \"bg-primary text-primary-foreground hover:bg-primary hover:brightness-[1.08] active:scale-[0.97] motion-reduce:active:scale-100\",\n        className,\n      )}\n      {...props}\n      disabled={isDisabled}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.apply();\n      }}\n    >\n      {isApplying ? (\n        <LoaderCircle\n          className={cn(context.chrome.iconClass, \"animate-spin motion-reduce:animate-none\")}\n          aria-hidden=\"true\"\n        />\n      ) : isApplied ? (\n        <Check\n          className={cn(\n            context.chrome.iconClass,\n            \"animate-in fade-in-0 zoom-in-50 duration-200 ease-out-quint motion-reduce:animate-none\",\n          )}\n          strokeWidth={2.2}\n          aria-hidden=\"true\"\n        />\n      ) : null}\n      {children ?? label}\n    </Button>\n  );\n}\n\nexport type CouponFieldFeedbackProps = ComponentProps<\"div\">;\n\nexport function CouponFieldFeedback({ children, className, ...props }: CouponFieldFeedbackProps) {\n  const context = useCouponField(\"CouponFieldFeedback\");\n  const isError = context.status === \"error\";\n  const isApplied = context.status === \"applied\";\n  const isApplying = context.status === \"applying\";\n\n  if (context.status === \"idle\" && !children) return null;\n\n  return (\n    <div\n      key={context.status}\n      data-slot=\"coupon-field-feedback\"\n      className={cn(\n        \"flex animate-in items-start duration-240 ease-out-quint fade-in-0 slide-in-from-bottom-1 motion-reduce:animate-none\",\n        context.chrome.feedbackClass,\n        isError ? \"text-destructive-ink\" : \"text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {isError ? (\n        <CircleAlert\n          className={cn(\"mt-px shrink-0\", context.chrome.iconClass)}\n          aria-hidden=\"true\"\n        />\n      ) : isApplied ? (\n        <CheckCircle2\n          className={cn(\"mt-px shrink-0 text-success-ink\", context.chrome.iconClass)}\n          aria-hidden=\"true\"\n        />\n      ) : isApplying ? (\n        <LoaderCircle\n          className={cn(\n            \"mt-px shrink-0 animate-spin motion-reduce:animate-none\",\n            context.chrome.iconClass,\n          )}\n          aria-hidden=\"true\"\n        />\n      ) : null}\n      {children}\n    </div>\n  );\n}\n\nexport type CouponFieldMessageProps = ComponentProps<\"span\">;\n\nexport function CouponFieldMessage({ children, className, ...props }: CouponFieldMessageProps) {\n  const context = useCouponField(\"CouponFieldMessage\");\n  const isError = context.status === \"error\";\n  const isStatus = context.status === \"applied\" || context.status === \"applying\";\n\n  return (\n    <span\n      data-slot=\"coupon-field-message\"\n      className={cn(\n        \"min-w-0 flex-1\",\n        context.status === \"applying\" && \"shimmer-text motion-reduce:text-muted-foreground\",\n        context.status === \"applied\" && \"text-foreground\",\n        className,\n      )}\n      {...props}\n      id={context.feedbackId}\n      role={isError ? \"alert\" : isStatus ? \"status\" : undefined}\n      aria-live={isError ? \"assertive\" : isStatus ? \"polite\" : undefined}\n      aria-atomic=\"true\"\n    >\n      {children}\n    </span>\n  );\n}\n\nexport type CouponFieldRemoveProps = ComponentProps<\"button\">;\n\nexport function CouponFieldRemove({\n  children = \"Remove\",\n  className,\n  onClick,\n  ...props\n}: CouponFieldRemoveProps) {\n  const context = useCouponField(\"CouponFieldRemove\");\n  if (!context.appliedCode) return null;\n\n  return (\n    <Button\n      type=\"button\"\n      data-slot=\"coupon-field-remove\"\n      variant=\"ghost\"\n      size=\"sm\"\n      className={cn(\n        \"inline-flex shrink-0 items-center gap-1 rounded-full text-[11.5px] font-medium text-subtle-foreground transition-[color,background-color,scale] duration-[120ms] ease-out hover:bg-accent hover:text-foreground focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid active:scale-[0.97] disabled:pointer-events-auto disabled:cursor-not-allowed disabled:opacity-100 motion-reduce:transition-none motion-reduce:active:scale-100 dark:hover:bg-accent\",\n        context.chrome.removeClass,\n        className,\n      )}\n      {...props}\n      disabled={context.disabled || context.status === \"applying\" || props.disabled}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.clear();\n      }}\n    >\n      <X className={context.chrome.iconClass} aria-hidden=\"true\" />\n      {children}\n    </Button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/coupon-field.tsx"
    }
  ],
  "categories": [
    "commerce",
    "forms"
  ],
  "type": "registry:ui"
}