{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select-react",
  "title": "Select (React Select)",
  "description": "A feature-rich select component built with React Select, supporting search, multi-select, and custom styling with Blok theming.",
  "dependencies": [
    "react-select",
    "@mdi/js"
  ],
  "registryDependencies": [
    "https://blok.sitecore.com/r/theme.json",
    "https://blok.sitecore.com/r/badge.json"
  ],
  "files": [
    {
      "path": "src/components/ui/select-react.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Icon } from \"@/lib/icon\";\nimport { mdiCheck, mdiChevronDown, mdiClose } from \"@mdi/js\";\nimport * as React from \"react\";\nimport ReactSelect, {\n  type Props as ReactSelectProps,\n  type GroupBase,\n  type StylesConfig,\n  type ClassNamesConfig,\n  components,\n  type DropdownIndicatorProps,\n  type ClearIndicatorProps,\n  type MultiValueRemoveProps,\n  type MultiValueGenericProps,\n  type OptionProps,\n} from \"react-select\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type SelectReactOption = {\n  value: string;\n  label: string;\n  description?: string;\n  disabled?: boolean;\n  icon?: React.ReactNode;\n};\n\nexport type SelectReactProps<\n  Option = SelectReactOption,\n  IsMulti extends boolean = false,\n  Group extends GroupBase<Option> = GroupBase<Option>,\n> = Omit<ReactSelectProps<Option, IsMulti, Group>, \"classNames\" | \"styles\"> & {\n  size?: \"sm\" | \"default\";\n  className?: string;\n};\n\n// Custom dropdown indicator matching Blok select styling\nfunction DropdownIndicator<\n  Option,\n  IsMulti extends boolean,\n  Group extends GroupBase<Option>,\n>(props: DropdownIndicatorProps<Option, IsMulti, Group>) {\n  return (\n    <components.DropdownIndicator {...props}>\n      <Icon path={mdiChevronDown} className=\"size-5 opacity-50\" />\n    </components.DropdownIndicator>\n  );\n}\n\n// Custom clear indicator matching Blok styling\nfunction ClearIndicator<\n  Option,\n  IsMulti extends boolean,\n  Group extends GroupBase<Option>,\n>(props: ClearIndicatorProps<Option, IsMulti, Group>) {\n  return (\n    <components.ClearIndicator {...props}>\n      <Icon path={mdiClose} className=\"size-4 opacity-50 hover:opacity-100\" />\n    </components.ClearIndicator>\n  );\n}\n\n// Custom multi-value container using Badge component\nfunction MultiValueContainer<\n  Option,\n  IsMulti extends boolean,\n  Group extends GroupBase<Option>,\n>(props: MultiValueGenericProps<Option, IsMulti, Group>) {\n  return (\n    <Badge size=\"md\" colorScheme=\"neutral\" className=\"gap-1 pr-0.5\">\n      {props.children}\n    </Badge>\n  );\n}\n\n// Custom multi-value remove button\nfunction MultiValueRemove<\n  Option,\n  IsMulti extends boolean,\n  Group extends GroupBase<Option>,\n>(props: MultiValueRemoveProps<Option, IsMulti, Group>) {\n  return (\n    <components.MultiValueRemove {...props}>\n      <Icon path={mdiClose} className=\"size-3 opacity-60 hover:opacity-100\" />\n    </components.MultiValueRemove>\n  );\n}\n\n// Custom option component with checkmark for selected items\nconst CustomOption = React.memo(function CustomOption<\n  Option extends SelectReactOption,\n  IsMulti extends boolean,\n  Group extends GroupBase<Option>,\n>(props: OptionProps<Option, IsMulti, Group>) {\n  const { isSelected, data } = props;\n  const hasDescription = Boolean(data.description);\n  return (\n    <components.Option {...props}>\n      <div\n        className={cn(\n          \"flex w-full gap-2\",\n          hasDescription ? \"items-start py-1\" : \"items-center\",\n        )}\n      >\n        {data.icon && (\n          <span className={cn(\"shrink-0\", hasDescription && \"mt-0.5\")}>\n            {data.icon}\n          </span>\n        )}\n        <span className=\"min-w-0 flex-1\">\n          {hasDescription ? (\n            <span className=\"flex flex-col gap-0.5\">\n              <span className=\"text-sm font-semibold leading-tight\">\n                {data.label}\n              </span>\n              <span className=\"text-xs leading-snug text-subtle-text\">\n                {data.description}\n              </span>\n            </span>\n          ) : (\n            props.children\n          )}\n        </span>\n        {isSelected && (\n          <span className={cn(\"shrink-0\", hasDescription && \"mt-0.5\")}>\n            <Icon path={mdiCheck} className=\"size-4\" />\n          </span>\n        )}\n      </div>\n    </components.Option>\n  );\n}) as typeof components.Option;\n\nfunction SelectReact<\n  Option extends SelectReactOption = SelectReactOption,\n  IsMulti extends boolean = false,\n  Group extends GroupBase<Option> = GroupBase<Option>,\n>({\n  size = \"default\",\n  className,\n  isDisabled,\n  instanceId,\n  components: userComponents,\n  ...props\n}: SelectReactProps<Option, IsMulti, Group>) {\n  // Generate a stable ID to prevent hydration mismatches\n  const id = React.useId();\n  const selectInstanceId = instanceId ?? id;\n\n  const classNames = React.useMemo<ClassNamesConfig<Option, IsMulti, Group>>(\n    () => ({\n      control: ({ isFocused, isDisabled: controlDisabled, menuIsOpen }) =>\n        cn(\n          // Base styles matching SelectTrigger\n          \"border-input text-md flex w-full items-center gap-2 rounded-md border-1 px-3 transition-[color] outline-none\",\n          // Size variants\n          size === \"default\" ? \"min-h-10\" : \"min-h-8\",\n          // Focus states - only apply ring when focused but menu is closed\n          isFocused && !menuIsOpen && \"border-input ring-ring/50 ring-1\",\n          // When menu is open, use border-2 (no ring to avoid double border)\n          menuIsOpen && \"border-primary border-2\",\n          // Disabled state\n          controlDisabled && \"cursor-not-allowed opacity-50\",\n          // Dark mode\n          \"dark:bg-input/30 dark:hover:bg-input/50\",\n          // Remove default react-select background\n          \"bg-body-bg\",\n          className,\n        ),\n      placeholder: () => \"text-muted-foreground\",\n      input: () => \"text-foreground\",\n      singleValue: () => \"text-foreground\",\n      valueContainer: () => \"gap-1 p-0\",\n      indicatorSeparator: () => \"hidden\",\n      indicatorsContainer: () => \"gap-1\",\n      dropdownIndicator: () => \"text-muted-foreground p-0\",\n      clearIndicator: () => \"text-muted-foreground p-0 cursor-pointer\",\n      menu: () =>\n        cn(\n          \"bg-popover text-popover-foreground relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md\",\n          // Animation classes matching SelectContent\n          \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n        ),\n      menuList: () => \"p-1 max-h-[300px]\",\n      option: ({ isFocused, isSelected, isDisabled: optionDisabled, data }) =>\n        cn(\n          // Base styles matching SelectItem\n          \"relative flex w-full cursor-default gap-2 rounded-sm py-1.5 px-2 !text-sm outline-hidden select-none\",\n          data.description ? \"items-start\" : \"items-center\",\n          // Hover/focus state\n          isFocused && \"bg-accent text-accent-foreground\",\n          // Selected state\n          isSelected && \"bg-accent text-accent-foreground\",\n          // Disabled state\n          optionDisabled && \"pointer-events-none opacity-50\",\n        ),\n      group: () => \"\",\n      groupHeading: () =>\n        \"text-muted-foreground px-2 py-1.5 text-xs font-semibold uppercase\",\n      noOptionsMessage: () => \"text-muted-foreground py-6 text-center text-sm\",\n      loadingMessage: () => \"text-muted-foreground py-6 text-center text-sm\",\n      multiValue: () => \"\",\n      multiValueLabel: () => \"text-sm\",\n      multiValueRemove: () =>\n        \"hover:bg-destructive/20 hover:text-destructive rounded-sm p-0.5 transition-colors cursor-pointer\",\n    }),\n    [size, className],\n  );\n\n  // Remove all default styles to use only classNames\n  const styles = React.useMemo<StylesConfig<Option, IsMulti, Group>>(\n    () => ({\n      control: () => ({\n        boxShadow: \"none\",\n      }),\n      option: (base) => ({\n        ...base,\n        backgroundColor: undefined,\n        color: undefined,\n        \"&:active\": {\n          backgroundColor: undefined,\n        },\n      }),\n      menu: (base) => ({\n        ...base,\n        backgroundColor: undefined,\n      }),\n      menuPortal: (base) => ({\n        ...base,\n        zIndex: 50,\n      }),\n      menuList: (base) => ({\n        ...base,\n        padding: undefined,\n      }),\n      multiValue: (base) => ({\n        ...base,\n        backgroundColor: undefined,\n      }),\n      multiValueLabel: (base) => ({\n        ...base,\n        color: undefined,\n        padding: undefined,\n      }),\n      multiValueRemove: (base) => ({\n        ...base,\n        color: undefined,\n        \"&:hover\": {\n          backgroundColor: undefined,\n          color: undefined,\n        },\n      }),\n      input: (base) => ({\n        ...base,\n        color: undefined,\n      }),\n      placeholder: (base) => ({\n        ...base,\n        color: undefined,\n      }),\n      singleValue: (base) => ({\n        ...base,\n        color: undefined,\n      }),\n      indicatorSeparator: () => ({\n        display: \"none\",\n      }),\n      dropdownIndicator: (base) => ({\n        ...base,\n        color: undefined,\n        padding: 0,\n      }),\n      clearIndicator: (base) => ({\n        ...base,\n        color: undefined,\n        padding: 0,\n      }),\n    }),\n    [],\n  );\n\n  const mergedComponents = React.useMemo(\n    () => ({\n      DropdownIndicator,\n      ClearIndicator,\n      MultiValueContainer,\n      MultiValueRemove,\n      Option: CustomOption,\n      ...userComponents,\n    }),\n    [userComponents],\n  );\n\n  return (\n    <ReactSelect<Option, IsMulti, Group>\n      unstyled\n      instanceId={selectInstanceId}\n      classNames={classNames}\n      styles={styles}\n      components={mergedComponents}\n      isDisabled={isDisabled}\n      isOptionDisabled={(option) => !!(option as SelectReactOption).disabled}\n      menuPortalTarget={typeof document !== \"undefined\" ? document.body : null}\n      {...props}\n    />\n  );\n}\n\nexport { SelectReact };\n",
      "type": "registry:ui"
    },
    {
      "path": "src/lib/icon.tsx",
      "content": "import type { SVGProps } from \"react\";\n\ntype IconProps = SVGProps<SVGSVGElement> & {\n  path: string;\n  title?: string;\n  size?: number | string;\n  className?: string;\n  fill?: string;\n};\n\nexport function Icon({\n  path,\n  title,\n  size = 1,\n  className,\n  fill = \"currentColor\",\n  ...rest\n}: IconProps) {\n  return (\n    <svg\n      transform={`scale(${size})`}\n      viewBox=\"0 0 24 24\"\n      aria-label={title}\n      className={className}\n      {...rest}\n    >\n      {title ? <title>{title}</title> : null}\n      <path d={path} fill={fill} />\n    </svg>\n  );\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}