shadcn-glass-ui 2.0.8 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +153 -109
  2. package/dist/cli/index.cjs +1 -1
  3. package/dist/components.cjs +4 -4
  4. package/dist/components.d.ts +63 -322
  5. package/dist/components.js +2 -2
  6. package/dist/hooks.cjs +2 -2
  7. package/dist/index.cjs +43 -6
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +12 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/r/alert-glass.json +2 -1
  12. package/dist/r/avatar-glass.json +2 -3
  13. package/dist/r/button-glass.json +1 -1
  14. package/dist/r/circular-progress-glass.json +1 -1
  15. package/dist/r/combobox-glass.json +1 -1
  16. package/dist/r/dropdown-glass.json +3 -5
  17. package/dist/r/dropdown-menu-glass.json +42 -0
  18. package/dist/r/language-bar-glass.json +2 -2
  19. package/dist/r/popover-glass.json +1 -1
  20. package/dist/r/profile-avatar-glass.json +5 -3
  21. package/dist/r/rainbow-progress-glass.json +1 -1
  22. package/dist/r/registry.json +10 -4
  23. package/dist/r/sort-dropdown-glass.json +3 -4
  24. package/dist/r/tooltip-glass.json +3 -5
  25. package/dist/r/trust-score-card-glass.json +1 -1
  26. package/dist/r/trust-score-display-glass.json +1 -1
  27. package/dist/shadcn-glass-ui.css +1 -1
  28. package/dist/{theme-context-CVW50BKW.cjs → theme-context-DNe_2vWJ.cjs} +2 -2
  29. package/dist/theme-context-DNe_2vWJ.cjs.map +1 -0
  30. package/dist/{theme-context-BZoCplcU.js → theme-context-_T5r1KG4.js} +1 -1
  31. package/dist/theme-context-_T5r1KG4.js.map +1 -0
  32. package/dist/themes.cjs +1 -1
  33. package/dist/themes.d.ts +89 -1
  34. package/dist/themes.js +1 -1
  35. package/dist/{trust-score-card-glass-BcZbul0P.js → trust-score-card-glass-A7kas5OS.js} +353 -279
  36. package/dist/trust-score-card-glass-A7kas5OS.js.map +1 -0
  37. package/dist/{trust-score-card-glass-r3qM09Jp.cjs → trust-score-card-glass-Dgu46oWI.cjs} +551 -313
  38. package/dist/trust-score-card-glass-Dgu46oWI.cjs.map +1 -0
  39. package/dist/{use-focus-ZE8ozmZE.cjs → use-focus-BRkQtQCj.cjs} +2 -2
  40. package/dist/{use-focus-ZE8ozmZE.cjs.map → use-focus-BRkQtQCj.cjs.map} +1 -1
  41. package/dist/{use-wallpaper-tint-BuS80tbN.cjs → use-wallpaper-tint-CfShPBo2.cjs} +2 -2
  42. package/dist/{use-wallpaper-tint-BuS80tbN.cjs.map → use-wallpaper-tint-CfShPBo2.cjs.map} +1 -1
  43. package/dist/{utils-DLXayspX.cjs → utils-BXN7AcRu.cjs} +2 -2
  44. package/dist/{utils-DLXayspX.cjs.map → utils-BXN7AcRu.cjs.map} +1 -1
  45. package/dist/utils.cjs +1 -1
  46. package/docs/AI_USAGE.md +1 -32
  47. package/docs/API_PATTERNS_COMPARISON.md +10 -9
  48. package/docs/COMPONENTS_CATALOG.md +140 -45
  49. package/docs/DROPDOWN_ARCHITECTURE.md +290 -0
  50. package/docs/GETTING_STARTED.md +6 -5
  51. package/docs/TOKEN_ARCHITECTURE.md +100 -0
  52. package/docs/api/README.md +3 -3
  53. package/docs/migration/compound-components-v2.md +384 -0
  54. package/docs/visual-testing-guide.md +50 -12
  55. package/package.json +2 -2
  56. package/dist/theme-context-BZoCplcU.js.map +0 -1
  57. package/dist/theme-context-CVW50BKW.cjs.map +0 -1
  58. package/dist/trust-score-card-glass-BcZbul0P.js.map +0 -1
  59. package/dist/trust-score-card-glass-r3qM09Jp.cjs.map +0 -1
  60. package/docs/ADVANCED_PATTERNS.md +0 -408
  61. package/docs/BREAKING_CHANGES.md +0 -213
  62. package/docs/announcements/v1.0.5-devto.md +0 -363
  63. package/docs/announcements/v1.0.5-reddit.md +0 -115
  64. package/docs/announcements/v1.0.5-twitter.md +0 -70
@@ -15,7 +15,7 @@
15
15
  {
16
16
  "path": "components/glass/ui/circular-progress-glass.tsx",
17
17
  "type": "registry:component",
18
- "content": "/**\n * CircularProgressGlass Component\n *\n * SVG-based circular progress indicator with:\n * - Determinate and indeterminate variants\n * - Configurable size and thickness\n * - Glow effect with SVG filters\n * - Theme-aware styling\n * - Optional label in center\n * - Gradient colors support\n */\n\nimport { forwardRef, useMemo, useId } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// VARIANTS\n// ========================================\n\nconst circularProgressVariants = cva('relative inline-flex items-center justify-center p-2', {\n variants: {\n size: {\n sm: 'w-16 h-16',\n md: 'w-24 h-24',\n lg: 'w-32 h-32',\n xl: 'w-40 h-40',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type CircularProgressGradient = 'violet' | 'blue' | 'cyan' | 'amber' | 'emerald' | 'rose';\n\nexport interface CircularProgressGlassProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof circularProgressVariants> {\n /** Progress value (0-100) for determinate variant */\n readonly value?: number;\n /** Variant type */\n readonly variant?: 'determinate' | 'indeterminate';\n /** Stroke width in pixels */\n readonly thickness?: number;\n /** Background track width in pixels */\n readonly trackWidth?: number;\n /** Progress color gradient */\n readonly color?: CircularProgressGradient;\n /** Track color (background circle) */\n readonly trackColor?: string;\n /** Show percentage label in center */\n readonly showLabel?: boolean;\n /** Custom label text (overrides percentage) */\n readonly label?: string;\n /** Custom color for the center label text */\n readonly labelColor?: string;\n /** Show glow effect */\n readonly showGlow?: boolean;\n /** Glow intensity */\n readonly glowIntensity?: 'low' | 'medium' | 'high';\n /** Stroke linecap style */\n readonly strokeLinecap?: 'round' | 'butt' | 'square';\n /** Animation duration in seconds */\n readonly animationDuration?: number;\n}\n\n// ========================================\n// HELPERS\n// ========================================\n\nconst getGradientColors = (gradient: CircularProgressGradient) => {\n const gradients: Record<CircularProgressGradient, { from: string; to: string; glowVar: string }> =\n {\n violet: { from: '#8b5cf6', to: '#a855f7', glowVar: '--progress-glow-violet' },\n blue: { from: '#3b82f6', to: '#60a5fa', glowVar: '--progress-glow-blue' },\n cyan: { from: '#06b6d4', to: '#22d3ee', glowVar: '--progress-glow-cyan' },\n amber: { from: '#f59e0b', to: '#fbbf24', glowVar: '--progress-glow-amber' },\n emerald: { from: '#10b981', to: '#34d399', glowVar: '--progress-glow-emerald' },\n rose: { from: '#f43f5e', to: '#fb7185', glowVar: '--progress-glow-rose' },\n };\n return gradients[gradient];\n};\n\nconst getGlowStdDeviation = (intensity: 'low' | 'medium' | 'high'): number => {\n const intensities = { low: 2, medium: 4, high: 6 };\n return intensities[intensity];\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const CircularProgressGlass = forwardRef<HTMLDivElement, CircularProgressGlassProps>(\n (\n {\n className,\n size = 'md',\n value = 0,\n variant = 'determinate',\n thickness = 8,\n trackWidth = 8,\n color = 'violet',\n trackColor = 'oklch(100% 0 0 / 0.1)',\n showLabel = true,\n label,\n labelColor,\n showGlow = true,\n glowIntensity = 'medium',\n strokeLinecap = 'round',\n animationDuration = 1,\n ...props\n },\n ref\n ) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const gradientColors = getGradientColors(color);\n\n // SVG dimensions\n const sizeMap = { sm: 64, md: 96, lg: 128, xl: 160 };\n const svgSize = sizeMap[size || 'md'];\n const radius = (svgSize - Math.max(thickness, trackWidth)) / 2;\n const circumference = 2 * Math.PI * radius;\n const center = svgSize / 2;\n\n // Calculate stroke dash offset for determinate progress\n const dashOffset = useMemo(() => {\n if (variant === 'indeterminate') return circumference * 0.75;\n return circumference * ((100 - clampedValue) / 100);\n }, [variant, clampedValue, circumference]);\n\n // Generate unique IDs for SVG elements (using useId for stable IDs)\n const uniqueId = useId();\n const gradientId = `circular-gradient-${uniqueId}`;\n const glowId = `circular-glow-${uniqueId}`;\n\n return (\n <div ref={ref} className={cn(circularProgressVariants({ size }), className)} {...props}>\n <svg\n width={svgSize}\n height={svgSize}\n className=\"transform -rotate-90 overflow-visible\"\n aria-hidden=\"true\"\n style={{ overflow: 'visible' }}\n >\n <defs>\n {/* Gradient definition */}\n <linearGradient id={gradientId} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n <stop offset=\"0%\" stopColor={gradientColors.from} />\n <stop offset=\"100%\" stopColor={gradientColors.to} />\n </linearGradient>\n\n {/* Glow filter */}\n {showGlow && (\n <filter id={glowId}>\n <feGaussianBlur\n stdDeviation={getGlowStdDeviation(glowIntensity)}\n result=\"coloredBlur\"\n />\n <feMerge>\n <feMergeNode in=\"coloredBlur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n )}\n </defs>\n\n {/* Background track */}\n <circle\n cx={center}\n cy={center}\n r={radius}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackWidth}\n />\n\n {/* Progress circle */}\n <circle\n cx={center}\n cy={center}\n r={radius}\n fill=\"none\"\n stroke={`url(#${gradientId})`}\n strokeWidth={thickness}\n strokeLinecap={strokeLinecap}\n strokeDasharray={circumference}\n strokeDashoffset={dashOffset}\n filter={showGlow ? `url(#${glowId})` : undefined}\n className={cn(\n 'transition-all',\n variant === 'indeterminate' && 'animate-circular-progress-spin'\n )}\n style={{\n transitionDuration: `${animationDuration}s`,\n transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n />\n </svg>\n\n {/* Center label */}\n {showLabel && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span\n className=\"text-sm font-semibold tabular-nums\"\n style={{ color: labelColor || gradientColors.to }}\n >\n {label || (variant === 'determinate' ? `${clampedValue}%` : '')}\n </span>\n </div>\n )}\n\n {/* Accessibility */}\n <div\n role=\"progressbar\"\n aria-valuenow={variant === 'determinate' ? clampedValue : undefined}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={\n label || (variant === 'determinate' ? `Progress: ${clampedValue}%` : 'Loading progress')\n }\n aria-valuetext={label || (variant === 'determinate' ? `${clampedValue}%` : 'Loading...')}\n className=\"sr-only\"\n >\n {label || (variant === 'determinate' ? `${clampedValue}%` : 'Loading...')}\n </div>\n </div>\n );\n }\n);\n\nCircularProgressGlass.displayName = 'CircularProgressGlass';\n"
18
+ "content": "/**\n * CircularProgressGlass Component\n *\n * SVG-based circular progress indicator with:\n * - Determinate and indeterminate variants\n * - Configurable size and thickness\n * - Glow effect with SVG filters\n * - Theme-aware styling\n * - Optional label in center\n * - Gradient colors support\n */\n\nimport { forwardRef, useMemo, useId } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// VARIANTS\n// ========================================\n\nconst circularProgressVariants = cva('relative inline-flex items-center justify-center p-4', {\n variants: {\n size: {\n sm: 'w-20 h-20',\n md: 'w-28 h-28',\n lg: 'w-36 h-36',\n xl: 'w-44 h-44',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type CircularProgressGradient = 'violet' | 'blue' | 'cyan' | 'amber' | 'emerald' | 'rose';\n\nexport interface CircularProgressGlassProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof circularProgressVariants> {\n /** Progress value (0-100) for determinate variant */\n readonly value?: number;\n /** Variant type */\n readonly variant?: 'determinate' | 'indeterminate';\n /** Stroke width in pixels */\n readonly thickness?: number;\n /** Background track width in pixels */\n readonly trackWidth?: number;\n /** Progress color gradient */\n readonly color?: CircularProgressGradient;\n /** Track color (background circle) */\n readonly trackColor?: string;\n /** Show percentage label in center */\n readonly showLabel?: boolean;\n /** Custom label text (overrides percentage) */\n readonly label?: string;\n /** Custom color for the center label text */\n readonly labelColor?: string;\n /** Show glow effect */\n readonly showGlow?: boolean;\n /** Glow intensity */\n readonly glowIntensity?: 'low' | 'medium' | 'high';\n /** Stroke linecap style */\n readonly strokeLinecap?: 'round' | 'butt' | 'square';\n /** Animation duration in seconds */\n readonly animationDuration?: number;\n}\n\n// ========================================\n// HELPERS\n// ========================================\n\nconst getGradientColors = (gradient: CircularProgressGradient) => {\n const gradients: Record<CircularProgressGradient, { from: string; to: string; glowVar: string }> =\n {\n violet: { from: '#8b5cf6', to: '#a855f7', glowVar: '--progress-glow-violet' },\n blue: { from: '#3b82f6', to: '#60a5fa', glowVar: '--progress-glow-blue' },\n cyan: { from: '#06b6d4', to: '#22d3ee', glowVar: '--progress-glow-cyan' },\n amber: { from: '#f59e0b', to: '#fbbf24', glowVar: '--progress-glow-amber' },\n emerald: { from: '#10b981', to: '#34d399', glowVar: '--progress-glow-emerald' },\n rose: { from: '#f43f5e', to: '#fb7185', glowVar: '--progress-glow-rose' },\n };\n return gradients[gradient];\n};\n\nconst getGlowStdDeviation = (intensity: 'low' | 'medium' | 'high'): number => {\n const intensities = { low: 2, medium: 4, high: 6 };\n return intensities[intensity];\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const CircularProgressGlass = forwardRef<HTMLDivElement, CircularProgressGlassProps>(\n (\n {\n className,\n size = 'md',\n value = 0,\n variant = 'determinate',\n thickness = 8,\n trackWidth = 8,\n color = 'violet',\n trackColor = 'oklch(100% 0 0 / 0.1)',\n showLabel = true,\n label,\n labelColor,\n showGlow = true,\n glowIntensity = 'medium',\n strokeLinecap = 'round',\n animationDuration = 1,\n ...props\n },\n ref\n ) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const gradientColors = getGradientColors(color);\n\n // SVG dimensions\n const sizeMap = { sm: 64, md: 96, lg: 128, xl: 160 };\n const svgSize = sizeMap[size || 'md'];\n const radius = (svgSize - Math.max(thickness, trackWidth)) / 2;\n const circumference = 2 * Math.PI * radius;\n const center = svgSize / 2;\n\n // Calculate stroke dash offset for determinate progress\n const dashOffset = useMemo(() => {\n if (variant === 'indeterminate') return circumference * 0.75;\n return circumference * ((100 - clampedValue) / 100);\n }, [variant, clampedValue, circumference]);\n\n // Generate unique IDs for SVG elements (using useId for stable IDs)\n const uniqueId = useId();\n const gradientId = `circular-gradient-${uniqueId}`;\n const glowId = `circular-glow-${uniqueId}`;\n\n return (\n <div ref={ref} className={cn(circularProgressVariants({ size }), className)} {...props}>\n <svg\n width={svgSize}\n height={svgSize}\n className=\"transform -rotate-90 overflow-visible\"\n aria-hidden=\"true\"\n style={{ overflow: 'visible' }}\n >\n <defs>\n {/* Gradient definition */}\n <linearGradient id={gradientId} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n <stop offset=\"0%\" stopColor={gradientColors.from} />\n <stop offset=\"100%\" stopColor={gradientColors.to} />\n </linearGradient>\n\n {/* Glow filter */}\n {showGlow && (\n <filter id={glowId}>\n <feGaussianBlur\n stdDeviation={getGlowStdDeviation(glowIntensity)}\n result=\"coloredBlur\"\n />\n <feMerge>\n <feMergeNode in=\"coloredBlur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n )}\n </defs>\n\n {/* Background track */}\n <circle\n cx={center}\n cy={center}\n r={radius}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackWidth}\n />\n\n {/* Progress circle */}\n <circle\n cx={center}\n cy={center}\n r={radius}\n fill=\"none\"\n stroke={`url(#${gradientId})`}\n strokeWidth={thickness}\n strokeLinecap={strokeLinecap}\n strokeDasharray={circumference}\n strokeDashoffset={dashOffset}\n filter={showGlow ? `url(#${glowId})` : undefined}\n className={cn(\n 'transition-all',\n variant === 'indeterminate' && 'animate-circular-progress-spin'\n )}\n style={{\n transitionDuration: `${animationDuration}s`,\n transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n />\n </svg>\n\n {/* Center label */}\n {showLabel && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span\n className=\"text-sm font-semibold tabular-nums\"\n style={{ color: labelColor || gradientColors.to }}\n >\n {label || (variant === 'determinate' ? `${clampedValue}%` : '')}\n </span>\n </div>\n )}\n\n {/* Accessibility */}\n <div\n role=\"progressbar\"\n aria-valuenow={variant === 'determinate' ? clampedValue : undefined}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={\n label || (variant === 'determinate' ? `Progress: ${clampedValue}%` : 'Loading progress')\n }\n aria-valuetext={label || (variant === 'determinate' ? `${clampedValue}%` : 'Loading...')}\n className=\"sr-only\"\n >\n {label || (variant === 'determinate' ? `${clampedValue}%` : 'Loading...')}\n </div>\n </div>\n );\n }\n);\n\nCircularProgressGlass.displayName = 'CircularProgressGlass';\n"
19
19
  }
