{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "variant-selector", "title": "Variant Selector", "description": "Option picker that detects which variant combinations are available.", "registryDependencies": [ "https://lumo-www.vercel.app/r/lib.json" ], "files": [ { "path": "packages/core/src/components/lumo/variant-selector.tsx", "content": "import * as React from \"react\"\r\nimport type { ProductOption, SelectedOption, Variant } from \"@/lib/lumo/types\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nexport interface VariantSelectorProps extends React.HTMLAttributes {\r\n options: ProductOption[]\r\n variants: Variant[]\r\n /** Currently selected option values. */\r\n value: SelectedOption[]\r\n onValueChange: (value: SelectedOption[]) => void\r\n}\r\n\r\n/**\r\n * Returns true if choosing `value` for `optionName` leads to at least one\r\n * available variant, given the other currently selected options.\r\n */\r\nfunction isValueAvailable(\r\n optionName: string,\r\n value: string,\r\n variants: Variant[],\r\n selected: SelectedOption[]\r\n): boolean {\r\n const others = selected.filter((option) => option.name !== optionName)\r\n return variants.some((variant) => {\r\n if (!variant.available) return false\r\n const matchesThis = variant.selectedOptions.some(\r\n (option) => option.name === optionName && option.value === value\r\n )\r\n if (!matchesThis) return false\r\n return others.every((option) =>\r\n variant.selectedOptions.some(\r\n (variantOption) =>\r\n variantOption.name === option.name && variantOption.value === option.value\r\n )\r\n )\r\n })\r\n}\r\n\r\nconst VariantSelector = React.forwardRef(\r\n ({ className, options, variants, value, onValueChange, ...props }, ref) => {\r\n const select = (optionName: string, optionValue: string) => {\r\n const next = value.filter((option) => option.name !== optionName)\r\n next.push({ name: optionName, value: optionValue })\r\n onValueChange(next)\r\n }\r\n\r\n return (\r\n
\r\n {options.map((option) => {\r\n const selectedValue = value.find((o) => o.name === option.name)?.value\r\n return (\r\n
\r\n \r\n {option.name}\r\n {selectedValue && (\r\n {selectedValue}\r\n )}\r\n \r\n
\r\n {option.values.map((optionValue) => {\r\n const isSelected = selectedValue === optionValue\r\n const available = isValueAvailable(\r\n option.name,\r\n optionValue,\r\n variants,\r\n value\r\n )\r\n return (\r\n select(option.name, optionValue)}\r\n className={cn(\r\n \"min-w-9 rounded-md border px-3 py-1.5 text-sm font-medium transition-[color,background-color,border-color,transform] active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\r\n isSelected\r\n ? \"border-primary bg-primary text-primary-foreground\"\r\n : \"border-input bg-background text-foreground hover:bg-accent\",\r\n !available &&\r\n \"cursor-not-allowed text-muted-foreground/50 line-through hover:bg-background\"\r\n )}\r\n >\r\n {optionValue}\r\n \r\n )\r\n })}\r\n
\r\n
\r\n )\r\n })}\r\n
\r\n )\r\n }\r\n)\r\nVariantSelector.displayName = \"VariantSelector\"\r\n\r\nexport { VariantSelector }\r\n", "type": "registry:ui", "target": "components/lumo/variant-selector.tsx" } ], "type": "registry:ui" }