{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "title": "Select",
  "author": "AlgorizeTech",
  "description": "A searchable select with single or multiple selection, option groups and per-option icons. Use it when a native <select> cannot carry enough structure.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/SelectBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AlertCircle, Check, ChevronDown, Search, X } from \"lucide-react\";\nimport React, { forwardRef, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getThemeStyles } from \"./select-theme\";\n\n// ==========================================\n// DYNAMIC THEME ENGINE\n// ==========================================\nexport type SelectColor = \"indigo\" | \"rose\" | \"emerald\" | \"zinc\" | string;\nexport type SelectVariant = \"default\" | \"filled\" | \"underlined\" | \"glow\" | \"glass\" | \"liquid-glass\";\nexport type SelectSize = \"sm\" | \"md\" | \"lg\";\n\n// ==========================================\n// SELECT COMPONENT\n// ==========================================\nexport interface SelectOption {\n  label: string;\n  value: string;\n  icon?: React.ElementType;\n  group?: string; // Added for nested/grouped options\n}\n\n// Walks up the tree to the first ancestor that clips its overflow (a modal\n// shell, a scroll pane, a card with overflow-hidden). That element — not the\n// viewport — is what actually crops the dropdown, so it defines the space the\n// menu has to work with. Returns null when nothing clips, meaning the viewport\n// is the only bound. Only overflow-y counts: the menu grows vertically, and a\n// box that clips just sideways (overflow-x: clip, e.g. a slide-in stage) leaves\n// it free to hang below — treating that as a clip flipped the menu up into it.\nconst getClippingRect = (el: HTMLElement): DOMRect | null => {\n  let node = el.parentElement;\n  while (node && node !== document.body) {\n    const { overflowY } = window.getComputedStyle(node);\n    if (/(auto|scroll|hidden|clip)/.test(overflowY)) {\n      return node.getBoundingClientRect();\n    }\n    node = node.parentElement;\n  }\n  return null;\n};\n\n/** Everything the two selection modes share. */\ninterface SelectBlockBaseProps {\n  options: SelectOption[];\n  placeholder?: string;\n  variant?: SelectVariant;\n  color?: SelectColor;\n  inputSize?: SelectSize;\n  label?: string;\n  helperText?: string;\n  error?: string;\n  icon?: React.ReactNode;\n  disabled?: boolean;\n  className?: string;\n  /** Adds a filter box above the options. */\n  searchable?: boolean;\n}\n\n/** The default: one option at a time, so the value is a plain string. */\nexport interface SelectBlockSingleProps extends SelectBlockBaseProps {\n  multiple?: false;\n  value?: string;\n  onChange?: (value: string) => void;\n}\n\n/** `multiple` — value and callback both deal in arrays. */\nexport interface SelectBlockMultipleProps extends SelectBlockBaseProps {\n  multiple: true;\n  value?: string[];\n  onChange?: (value: string[]) => void;\n}\n\n/**\n * Discriminated on `multiple`, so a single select hands your callback a\n * `string` and a multi select hands it a `string[]` — neither needs a cast,\n * and passing a `setState` straight in just works.\n */\nexport type SelectBlockProps = SelectBlockSingleProps | SelectBlockMultipleProps;\n\n/** The two modes collapsed, which is what the implementation works with. */\ntype SelectBlockImplProps = SelectBlockBaseProps & {\n  multiple?: boolean;\n  value?: string | string[];\n  onChange?: (value: string | string[]) => void;\n};\n\nconst SelectBlockImpl = forwardRef<HTMLDivElement, SelectBlockImplProps>(\n  (\n    {\n      options = [],\n      value,\n      onChange,\n      placeholder = \"Select an option\",\n      variant = \"default\",\n      color = \"primary\",\n      inputSize = \"md\",\n      label,\n      helperText,\n      error,\n      icon,\n      disabled = false,\n      className,\n      multiple = false,\n      searchable = false,\n    },\n    ref,\n  ) => {\n    const [isOpen, setIsOpen] = useState(false);\n    const [dropUp, setDropUp] = useState(false);\n    const [searchQuery, setSearchQuery] = useState(\"\");\n    const selectRef = useRef<HTMLDivElement>(null);\n    const dropdownRef = useRef<HTMLDivElement>(null);\n    const searchInputRef = useRef<HTMLInputElement>(null);\n    const { theme, style } = getThemeStyles(color);\n\n    // selectRef drives the click-outside and drop-direction logic, so the\n    // forwarded ref is mirrored onto it rather than replacing it.\n    React.useImperativeHandle(ref, () => selectRef.current as HTMLDivElement);\n\n    const hasError = Boolean(error);\n\n    // Filter options based on search\n    const filteredOptions = useMemo(() => {\n      if (!searchQuery) return options;\n      return options.filter((opt) =>\n        opt.label.toLowerCase().includes(searchQuery.toLowerCase()),\n      );\n    }, [options, searchQuery]);\n\n    // Group options if \"group\" property is provided\n    const groupedOptions = useMemo(() => {\n      return filteredOptions.reduce(\n        (acc, opt) => {\n          const group = opt.group || \"ungrouped\";\n          if (!acc[group]) acc[group] = [];\n          acc[group].push(opt);\n          return acc;\n        },\n        {} as Record<string, SelectOption[]>,\n      );\n    }, [filteredOptions]);\n\n    // Focus search input automatically when opened\n    useEffect(() => {\n      if (isOpen && searchable && searchInputRef.current) {\n        setTimeout(() => searchInputRef.current?.focus(), 50);\n      }\n      if (!isOpen) setSearchQuery(\"\");\n    }, [isOpen, searchable]);\n\n    // Close dropdown on click outside\n    useEffect(() => {\n      const handleClickOutside = (event: MouseEvent) => {\n        if (\n          selectRef.current &&\n          !selectRef.current.contains(event.target as Node)\n        ) {\n          setIsOpen(false);\n        }\n      };\n      document.addEventListener(\"mousedown\", handleClickOutside);\n      return () =>\n        document.removeEventListener(\"mousedown\", handleClickOutside);\n    }, []);\n\n    // Decide which way the menu opens *before* it becomes visible, so it never\n    // renders in the wrong place for a frame. The menu is always mounted (it is\n    // only hidden via opacity), so its height is measurable while closed.\n    const handleToggle = () => {\n      if (!isOpen && selectRef.current) {\n        const trigger = selectRef.current.getBoundingClientRect();\n        const clip = getClippingRect(selectRef.current);\n        const menuHeight = dropdownRef.current?.offsetHeight ?? 240;\n\n        const bottomBound = Math.min(\n          window.innerHeight,\n          clip ? clip.bottom : Number.POSITIVE_INFINITY,\n        );\n        const topBound = Math.max(0, clip ? clip.top : 0);\n\n        const spaceBelow = bottomBound - trigger.bottom - 8;\n        const spaceAbove = trigger.top - topBound - 8;\n\n        // Only flip when below genuinely can't fit and above is roomier.\n        setDropUp(spaceBelow < menuHeight && spaceAbove > spaceBelow);\n      }\n      setIsOpen(!isOpen);\n    };\n\n    const handleSelect = (optValue: string, e: React.MouseEvent) => {\n      e.stopPropagation();\n      if (multiple) {\n        const currentValues = Array.isArray(value) ? value : [];\n        const newValues = currentValues.includes(optValue)\n          ? currentValues.filter((v) => v !== optValue)\n          : [...currentValues, optValue];\n        if (onChange) onChange(newValues);\n      } else {\n        if (onChange) onChange(optValue);\n        setIsOpen(false);\n      }\n    };\n\n    const handleRemoveTag = (optValue: string, e: React.MouseEvent) => {\n      e.stopPropagation();\n      if (multiple && Array.isArray(value)) {\n        if (onChange) onChange(value.filter((v) => v !== optValue));\n      }\n    };\n\n    // --- Size Classes ---\n    const sizeClasses = {\n      sm: {\n        wrapper: \"min-h-9 px-3 py-1.5\",\n        text: \"text-xs\",\n        icon: \"w-3.5 h-3.5\",\n        option: \"px-3 py-2 text-xs\",\n        tag: \"px-1.5 py-0.5 text-[10px]\",\n      },\n      md: {\n        wrapper: \"min-h-11 px-4 py-2.5\",\n        text: \"text-sm\",\n        icon: \"w-4 h-4\",\n        option: \"px-4 py-2.5 text-sm\",\n        tag: \"px-2 py-1 text-xs\",\n      },\n      lg: {\n        wrapper: \"min-h-14 px-5 py-3.5\",\n        text: \"text-base\",\n        icon: \"w-5 h-5\",\n        option: \"px-5 py-3 text-base\",\n        tag: \"px-2.5 py-1 text-sm\",\n      },\n    };\n    const s = sizeClasses[inputSize as SelectSize] || sizeClasses.md;\n\n    // --- Variant Styles ---\n    const getVariantStyles = () => {\n      const baseWrapper = cn(\n        \"relative flex items-center justify-between w-full transition-all duration-300 cursor-pointer outline-none\",\n        s.wrapper,\n      );\n\n      if (disabled)\n        return {\n          wrapper: cn(\n            baseWrapper,\n            \"opacity-50 cursor-not-allowed border border-border bg-muted\",\n          ),\n          active: \"\",\n        };\n      if (hasError)\n        return {\n          wrapper: cn(\n            baseWrapper,\n            \"border border-red-500 bg-background\",\n            variant === \"underlined\" && \"border-0 border-b-2 rounded-none\",\n            variant !== \"underlined\" && \"rounded-xl\",\n          ),\n          active: \"ring-4 ring-red-500/10\",\n        };\n\n      switch (variant) {\n        case \"filled\":\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"bg-muted border border-transparent rounded-xl\",\n            ),\n            active: cn(\"bg-background ring-4\", theme.ring),\n          };\n        case \"underlined\":\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"border-b-2 border-border bg-transparent rounded-none px-0\",\n            ),\n            active: theme.ring.split(\" \")[1],\n          };\n        case \"glow\":\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"bg-background border border-border rounded-xl shadow-sm\",\n            ),\n            active: theme.glow,\n          };\n        case \"liquid-glass\":\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"min-h-[38px] px-3 py-2 text-sm border-white/25 text-white bg-white/10 hover:bg-white/15 focus:bg-white/20 focus:border-white/50 border rounded-xl shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)] transition-all\",\n            ),\n            active: \"ring-2 ring-white/30 border-white/50 bg-white/20\",\n          };\n        case \"glass\":\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"min-h-[38px] px-3 py-2 text-sm border-black/15 text-neutral-900 bg-black/5 hover:bg-black/10 focus:bg-white/40 focus:border-black/30 dark:border-white/25 dark:text-white dark:bg-white/10 dark:hover:bg-white/15 dark:focus:bg-white/20 dark:focus:border-white/50 border rounded-xl shadow-xs transition-all\",\n            ),\n            active: \"ring-2 ring-black/10 dark:ring-white/20 border-black/30 dark:border-white/40 bg-black/10 dark:bg-white/15\",\n          };\n        case \"default\":\n        default:\n          return {\n            wrapper: cn(\n              baseWrapper,\n              \"bg-background border border-border rounded-xl shadow-sm\",\n            ),\n            active: cn(\"ring-4\", theme.ring),\n          };\n      }\n    };\n\n    const styles = getVariantStyles();\n\n    // Renders the selected content (Tags for multi, text for single)\n    const renderSelectedContent = () => {\n      if (multiple && Array.isArray(value) && value.length > 0) {\n        return (\n          <div className=\"flex flex-wrap items-center gap-1.5 w-full pr-6 overflow-hidden\">\n            {value.map((val) => {\n              const opt = options.find((o) => o.value === val);\n              if (!opt) return null;\n              return (\n                <span\n                  key={val}\n                  onClick={(e) => handleRemoveTag(val, e)}\n                  className={cn(\n                    \"inline-flex items-center gap-1 font-semibold rounded-md border border-transparent hover:border-red-200 dark:hover:border-red-900 transition-colors\",\n                    s.tag,\n                    theme.bgSoft,\n                  )}\n                >\n                  {opt.label}\n                  <X\n                    className={cn(\"shrink-0 hover:text-red-500\", s.icon)}\n                    style={{ width: \"0.8em\", height: \"0.8em\" }}\n                  />\n                </span>\n              );\n            })}\n          </div>\n        );\n      }\n\n      const singleOption = options.find((opt) => opt.value === value);\n      return (\n        <span\n          className={cn(\n            \"truncate pr-6\",\n            s.text,\n            !singleOption\n              ? variant === \"liquid-glass\"\n                ? \"text-white/60\"\n                : variant === \"glass\"\n                  ? \"text-neutral-500 dark:text-white/60\"\n                  : \"text-muted-foreground\"\n              : variant === \"liquid-glass\"\n                ? \"text-white font-medium\"\n                : variant === \"glass\"\n                  ? \"text-neutral-900 font-medium dark:text-white\"\n                  : \"text-foreground\",\n          )}\n        >\n          {singleOption ? singleOption.label : placeholder}\n        </span>\n      );\n    };\n\n    return (\n      <div\n        ref={selectRef}\n        className={cn(\"flex flex-col gap-1.5 w-full relative\", className)}\n        style={style}\n      >\n        {label && (\n          <label\n            className={cn(\n              \"text-sm font-semibold\",\n              hasError\n                ? \"text-red-500\"\n                : variant === \"liquid-glass\"\n                  ? \"text-white/90\"\n                  : variant === \"glass\"\n                    ? \"text-white/90 dark:text-white/90\"\n                    : \"text-foreground\",\n              disabled && \"opacity-50\",\n            )}\n          >\n            {label}\n          </label>\n        )}\n\n        {/* Trigger Button */}\n        <button\n          type=\"button\"\n          disabled={disabled}\n          onClick={handleToggle}\n          className={cn(\n            styles.wrapper,\n            isOpen && styles.active,\n            multiple && Array.isArray(value) && value.length > 0 && \"py-1.5\",\n          )}\n        >\n          <div className=\"flex items-center gap-2 overflow-hidden w-full\">\n            {icon && (\n              <div\n                className={cn(\n                  \"shrink-0 text-muted-foreground\",\n                  isOpen && theme.text,\n                )}\n              >\n                {icon}\n              </div>\n            )}\n            {renderSelectedContent()}\n          </div>\n\n          <div className=\"absolute right-4 flex items-center gap-2 shrink-0 bg-transparent\">\n            {hasError && <AlertCircle className={cn(s.icon, \"text-red-500\")} />}\n            <ChevronDown\n              className={cn(\n                s.icon,\n                variant === \"liquid-glass\"\n                  ? \"text-white/80\"\n                  : variant === \"glass\"\n                    ? \"text-neutral-700 dark:text-white/70\"\n                    : \"text-muted-foreground\",\n                \"transition-transform duration-300\",\n                isOpen && \"rotate-180\",\n                isOpen && theme.text,\n              )}\n            />\n          </div>\n        </button>\n\n        {/* Dropdown Menu */}\n        <div\n          ref={dropdownRef}\n          className={cn(\n            \"absolute left-0 right-0 z-50 overflow-hidden border rounded-2xl shadow-2xl transition-all duration-200 ease-out\",\n            variant === \"liquid-glass\"\n              ? \"bg-black/50 backdrop-blur-3xl border border-white/30 text-white shadow-[0_25px_60px_rgba(0,0,0,0.6),inset_0_1px_1px_rgba(255,255,255,0.4),inset_-1px_-1px_1px_rgba(255,255,255,0.15)]\"\n              : variant === \"glass\"\n                ? \"bg-white/50 dark:bg-black/60 backdrop-blur-3xl border border-white/50 dark:border-white/20 text-neutral-900 dark:text-white shadow-2xl\"\n                : \"bg-popover text-popover-foreground border-border\",\n            dropUp\n              ? cn(\n                  \"origin-bottom\",\n                  variant === \"underlined\"\n                    ? \"bottom-[100%]\"\n                    : \"bottom-[calc(100%+8px)]\",\n                )\n              : cn(\n                  \"origin-top\",\n                  variant === \"underlined\"\n                    ? \"top-[100%]\"\n                    : \"top-[calc(100%+8px)]\",\n                ),\n            isOpen\n              ? \"opacity-100 scale-y-100 translate-y-0 pointer-events-auto\"\n              : cn(\n                  \"opacity-0 scale-y-95 pointer-events-none\",\n                  dropUp ? \"translate-y-2\" : \"-translate-y-2\",\n                ),\n          )}\n        >\n          {searchable && (\n            <div className=\"p-2 border-b border-border bg-muted/50\">\n              <div className=\"relative\">\n                <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground\" />\n                <input\n                  ref={searchInputRef}\n                  value={searchQuery}\n                  onChange={(e) => setSearchQuery(e.target.value)}\n                  onClick={(e) => e.stopPropagation()}\n                  placeholder=\"Search options...\"\n                  className=\"w-full pl-9 pr-3 py-2 bg-card border border-border rounded-lg text-sm text-foreground outline-none focus:ring-2 focus:border-transparent transition-all\"\n                  style={{ \"--tw-ring-color\": `var(--theme-color)` } as React.CSSProperties}\n                />\n              </div>\n            </div>\n          )}\n\n          <div className=\"max-h-60 overflow-y-auto p-1 flex flex-col gap-0.5 scrollbar-thin scrollbar-thumb-border\">\n            {Object.keys(groupedOptions).length === 0 ? (\n              <div className=\"px-4 py-3 text-sm text-muted-foreground text-center\">\n                No results found\n              </div>\n            ) : (\n              Object.entries(groupedOptions).map(([group, opts]) => (\n                <div key={group} className=\"flex flex-col gap-0.5\">\n                  {/* Group Header */}\n                  {group !== \"ungrouped\" && (\n                    <div className=\"px-3 py-2 mt-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n                      {group}\n                    </div>\n                  )}\n                  {/* Options */}\n                  {opts.map((opt) => {\n                    const isSelected =\n                      multiple && Array.isArray(value)\n                        ? value.includes(opt.value)\n                        : value === opt.value;\n                    const OptIcon = opt.icon;\n                    return (\n                      <button\n                        key={opt.value}\n                        type=\"button\"\n                        onClick={(e) => handleSelect(opt.value, e)}\n                        className={cn(\n                          \"w-full flex items-center justify-between rounded-lg transition-colors font-medium text-left cursor-pointer active:scale-[0.98]\",\n                          s.option,\n                          isSelected\n                            ? variant === \"liquid-glass\"\n                              ? \"bg-white/30 text-white font-bold border border-white/40 shadow-[0_0_10px_rgba(255,255,255,0.25),inset_0_1px_1px_rgba(255,255,255,0.4)]\"\n                              : cn(theme.bgSoft, theme.text, \"font-semibold shadow-xs\")\n                            : cn(\n                                variant === \"liquid-glass\"\n                                  ? \"text-white hover:bg-white/20\"\n                                  : variant === \"glass\"\n                                    ? \"text-neutral-900 dark:text-white/90\"\n                                    : \"text-foreground\",\n                                theme.hoverBg,\n                              ),\n                        )}\n                      >\n                        <div className=\"flex items-center gap-2\">\n                          {multiple && (\n                            <div\n                              className={cn(\n                                \"w-4 h-4 rounded-[4px] border flex items-center justify-center transition-colors\",\n                                isSelected\n                                  ? cn(theme.bg, \"border-transparent\")\n                                  : \"border-input bg-card\",\n                              )}\n                            >\n                              {/* No hardcoded colour: theme.bg already carries the\n                                  correct contrasting text colour for each theme\n                                  (text-white for indigo/rose/hex, but\n                                  text-primary-foreground for \"primary\", which\n                                  inverts in dark mode). */}\n                              {isSelected && (\n                                <Check className=\"w-3 h-3\" strokeWidth={3} />\n                              )}\n                            </div>\n                          )}\n                          {OptIcon && <OptIcon className={s.icon} />}\n                          <span>{opt.label}</span>\n                        </div>\n                        {isSelected && !multiple && (\n                          <Check\n                            className={cn(s.icon, theme.text)}\n                            strokeWidth={3}\n                          />\n                        )}\n                      </button>\n                    );\n                  })}\n                </div>\n              ))\n            )}\n          </div>\n        </div>\n\n        {error && (\n          <p className=\"text-xs font-semibold text-red-500 flex items-center gap-1 mt-0.5\">\n            <AlertCircle className=\"w-3.5 h-3.5\" /> {error}\n          </p>\n        )}\n        {helperText && !error && (\n          <p className=\"text-xs font-medium text-muted-foreground mt-0.5\">\n            {helperText}\n          </p>\n        )}\n      </div>\n    );\n  },\n);\nSelectBlockImpl.displayName = \"SelectBlock\";\n\n/**\n * The implementation is written against the collapsed props; this cast is\n * what hands callers the discriminated pair instead.\n */\nexport const SelectBlock = SelectBlockImpl as React.ForwardRefExoticComponent<\n  SelectBlockProps & React.RefAttributes<HTMLDivElement>\n>;\n",
      "type": "registry:component",
      "target": "components/custom/blocks/SelectBlock.tsx"
    },
    {
      "path": "src/components/custom/blocks/select-theme.ts",
      "content": "\"use client\";\n\nimport React from \"react\";\n\nexport interface SelectThemeConfig {\n  ring: string;\n  text: string;\n  bg: string;\n  bgSoft: string;\n  hoverBg: string;\n  glow: string;\n}\n\nconst colorThemes: Record<string, SelectThemeConfig> = {\n  primary: {\n    ring: \"ring-primary/30 border-primary/30\",\n    text: \"text-primary\",\n    bg: \"bg-primary text-primary-foreground\",\n    bgSoft: \"bg-primary/15 text-primary dark:bg-primary/20\",\n    hoverBg: \"hover:bg-primary/10 hover:text-primary\",\n    glow: \"shadow-[0_0_20px_var(--primary)] border-primary/30\",\n  },\n  indigo: {\n    ring: \"ring-indigo-500/30 border-indigo-500\",\n    text: \"text-indigo-600 dark:text-indigo-400\",\n    bg: \"bg-indigo-600 text-white\",\n    bgSoft: \"bg-indigo-500/15 text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300\",\n    hoverBg: \"hover:bg-indigo-500/10 hover:text-indigo-600 dark:hover:text-indigo-400\",\n    glow: \"shadow-[0_0_20px_rgba(99,102,241,0.4)] border-indigo-500\",\n  },\n  rose: {\n    ring: \"ring-rose-500/30 border-rose-500\",\n    text: \"text-rose-600 dark:text-rose-400\",\n    bg: \"bg-rose-600 text-white\",\n    bgSoft: \"bg-rose-500/15 text-rose-700 dark:bg-rose-500/20 dark:text-rose-300\",\n    hoverBg: \"hover:bg-rose-500/10 hover:text-rose-600 dark:hover:text-rose-400\",\n    glow: \"shadow-[0_0_20px_rgba(244,63,94,0.4)] border-rose-500\",\n  },\n  emerald: {\n    ring: \"ring-emerald-500/30 border-emerald-500\",\n    text: \"text-emerald-600 dark:text-emerald-400\",\n    bg: \"bg-emerald-600 text-white\",\n    bgSoft: \"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300\",\n    hoverBg: \"hover:bg-emerald-500/10 hover:text-emerald-600 dark:hover:text-emerald-400\",\n    glow: \"shadow-[0_0_20px_rgba(16,185,129,0.4)] border-emerald-500\",\n  },\n  lavender: {\n    ring: \"ring-[#9b8afb]/30 border-[#9b8afb]\",\n    text: \"text-[#8774f8] dark:text-[#b4a7fc]\",\n    bg: \"bg-[#9b8afb] text-white\",\n    bgSoft: \"bg-[#9b8afb]/15 text-[#7a64f7] dark:bg-[#9b8afb]/20 dark:text-[#c4bbfd]\",\n    hoverBg: \"hover:bg-[#9b8afb]/10 hover:text-[#8774f8] dark:hover:text-[#b4a7fc]\",\n    glow: \"shadow-[0_0_20px_rgba(155,138,251,0.4)] border-[#9b8afb]\",\n  },\n  blue: {\n    ring: \"ring-blue-500/30 border-blue-500\",\n    text: \"text-blue-600 dark:text-blue-400\",\n    bg: \"bg-blue-600 text-white\",\n    bgSoft: \"bg-blue-500/15 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300\",\n    hoverBg: \"hover:bg-blue-500/10 hover:text-blue-600 dark:hover:text-blue-400\",\n    glow: \"shadow-[0_0_20px_rgba(59,130,246,0.4)] border-blue-500\",\n  },\n  zinc: {\n    ring: \"ring-zinc-500/30 border-zinc-500 dark:ring-white/20 dark:border-white\",\n    text: \"text-zinc-900 dark:text-zinc-100\",\n    bg: \"bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900\",\n    bgSoft: \"bg-zinc-500/15 text-zinc-900 dark:bg-zinc-500/25 dark:text-zinc-100\",\n    hoverBg: \"hover:bg-zinc-500/10 hover:text-zinc-900 dark:hover:text-zinc-100\",\n    glow: \"shadow-[0_0_20px_rgba(255,255,255,0.15)] border-zinc-900 dark:border-white\",\n  },\n};\n\nconst PRESET_HEX: Record<string, string> = {\n  indigo: \"#6366f1\",\n  rose: \"#f43f5e\",\n  emerald: \"#10b981\",\n  lavender: \"#9b8afb\",\n  blue: \"#3b82f6\",\n  zinc: \"#71717a\",\n};\n\nconst hexToRgb = (hex: string) => {\n  let cleanHex = hex.replace(/^#/, \"\");\n  if (cleanHex.length === 3)\n    cleanHex = cleanHex\n      .split(\"\")\n      .map((x) => x + x)\n      .join(\"\");\n  const result = /^([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(cleanHex);\n  return result\n    ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`\n    : \"99, 102, 241\";\n};\n\nexport const getThemeStyles = (color: string) => {\n  if (color && (color.startsWith(\"#\") || color.startsWith(\"rgb\"))) {\n    const rgb = hexToRgb(color);\n    return {\n      theme: {\n        ring: \"ring-[var(--theme-color)]/30 border-[var(--theme-color)]\",\n        text: \"text-[var(--theme-color)]\",\n        bg: \"bg-[var(--theme-color)] text-white\",\n        bgSoft:\n          \"bg-[rgba(var(--theme-color-rgb),0.15)] text-[var(--theme-color)]\",\n        hoverBg:\n          \"hover:bg-[rgba(var(--theme-color-rgb),0.1)] hover:text-[var(--theme-color)]\",\n        glow: \"shadow-[0_0_20px_rgba(var(--theme-color-rgb),0.5)] border-[var(--theme-color)]\",\n      },\n      style: {\n        \"--theme-color\": color,\n        \"--theme-color-rgb\": rgb,\n      } as React.CSSProperties,\n    };\n  }\n\n  const presetHex = PRESET_HEX[color];\n  const styleObj: React.CSSProperties = presetHex\n    ? ({\n        \"--theme-color\": presetHex,\n        \"--theme-color-rgb\": hexToRgb(presetHex),\n      } as React.CSSProperties)\n    : {};\n\n  return { theme: colorThemes[color] || colorThemes.primary, style: styleObj };\n};\n",
      "type": "registry:component",
      "target": "components/custom/blocks/select-theme.ts"
    }
  ],
  "docs": "Named exports: SelectBlock, getThemeStyles (from select-theme.ts, which installs alongside). `options` take { label, value, icon?, group? } — supplying `group` renders grouped headings. `value` accepts a string, or an array when multiple selection is enabled. Several other blocks depend on this one.",
  "categories": [
    "forms"
  ],
  "type": "registry:component"
}