20
20
  ],
21
21
  "categories": [
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "path": "components/glass/ui/combobox-glass.tsx",
18
18
  "type": "registry:component",
19
- "content": "/**\n * ComboBoxGlass Component\n *\n * Glass-themed combobox (searchable select) with:\n * - Keyboard navigation support\n * - Search/filter functionality\n * - Custom rendering options\n * - Async data loading support\n * - Glass styling with theme support\n * - Accessibility features\n * - Form field wrapper support (label, error, success)\n * - Size variants (sm, md, lg, xl)\n * - Optional searchable mode\n * - Icon support for trigger and options\n */\n\n'use client';\n\nimport { useState, useMemo, useCallback, forwardRef, useId } from 'react';\nimport { CheckIcon, ChevronsUpDownIcon, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from './button-glass';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { FormFieldWrapper } from '../primitives/form-field-wrapper';\nimport { inputVariants, type InputGlassSize } from '@/lib/variants/input-glass-variants';\nimport { getDropdownContentStyles } from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type GlassVariant = 'glass' | 'frosted' | 'fluted' | 'crystal';\n\nexport interface ComboBoxOption<T = string> {\n readonly value: T;\n readonly label: string;\n readonly disabled?: boolean;\n /** Optional icon component for the option */\n readonly icon?: LucideIcon;\n}\n\nexport interface ComboBoxGlassProps<T = string> {\n /** Available options */\n readonly options: readonly ComboBoxOption<T>[];\n /** Currently selected value */\n readonly value?: T;\n /** Callback when value changes */\n readonly onChange?: (value: T | undefined) => void;\n /** Placeholder text for trigger button */\n readonly placeholder?: string;\n /** Text shown when no results found */\n readonly emptyText?: string;\n /** Placeholder for search input */\n readonly searchPlaceholder?: string;\n /** Glass variant style */\n readonly glassVariant?: GlassVariant;\n /** Disabled state */\n readonly disabled?: boolean;\n /** Custom className for container */\n readonly className?: string;\n /** Custom className for popover content */\n readonly popoverClassName?: string;\n /** Allow clearing selection */\n readonly clearable?: boolean;\n /** Popover side */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n /** Popover alignment */\n readonly align?: 'start' | 'center' | 'end';\n\n // ========================================\n // NEW PROPS (Week 3 Enhancement)\n // ========================================\n\n /** Label text displayed above the field */\n readonly label?: string;\n /** Error message - displays in red below the field */\n readonly error?: string;\n /** Success message - displays in green if no error */\n readonly success?: string;\n /** Shows required asterisk (*) next to label */\n readonly required?: boolean;\n /** Size variant (affects trigger button height and padding) */\n readonly size?: InputGlassSize;\n /** Enable/disable search functionality */\n readonly searchable?: boolean;\n /** Optional icon for trigger button (displayed before text) */\n readonly icon?: LucideIcon;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nfunction ComboBoxGlassInner<T = string>(\n {\n options,\n value,\n onChange,\n placeholder = 'Select option...',\n emptyText = 'No results found.',\n searchPlaceholder = 'Search...',\n glassVariant = 'glass',\n disabled = false,\n className,\n popoverClassName,\n clearable = false,\n side = 'bottom',\n align = 'start',\n // NEW PROPS\n label,\n error,\n success,\n required = false,\n size = 'md',\n searchable = true,\n icon: TriggerIcon,\n }: ComboBoxGlassProps<T>,\n ref: React.ForwardedRef<HTMLButtonElement>\n) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const fieldId = useId();\n\n // Find selected option\n const selectedOption = useMemo(\n () => options.find((opt) => opt.value === value),\n [options, value]\n );\n\n // Filter options based on search\n const filteredOptions = useMemo(() => {\n if (!search) return options;\n const searchLower = search.toLowerCase();\n return options.filter((opt) => opt.label.toLowerCase().includes(searchLower));\n }, [options, search]);\n\n // Handle option selection\n const handleSelect = useCallback(\n (optionValue: T) => {\n if (clearable && value === optionValue) {\n onChange?.(undefined);\n } else {\n onChange?.(optionValue);\n }\n setOpen(false);\n setSearch('');\n },\n [value, onChange, clearable]\n );\n\n // Get glass variant class\n const getGlassClass = () => {\n const variants: Record<GlassVariant, string> = {\n glass: 'glass',\n frosted: 'frosted',\n fluted: 'fluted',\n crystal: 'crystal',\n };\n return variants[glassVariant];\n };\n\n // Render content\n const comboboxContent = (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ButtonGlass\n ref={ref}\n variant=\"secondary\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label={selectedOption?.label || placeholder}\n aria-describedby={error ? `${fieldId}-error` : success ? `${fieldId}-success` : undefined}\n disabled={disabled}\n className={cn(\n 'w-full justify-between',\n // Apply size variant via inputVariants\n inputVariants({ size }),\n !selectedOption && 'text-muted-foreground',\n className\n )}\n >\n <span className=\"flex items-center gap-2 truncate\">\n {TriggerIcon && <TriggerIcon className={ICON_SIZES.md} />}\n {selectedOption ? selectedOption.label : placeholder}\n </span>\n <ChevronsUpDownIcon className={cn(ICON_SIZES.md, 'shrink-0 opacity-50')} />\n </ButtonGlass>\n </PopoverTrigger>\n <PopoverContent\n side={side}\n align={align}\n className={cn(\n 'w-[--radix-popover-trigger-width] p-0 rounded-2xl overflow-hidden border-0',\n getGlassClass(),\n popoverClassName\n )}\n style={getDropdownContentStyles()}\n >\n <Command\n shouldFilter={false}\n className={cn(\n 'bg-transparent',\n // Hide default border, use subtle divider instead\n '[&_[data-slot=command-input-wrapper]]:border-b-0',\n // Fix search icon - use glass theme color with better visibility\n '[&_[data-slot=command-input-wrapper]_svg]:text-[var(--text-muted)]',\n '[&_[data-slot=command-input-wrapper]_svg]:opacity-80'\n )}\n >\n {searchable && (\n <CommandInput\n placeholder={searchPlaceholder}\n value={search}\n onValueChange={setSearch}\n className=\"text-[var(--text-primary)] placeholder:text-[var(--text-muted)] h-10 font-medium\"\n />\n )}\n <CommandList className=\"p-1.5 scrollbar-hide\">\n <CommandEmpty className=\"text-[var(--text-muted)] py-4\">{emptyText}</CommandEmpty>\n <CommandGroup className=\"text-[var(--text-primary)] p-0\">\n {filteredOptions.map((option) => {\n const OptionIcon = option.icon;\n const isSelected = value === option.value;\n\n return (\n <CommandItem\n key={String(option.value)}\n value={String(option.value)}\n disabled={option.disabled}\n onSelect={() => handleSelect(option.value)}\n className={cn(\n 'w-full px-3 py-2.5 text-sm flex items-center gap-2 rounded-lg',\n 'cursor-pointer transition-colors duration-150',\n 'text-[var(--dropdown-item-text)]',\n 'data-[selected=true]:bg-[var(--dropdown-item-hover)]',\n isSelected &&\n 'bg-[var(--select-item-selected-bg)] text-[var(--select-item-selected-text)]',\n option.disabled && 'cursor-not-allowed opacity-50'\n )}\n >\n <CheckIcon\n className={cn(\n ICON_SIZES.md,\n 'shrink-0',\n isSelected ? 'opacity-100 text-[var(--text-accent)]' : 'opacity-0'\n )}\n />\n {OptionIcon && (\n <OptionIcon\n className={cn(ICON_SIZES.md, 'shrink-0 text-[var(--dropdown-icon)]')}\n />\n )}\n <span className=\"truncate\">{option.label}</span>\n </CommandItem>\n );\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n\n // Wrap with FormFieldWrapper if label/error/success provided\n if (label || error || success) {\n return (\n <FormFieldWrapper\n label={label}\n error={error}\n success={success}\n required={required}\n htmlFor={fieldId}\n >\n {comboboxContent}\n </FormFieldWrapper>\n );\n }\n\n return comboboxContent;\n}\n\n// Export with generic type support\nexport const ComboBoxGlass = forwardRef(ComboBoxGlassInner) as <T = string>(\n props: ComboBoxGlassProps<T> & { ref?: React.ForwardedRef<HTMLButtonElement> }\n) => ReturnType<typeof ComboBoxGlassInner>;\n\n// Add display name for debugging\nComboBoxGlassInner.displayName = 'ComboBoxGlass';\n"
19
+ "content": "/**\n * ComboBoxGlass Component\n *\n * Glass-themed combobox (searchable select) with:\n * - Keyboard navigation support\n * - Search/filter functionality\n * - Custom rendering options\n * - Async data loading support\n * - Glass styling with theme support\n * - Accessibility features\n * - Form field wrapper support (label, error, success)\n * - Size variants (sm, md, lg, xl)\n * - Optional searchable mode\n * - Icon support for trigger and options\n */\n\n'use client';\n\nimport { useState, useMemo, useCallback, forwardRef, useId } from 'react';\nimport { CheckIcon, ChevronsUpDownIcon, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from './button-glass';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { FormFieldWrapper } from '../primitives/form-field-wrapper';\nimport { inputVariants, type InputGlassSize } from '@/lib/variants/input-glass-variants';\nimport {\n getDropdownContentStyles,\n dropdownContentClasses,\n getDropdownItemClasses,\n} from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type GlassVariant = 'glass' | 'frosted' | 'fluted' | 'crystal';\n\nexport interface ComboBoxOption<T = string> {\n readonly value: T;\n readonly label: string;\n readonly disabled?: boolean;\n /** Optional icon component for the option */\n readonly icon?: LucideIcon;\n}\n\nexport interface ComboBoxGlassProps<T = string> {\n /** Available options */\n readonly options: readonly ComboBoxOption<T>[];\n /** Currently selected value */\n readonly value?: T;\n /** Callback when value changes */\n readonly onChange?: (value: T | undefined) => void;\n /** Placeholder text for trigger button */\n readonly placeholder?: string;\n /** Text shown when no results found */\n readonly emptyText?: string;\n /** Placeholder for search input */\n readonly searchPlaceholder?: string;\n /** Glass variant style */\n readonly glassVariant?: GlassVariant;\n /** Disabled state */\n readonly disabled?: boolean;\n /** Custom className for container */\n readonly className?: string;\n /** Custom className for popover content */\n readonly popoverClassName?: string;\n /** Allow clearing selection */\n readonly clearable?: boolean;\n /** Popover side */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n /** Popover alignment */\n readonly align?: 'start' | 'center' | 'end';\n\n // ========================================\n // NEW PROPS (Week 3 Enhancement)\n // ========================================\n\n /** Label text displayed above the field */\n readonly label?: string;\n /** Error message - displays in red below the field */\n readonly error?: string;\n /** Success message - displays in green if no error */\n readonly success?: string;\n /** Shows required asterisk (*) next to label */\n readonly required?: boolean;\n /** Size variant (affects trigger button height and padding) */\n readonly size?: InputGlassSize;\n /** Enable/disable search functionality */\n readonly searchable?: boolean;\n /** Optional icon for trigger button (displayed before text) */\n readonly icon?: LucideIcon;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nfunction ComboBoxGlassInner<T = string>(\n {\n options,\n value,\n onChange,\n placeholder = 'Select option...',\n emptyText = 'No results found.',\n searchPlaceholder = 'Search...',\n glassVariant = 'glass',\n disabled = false,\n className,\n popoverClassName,\n clearable = false,\n side = 'bottom',\n align = 'start',\n // NEW PROPS\n label,\n error,\n success,\n required = false,\n size = 'md',\n searchable = true,\n icon: TriggerIcon,\n }: ComboBoxGlassProps<T>,\n ref: React.ForwardedRef<HTMLButtonElement>\n) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const fieldId = useId();\n\n // Find selected option\n const selectedOption = useMemo(\n () => options.find((opt) => opt.value === value),\n [options, value]\n );\n\n // Filter options based on search\n const filteredOptions = useMemo(() => {\n if (!search) return options;\n const searchLower = search.toLowerCase();\n return options.filter((opt) => opt.label.toLowerCase().includes(searchLower));\n }, [options, search]);\n\n // Handle option selection\n const handleSelect = useCallback(\n (optionValue: T) => {\n if (clearable && value === optionValue) {\n onChange?.(undefined);\n } else {\n onChange?.(optionValue);\n }\n setOpen(false);\n setSearch('');\n },\n [value, onChange, clearable]\n );\n\n // Get glass variant class\n const getGlassClass = () => {\n const variants: Record<GlassVariant, string> = {\n glass: 'glass',\n frosted: 'frosted',\n fluted: 'fluted',\n crystal: 'crystal',\n };\n return variants[glassVariant];\n };\n\n // Render content\n const comboboxContent = (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ButtonGlass\n ref={ref}\n variant=\"secondary\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label={selectedOption?.label || placeholder}\n aria-describedby={error ? `${fieldId}-error` : success ? `${fieldId}-success` : undefined}\n disabled={disabled}\n className={cn(\n 'w-full justify-between',\n // Apply size variant via inputVariants\n inputVariants({ size }),\n !selectedOption && 'text-muted-foreground',\n className\n )}\n >\n <span className=\"flex items-center gap-2 truncate\">\n {TriggerIcon && <TriggerIcon className={ICON_SIZES.md} />}\n {selectedOption ? selectedOption.label : placeholder}\n </span>\n <ChevronsUpDownIcon className={cn(ICON_SIZES.md, 'shrink-0 opacity-50')} />\n </ButtonGlass>\n </PopoverTrigger>\n <PopoverContent\n side={side}\n align={align}\n className={cn(\n dropdownContentClasses,\n 'w-[--radix-popover-trigger-width] p-0 border-0',\n getGlassClass(),\n popoverClassName\n )}\n style={getDropdownContentStyles()}\n >\n <Command\n shouldFilter={false}\n className={cn(\n 'bg-transparent',\n // Hide default border, use subtle divider instead\n '[&_[data-slot=command-input-wrapper]]:border-b-0',\n // Fix search icon - use glass theme color with better visibility\n '[&_[data-slot=command-input-wrapper]_svg]:text-[var(--text-muted)]',\n '[&_[data-slot=command-input-wrapper]_svg]:opacity-80'\n )}\n >\n {searchable && (\n <CommandInput\n placeholder={searchPlaceholder}\n value={search}\n onValueChange={setSearch}\n className=\"text-[var(--text-primary)] placeholder:text-[var(--text-muted)] h-10 font-medium\"\n />\n )}\n <CommandList className=\"p-1.5 scrollbar-hide\">\n <CommandEmpty className=\"text-[var(--text-muted)] py-4\">{emptyText}</CommandEmpty>\n <CommandGroup className=\"text-[var(--text-primary)] p-0\">\n {filteredOptions.map((option) => {\n const OptionIcon = option.icon;\n const isSelected = value === option.value;\n\n return (\n <CommandItem\n key={String(option.value)}\n value={String(option.value)}\n disabled={option.disabled}\n onSelect={() => handleSelect(option.value)}\n className={cn(\n getDropdownItemClasses({ selected: isSelected }),\n option.disabled && 'cursor-not-allowed opacity-50'\n )}\n >\n <CheckIcon\n className={cn(\n ICON_SIZES.md,\n 'shrink-0',\n isSelected ? 'opacity-100 text-[var(--text-accent)]' : 'opacity-0'\n )}\n />\n {OptionIcon && (\n <OptionIcon\n className={cn(ICON_SIZES.md, 'shrink-0 text-[var(--dropdown-icon)]')}\n />\n )}\n <span className=\"truncate\">{option.label}</span>\n </CommandItem>\n );\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n\n // Wrap with FormFieldWrapper if label/error/success provided\n if (label || error || success) {\n return (\n <FormFieldWrapper\n label={label}\n error={error}\n success={success}\n required={required}\n htmlFor={fieldId}\n >\n {comboboxContent}\n </FormFieldWrapper>\n );\n }\n\n return comboboxContent;\n}\n\n// Export with generic type support\nexport const ComboBoxGlass = forwardRef(ComboBoxGlassInner) as <T = string>(\n props: ComboBoxGlassProps<T> & { ref?: React.ForwardedRef<HTMLButtonElement> }\n) => ReturnType<typeof ComboBoxGlassInner>;\n\n// Add display name for debugging\nComboBoxGlassInner.displayName = 'ComboBoxGlass';\n"
20
20
  }
