{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "choice-group",
  "title": "choice-group",
  "dependencies": [
    "class-variance-authority@^0.7"
  ],
  "registryDependencies": [
    "@dwellir/dwellir-theme",
    "@dwellir/utils"
  ],
  "files": [
    {
      "path": "components/ui/choice-group.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { type ComponentProps, createContext, type ReactNode, useContext, useId } from 'react';\nimport { cn } from '@/lib/utils';\n\n/* ToggleGroup shares these looks, so a choice and a toggle read the same. */\nconst choiceGroupVariants = cva('flex w-fit max-w-full items-center', {\n  variants: {\n    variant: {\n      /* One choice from a few (Monthly or Yearly): the same pill track as Tabs, scrolling when it is too wide. */\n      segmented: 'choice-segmented-2',\n      /* Separate pills. */\n      pills: 'flex-wrap gap-2',\n      /* Options that need a line of explanation: tiles in a grid; set the columns. */\n      tiles: 'grid w-full items-stretch gap-2',\n      /* Each item brings its own look, such as a badge. */\n      bare: 'flex-wrap gap-2.5',\n    },\n    orientation: {\n      horizontal: '',\n      vertical: 'w-full flex-col items-stretch rounded-xl',\n    },\n  },\n  compoundVariants: [{ variant: 'tiles', orientation: 'vertical', className: 'grid-cols-1' }],\n  defaultVariants: { variant: 'segmented', orientation: 'horizontal' },\n});\n\nconst choiceItemVariants = cva('', {\n  variants: {\n    variant: {\n      segmented: 'ui-choice choice-segmented text-sm',\n      /*\n       * Every pill fills with the card colour, as a field does: a sheet's grain\n       * stays out of it. Hover and the chosen pill lay their tint over that fill.\n       */\n      pills: 'ui-choice choice-pills text-sm',\n      /* The chosen tile lights the aurora rim, as a featured card does. Its corner caps the control's at xl. */\n      tiles:\n        'ui-choice choice-tiles relative h-auto flex-col items-start justify-start gap-0.5 px-4 py-3 text-sm font-normal data-[state=on]:rim',\n      /* The item adds only the focus ring and a small lift under the pointer. */\n      bare: 'choice-bare',\n    },\n    orientation: {\n      horizontal: '',\n      vertical: 'h-auto min-h-11 w-full justify-start rounded-lg px-4 py-3 whitespace-normal',\n    },\n  },\n  defaultVariants: { variant: 'segmented', orientation: 'horizontal' },\n});\n\ntype Variant = VariantProps<typeof choiceGroupVariants>['variant'];\n\ninterface ChoiceContextValue {\n  id: string;\n  name: string;\n  value: string;\n  onValueChange: (value: string) => void;\n  variant: Variant;\n  orientation: VariantProps<typeof choiceGroupVariants>['orientation'];\n  disabled?: boolean;\n}\n\nconst ChoiceContext = createContext<ChoiceContextValue | null>(null);\n\n/**\n * One choice from a few, as native radios: the arrow keys move the choice, and\n * the browser does it without script. For on/off toggles use ToggleGroup.\n */\nfunction ChoiceGroup({\n  className,\n  variant,\n  orientation = 'horizontal',\n  name,\n  value,\n  onValueChange,\n  disabled,\n  children,\n  onKeyDown,\n  ...props\n}: Omit<ComponentProps<'div'>, 'onChange'> &\n  VariantProps<typeof choiceGroupVariants> & {\n    value: string;\n    onValueChange: (value: string) => void;\n    /** Shared by the radios. Defaults to a unique id. */\n    name?: string;\n    /** Locks every radio, for example while a stream runs. */\n    disabled?: boolean;\n  }) {\n  const fallbackName = useId();\n  return (\n    <div\n      role=\"radiogroup\"\n      data-slot=\"choice-group\"\n      data-variant={variant ?? 'segmented'}\n      aria-orientation={orientation === 'vertical' ? orientation : undefined}\n      data-orientation={orientation === 'vertical' ? orientation : undefined}\n      className={cn(choiceGroupVariants({ variant, orientation }), className)}\n      onKeyDown={(event) => {\n        onKeyDown?.(event);\n        if (event.defaultPrevented || orientation !== 'vertical') return;\n        if (event.key !== 'Home' && event.key !== 'End') return;\n        const radios = [\n          ...event.currentTarget.querySelectorAll<HTMLInputElement>(\n            'input[type=\"radio\"]:not(:disabled)',\n          ),\n        ].filter((radio) => radio.closest('[role=\"radiogroup\"]') === event.currentTarget);\n        const target = event.key === 'Home' ? radios[0] : radios.at(-1);\n        if (!target) return;\n        event.preventDefault();\n        target.focus();\n        target.click();\n      }}\n      {...props}\n    >\n      <ChoiceContext.Provider\n        value={{\n          id: fallbackName,\n          name: name ?? fallbackName,\n          value,\n          onValueChange,\n          variant,\n          orientation,\n          disabled,\n        }}\n      >\n        {children}\n      </ChoiceContext.Provider>\n    </div>\n  );\n}\n\nfunction ChoiceGroupItem({\n  className,\n  value,\n  disabled,\n  description,\n  children,\n  ...props\n}: Omit<ComponentProps<'label'>, 'onChange'> & {\n  value: string;\n  disabled?: boolean;\n  description?: ReactNode;\n}) {\n  const group = useContext(ChoiceContext);\n  if (!group) throw new Error('ChoiceGroupItem must sit inside a ChoiceGroup');\n  const checked = group.value === value;\n  const id = `${group.id}-${encodeURIComponent(value)}`;\n  return (\n    <label\n      data-slot=\"choice-group-item\"\n      data-state={checked ? 'on' : 'off'}\n      className={cn(\n        choiceItemVariants({ variant: group.variant, orientation: group.orientation }),\n        /* The radio is hidden, so its keyboard ring draws on the label. */\n        'choice-focus',\n        /* A disabled item takes no pointer, so it shows the group's default cursor. */\n        'choice-disabled',\n        className,\n      )}\n      {...props}\n    >\n      <input\n        data-slot=\"choice-group-radio\"\n        type=\"radio\"\n        className=\"sr-only\"\n        name={group.name}\n        value={value}\n        checked={checked}\n        disabled={group.disabled || disabled}\n        aria-labelledby={description ? `${id}-label` : undefined}\n        aria-describedby={description ? `${id}-description` : undefined}\n        onChange={() => group.onValueChange(value)}\n      />\n      {description ? (\n        <span data-slot=\"choice-group-content\" className=\"flex min-w-0 flex-col items-start\">\n          <span data-slot=\"choice-group-label\" id={`${id}-label`} className=\"font-bold\">\n            {children}\n          </span>\n          <span\n            data-slot=\"choice-group-description\"\n            id={`${id}-description`}\n            className=\"text-sm font-normal whitespace-normal text-muted-foreground\"\n          >\n            {description}\n          </span>\n        </span>\n      ) : (\n        children\n      )}\n    </label>\n  );\n}\n\nexport { ChoiceGroup, ChoiceGroupItem, choiceGroupVariants, choiceItemVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}