21
21
  ],
22
22
  "categories": [
@@ -3,22 +3,20 @@
3
3
  "name": "dropdown-glass",
4
4
  "type": "registry:ui",
5
5
  "title": "Dropdown Glass",
6
- "description": "Glass-themed dropdown menu based on Radix UI with:",
6
+ "description": "Glass-themed dropdown menu with two APIs:\n *\n * 1. **Simple API** (items prop) - Quick setup for basic menus\n * 2. **Compound API** (DropdownMenuGlass.*) - Full shadcn/ui pattern for complex menus\n *\n * @example Simple API (recommended for basic dropdowns)\n * ```tsx\n * import { DropdownGlass } from '@/components/glass/ui/dropdown-glass';\n *\n * <DropdownGlass\n * trigger={<button><MoreVertical /></button>}\n * items={[\n * { label: 'Edit', icon: Edit, onClick: handleEdit },\n * { divider: true },\n * { label: 'Delete', icon: Trash, onClick: handleDelete, danger: true }\n * ]}\n * />\n * ```\n *\n * @example Compound API (for complex dropdowns)\n * ```tsx\n * import {\n * DropdownMenuGlass,\n * DropdownMenuGlassTrigger,\n * DropdownMenuGlassContent,\n * DropdownMenuGlassItem,\n * DropdownMenuGlassSeparator,\n * } from '@/components/glass/ui/dropdown-menu-glass';\n *\n * <DropdownMenuGlass>\n * <DropdownMenuGlassTrigger asChild>\n * <Button>Open Menu</Button>\n * </DropdownMenuGlassTrigger>\n * <DropdownMenuGlassContent>\n * <DropdownMenuGlassItem>Edit</DropdownMenuGlassItem>\n * <DropdownMenuGlassSeparator />\n * <DropdownMenuGlassItem variant=\"destructive\">Delete</DropdownMenuGlassItem>\n * </DropdownMenuGlassContent>\n * </DropdownMenuGlass>\n * ```\n *\n * @see ./dropdown-menu-glass.tsx for compound component exports",
7
7
  "dependencies": [
8
- "@radix-ui/react-dropdown-menu",
9
8
  "lucide-react",
10
9
  "react"
11
10
  ],
12
11
  "registryDependencies": [
13
12
  "cn",
14
- "primitives",
15
- "variants"
13
+ "primitives"
16
14
  ],
17
15
  "files": [
18
16
  {
19
17
  "path": "components/glass/ui/dropdown-glass.tsx",
20
18
  "type": "registry:component",
21
- "content": "/**\n * DropdownGlass Component\n *\n * Glass-themed dropdown menu based on Radix UI with:\n * - Theme-aware styling (glass/light/aurora)\n * - Smooth animations\n * - Proper positioning and accessibility\n * - Optional item icons and dividers\n *\n * @example\n * Simple API (recommended for basic dropdowns):\n * ```tsx\n * import { DropdownGlass } from '@/components/glass/ui/dropdown-glass';\n * import { MoreVertical, Edit, Trash } from 'lucide-react';\n *\n * <DropdownGlass\n * trigger={\n * <button>\n * <MoreVertical />\n * </button>\n * }\n * items={[\n * { label: 'Edit', icon: Edit, onClick: () => handleEdit() },\n * { divider: true },\n * { label: 'Delete', icon: Trash, onClick: () => handleDelete(), danger: true }\n * ]}\n * />\n * ```\n *\n * @example\n * Advanced: Using Radix UI primitives directly (for complex dropdowns):\n * ```tsx\n * import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\n * import { getDropdownContentStyles, dropdownContentClasses } from '@/lib/variants/dropdown-content-styles';\n *\n * <DropdownMenuPrimitive.Root>\n * <DropdownMenuPrimitive.Trigger asChild>\n * <button>Open Menu</button>\n * </DropdownMenuPrimitive.Trigger>\n *\n * <DropdownMenuPrimitive.Portal>\n * <DropdownMenuPrimitive.Content\n * className={dropdownContentClasses}\n * style={getDropdownContentStyles()}\n * align=\"start\"\n * sideOffset={8}\n * >\n * <DropdownMenuPrimitive.Label className=\"px-3 py-1.5 text-xs font-medium\">\n * Actions\n * </DropdownMenuPrimitive.Label>\n *\n * <DropdownMenuPrimitive.Item\n * className=\"px-3 py-2 cursor-pointer hover:bg-[var(--dropdown-item-hover)]\"\n * onSelect={() => handleAction()}\n * >\n * Action Item\n * </DropdownMenuPrimitive.Item>\n *\n * <DropdownMenuPrimitive.Separator className=\"h-px my-1 bg-[var(--dropdown-border)]\" />\n *\n * <DropdownMenuPrimitive.CheckboxItem\n * checked={isChecked}\n * onCheckedChange={setIsChecked}\n * >\n * <DropdownMenuPrimitive.ItemIndicator>\n * <Check className=\"w-4 h-4\" />\n * </DropdownMenuPrimitive.ItemIndicator>\n * Checkbox Item\n * </DropdownMenuPrimitive.CheckboxItem>\n *\n * <DropdownMenuPrimitive.Sub>\n * <DropdownMenuPrimitive.SubTrigger>\n * More Options\n * </DropdownMenuPrimitive.SubTrigger>\n * <DropdownMenuPrimitive.SubContent>\n * <DropdownMenuPrimitive.Item>Sub Item 1</DropdownMenuPrimitive.Item>\n * <DropdownMenuPrimitive.Item>Sub Item 2</DropdownMenuPrimitive.Item>\n * </DropdownMenuPrimitive.SubContent>\n * </DropdownMenuPrimitive.Sub>\n * </DropdownMenuPrimitive.Content>\n * </DropdownMenuPrimitive.Portal>\n * </DropdownMenuPrimitive.Root>\n * ```\n *\n * @see {@link https://www.radix-ui.com/primitives/docs/components/dropdown-menu Radix UI Dropdown Menu Documentation}\n *\n * Available Radix primitives:\n * - `DropdownMenuPrimitive.Root` - Root component\n * - `DropdownMenuPrimitive.Trigger` - Trigger button (use `asChild` for custom triggers)\n * - `DropdownMenuPrimitive.Content` - Dropdown content container\n * - `DropdownMenuPrimitive.Item` - Menu item\n * - `DropdownMenuPrimitive.CheckboxItem` - Checkbox menu item\n * - `DropdownMenuPrimitive.RadioGroup` + `RadioItem` - Radio group\n * - `DropdownMenuPrimitive.Label` - Section label\n * - `DropdownMenuPrimitive.Separator` - Visual separator\n * - `DropdownMenuPrimitive.Sub` + `SubTrigger` + `SubContent` - Nested menus\n * - `DropdownMenuPrimitive.Portal` - Portal for dropdown content\n *\n * Use `getDropdownContentStyles()` and `dropdownContentClasses` from\n * `@/lib/variants/dropdown-content-styles` for consistent glass styling.\n */\n\nimport * as React from 'react';\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport type { LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport { getDropdownContentStyles, dropdownContentClasses } from '@/lib/variants/dropdown-content-styles';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface DropdownItem {\n readonly label?: string;\n readonly icon?: LucideIcon;\n readonly onClick?: () => void;\n readonly danger?: boolean;\n readonly divider?: boolean;\n}\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the DropdownGlass component\n *\n * A glass-themed dropdown menu with accessible keyboard navigation, based on Radix UI primitives.\n * Features theme-aware styling, smooth animations, and WCAG-compliant interactions.\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate menu items, Enter/Space activates, Escape closes (WCAG 2.1.1)\n * - **Focus Management:** Focus trapped within menu when open, returned to trigger on close (WCAG 2.4.3)\n * - **Screen Readers:** Uses `role=\"menu\"` and `role=\"menuitem\"` for proper menu semantics (WCAG 4.1.3)\n * - **ARIA Attributes:** Items marked with `data-highlighted` state for screen reader announcement\n * - **Trigger Association:** Menu automatically associated with trigger button via Radix UI primitives\n * - **Touch Targets:** All menu items meet minimum 44x44px touch target (WCAG 2.5.5)\n * - **Color Contrast:** Menu text and backgrounds meet WCAG AA contrast ratio 4.5:1\n * - **Motion:** Open/close animations respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic dropdown with icon items\n * <DropdownGlass\n * trigger={\n * <button aria-label=\"Open menu\">\n * <MoreVertical className=\"w-5 h-5\" />\n * </button>\n * }\n * items={[\n * { label: 'Edit', icon: Edit, onClick: handleEdit },\n * { label: 'Delete', icon: Trash, onClick: handleDelete, danger: true }\n * ]}\n * />\n *\n * // Dropdown with dividers and alignment\n * <DropdownGlass\n * trigger={<ButtonGlass>Actions</ButtonGlass>}\n * align=\"right\"\n * items={[\n * { label: 'View', icon: Eye, onClick: () => navigate('/view') },\n * { label: 'Edit', icon: Edit, onClick: () => setEditMode(true) },\n * { divider: true },\n * { label: 'Archive', icon: Archive, onClick: handleArchive },\n * { divider: true },\n * { label: 'Delete', icon: Trash, onClick: handleDelete, danger: true }\n * ]}\n * />\n *\n * // Dropdown in table row (ensure trigger has accessible label)\n * <DropdownGlass\n * trigger={\n * <button aria-label={`Actions for ${user.name}`}>\n * <MoreHorizontal />\n * </button>\n * }\n * items={[\n * { label: 'View Profile', onClick: () => viewProfile(user.id) },\n * { label: 'Send Message', onClick: () => sendMessage(user.id) }\n * ]}\n * />\n *\n * // Dropdown with keyboard-friendly trigger\n * <DropdownGlass\n * trigger={\n * <ButtonGlass\n * icon={Settings}\n * variant=\"ghost\"\n * aria-haspopup=\"menu\"\n * aria-label=\"Settings menu\"\n * >\n * Settings\n * </ButtonGlass>\n * }\n * items={[\n * { label: 'Preferences', icon: Sliders, onClick: () => navigate('/settings/preferences') },\n * { label: 'Security', icon: Lock, onClick: () => navigate('/settings/security') },\n * { divider: true },\n * { label: 'Log Out', icon: LogOut, onClick: handleLogout }\n * ]}\n * />\n *\n * // Advanced: Using Radix primitives directly (see component JSDoc for examples)\n * ```\n */\nexport interface DropdownGlassProps {\n readonly trigger: React.ReactNode;\n readonly items: readonly DropdownItem[];\n readonly align?: 'left' | 'right';\n readonly className?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const DropdownGlass = React.forwardRef<\n HTMLDivElement,\n DropdownGlassProps\n>(({ trigger, items, align = 'left', className }, ref) => {\n return (\n <div ref={ref} className={cn('relative inline-block', className)}>\n <DropdownMenuPrimitive.Root>\n <DropdownMenuPrimitive.Trigger asChild>\n {trigger}\n </DropdownMenuPrimitive.Trigger>\n\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n align={align === 'left' ? 'start' : 'end'}\n sideOffset={8}\n className={dropdownContentClasses}\n style={getDropdownContentStyles()}\n role=\"menu\"\n aria-orientation=\"vertical\"\n >\n {items.map((item, idx) =>\n item.divider ? (\n <DropdownMenuPrimitive.Separator\n key={`divider-${idx}`}\n className=\"my-2 mx-3 h-px\"\n style={{\n borderTop: '1px solid var(--dropdown-divider)',\n }}\n role=\"separator\"\n />\n ) : (\n <DropdownMenuPrimitive.Item\n key={`item-${idx}`}\n onClick={item.onClick}\n className={cn(\n 'group w-full px-3 py-2 md:px-4 md:py-2.5 text-xs md:text-sm text-left flex items-center gap-2 md:gap-3',\n 'cursor-default select-none',\n 'transition-colors duration-200 ease-out',\n 'focus-visible:outline-none focus-visible:shadow-(--focus-glow)',\n 'data-[highlighted]:bg-[var(--dropdown-item-hover)]',\n item.danger\n ? 'text-[var(--alert-danger-text)] data-[highlighted]:text-[var(--alert-danger-text)]'\n : 'text-[var(--dropdown-item-text)]'\n )}\n role=\"menuitem\"\n >\n {item.icon && (\n <item.icon\n className={cn(\n ICON_SIZES.md,\n 'transition-colors duration-200 ease-out shrink-0',\n item.danger\n ? 'text-[var(--alert-danger-text)]'\n : 'text-[var(--dropdown-icon)] group-data-[highlighted]:text-[var(--dropdown-icon-hover)]'\n )}\n />\n )}\n <span className=\"font-medium\">{item.label}</span>\n </DropdownMenuPrimitive.Item>\n )\n )}\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n </DropdownMenuPrimitive.Root>\n </div>\n );\n});\n\nDropdownGlass.displayName = 'DropdownGlass';\n"
19
+ "content": "/**\n * DropdownGlass Component\n *\n * Glass-themed dropdown menu with two APIs:\n *\n * 1. **Simple API** (items prop) - Quick setup for basic menus\n * 2. **Compound API** (DropdownMenuGlass.*) - Full shadcn/ui pattern for complex menus\n *\n * @example Simple API (recommended for basic dropdowns)\n * ```tsx\n * import { DropdownGlass } from '@/components/glass/ui/dropdown-glass';\n *\n * <DropdownGlass\n * trigger={<button><MoreVertical /></button>}\n * items={[\n * { label: 'Edit', icon: Edit, onClick: handleEdit },\n * { divider: true },\n * { label: 'Delete', icon: Trash, onClick: handleDelete, danger: true }\n * ]}\n * />\n * ```\n *\n * @example Compound API (for complex dropdowns)\n * ```tsx\n * import {\n * DropdownMenuGlass,\n * DropdownMenuGlassTrigger,\n * DropdownMenuGlassContent,\n * DropdownMenuGlassItem,\n * DropdownMenuGlassSeparator,\n * } from '@/components/glass/ui/dropdown-menu-glass';\n *\n * <DropdownMenuGlass>\n * <DropdownMenuGlassTrigger asChild>\n * <Button>Open Menu</Button>\n * </DropdownMenuGlassTrigger>\n * <DropdownMenuGlassContent>\n * <DropdownMenuGlassItem>Edit</DropdownMenuGlassItem>\n * <DropdownMenuGlassSeparator />\n * <DropdownMenuGlassItem variant=\"destructive\">Delete</DropdownMenuGlassItem>\n * </DropdownMenuGlassContent>\n * </DropdownMenuGlass>\n * ```\n *\n * @see ./dropdown-menu-glass.tsx for compound component exports\n */\n\n'use client';\n\nimport * as React from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport {\n DropdownMenuGlass,\n DropdownMenuGlassTrigger,\n DropdownMenuGlassContent,\n DropdownMenuGlassItem,\n DropdownMenuGlassSeparator,\n} from './dropdown-menu-glass';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface DropdownItem {\n readonly label?: string;\n readonly icon?: LucideIcon;\n readonly onClick?: () => void;\n readonly danger?: boolean;\n readonly divider?: boolean;\n}\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the DropdownGlass component (Simple API)\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate, Enter/Space activates, Escape closes\n * - **Focus Management:** Focus trapped within menu when open\n * - **Screen Readers:** Uses role=\"menu\" and role=\"menuitem\"\n * - **Touch Targets:** All items meet minimum 44x44px\n */\nexport interface DropdownGlassProps {\n /** Trigger element (button, etc.) */\n readonly trigger: React.ReactNode;\n /** Menu items array */\n readonly items: readonly DropdownItem[];\n /** Dropdown alignment */\n readonly align?: 'left' | 'right';\n /** Additional className */\n readonly className?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\n/**\n * DropdownGlass - Simple API wrapper\n *\n * For complex dropdowns with checkboxes, radio groups, sub-menus, etc.,\n * use the compound components from dropdown-menu-glass.tsx directly.\n */\nexport const DropdownGlass = React.forwardRef<HTMLDivElement, DropdownGlassProps>(\n ({ trigger, items, align = 'left', className }, ref) => {\n return (\n <div ref={ref} className={cn('relative inline-block', className)}>\n <DropdownMenuGlass>\n <DropdownMenuGlassTrigger asChild>{trigger}</DropdownMenuGlassTrigger>\n\n <DropdownMenuGlassContent align={align === 'left' ? 'start' : 'end'}>\n {items.map((item, idx) =>\n item.divider ? (\n <DropdownMenuGlassSeparator key={`divider-${idx}`} />\n ) : (\n <DropdownMenuGlassItem\n key={`item-${idx}`}\n variant={item.danger ? 'destructive' : 'default'}\n onSelect={item.onClick}\n >\n {item.icon && (\n <item.icon\n className={cn(\n ICON_SIZES.md,\n 'shrink-0',\n item.danger\n ? 'text-(--alert-danger-text)'\n : 'text-(--dropdown-icon) group-data-highlighted:text-(--dropdown-icon-hover)'\n )}\n />\n )}\n <span className=\"font-medium\">{item.label}</span>\n </DropdownMenuGlassItem>\n )\n )}\n </DropdownMenuGlassContent>\n </DropdownMenuGlass>\n </div>\n );\n }\n);\n\nDropdownGlass.displayName = 'DropdownGlass';\n\n// ========================================\n// RE-EXPORTS (for convenience)\n// ========================================\n\nexport {\n DropdownMenuGlass,\n DropdownMenuGlassTrigger,\n DropdownMenuGlassContent,\n DropdownMenuGlassItem,\n DropdownMenuGlassSeparator,\n} from './dropdown-menu-glass';\n"
22
20
  }
23
21
  ],
24
22
  "categories": [
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "dropdown-menu-glass",
4
+ "type": "registry:ui",
5
+ "title": "Dropdown Menu Glass",
6
+ "description": "DropdownMenuGlass - Compound Component",
7
+ "dependencies": [
8
+ "@radix-ui/react-dropdown-menu",
9
+ "lucide-react",
10
+ "react"
11
+ ],
12
+ "registryDependencies": [
13
+ "cn",
14
+ "variants"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "components/glass/ui/dropdown-menu-glass.tsx",
19
+ "type": "registry:component",
20
+ "content": "/**\n * DropdownMenuGlass - Compound Component\n *\n * Glass-themed dropdown menu following shadcn/ui compound component pattern.\n * Built on Radix UI primitives with unified glass styling.\n *\n * @example Basic usage\n * ```tsx\n * <DropdownMenuGlass>\n * <DropdownMenuGlassTrigger asChild>\n * <Button>Open Menu</Button>\n * </DropdownMenuGlassTrigger>\n * <DropdownMenuGlassContent>\n * <DropdownMenuGlassItem onSelect={() => console.log('Edit')}>\n * <Edit className=\"mr-2 h-4 w-4\" />\n * Edit\n * </DropdownMenuGlassItem>\n * <DropdownMenuGlassSeparator />\n * <DropdownMenuGlassItem variant=\"destructive\">\n * <Trash className=\"mr-2 h-4 w-4\" />\n * Delete\n * </DropdownMenuGlassItem>\n * </DropdownMenuGlassContent>\n * </DropdownMenuGlass>\n * ```\n *\n * @example With labels and groups\n * ```tsx\n * <DropdownMenuGlass>\n * <DropdownMenuGlassTrigger asChild>\n * <Button variant=\"outline\">Options</Button>\n * </DropdownMenuGlassTrigger>\n * <DropdownMenuGlassContent>\n * <DropdownMenuGlassLabel>Actions</DropdownMenuGlassLabel>\n * <DropdownMenuGlassGroup>\n * <DropdownMenuGlassItem>Copy</DropdownMenuGlassItem>\n * <DropdownMenuGlassItem>Paste</DropdownMenuGlassItem>\n * </DropdownMenuGlassGroup>\n * <DropdownMenuGlassSeparator />\n * <DropdownMenuGlassLabel>Danger Zone</DropdownMenuGlassLabel>\n * <DropdownMenuGlassItem variant=\"destructive\">Delete</DropdownMenuGlassItem>\n * </DropdownMenuGlassContent>\n * </DropdownMenuGlass>\n * ```\n *\n * @see https://www.radix-ui.com/primitives/docs/components/dropdown-menu\n */\n\n'use client';\n\nimport * as React from 'react';\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport {\n getDropdownContentStyles,\n dropdownContentClasses,\n getDropdownItemClasses,\n dropdownSeparatorClasses,\n dropdownLabelClasses,\n} from '@/lib/variants/dropdown-content-styles';\nimport '@/glass-theme.css';\n\n// ========================================\n// ROOT\n// ========================================\n\nconst DropdownMenuGlass = DropdownMenuPrimitive.Root;\n\n// ========================================\n// TRIGGER\n// ========================================\n\nconst DropdownMenuGlassTrigger = DropdownMenuPrimitive.Trigger;\n\n// ========================================\n// GROUP\n// ========================================\n\nconst DropdownMenuGlassGroup = DropdownMenuPrimitive.Group;\n\n// ========================================\n// PORTAL\n// ========================================\n\nconst DropdownMenuGlassPortal = DropdownMenuPrimitive.Portal;\n\n// ========================================\n// SUB\n// ========================================\n\nconst DropdownMenuGlassSub = DropdownMenuPrimitive.Sub;\n\n// ========================================\n// RADIO GROUP\n// ========================================\n\nconst DropdownMenuGlassRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\n// ========================================\n// SUB TRIGGER\n// ========================================\n\nconst DropdownMenuGlassSubTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n getDropdownItemClasses(),\n 'data-[state=open]:bg-[var(--dropdown-item-hover)]',\n inset && 'pl-8',\n className\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className=\"ml-auto h-4 w-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuGlassSubTrigger.displayName = 'DropdownMenuGlassSubTrigger';\n\n// ========================================\n// SUB CONTENT\n// ========================================\n\nconst DropdownMenuGlassSubContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(dropdownContentClasses, 'p-1.5', className)}\n style={getDropdownContentStyles()}\n {...props}\n />\n));\nDropdownMenuGlassSubContent.displayName = 'DropdownMenuGlassSubContent';\n\n// ========================================\n// CONTENT\n// ========================================\n\nconst DropdownMenuGlassContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 8, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(dropdownContentClasses, 'p-1.5', className)}\n style={getDropdownContentStyles()}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuGlassContent.displayName = 'DropdownMenuGlassContent';\n\n// ========================================\n// ITEM\n// ========================================\n\nexport interface DropdownMenuGlassItemProps extends React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Item\n> {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}\n\nconst DropdownMenuGlassItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuGlassItemProps\n>(({ className, inset, variant = 'default', ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n getDropdownItemClasses({ danger: variant === 'destructive' }),\n inset && 'pl-8',\n className\n )}\n {...props}\n />\n));\nDropdownMenuGlassItem.displayName = 'DropdownMenuGlassItem';\n\n// ========================================\n// CHECKBOX ITEM\n// ========================================\n\nconst DropdownMenuGlassCheckboxItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(getDropdownItemClasses(), 'pl-8 pr-2', className)}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon className=\"h-4 w-4\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuGlassCheckboxItem.displayName = 'DropdownMenuGlassCheckboxItem';\n\n// ========================================\n// RADIO ITEM\n// ========================================\n\nconst DropdownMenuGlassRadioItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(getDropdownItemClasses(), 'pl-8 pr-2', className)}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CircleIcon className=\"h-2 w-2 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuGlassRadioItem.displayName = 'DropdownMenuGlassRadioItem';\n\n// ========================================\n// LABEL\n// ========================================\n\nconst DropdownMenuGlassLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(dropdownLabelClasses, inset && 'pl-8', className)}\n {...props}\n />\n));\nDropdownMenuGlassLabel.displayName = 'DropdownMenuGlassLabel';\n\n// ========================================\n// SEPARATOR\n// ========================================\n\nconst DropdownMenuGlassSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(dropdownSeparatorClasses, '-mx-1 my-1', className)}\n {...props}\n />\n));\nDropdownMenuGlassSeparator.displayName = 'DropdownMenuGlassSeparator';\n\n// ========================================\n// SHORTCUT\n// ========================================\n\nconst DropdownMenuGlassShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n return (\n <span\n className={cn('ml-auto text-xs tracking-widest text-(--text-muted)', className)}\n {...props}\n />\n );\n};\nDropdownMenuGlassShortcut.displayName = 'DropdownMenuGlassShortcut';\n\n// ========================================\n// EXPORTS\n// ========================================\n\nexport {\n DropdownMenuGlass,\n DropdownMenuGlassTrigger,\n DropdownMenuGlassContent,\n DropdownMenuGlassItem,\n DropdownMenuGlassCheckboxItem,\n DropdownMenuGlassRadioItem,\n DropdownMenuGlassLabel,\n DropdownMenuGlassSeparator,\n DropdownMenuGlassShortcut,\n DropdownMenuGlassGroup,\n DropdownMenuGlassPortal,\n DropdownMenuGlassSub,\n DropdownMenuGlassSubContent,\n DropdownMenuGlassSubTrigger,\n DropdownMenuGlassRadioGroup,\n};\n"
21
+ }
22
+ ],
23
+ "categories": [
24
+ "ui"
25
+ ],
26
+ "cssVars": {
27
+ "light": {
28
+ "--glass-bg": "rgba(255, 255, 255, 0.1)",
29
+ "--glass-border": "rgba(255, 255, 255, 0.2)",
30
+ "--blur-sm": "8px",
31
+ "--blur-md": "16px",
32
+ "--blur-lg": "24px"
33
+ },
34
+ "dark": {
35
+ "--glass-bg": "rgba(255, 255, 255, 0.05)",
36
+ "--glass-border": "rgba(255, 255, 255, 0.1)",
37
+ "--blur-sm": "8px",
38
+ "--blur-md": "16px",
39
+ "--blur-lg": "24px"
40
+ }
41
+ }
42
+ }
@@ -3,7 +3,7 @@
3
3
  "name": "language-bar-glass",
4
4
  "type": "registry:component",
5
5
  "title": "Language Bar Glass",
6
- "description": "Language Bar Glass component with glass effects",
6
+ "description": "Custom color for the language segment.",
7
7
  "dependencies": [
8
8
  "react"
9
9
  ],
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "path": "components/glass/specialized/language-bar-glass.tsx",
16
16
  "type": "registry:component",
17
- "content": "// ========================================\n// LANGUAGE BAR GLASS COMPONENT\n// Language/skill proficiency bar with legend\n// ========================================\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport interface LanguageData {\n readonly name: string;\n readonly percent: number;\n readonly color?: string;\n}\n\nexport interface LanguageBarGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly languages: readonly LanguageData[];\n readonly showLegend?: boolean;\n}\n\nconst defaultLangColors: Record<string, string> = {\n TypeScript: 'bg-blue-500',\n JavaScript: 'bg-yellow-400',\n Python: 'bg-emerald-500',\n HTML: 'bg-orange-500',\n CSS: 'bg-purple-500',\n Java: 'bg-red-500',\n Go: 'bg-cyan-500',\n Rust: 'bg-orange-600',\n Ruby: 'bg-red-600',\n PHP: 'bg-indigo-500',\n};\n\nexport const LanguageBarGlass = forwardRef<HTMLDivElement, LanguageBarGlassProps>(\n ({ languages = [], showLegend = true, className, ...props }, ref) => {\n const [hoveredLang, setHoveredLang] = useState<number | null>(null);\n\n const barStyles: CSSProperties = {\n boxShadow: 'var(--rainbow-glow)',\n };\n\n // Early return if no languages provided\n if (!languages || languages.length === 0) {\n return null;\n }\n\n return (\n <div ref={ref} className={cn('w-full', className)} {...props}>\n {/* Progress bar */}\n <div\n className=\"flex h-2 md:h-2.5 rounded-full overflow-hidden\"\n style={barStyles}\n role=\"group\"\n aria-label=\"Language distribution\"\n >\n {languages.map((lang, i) => {\n const colorClass = lang.color ?? defaultLangColors[lang.name] ?? 'bg-slate-400';\n const segmentStyles: CSSProperties = {\n width: `${lang.percent}%`,\n opacity: hoveredLang !== null && hoveredLang !== i ? 0.5 : 1,\n transition: 'all 0.3s',\n };\n\n return (\n <div\n key={`bar-${lang.name}-${i}`}\n className={cn(colorClass)}\n style={segmentStyles}\n role=\"progressbar\"\n aria-label={`${lang.name}: ${lang.percent}%`}\n aria-valuenow={lang.percent}\n aria-valuemin={0}\n aria-valuemax={100}\n onMouseEnter={() => setHoveredLang(i)}\n onMouseLeave={() => setHoveredLang(null)}\n />\n );\n })}\n </div>\n\n {/* Legend */}\n {showLegend && (\n <div className=\"flex items-center gap-3 md:gap-4 mt-1.5 md:mt-2 text-(length:--font-size-2xs) md:text-xs flex-wrap text-(--text-secondary)\">\n {languages.map((lang, i) => {\n const colorClass = lang.color ?? defaultLangColors[lang.name] ?? 'bg-slate-400';\n\n return (\n <span\n key={`legend-${lang.name}-${i}`}\n className=\"flex items-center gap-1 md:gap-1.5 cursor-pointer\"\n onMouseEnter={() => setHoveredLang(i)}\n onMouseLeave={() => setHoveredLang(null)}\n >\n <span className={cn('w-2 h-2 md:w-2.5 md:h-2.5 rounded-full', colorClass)} />\n {lang.name} {lang.percent}%\n </span>\n );\n })}\n </div>\n )}\n </div>\n );\n }\n);\n\nLanguageBarGlass.displayName = 'LanguageBarGlass';\n"
17
+ "content": "// ========================================\n// LANGUAGE BAR GLASS COMPONENT\n// Language/skill proficiency bar with legend\n// ========================================\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport interface LanguageData {\n readonly name: string;\n readonly percent: number;\n /**\n * Custom color for the language segment.\n * Accepts any valid CSS color value:\n * - OKLCH (recommended): `oklch(66.6% 0.159 303)`\n * - Hex: `#3b82f6`\n * - RGB: `rgb(59, 130, 246)`\n * - HSL: `hsl(221, 83%, 53%)`\n *\n * **Note:** CSS variables like `var(--oklch-blue-500)` may not work if they're\n * tree-shaken out in production builds. Use direct color values instead.\n *\n * If not provided, falls back to default language colors defined in `defaultLangColors`.\n */\n readonly color?: string;\n}\n\nexport interface LanguageBarGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly languages: readonly LanguageData[];\n readonly showLegend?: boolean;\n}\n\n// CSS variable names for language colors (defined in themes)\nconst defaultLangColors: Record<string, string> = {\n TypeScript: 'var(--language-typescript)',\n JavaScript: 'var(--language-javascript)',\n Python: 'var(--language-python)',\n HTML: 'var(--language-html)',\n CSS: 'var(--language-css)',\n Java: 'var(--language-java)',\n Go: 'var(--language-go)',\n Rust: 'var(--language-rust)',\n Ruby: 'var(--language-ruby)',\n PHP: 'var(--language-php)',\n};\n\nexport const LanguageBarGlass = forwardRef<HTMLDivElement, LanguageBarGlassProps>(\n ({ languages = [], showLegend = true, className, ...props }, ref) => {\n const [hoveredLang, setHoveredLang] = useState<number | null>(null);\n\n const barStyles: CSSProperties = {\n boxShadow: 'var(--rainbow-glow)',\n };\n\n // Early return if no languages provided\n if (!languages || languages.length === 0) {\n return null;\n }\n\n return (\n <div ref={ref} className={cn('w-full', className)} {...props}>\n {/* Progress bar */}\n <div\n className=\"flex h-2 md:h-2.5 rounded-full overflow-hidden\"\n style={barStyles}\n role=\"group\"\n aria-label=\"Language distribution\"\n >\n {languages.map((lang, i) => {\n const bgColor = lang.color ?? defaultLangColors[lang.name] ?? 'var(--oklch-slate-400)';\n const segmentStyles: CSSProperties = {\n width: `${lang.percent}%`,\n backgroundColor: bgColor,\n opacity: hoveredLang !== null && hoveredLang !== i ? 0.5 : 1,\n transition: 'all 0.3s',\n };\n\n return (\n <div\n key={`bar-${lang.name}-${i}`}\n style={segmentStyles}\n role=\"progressbar\"\n aria-label={`${lang.name}: ${lang.percent}%`}\n aria-valuenow={lang.percent}\n aria-valuemin={0}\n aria-valuemax={100}\n onMouseEnter={() => setHoveredLang(i)}\n onMouseLeave={() => setHoveredLang(null)}\n />\n );\n })}\n </div>\n\n {/* Legend */}\n {showLegend && (\n <div className=\"flex items-center gap-3 md:gap-4 mt-1.5 md:mt-2 text-(length:--font-size-2xs) md:text-xs flex-wrap text-(--text-secondary)\">\n {languages.map((lang, i) => {\n const bgColor =\n lang.color ?? defaultLangColors[lang.name] ?? 'var(--oklch-slate-400)';\n\n return (\n <span\n key={`legend-${lang.name}-${i}`}\n className=\"flex items-center gap-1 md:gap-1.5 cursor-pointer\"\n onMouseEnter={() => setHoveredLang(i)}\n onMouseLeave={() => setHoveredLang(null)}\n >\n <span\n className=\"w-2 h-2 md:w-2.5 md:h-2.5 rounded-full\"\n style={{ backgroundColor: bgColor }}\n />\n {lang.name} {lang.percent}%\n </span>\n );\n })}\n </div>\n )}\n </div>\n );\n }\n);\n\nLanguageBarGlass.displayName = 'LanguageBarGlass';\n"
18
18
  }
19
19
  ],
20
20
  "categories": [
@@ -15,7 +15,7 @@
15
15
  {
16
16
  "path": "components/glass/ui/popover-glass.tsx",
17
17
  "type": "registry:component",
18
- "content": "/**\n * PopoverGlass Component\n *\n * Floating glass-themed container for tooltips, dropdowns, and overlays with:\n * - Theme-aware styling (glass/light/aurora)\n * - Smooth animations with fade-in effect\n * - Arrow pointer with glass styling\n * - ESC key and click-outside to close\n * - Focus trap for accessibility\n * - All position/alignment options (top/right/bottom/left × start/center/end)\n *\n * @example\n * ```tsx\n * <PopoverGlass\n * trigger={<ButtonGlass>Open</ButtonGlass>}\n * side=\"top\"\n * align=\"center\"\n * >\n * <div className=\"p-4\">\n * <h3 style={{ color: 'var(--text-primary)' }}>Title</h3>\n * <p style={{ color: 'var(--text-secondary)' }}>Content</p>\n * </div>\n * </PopoverGlass>\n * ```\n */\n\nimport * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface PopoverGlassProps {\n /** The trigger element that opens the popover */\n readonly trigger: React.ReactNode;\n /** The content to display inside the popover */\n readonly children: React.ReactNode;\n /** The preferred side of the trigger to render against */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n /** The preferred alignment against the trigger */\n readonly align?: 'start' | 'center' | 'end';\n /** The distance in pixels from the trigger */\n readonly sideOffset?: number;\n /** Controlled open state */\n readonly open?: boolean;\n /** Callback when open state changes */\n readonly onOpenChange?: (open: boolean) => void;\n /** Whether to show the arrow pointer */\n readonly showArrow?: boolean;\n /** Additional class name for the content wrapper */\n readonly className?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const PopoverGlass = React.forwardRef<\n HTMLDivElement,\n PopoverGlassProps\n>(\n (\n {\n trigger,\n children,\n side = 'bottom',\n align = 'center',\n sideOffset = 8,\n open,\n onOpenChange,\n showArrow = true,\n className,\n },\n ref\n ) => {\n // Popover content styles with CSS variables\n const popoverStyles: React.CSSProperties = {\n background: 'var(--popover-bg)',\n border: '1px solid var(--popover-border)',\n boxShadow: 'var(--popover-shadow)',\n backdropFilter: 'blur(var(--blur-md))', // 16px - standard popover blur\n WebkitBackdropFilter: 'blur(var(--blur-md))',\n };\n\n // Arrow styles\n const arrowStyles: React.CSSProperties = {\n fill: 'var(--popover-arrow-bg)',\n };\n\n return (\n <PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>\n <PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>\n\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n side={side}\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-[50003] rounded-2xl',\n 'animate-in fade-in-0 zoom-in-95 duration-200',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'outline-none',\n className\n )}\n style={popoverStyles}\n role=\"dialog\"\n aria-modal=\"false\"\n >\n {children}\n\n {showArrow && (\n <PopoverPrimitive.Arrow\n className=\"fill-current\"\n style={arrowStyles}\n width={16}\n height={8}\n />\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n );\n }\n);\n\nPopoverGlass.displayName = 'PopoverGlass';\n"
18
+ "content": "/**\n * PopoverGlass Component\n *\n * Floating glass-themed container for tooltips, dropdowns, and overlays with:\n * - Theme-aware styling (glass/light/aurora)\n * - Smooth animations with fade-in effect\n * - Arrow pointer with glass styling\n * - ESC key and click-outside to close\n * - Focus trap for accessibility\n * - All position/alignment options (top/right/bottom/left × start/center/end)\n *\n * @example Compound API (recommended)\n * ```tsx\n * <PopoverGlass>\n * <PopoverGlassTrigger asChild>\n * <ButtonGlass>Open</ButtonGlass>\n * </PopoverGlassTrigger>\n * <PopoverGlassContent side=\"top\">\n * <div className=\"p-4\">\n * <h3 style={{ color: 'var(--text-primary)' }}>Title</h3>\n * <p style={{ color: 'var(--text-secondary)' }}>Content</p>\n * </div>\n * </PopoverGlassContent>\n * </PopoverGlass>\n * ```\n *\n * @example Legacy API (backward compatible)\n * ```tsx\n * <PopoverGlassLegacy\n * trigger={<ButtonGlass>Open</ButtonGlass>}\n * side=\"top\"\n * >\n * <div className=\"p-4\">Content</div>\n * </PopoverGlassLegacy>\n * ```\n */\n\n'use client';\n\nimport * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\nconst PopoverGlassRoot = PopoverPrimitive.Root;\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\nconst PopoverGlassTrigger = PopoverPrimitive.Trigger;\n\n// ========================================\n// COMPOUND COMPONENT: ANCHOR\n// ========================================\n\nconst PopoverGlassAnchor = PopoverPrimitive.Anchor;\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ninterface PopoverGlassContentProps extends React.ComponentPropsWithoutRef<\n typeof PopoverPrimitive.Content\n> {\n /** Whether to show the arrow pointer */\n showArrow?: boolean;\n}\n\nconst PopoverGlassContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n PopoverGlassContentProps\n>(({ className, align = 'center', sideOffset = 8, showArrow = true, children, ...props }, ref) => {\n // Popover content styles with CSS variables\n const popoverStyles: React.CSSProperties = {\n background: 'var(--popover-bg)',\n border: '1px solid var(--popover-border)',\n boxShadow: 'var(--popover-shadow)',\n backdropFilter: 'blur(var(--blur-md))',\n WebkitBackdropFilter: 'blur(var(--blur-md))',\n };\n\n // Arrow styles\n const arrowStyles: React.CSSProperties = {\n fill: 'var(--popover-arrow-bg)',\n };\n\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-50003 rounded-2xl',\n 'animate-in fade-in-0 zoom-in-95 duration-200',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'outline-none',\n className\n )}\n style={popoverStyles}\n {...props}\n >\n {children}\n\n {showArrow && (\n <PopoverPrimitive.Arrow\n className=\"fill-current\"\n style={arrowStyles}\n width={16}\n height={8}\n />\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n );\n});\n\nPopoverGlassContent.displayName = 'PopoverGlassContent';\n\n// ========================================\n// LEGACY API (backward compatible)\n// ========================================\n\nexport interface PopoverGlassLegacyProps {\n /** The trigger element that opens the popover */\n readonly trigger: React.ReactNode;\n /** The content to display inside the popover */\n readonly children: React.ReactNode;\n /** The preferred side of the trigger to render against */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n /** The preferred alignment against the trigger */\n readonly align?: 'start' | 'center' | 'end';\n /** The distance in pixels from the trigger */\n readonly sideOffset?: number;\n /** Controlled open state */\n readonly open?: boolean;\n /** Callback when open state changes */\n readonly onOpenChange?: (open: boolean) => void;\n /** Whether to show the arrow pointer */\n readonly showArrow?: boolean;\n /** Additional class name for the content wrapper */\n readonly className?: string;\n}\n\nconst PopoverGlassLegacy = React.forwardRef<HTMLDivElement, PopoverGlassLegacyProps>(\n (\n {\n trigger,\n children,\n side = 'bottom',\n align = 'center',\n sideOffset = 8,\n open,\n onOpenChange,\n showArrow = true,\n className,\n },\n ref\n ) => {\n return (\n <PopoverGlassRoot open={open} onOpenChange={onOpenChange}>\n <PopoverGlassTrigger asChild>{trigger}</PopoverGlassTrigger>\n <PopoverGlassContent\n ref={ref}\n side={side}\n align={align}\n sideOffset={sideOffset}\n showArrow={showArrow}\n className={className}\n >\n {children}\n </PopoverGlassContent>\n </PopoverGlassRoot>\n );\n }\n);\n\nPopoverGlassLegacy.displayName = 'PopoverGlassLegacy';\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const PopoverGlass = PopoverGlassRoot;\nexport { PopoverGlassTrigger, PopoverGlassContent, PopoverGlassAnchor };\n\n// Legacy API (backward compatible)\nexport { PopoverGlassLegacy };\n\n// For backward compatibility, also export as default\nexport { PopoverGlassLegacy as default };\n"
19
19
  }
20
20
  ],
21
21
  "categories": [
@@ -3,18 +3,20 @@
3
3
  "name": "profile-avatar-glass",
4
4
  "type": "registry:component",
5
5
  "title": "Profile Avatar Glass",
6
- "description": "Profile Avatar Glass component with glass effects",
6
+ "description": "Profile-specific size classes (larger than standard AvatarGlass)",
7
7
  "dependencies": [
8
8
  "react"
9
9
  ],
10
10
  "registryDependencies": [
11
- "cn"
11
+ "cn",
12
+ "use-hover",
13
+ "variants"
12
14
  ],
13
15
  "files": [
14
16
  {
15
17
  "path": "components/glass/specialized/profile-avatar-glass.tsx",
16
18
  "type": "registry:component",
17
- "content": "// ========================================\n// PROFILE AVATAR GLASS COMPONENT\n// Large avatar with glow animation for profiles\n// ========================================\n\nimport { forwardRef, useState, type CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport \"@/glass-theme.css\";\n\nexport type ProfileAvatarSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type ProfileAvatarStatus = \"online\" | \"offline\" | \"busy\" | \"away\";\n\nconst sizeClasses: Record<ProfileAvatarSize, string> = {\n sm: \"w-9 h-9 md:w-10 md:h-10 text-xs md:text-sm\",\n md: \"w-12 h-12 md:w-14 md:h-14 text-base md:text-lg\",\n lg: \"w-14 h-14 md:w-16 md:h-16 text-lg md:text-xl\",\n xl: \"w-18 h-18 md:w-20 md:h-20 text-xl md:text-2xl\",\n};\n\nconst statusSizeClasses: Record<ProfileAvatarSize, string> = {\n sm: \"w-2.5 h-2.5 md:w-3 md:h-3\",\n md: \"w-3 h-3 md:w-3.5 md:h-3.5\",\n lg: \"w-3.5 h-3.5 md:w-4 md:h-4\",\n xl: \"w-4 h-4 md:w-5 md:h-5\",\n};\n\nconst statusPositionClasses: Record<ProfileAvatarSize, string> = {\n sm: \"bottom-0 right-0\",\n md: \"bottom-0 right-0\",\n lg: \"-bottom-0.5 -right-0.5\",\n xl: \"-bottom-1 -right-1\",\n};\n\n// CSS variable maps for status colors (using semantic naming)\nconst statusVarMap: Record<ProfileAvatarStatus, string> = {\n online: \"var(--status-online)\",\n offline: \"var(--text-muted)\",\n busy: \"var(--status-busy)\",\n away: \"var(--status-away)\",\n};\n\nexport interface ProfileAvatarGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly initials: string;\n readonly size?: ProfileAvatarSize;\n readonly status?: ProfileAvatarStatus;\n readonly glowing?: boolean;\n}\n\nexport const ProfileAvatarGlass = forwardRef<HTMLDivElement, ProfileAvatarGlassProps>(\n ({ initials, size = \"lg\", status, glowing = true, className, ...props }, ref) => {\n const [isHovered, setIsHovered] = useState(false);\n\n const avatarStyles: CSSProperties = {\n boxShadow: isHovered ? \"var(--profile-avatar-glow)\" : \"none\",\n border: \"3px solid var(--profile-avatar-border)\",\n };\n\n return (\n <div\n ref={ref}\n className={cn(\"relative inline-flex\", className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n {...props}\n >\n <div\n className={cn(\n \"rounded-full bg-gradient-to-br from-blue-400 via-violet-500 to-indigo-500\",\n \"flex items-center justify-center text-white font-bold transition-all duration-300\",\n sizeClasses[size],\n glowing && \"animate-[glow-pulse_2s_ease-in-out_infinite]\"\n )}\n style={avatarStyles}\n role=\"img\"\n aria-label={`Profile avatar with initials ${initials}`}\n >\n {initials}\n </div>\n {status && (\n <span\n className={cn(\n \"absolute rounded-full\",\n statusPositionClasses[size],\n statusSizeClasses[size]\n )}\n style={{\n background: statusVarMap[status],\n border: \"none\",\n boxShadow: \"none\",\n }}\n role=\"status\"\n aria-label={`Status: ${status}`}\n />\n )}\n </div>\n );\n }\n);\n\nProfileAvatarGlass.displayName = \"ProfileAvatarGlass\";\n"
19
+ "content": "// ========================================\n// PROFILE AVATAR GLASS - SPECIALIZED COMPONENT\n// Large profile avatar wrapper with custom sizing\n// Level 4: Specialized (wrapper over AvatarGlass)\n// ========================================\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { type AvatarStatus } from '@/components/glass/ui/avatar-glass';\nimport { statusSizes } from '@/lib/variants/avatar-glass-variants';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\n\nexport type ProfileAvatarSize = 'sm' | 'md' | 'lg' | 'xl';\n\nexport interface ProfileAvatarGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n /** User initials to display */\n readonly initials: string;\n /** Size variant (profile-specific sizing - larger than AvatarGlass) */\n readonly size?: ProfileAvatarSize;\n /** Optional status indicator */\n readonly status?: AvatarStatus;\n /** Enable pulsing glow animation */\n readonly glowing?: boolean;\n}\n\n/**\n * Profile-specific size classes (larger than standard AvatarGlass)\n */\nconst profileSizeClasses: Record<ProfileAvatarSize, string> = {\n sm: 'w-9 h-9 md:w-10 md:h-10 text-xs md:text-sm',\n md: 'w-12 h-12 md:w-14 md:h-14 text-base md:text-lg',\n lg: 'w-14 h-14 md:w-16 md:h-16 text-lg md:text-xl',\n xl: 'w-18 h-18 md:w-20 md:h-20 text-xl md:text-2xl',\n};\n\n/**\n * Get CSS variables for status indicator colors\n */\nconst getStatusVars = (statusType: AvatarStatus): { bg: string; glow: string } => {\n const statusVars: Record<AvatarStatus, { bg: string; glow: string }> = {\n online: { bg: 'var(--status-online)', glow: 'var(--status-online-glow)' },\n offline: { bg: 'var(--status-offline)', glow: 'none' },\n busy: { bg: 'var(--status-busy)', glow: 'var(--status-busy-glow)' },\n away: { bg: 'var(--status-away)', glow: 'var(--status-away-glow)' },\n };\n return statusVars[statusType];\n};\n\n/**\n * ProfileAvatarGlass - Large profile avatar with bold styling\n *\n * Built on top of the same CSS variables as AvatarGlass but with\n * profile-specific sizing and bold font weight.\n *\n * @example\n * ```tsx\n * <ProfileAvatarGlass initials=\"JD\" size=\"lg\" glowing />\n * <ProfileAvatarGlass initials=\"AS\" status=\"online\" />\n * ```\n */\nexport const ProfileAvatarGlass = forwardRef<HTMLDivElement, ProfileAvatarGlassProps>(\n ({ initials, size = 'lg', status, glowing = true, className, ...props }, ref) => {\n const { isHovered, hoverProps } = useHover();\n\n const avatarStyles: CSSProperties = {\n background: 'var(--avatar-bg)',\n border: '3px solid var(--avatar-border)',\n boxShadow: isHovered ? 'var(--avatar-hover-glow)' : 'var(--avatar-shadow)',\n color: 'var(--text-inverse)',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative inline-flex', className)}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n {...props}\n >\n {/* Avatar circle */}\n <div\n className={cn(\n 'rounded-full flex items-center justify-center font-bold transition-all duration-300',\n profileSizeClasses[size],\n glowing && 'animate-[glow-pulse_2s_ease-in-out_infinite]'\n )}\n style={avatarStyles}\n role=\"img\"\n aria-label={`Profile avatar with initials ${initials}`}\n >\n {initials}\n </div>\n\n {/* Status indicator */}\n {status && (\n <span\n className={cn(statusSizes({ size }), 'absolute -bottom-0.5 -right-0.5')}\n style={{\n background: getStatusVars(status).bg,\n boxShadow: getStatusVars(status).glow,\n }}\n role=\"status\"\n aria-label={`Status: ${status}`}\n />\n )}\n </div>\n );\n }\n);\n\nProfileAvatarGlass.displayName = 'ProfileAvatarGlass';\n"
18
20
  }
19
21
  ],
20
22
  "categories": [
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "path": "components/glass/specialized/rainbow-progress-glass.tsx",
16
16
  "type": "registry:component",
17
- "content": "// ========================================\n// RAINBOW PROGRESS GLASS COMPONENT\n// Animated rainbow gradient progress bar\n// ========================================\n\nimport { forwardRef, type CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport \"@/glass-theme.css\";\n\nexport type RainbowProgressSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport interface RainbowProgressGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly value: number;\n readonly size?: RainbowProgressSize;\n readonly showGlow?: boolean;\n}\n\nconst sizeClasses: Record<RainbowProgressSize, string> = {\n sm: \"h-2.5 md:h-2\",\n md: \"h-3.5 md:h-3\",\n lg: \"h-[1.125rem] md:h-4\",\n xl: \"h-6 md:h-5\",\n};\n\nexport const RainbowProgressGlass = forwardRef<HTMLDivElement, RainbowProgressGlassProps>(\n ({ value, size = \"lg\", showGlow = true, className, ...props }, ref) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n\n const trackStyles: CSSProperties = {\n background: \"var(--progress-bg)\",\n };\n\n const fillStyles: CSSProperties = {\n width: `${clampedValue}%`,\n background:\n \"linear-gradient(90deg, #f59e0b, #fbbf24, #84cc16, #22c55e, #14b8a6, #06b6d4, #3b82f6)\",\n boxShadow: showGlow ? \"var(--rainbow-glow)\" : \"none\",\n animation: showGlow ? \"var(--rainbow-animation)\" : \"none\",\n };\n\n return (\n <div\n ref={ref}\n className={cn(\"rounded-full overflow-hidden\", sizeClasses[size], className)}\n style={trackStyles}\n role=\"progressbar\"\n aria-valuenow={clampedValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`Rainbow progress: ${clampedValue}%`}\n {...props}\n >\n <div\n className=\"h-full rounded-full transition-all duration-1000\"\n style={fillStyles}\n />\n </div>\n );\n }\n);\n\nRainbowProgressGlass.displayName = \"RainbowProgressGlass\";\n"
17
+ "content": "// ========================================\n// RAINBOW PROGRESS GLASS COMPONENT\n// Animated rainbow gradient progress bar\n// ========================================\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport type RainbowProgressSize = 'sm' | 'md' | 'lg' | 'xl';\n\nexport interface RainbowProgressGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly value: number;\n readonly size?: RainbowProgressSize;\n readonly showGlow?: boolean;\n}\n\nconst sizeClasses: Record<RainbowProgressSize, string> = {\n sm: 'h-2.5 md:h-2',\n md: 'h-3.5 md:h-3',\n lg: 'h-[1.125rem] md:h-4',\n xl: 'h-6 md:h-5',\n};\n\nexport const RainbowProgressGlass = forwardRef<HTMLDivElement, RainbowProgressGlassProps>(\n ({ value, size = 'lg', showGlow = true, className, ...props }, ref) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n\n const trackStyles: CSSProperties = {\n background: 'var(--progress-bg)',\n };\n\n const fillStyles: CSSProperties = {\n width: `${clampedValue}%`,\n background: 'var(--rainbow-gradient)',\n boxShadow: showGlow ? 'var(--rainbow-glow)' : 'none',\n animation: showGlow ? 'var(--rainbow-animation)' : 'none',\n };\n\n return (\n <div\n ref={ref}\n className={cn('rounded-full overflow-hidden', sizeClasses[size], className)}\n style={trackStyles}\n role=\"progressbar\"\n aria-valuenow={clampedValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`Rainbow progress: ${clampedValue}%`}\n {...props}\n >\n <div className=\"h-full rounded-full transition-all duration-1000\" style={fillStyles} />\n </div>\n );\n }\n);\n\nRainbowProgressGlass.displayName = 'RainbowProgressGlass';\n"
18
18
  }
19
19
  ],
20
20
  "categories": [
@@ -69,11 +69,17 @@
69
69
  "title": "Glass Card",
70
70
  "description": "Glass-themed container with:"
71
71
  },
72
+ {
73
+ "name": "dropdown-menu-glass",
74
+ "type": "registry:ui",
75
+ "title": "Dropdown Menu Glass",
76
+ "description": "DropdownMenuGlass - Compound Component"
77
+ },
72
78
  {
73
79
  "name": "dropdown-glass",
74
80
  "type": "registry:ui",
75
81
  "title": "Dropdown Glass",
76
- "description": "Glass-themed dropdown menu based on Radix UI with:"
82
+ "description": "Glass-themed dropdown menu with two APIs:\n *\n * 1. **Simple API** (items prop) - Quick setup for basic menus\n * 2. **Compound API** (DropdownMenuGlass.*) - Full shadcn/ui pattern for complex menus\n *\n * @example Simple API (recommended for basic dropdowns)\n * ```tsx\n * import { DropdownGlass } from '@/components/glass/ui/dropdown-glass';\n *\n * <DropdownGlass\n * trigger={<button><MoreVertical /></button>}\n * items={[\n * { label: 'Edit', icon: Edit, onClick: handleEdit },\n * { divider: true },\n * { label: 'Delete', icon: Trash, onClick: handleDelete, danger: true }\n * ]}\n * />\n * ```\n *\n * @example Compound API (for complex dropdowns)\n * ```tsx\n * import {\n * DropdownMenuGlass,\n * DropdownMenuGlassTrigger,\n * DropdownMenuGlassContent,\n * DropdownMenuGlassItem,\n * DropdownMenuGlassSeparator,\n * } from '@/components/glass/ui/dropdown-menu-glass';\n *\n * <DropdownMenuGlass>\n * <DropdownMenuGlassTrigger asChild>\n * <Button>Open Menu</Button>\n * </DropdownMenuGlassTrigger>\n * <DropdownMenuGlassContent>\n * <DropdownMenuGlassItem>Edit</DropdownMenuGlassItem>\n * <DropdownMenuGlassSeparator />\n * <DropdownMenuGlassItem variant=\"destructive\">Delete</DropdownMenuGlassItem>\n * </DropdownMenuGlassContent>\n * </DropdownMenuGlass>\n * ```\n *\n * @see ./dropdown-menu-glass.tsx for compound component exports"
77
83
  },
78
84
  {
79
85
  "name": "combobox-glass",
@@ -151,13 +157,13 @@
151
157
  "name": "profile-avatar-glass",
152
158
  "type": "registry:component",
153
159
  "title": "Profile Avatar Glass",
154
- "description": "Profile Avatar Glass component with glass effects"
160
+ "description": "Profile-specific size classes (larger than standard AvatarGlass)"
155
161
  },
156
162
  {
157
163
  "name": "language-bar-glass",
158
164
  "type": "registry:component",
159
165
  "title": "Language Bar Glass",
160
- "description": "Language Bar Glass component with glass effects"
166
+ "description": "Custom color for the language segment."
161
167
  },
162
168
  {
163
169
  "name": "flag-alert-glass",
@@ -325,7 +331,7 @@
325
331
  "name": "sort-dropdown-glass",
326
332
  "type": "registry:component",
327
333
  "title": "Sort Dropdown Glass",
328
- "description": "Atomic component for sorting controls with:"
334
+ "description": "Atomic component for sorting controls built on DropdownMenuGlass primitives.\n *\n * Features:"
329
335
  },
330
336
  {
331
337
  "name": "search-box-glass",
@@ -3,20 +3,19 @@
3
3
  "name": "sort-dropdown-glass",
4
4
  "type": "registry:component",
5
5
  "title": "Sort Dropdown Glass",
6
- "description": "Atomic component for sorting controls with:",
6
+ "description": "Atomic component for sorting controls built on DropdownMenuGlass primitives.\n *\n * Features:",
7
7
  "dependencies": [
8
8
  "lucide-react",
9
9
  "react"
10
10
  ],
11
11
  "registryDependencies": [
12
- "cn",
13
- "variants"
12
+ "cn"
14
13
  ],
15
14
  "files": [
16
15
  {
17
16
  "path": "components/glass/atomic/sort-dropdown-glass.tsx",
18
17
  "type": "registry:component",
19
- "content": "/**\n * SortDropdownGlass Component\n *\n * Atomic component for sorting controls with:\n * - Theme-aware glass styling\n * - Responsive design (compact/full mode)\n * - Sort field selection (commits, stars, name, contribution)\n * - Sort order toggle (asc/desc)\n */\n\nimport { forwardRef, useState, useRef, useEffect, useCallback, useMemo, type CSSProperties } from 'react';\nimport { ChevronDown, ArrowUp, ArrowDown, Check } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { getDropdownContentStyles } from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type SortField = 'commits' | 'stars' | 'name' | 'contribution';\nexport type SortOrder = 'asc' | 'desc';\n\n// ========================================\n// FIELD LABELS\n// ========================================\n\nconst fieldLabels: Record<SortField, string> = {\n commits: 'Commits',\n stars: 'Stars',\n name: 'Name',\n contribution: 'Contribution',\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SortDropdownGlassProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Current sort field */\n readonly sortBy: SortField;\n /** Current sort order */\n readonly sortOrder: SortOrder;\n /** Callback when sort changes */\n readonly onSortChange: (field: SortField, order: SortOrder) => void;\n /** Available sort options (default: all) */\n readonly options?: readonly SortField[];\n /** Compact mode for mobile */\n readonly compact?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SortDropdownGlass = forwardRef<HTMLDivElement, SortDropdownGlassProps>(\n (\n {\n sortBy,\n sortOrder,\n onSortChange,\n options = ['commits', 'stars', 'name', 'contribution'],\n compact = false,\n className,\n ...props\n },\n ref\n ) => {\n const [isOpen, setIsOpen] = useState(false);\n const internalRef = useRef<HTMLDivElement>(null);\n\n // Close on click outside or escape\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (event: MouseEvent): void => {\n if (internalRef.current && !internalRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n const handleEscape = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('keydown', handleEscape);\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('keydown', handleEscape);\n };\n }, [isOpen]);\n\n const handleToggle = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n const handleFieldSelect = useCallback((field: SortField) => {\n if (field === sortBy) {\n // Toggle order if same field\n onSortChange(field, sortOrder === 'asc' ? 'desc' : 'asc');\n } else {\n // New field, default to desc\n onSortChange(field, 'desc');\n }\n setIsOpen(false);\n }, [sortBy, sortOrder, onSortChange]);\n\n const handleKeyDown = (e: React.KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle();\n }\n };\n\n // Styles\n const buttonStyles: CSSProperties = useMemo(() => ({\n background: 'var(--segmented-container-bg)',\n border: '1px solid var(--segmented-container-border)',\n color: 'var(--text-primary)',\n }), []);\n\n const dropdownStyles: CSSProperties = useMemo(() => ({\n ...getDropdownContentStyles(),\n animation: 'dropdownFadeIn 0.2s ease-out',\n }), []);\n\n const SortIcon = sortOrder === 'asc' ? ArrowUp : ArrowDown;\n\n return (\n <div\n ref={(node) => {\n // Handle both refs\n (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n }}\n className={cn('relative inline-block', className)}\n style={{ zIndex: isOpen ? 50000 : 'auto' }}\n {...props}\n >\n {/* Trigger Button */}\n <button\n type=\"button\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n className={cn(\n 'flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-medium transition-all duration-200',\n 'hover:opacity-80 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',\n 'sm:gap-2 sm:px-4 sm:py-2 sm:text-sm'\n )}\n style={buttonStyles}\n aria-expanded={isOpen}\n aria-haspopup=\"listbox\"\n >\n {compact ? (\n <>\n <span>Sort</span>\n <SortIcon className={ICON_SIZES.sm} style={{ color: 'var(--text-accent)' }} />\n </>\n ) : (\n <>\n <span className=\"hidden sm:inline\" style={{ color: 'var(--text-muted)' }}>Sort:</span>\n <span>{fieldLabels[sortBy]}</span>\n <SortIcon className={ICON_SIZES.sm} style={{ color: 'var(--text-accent)' }} />\n <ChevronDown\n className={cn(\n ICON_SIZES.sm,\n 'transition-transform duration-200',\n isOpen && 'rotate-180'\n )}\n style={{ color: 'var(--text-muted)' }}\n />\n </>\n )}\n </button>\n\n {/* Dropdown Menu */}\n {isOpen && (\n <>\n {/* Backdrop */}\n <div\n className=\"fixed inset-0\"\n style={{ zIndex: 50001 }}\n onClick={() => setIsOpen(false)}\n />\n {/* Menu */}\n <div\n className=\"absolute left-0 mt-2 min-w-[140px] py-1.5 rounded-xl overflow-hidden\"\n style={{ ...dropdownStyles, zIndex: 50002 }}\n role=\"listbox\"\n aria-label=\"Sort options\"\n >\n {options.map((field) => {\n const isSelected = field === sortBy;\n return (\n <button\n key={field}\n type=\"button\"\n onClick={() => handleFieldSelect(field)}\n className={cn(\n 'w-full px-3 py-2 text-xs sm:text-sm text-left flex items-center justify-between gap-2',\n 'transition-colors duration-150 hover:bg-white/5'\n )}\n style={{\n color: isSelected ? 'var(--text-accent)' : 'var(--text-primary)',\n background: isSelected ? 'var(--dropdown-item-hover)' : 'transparent',\n }}\n role=\"option\"\n aria-selected={isSelected}\n >\n <span className=\"font-medium\">{fieldLabels[field]}</span>\n {isSelected && (\n <div className=\"flex items-center gap-1\">\n {sortOrder === 'asc' ? (\n <ArrowUp className={ICON_SIZES.sm} />\n ) : (\n <ArrowDown className={ICON_SIZES.sm} />\n )}\n <Check className={ICON_SIZES.sm} />\n </div>\n )}\n </button>\n );\n })}\n </div>\n </>\n )}\n </div>\n );\n }\n);\n\nSortDropdownGlass.displayName = 'SortDropdownGlass';\n"
18
+ "content": "/**\n * SortDropdownGlass Component\n *\n * Atomic component for sorting controls built on DropdownMenuGlass primitives.\n *\n * Features:\n * - Theme-aware glass styling (unified with DropdownMenuGlass)\n * - Responsive design (compact/full mode)\n * - Sort field selection (commits, stars, name, contribution)\n * - Sort order toggle (asc/desc)\n * - Built on shadcn/ui compound component pattern\n *\n * @example\n * ```tsx\n * <SortDropdownGlass\n * sortBy=\"commits\"\n * sortOrder=\"desc\"\n * onSortChange={(field, order) => console.log(field, order)}\n * />\n * ```\n */\n\n'use client';\n\nimport * as React from 'react';\nimport { ChevronDown, ArrowUp, ArrowDown, Check } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport {\n DropdownMenuGlass,\n DropdownMenuGlassTrigger,\n DropdownMenuGlassContent,\n DropdownMenuGlassItem,\n} from '@/components/glass/ui/dropdown-menu-glass';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type SortField = 'commits' | 'stars' | 'name' | 'contribution';\nexport type SortOrder = 'asc' | 'desc';\n\n// ========================================\n// FIELD LABELS\n// ========================================\n\nconst fieldLabels: Record<SortField, string> = {\n commits: 'Commits',\n stars: 'Stars',\n name: 'Name',\n contribution: 'Contribution',\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SortDropdownGlassProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange'\n> {\n /** Current sort field */\n readonly sortBy: SortField;\n /** Current sort order */\n readonly sortOrder: SortOrder;\n /** Callback when sort changes */\n readonly onSortChange: (field: SortField, order: SortOrder) => void;\n /** Available sort options (default: all) */\n readonly options?: readonly SortField[];\n /** Compact mode for mobile */\n readonly compact?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SortDropdownGlass = React.forwardRef<HTMLDivElement, SortDropdownGlassProps>(\n (\n {\n sortBy,\n sortOrder,\n onSortChange,\n options = ['commits', 'stars', 'name', 'contribution'],\n compact = false,\n className,\n ...props\n },\n ref\n ) => {\n const handleFieldSelect = React.useCallback(\n (field: SortField) => {\n if (field === sortBy) {\n // Toggle order if same field\n onSortChange(field, sortOrder === 'asc' ? 'desc' : 'asc');\n } else {\n // New field, default to desc\n onSortChange(field, 'desc');\n }\n },\n [sortBy, sortOrder, onSortChange]\n );\n\n const SortIcon = sortOrder === 'asc' ? ArrowUp : ArrowDown;\n\n return (\n <div ref={ref} className={cn('relative inline-block', className)} {...props}>\n <DropdownMenuGlass>\n <DropdownMenuGlassTrigger asChild>\n <button\n type=\"button\"\n className={cn(\n // Layout\n 'flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-medium',\n 'sm:gap-2 sm:px-4 sm:py-2 sm:text-sm',\n // Glass surface\n 'bg-(--dropdown-bg) border border-(--dropdown-border)',\n 'backdrop-blur-md',\n // Transitions\n 'transition-all duration-200',\n // Hover\n 'hover:opacity-90 hover:shadow-(--dropdown-glow)',\n // Focus\n 'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',\n 'focus-visible:ring-(--text-accent)',\n // Text color\n 'text-(--dropdown-item-text)'\n )}\n aria-haspopup=\"menu\"\n >\n {compact ? (\n <>\n <span>Sort</span>\n <SortIcon className={cn(ICON_SIZES.sm, 'text-(--text-accent)')} />\n </>\n ) : (\n <>\n <span className=\"hidden sm:inline text-(--text-muted)\">Sort:</span>\n <span>{fieldLabels[sortBy]}</span>\n <SortIcon className={cn(ICON_SIZES.sm, 'text-(--text-accent)')} />\n <ChevronDown\n className={cn(\n ICON_SIZES.sm,\n 'text-(--text-muted)',\n 'transition-transform duration-200',\n 'group-data-[state=open]:rotate-180'\n )}\n />\n </>\n )}\n </button>\n </DropdownMenuGlassTrigger>\n\n <DropdownMenuGlassContent align=\"start\" aria-label=\"Sort options\">\n {options.map((field) => {\n const isSelected = field === sortBy;\n return (\n <DropdownMenuGlassItem\n key={field}\n onSelect={() => handleFieldSelect(field)}\n className={cn('justify-between', isSelected && 'bg-(--select-item-selected-bg)')}\n >\n <span className=\"font-medium\">{fieldLabels[field]}</span>\n {isSelected && (\n <div className=\"flex items-center gap-1 text-(--text-accent)\">\n {sortOrder === 'asc' ? (\n <ArrowUp className={ICON_SIZES.sm} />\n ) : (\n <ArrowDown className={ICON_SIZES.sm} />\n )}\n <Check className={ICON_SIZES.sm} />\n </div>\n )}\n </DropdownMenuGlassItem>\n );\n })}\n </DropdownMenuGlassContent>\n </DropdownMenuGlass>\n </div>\n );\n }\n);\n\nSortDropdownGlass.displayName = 'SortDropdownGlass';\n"
20
19
  }
21
20
  ],
22
21
  "categories": [
@@ -5,19 +5,17 @@
5
5
  "title": "Tooltip Glass",
6
6
  "description": "Glass-themed tooltip with:",
7
7
  "dependencies": [
8
- "class-variance-authority",
8
+ "@radix-ui/react-tooltip",
9
9
  "react"
10
10
  ],
11
11
  "registryDependencies": [
12
- "cn",
13
- "use-hover",
14
- "variants"
12
+ "cn"
15
13
  ],
16
14
  "files": [
17
15
  {
18
16
  "path": "components/glass/ui/tooltip-glass.tsx",
19
17
  "type": "registry:component",
20
- "content": "/**\n * TooltipGlass Component\n *\n * Glass-themed tooltip with:\n * - Unified dark design (consistent UX across themes)\n * - Position variants (top/bottom/left/right)\n * - Smooth animation\n */\n\nimport { forwardRef, useId, type ReactNode, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { tooltipPositions, type TooltipPosition } from '@/lib/variants/tooltip-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the TooltipGlass component\n *\n * A glass-themed tooltip with configurable positioning and unified dark design.\n * Features smooth animations and WCAG-compliant accessibility attributes.\n *\n * @accessibility\n * - **Keyboard Navigation:** Tooltip appears on focus for keyboard users (same as hover)\n * - **Focus Management:** Tooltip does not trap focus, allows normal navigation flow\n * - **Screen Readers:** Uses `aria-describedby` to associate tooltip with trigger element (WCAG 4.1.3)\n * - **ARIA Attributes:** Tooltip marked with `role=\"tooltip\"` and unique ID for proper association\n * - **Dismissible:** Tooltip dismisses on mouse leave, focus blur, or Escape key\n * - **Touch Targets:** N/A - tooltips appear on hover/focus, do not require direct interaction\n * - **Color Contrast:** Tooltip text meets WCAG AA contrast ratio 4.5:1 against dark background\n * - **Motion:** Fade-in animation respects `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic tooltip\n * <TooltipGlass content=\"Click to edit\">\n * <button><Edit className=\"w-4 h-4\" /></button>\n * </TooltipGlass>\n *\n * // Different positions\n * <TooltipGlass content=\"Top tooltip\" position=\"top\">\n * <ButtonGlass>Hover me</ButtonGlass>\n * </TooltipGlass>\n * <TooltipGlass content=\"Bottom tooltip\" position=\"bottom\">\n * <ButtonGlass>Hover me</ButtonGlass>\n * </TooltipGlass>\n * <TooltipGlass content=\"Left tooltip\" position=\"left\">\n * <ButtonGlass>Hover me</ButtonGlass>\n * </TooltipGlass>\n * <TooltipGlass content=\"Right tooltip\" position=\"right\">\n * <ButtonGlass>Hover me</ButtonGlass>\n * </TooltipGlass>\n *\n * // Icon button with accessible tooltip (provides label)\n * <TooltipGlass content=\"Delete item\">\n * <ButtonGlass\n * icon={Trash}\n * size=\"icon\"\n * variant=\"ghost\"\n * aria-label=\"Delete item\"\n * />\n * </TooltipGlass>\n *\n * // Informational tooltip on text\n * <TooltipGlass content=\"This feature requires a Pro subscription\">\n * <span className=\"underline decoration-dotted\">Pro Feature</span>\n * </TooltipGlass>\n *\n * // Badge with tooltip for additional context\n * <TooltipGlass content=\"Last updated 2 hours ago\" position=\"top\">\n * <BadgeGlass variant=\"success\" dot>\n * Active\n * </BadgeGlass>\n * </TooltipGlass>\n *\n * // Disabled button with explanation tooltip\n * <TooltipGlass content=\"Save your changes first to enable this action\">\n * <span>\n * <ButtonGlass disabled aria-describedby=\"tooltip-id\">\n * Export\n * </ButtonGlass>\n * </span>\n * </TooltipGlass>\n * ```\n */\nexport interface TooltipGlassProps extends VariantProps<typeof tooltipPositions> {\n readonly children: ReactNode;\n readonly content: string;\n readonly position?: TooltipPosition;\n readonly className?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const TooltipGlass = forwardRef<HTMLDivElement, TooltipGlassProps>(\n ({ children, content, position = 'top', className }, ref) => {\n const { isHovered, hoverProps } = useHover();\n const tooltipId = useId();\n\n // Glass tooltip with same background as modal (oklch(100% 0 0 / 0.06))\n const tooltipStyles: CSSProperties = {\n background: 'var(--tooltip-bg)',\n color: 'var(--tooltip-text)',\n border: '1px solid var(--tooltip-border)',\n boxShadow: 'var(--tooltip-shadow)',\n backdropFilter: 'blur(var(--blur-xl))',\n WebkitBackdropFilter: 'blur(var(--blur-xl))',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative inline-flex', className)}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n aria-describedby={isHovered ? tooltipId : undefined}\n >\n {children}\n {isHovered && (\n <div\n id={tooltipId}\n className={cn(tooltipPositions({ position }))}\n style={tooltipStyles}\n role=\"tooltip\"\n >\n {content}\n </div>\n )}\n </div>\n );\n }\n);\n\nTooltipGlass.displayName = 'TooltipGlass';\n"
18
+ "content": "/**\n * TooltipGlass Component\n *\n * Glass-themed tooltip with:\n * - Unified dark design (consistent UX across themes)\n * - Position variants (top/bottom/left/right)\n * - Smooth animation\n * - Built on Radix UI primitives\n *\n * @example Compound API (recommended)\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlass>\n * <TooltipGlassTrigger asChild>\n * <Button>Hover</Button>\n * </TooltipGlassTrigger>\n * <TooltipGlassContent>\n * <p>Add to library</p>\n * </TooltipGlassContent>\n * </TooltipGlass>\n * </TooltipGlassProvider>\n * ```\n *\n * @example Simple wrapper\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlassSimple content=\"Click to edit\">\n * <button>Edit</button>\n * </TooltipGlassSimple>\n * </TooltipGlassProvider>\n * ```\n */\n\n'use client';\n\nimport * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// COMPOUND COMPONENT: PROVIDER\n// ========================================\n\ntype TooltipGlassProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\nconst TooltipGlassProvider: React.FC<TooltipGlassProviderProps> = ({\n delayDuration = 0,\n ...props\n}) => {\n return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\nconst TooltipGlassRoot = TooltipPrimitive.Root;\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\nconst TooltipGlassTrigger = TooltipPrimitive.Trigger;\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ntype TooltipGlassContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>;\n\nconst TooltipGlassContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n TooltipGlassContentProps\n>(({ className, sideOffset = 4, ...props }, ref) => {\n const tooltipStyles: React.CSSProperties = {\n background: 'var(--tooltip-bg)',\n color: 'var(--tooltip-text)',\n border: '1px solid var(--tooltip-border)',\n boxShadow: 'var(--tooltip-shadow)',\n backdropFilter: 'blur(var(--blur-xl))',\n WebkitBackdropFilter: 'blur(var(--blur-xl))',\n };\n\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs text-balance',\n 'animate-in fade-in-0 zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n className\n )}\n style={tooltipStyles}\n {...props}\n />\n </TooltipPrimitive.Portal>\n );\n});\n\nTooltipGlassContent.displayName = 'TooltipGlassContent';\n\n// ========================================\n// SIMPLE WRAPPER (convenience)\n// ========================================\n\ninterface TooltipGlassSimpleProps {\n content: string;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n className?: string;\n}\n\nconst TooltipGlassSimple: React.FC<TooltipGlassSimpleProps> = ({\n content,\n children,\n side = 'top',\n className,\n}) => {\n return (\n <TooltipGlassRoot>\n <TooltipGlassTrigger asChild>{children}</TooltipGlassTrigger>\n <TooltipGlassContent side={side} className={className}>\n {content}\n </TooltipGlassContent>\n </TooltipGlassRoot>\n );\n};\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const TooltipGlass = TooltipGlassRoot;\nexport { TooltipGlassProvider, TooltipGlassTrigger, TooltipGlassContent, TooltipGlassSimple };\n"
21
19
  }
22
20
  ],
23
21
  "categories": [
@@ -15,7 +15,7 @@
15
15
  {
16
16
  "path": "components/glass/sections/trust-score-card-glass.tsx",
17
17
  "type": "registry:component",
18
- "content": "// ========================================\n// TRUST SCORE CARD GLASS COMPONENT\n// Overall trust score display with metrics\n// ========================================\n\nimport { forwardRef } from 'react';\nimport { Target } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { GlassCard } from '../ui/glass-card';\nimport { RainbowProgressGlass } from '../specialized/rainbow-progress-glass';\nimport { MetricCardGlass, type MetricVariant } from '../composite/metric-card-glass';\nimport '@/glass-theme.css';\n\nexport interface MetricData {\n readonly title: string;\n readonly value: string | number;\n readonly variant: MetricVariant;\n}\n\nexport interface TrustScoreCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly score?: number;\n readonly metrics?: readonly MetricData[];\n}\n\nexport const TrustScoreCardGlass = forwardRef<HTMLDivElement, TrustScoreCardGlassProps>(\n ({ score = 72, metrics = [], className, ...props }, ref) => {\n return (\n <GlassCard\n ref={ref}\n className={cn('p-4 md:p-5', className)}\n intensity=\"strong\"\n glow=\"cyan\"\n hover={false}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3 md:mb-4\">\n <h2\n className=\"font-semibold flex items-center gap-1.5 md:gap-2 text-base md:text-lg\"\n style={{ color: 'var(--text-primary)' }}\n >\n <Target className=\"w-4 h-4 md:w-5 md:h-5\" style={{ color: 'var(--text-accent)' }} />\n Overall Trust Score\n </h2>\n <div className=\"flex items-center gap-1.5 md:gap-2 animate-[score-pulse_2s_ease-in-out_infinite]\">\n <span className=\"text-3xl md:text-4xl font-bold bg-linear-to-r from-amber-400 via-emerald-400 to-cyan-400 bg-clip-text text-transparent\">\n {score}\n </span>\n <span className=\"text-lg md:text-xl\" style={{ color: 'var(--text-muted)' }}>\n / 100\n </span>\n </div>\n </div>\n <RainbowProgressGlass value={score} size=\"lg\" showGlow />\n {metrics.length > 0 && (\n <div className=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 md:gap-4 mt-4 md:mt-5\">\n {metrics.map((m) => (\n <MetricCardGlass key={m.title} title={m.title} value={m.value} variant={m.variant} />\n ))}\n </div>\n )}\n </GlassCard>\n );\n }\n);\n\nTrustScoreCardGlass.displayName = 'TrustScoreCardGlass';\n"
18
+ "content": "// ========================================\n// TRUST SCORE CARD GLASS COMPONENT\n// Overall trust score display with metrics\n// ========================================\n\nimport { forwardRef } from 'react';\nimport { Target } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { GlassCard } from '../ui/glass-card';\nimport { RainbowProgressGlass } from '../specialized/rainbow-progress-glass';\nimport { MetricCardGlass, type MetricVariant } from '../composite/metric-card-glass';\nimport '@/glass-theme.css';\n\nexport interface MetricData {\n readonly title: string;\n readonly value: string | number;\n readonly variant: MetricVariant;\n}\n\nexport interface TrustScoreCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly score?: number;\n readonly metrics?: readonly MetricData[];\n}\n\nexport const TrustScoreCardGlass = forwardRef<HTMLDivElement, TrustScoreCardGlassProps>(\n ({ score = 72, metrics = [], className, ...props }, ref) => {\n return (\n <GlassCard\n ref={ref}\n className={cn('p-4 md:p-5', className)}\n intensity=\"strong\"\n glow=\"cyan\"\n hover={false}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3 md:mb-4\">\n <h2\n className=\"font-semibold flex items-center gap-1.5 md:gap-2 text-base md:text-lg\"\n style={{ color: 'var(--text-primary)' }}\n >\n <Target className=\"w-4 h-4 md:w-5 md:h-5\" style={{ color: 'var(--text-accent)' }} />\n Overall Trust Score\n </h2>\n <div className=\"flex items-center gap-1.5 md:gap-2 animate-[score-pulse_2s_ease-in-out_infinite]\">\n <span\n className=\"text-3xl md:text-4xl font-bold bg-clip-text text-transparent\"\n style={{ backgroundImage: 'var(--score-gradient)' }}\n >\n {score}\n </span>\n <span className=\"text-lg md:text-xl\" style={{ color: 'var(--text-muted)' }}>\n / 100\n </span>\n </div>\n </div>\n <RainbowProgressGlass value={score} size=\"lg\" showGlow />\n {metrics.length > 0 && (\n <div className=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 md:gap-4 mt-4 md:mt-5\">\n {metrics.map((m) => (\n <MetricCardGlass key={m.title} title={m.title} value={m.value} variant={m.variant} />\n ))}\n </div>\n )}\n </GlassCard>\n );\n }\n);\n\nTrustScoreCardGlass.displayName = 'TrustScoreCardGlass';\n"
19
19
  }
20
20
  ],
21
21
  "categories": [