shadcn-glass-ui 2.0.0 → 2.0.2

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.
@@ -4,8 +4,13 @@
4
4
  "type": "registry:block",
5
5
  "title": "Ai Card Glass",
6
6
  "description": "Ai Card Glass component with glass effects",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn"
13
+ ],
9
14
  "files": [
10
15
  {
11
16
  "path": "components/glass/composite/ai-card-glass.tsx",
@@ -13,5 +18,7 @@
13
18
  "content": "// ========================================\n// AI CARD GLASS COMPONENT\n// AI summary card with feature list\n// ========================================\n\nimport { forwardRef } from 'react';\nimport { Sparkles, Check, Zap, Clock } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from '../ui/button-glass';\nimport { InteractiveCard } from '../primitives';\nimport '@/glass-theme.css';\n\nexport interface AICardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly onGenerate?: () => void;\n readonly features?: readonly string[];\n readonly estimatedTime?: string;\n}\n\nconst defaultFeatures: readonly string[] = [\n 'Code quality assessment',\n 'Architecture patterns',\n 'Best practices',\n];\n\nexport const AICardGlass = forwardRef<HTMLDivElement, AICardGlassProps>(\n (\n { onGenerate, features = defaultFeatures, estimatedTime = '~30 seconds', className, ...props },\n ref\n ) => {\n return (\n <InteractiveCard\n ref={ref}\n baseBg=\"var(--ai-card-bg)\"\n borderColor=\"var(--ai-card-border)\"\n hoverGlow=\"var(--ai-card-hover-glow)\"\n hoverLift\n blur=\"sm\"\n rounded=\"rounded-xl\"\n className={cn('w-full sm:w-56 md:w-64 p-3 md:p-4', className)}\n {...props}\n >\n <div\n className=\"flex items-center gap-1.5 md:gap-2 font-semibold text-xs md:text-sm mb-1.5 md:mb-2\"\n style={{ color: 'var(--text-accent)' }}\n >\n <Sparkles className=\"w-3.5 h-3.5 md:w-4 md:h-4\" />\n AI Summary\n </div>\n <p className=\"text-(length:--font-size-2xs) md:text-xs mb-1.5 md:mb-2 text-(--text-secondary)\">\n Get comprehensive analysis:\n </p>\n <ul className=\"text-(length:--font-size-2xs) md:text-xs space-y-0.5 md:space-y-1 mb-2 md:mb-3\">\n {features.map((feature, i) => (\n <li\n key={`feature-${i}`}\n className=\"flex items-center gap-1\"\n style={{ color: 'var(--text-muted)' }}\n >\n <Check\n className=\"w-2.5 h-2.5 md:w-3 md:h-3\"\n style={{ color: 'var(--status-online)' }}\n />\n {feature}\n </li>\n ))}\n </ul>\n <ButtonGlass variant=\"primary\" size=\"sm\" icon={Zap} onClick={onGenerate} className=\"w-full\">\n Generate Report\n </ButtonGlass>\n <p className=\"text-(length:--font-size-2xs) md:text-xs mt-1.5 md:mt-2 text-center flex items-center justify-center gap-1 text-(--text-muted)\">\n <Clock className=\"w-2.5 h-2.5 md:w-3 md:h-3\" />\n {estimatedTime}\n </p>\n </InteractiveCard>\n );\n }\n);\n\nAICardGlass.displayName = 'AICardGlass';\n"
14
19
  }
15
20
  ],
16
- "categories": ["composite"]
17
- }
21
+ "categories": [
22
+ "composite"
23
+ ]
24
+ }
@@ -4,8 +4,16 @@
4
4
  "type": "registry:ui",
5
5
  "title": "Avatar Glass",
6
6
  "description": "Glass-themed avatar with:",
7
- "dependencies": ["@radix-ui/react-slot", "class-variance-authority", "react"],
8
- "registryDependencies": ["cn", "use-hover", "variants"],
7
+ "dependencies": [
8
+ "@radix-ui/react-slot",
9
+ "class-variance-authority",
10
+ "react"
11
+ ],
12
+ "registryDependencies": [
13
+ "cn",
14
+ "use-hover",
15
+ "variants"
16
+ ],
9
17
  "files": [
10
18
  {
11
19
  "path": "components/glass/ui/avatar-glass.tsx",
@@ -13,7 +21,9 @@
13
21
  "content": "/**\n * AvatarGlass Component\n *\n * Glass-themed avatar with:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect on hover\n * - Status indicator with glow\n * - Size variants\n */\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { avatarSizes, statusSizes } from '@/lib/variants/avatar-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\n/**\n * Avatar status indicator type\n */\nexport type AvatarStatus = 'online' | 'offline' | 'busy' | 'away';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the AvatarGlass component\n *\n * A glass-themed avatar component with status indicators and size variants.\n * Displays user initials with theme-aware styling and optional status badge.\n *\n * @example\n * ```tsx\n * // Basic avatar\n * <AvatarGlass name=\"John Doe\" />\n *\n * // With status indicator\n * <AvatarGlass name=\"Jane Smith\" status=\"online\" size=\"lg\" />\n *\n * // Different sizes\n * <AvatarGlass name=\"Alex\" size=\"sm\" />\n * <AvatarGlass name=\"Sam\" size=\"xl\" />\n *\n * // As a link (asChild pattern)\n * <AvatarGlass asChild name=\"Sarah Connor\" status=\"online\">\n * <a href=\"/profile/sarah\">View Profile</a>\n * </AvatarGlass>\n * ```\n */\nexport interface AvatarGlassProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>, VariantProps<typeof avatarSizes> {\n /**\n * Render as child element instead of div (polymorphic rendering).\n * Useful for making avatars clickable links.\n * @default false\n * @example\n * ```tsx\n * <AvatarGlass asChild name=\"John\">\n * <a href=\"/profile\">View Profile</a>\n * </AvatarGlass>\n * ```\n */\n readonly asChild?: boolean;\n\n /**\n * Full name of the user. Automatically generates initials (first 2 letters).\n * @example \"John Doe\" → \"JD\"\n */\n readonly name: string;\n\n /**\n * Optional status indicator with glow effect\n * @default undefined\n */\n readonly status?: AvatarStatus;\n\n /**\n * Size variant of the avatar\n * @default \"md\"\n */\n readonly size?: 'sm' | 'md' | 'lg' | 'xl';\n}\n\n// ========================================\n// HELPERS\n// ========================================\n\nconst getInitials = (name: string): string => {\n if (!name || name.trim().length === 0) return '?';\n return name\n .split(' ')\n .map((part) => part[0])\n .join('')\n .toUpperCase()\n .slice(0, 2);\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\n// Status colors mapping to CSS variables\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\nexport const AvatarGlass = forwardRef<HTMLDivElement, AvatarGlassProps>(\n ({ asChild = false, name, size = 'md', status, className, ...props }, ref) => {\n const { isHovered, hoverProps } = useHover();\n\n const avatarStyles: CSSProperties = {\n background: 'var(--avatar-bg)',\n border: '2px solid var(--avatar-border)',\n boxShadow: isHovered ? 'var(--avatar-hover-glow)' : 'var(--avatar-shadow)',\n color: 'var(--text-inverse)',\n };\n\n const initials = getInitials(name);\n\n // Polymorphic component - render as Slot when asChild is true\n const Comp = asChild ? Slot : 'div';\n\n return (\n <Comp\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(avatarSizes({ size }))}\n style={avatarStyles}\n role=\"img\"\n aria-label={`Avatar for ${name}`}\n >\n {initials}\n </div>\n\n {/* Status indicator */}\n {status && (\n <span\n className={cn(statusSizes({ size }))}\n style={{\n background: getStatusVars(status).bg,\n boxShadow: getStatusVars(status).glow,\n }}\n role=\"status\"\n aria-label={`Status: ${status}`}\n />\n )}\n </Comp>\n );\n }\n);\n\nAvatarGlass.displayName = 'AvatarGlass';\n"
14
22
  }
15
23
  ],
16
- "categories": ["ui"],
24
+ "categories": [
25
+ "ui"
26
+ ],
17
27
  "cssVars": {
18
28
  "light": {
19
29
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +40,4 @@
30
40
  "--blur-lg": "24px"
31
41
  }
32
42
  }
33
- }
43
+ }
@@ -4,8 +4,12 @@
4
4
  "type": "registry:block",
5
5
  "title": "Circular Metric Glass",
6
6
  "description": "CircularMetricGlass - Compact circular progress metric display",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn"
12
+ ],
9
13
  "files": [
10
14
  {
11
15
  "path": "components/glass/composite/circular-metric-glass.tsx",
@@ -13,5 +17,7 @@
13
17
  "content": "// ========================================\n// CIRCULAR METRIC GLASS COMPONENT\n// Compact circular metric display for mobile\n// ========================================\n\nimport { forwardRef } from 'react';\nimport { cn } from '@/lib/utils';\nimport {\n CircularProgressGlass,\n type CircularProgressGradient,\n} from '../ui/circular-progress-glass';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type CircularMetricColor = 'emerald' | 'amber' | 'blue' | 'red';\n\nexport interface CircularMetricGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Metric label (e.g., \"Reg\", \"Imp\") */\n readonly label: string;\n /** Metric value (0-100) */\n readonly value: number;\n /** Metric color */\n readonly color?: CircularMetricColor;\n /** Size variant */\n readonly size?: 'sm' | 'md';\n}\n\n// ========================================\n// HELPERS\n// ========================================\n\n// Map CircularMetricColor to CircularProgressGlass gradient and CSS variable\nconst colorMap: Record<\n CircularMetricColor,\n { gradient: CircularProgressGradient; textVar: string }\n> = {\n emerald: { gradient: 'emerald', textVar: 'var(--metric-success-text)' },\n amber: { gradient: 'amber', textVar: 'var(--metric-warning-text)' },\n blue: { gradient: 'blue', textVar: 'var(--metric-default-text)' },\n red: { gradient: 'rose', textVar: 'var(--metric-destructive-text)' },\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\n/**\n * CircularMetricGlass - Compact circular progress metric display\n *\n * Designed for mobile layouts where rectangular MetricCardGlass is too wide.\n * Shows a circular progress indicator with percentage inside and label below.\n *\n * @example\n * ```tsx\n * <CircularMetricGlass label=\"Reg\" value={84} color=\"emerald\" />\n * ```\n */\nexport const CircularMetricGlass = forwardRef<HTMLDivElement, CircularMetricGlassProps>(\n ({ label, value, color = 'blue', size = 'sm', className, ...props }, ref) => {\n const { gradient, textVar } = colorMap[color];\n\n return (\n <div ref={ref} className={cn('flex flex-col items-center gap-1', className)} {...props}>\n <CircularProgressGlass\n value={value}\n size={size}\n color={gradient}\n labelColor={textVar}\n thickness={size === 'sm' ? 6 : 8}\n showGlow\n glowIntensity=\"medium\"\n />\n <span className=\"text-xs font-medium\" style={{ color: textVar }}>\n {label}\n </span>\n </div>\n );\n }\n);\n\nCircularMetricGlass.displayName = 'CircularMetricGlass';\n"
14
18
  }
15
19
  ],
16
- "categories": ["composite"]
17
- }
20
+ "categories": [
21
+ "composite"
22
+ ]
23
+ }
@@ -4,8 +4,13 @@
4
4
  "type": "registry:ui",
5
5
  "title": "Circular Progress Glass",
6
6
  "description": "SVG-based circular progress indicator with:",
7
- "dependencies": ["class-variance-authority", "react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "class-variance-authority",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn"
13
+ ],
9
14
  "files": [
10
15
  {
11
16
  "path": "components/glass/ui/circular-progress-glass.tsx",
@@ -13,7 +18,9 @@
13
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"
14
19
  }
15
20
  ],
16
- "categories": ["ui"],
21
+ "categories": [
22
+ "ui"
23
+ ],
17
24
  "cssVars": {
18
25
  "light": {
19
26
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +37,4 @@
30
37
  "--blur-lg": "24px"
31
38
  }
32
39
  }
33
- }
40
+ }
@@ -4,8 +4,14 @@
4
4
  "type": "registry:ui",
5
5
  "title": "Combobox Glass",
6
6
  "description": "Glass-themed combobox (searchable select) with:",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn",
13
+ "variants"
14
+ ],
9
15
  "files": [
10
16
  {
11
17
  "path": "components/glass/ui/combobox-glass.tsx",
@@ -13,7 +19,9 @@
13
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"
14
20
  }
15
21
  ],
16
- "categories": ["ui"],
22
+ "categories": [
23
+ "ui"
24
+ ],
17
25
  "cssVars": {
18
26
  "light": {
19
27
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +38,4 @@
30
38
  "--blur-lg": "24px"
31
39
  }
32
40
  }
33
- }
41
+ }
@@ -4,8 +4,12 @@
4
4
  "type": "registry:component",
5
5
  "title": "Flag Alert Glass",
6
6
  "description": "Flag Alert Glass component with glass effects",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn"
12
+ ],
9
13
  "files": [
10
14
  {
11
15
  "path": "components/glass/specialized/flag-alert-glass.tsx",
@@ -13,5 +17,7 @@
13
17
  "content": "// ========================================\n// FLAG ALERT GLASS COMPONENT\n// Individual warning/danger flag alert\n// ========================================\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport { StatusIndicatorGlass, type StatusType } from './status-indicator-glass';\nimport '@/glass-theme.css';\n\nexport type FlagType = 'warning' | 'danger';\n\nexport interface FlagAlertGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly type?: FlagType;\n readonly title: string;\n readonly description?: string;\n}\n\n// CSS variable maps for flag types\nconst flagVarMap: Record<\n FlagType,\n { bg: string; border: string; text: string; statusType: StatusType }\n> = {\n danger: {\n bg: 'var(--alert-danger-bg)',\n border: 'var(--alert-danger-border)',\n text: 'var(--alert-danger-text)',\n statusType: 'red',\n },\n warning: {\n bg: 'var(--alert-warning-bg)',\n border: 'var(--alert-warning-border)',\n text: 'var(--alert-warning-text)',\n statusType: 'yellow',\n },\n};\n\nexport const FlagAlertGlass = forwardRef<HTMLDivElement, FlagAlertGlassProps>(\n ({ type = 'warning', title, description, className, ...props }, ref) => {\n const [isHovered, setIsHovered] = useState(false);\n const config = flagVarMap[type];\n\n const alertStyles: CSSProperties = {\n background: config.bg,\n borderColor: config.border,\n transform: isHovered ? 'translateX(4px)' : 'translateX(0)',\n };\n\n return (\n <div\n ref={ref}\n className={cn('p-2.5 md:p-3 rounded-xl border transition-all duration-300', className)}\n style={alertStyles}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n role=\"alert\"\n {...props}\n >\n <div\n className=\"flex items-center gap-1.5 md:gap-2 font-medium text-xs md:text-sm\"\n style={{ color: config.text }}\n >\n <StatusIndicatorGlass type={config.statusType} />\n {title}\n </div>\n {description && (\n <p className=\"text-(length:--font-size-2xs) md:text-xs mt-0.5 md:mt-1 ml-4 md:ml-5 text-(--text-muted)\">\n {description}\n </p>\n )}\n </div>\n );\n }\n);\n\nFlagAlertGlass.displayName = 'FlagAlertGlass';\n"
14
18
  }
15
19
  ],
16
- "categories": ["specialized"]
17
- }
20
+ "categories": [
21
+ "specialized"
22
+ ]
23
+ }
@@ -4,8 +4,13 @@
4
4
  "type": "registry:block",
5
5
  "title": "Flags Section Glass",
6
6
  "description": "Flags Section Glass component with glass effects",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn"
13
+ ],
9
14
  "files": [
10
15
  {
11
16
  "path": "components/glass/sections/flags-section-glass.tsx",
@@ -13,5 +18,7 @@
13
18
  "content": "// ========================================\n// FLAGS SECTION GLASS COMPONENT\n// Expandable flags/warnings section\n// ========================================\n\nimport { forwardRef } from 'react';\nimport { AlertTriangle, ChevronUp, ChevronDown } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { GlassCard } from '../ui/glass-card';\nimport { FlagAlertGlass, type FlagType } from '../specialized/flag-alert-glass';\nimport '@/glass-theme.css';\n\nexport interface FlagData {\n readonly type: FlagType;\n readonly title: string;\n readonly description?: string;\n}\n\nexport interface FlagsSectionGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly flags?: readonly FlagData[];\n readonly expanded?: boolean;\n readonly onToggle?: () => void;\n}\n\nexport const FlagsSectionGlass = forwardRef<HTMLDivElement, FlagsSectionGlassProps>(\n ({ flags = [], expanded = false, onToggle, className, ...props }, ref) => {\n return (\n <GlassCard\n ref={ref}\n className={cn('w-full max-w-2xl', className)}\n intensity=\"medium\"\n hover={false}\n {...props}\n >\n <button\n onClick={onToggle}\n className=\"w-full p-3 md:p-4 flex items-center justify-between rounded-2xl\"\n style={{ color: 'var(--text-primary)' }}\n type=\"button\"\n aria-expanded={expanded}\n >\n <div className=\"flex items-center gap-1.5 md:gap-2\">\n <AlertTriangle\n className=\"w-4 h-4 md:w-5 md:h-5\"\n style={{ color: 'var(--status-away)' }}\n />\n <span className=\"font-medium text-sm md:text-base\">{flags.length} flags detected</span>\n </div>\n {expanded ? (\n <ChevronUp className=\"w-4 h-4 md:w-5 md:h-5\" style={{ color: 'var(--text-muted)' }} />\n ) : (\n <ChevronDown className=\"w-4 h-4 md:w-5 md:h-5\" style={{ color: 'var(--text-muted)' }} />\n )}\n </button>\n {expanded && (\n <div className=\"px-3 pb-3 md:px-4 md:pb-4 space-y-1.5 md:space-y-2\">\n {flags.map((flag, i) => (\n <FlagAlertGlass\n key={`flag-${i}`}\n type={flag.type}\n title={flag.title}\n description={flag.description}\n />\n ))}\n </div>\n )}\n </GlassCard>\n );\n }\n);\n\nFlagsSectionGlass.displayName = 'FlagsSectionGlass';\n"
14
19
  }
15
20
  ],
16
- "categories": ["sections"]
17
- }
21
+ "categories": [
22
+ "sections"
23
+ ]
24
+ }
@@ -4,8 +4,15 @@
4
4
  "type": "registry:component",
5
5
  "title": "Insight Card Glass",
6
6
  "description": "Insight Card Glass component with glass effects",
7
- "dependencies": ["class-variance-authority", "lucide-react", "react"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "class-variance-authority",
9
+ "lucide-react",
10
+ "react"
11
+ ],
12
+ "registryDependencies": [
13
+ "cn",
14
+ "variants"
15
+ ],
9
16
  "files": [
10
17
  {
11
18
  "path": "components/glass/atomic/insight-card-glass.tsx",
@@ -13,5 +20,7 @@
13
20
  "content": "import { forwardRef, type CSSProperties, type KeyboardEvent } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { ChevronRight } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport {\n insightCardVariants,\n insightVariantConfig,\n type InsightVariant,\n} from '@/lib/variants/insight-card-glass-variants';\nimport '@/glass-theme.css';\n\nexport interface InsightCardGlassProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>,\n VariantProps<typeof insightCardVariants> {\n /** Emoji icon for the insight */\n readonly emoji?: string;\n /** Main insight text */\n readonly text: string;\n /** Additional details */\n readonly detail?: string;\n /** Insight type variant */\n readonly variant?: InsightVariant;\n /** Inline display mode */\n readonly inline?: boolean;\n /** Click handler (makes the insight clickable) */\n readonly onClick?: () => void;\n /** Show arrow indicator */\n readonly showArrow?: boolean;\n /** Fade-in animation */\n readonly animated?: boolean;\n}\n\nexport const InsightCardGlass = forwardRef<HTMLDivElement, InsightCardGlassProps>(\n (\n {\n emoji,\n text,\n detail,\n variant = 'default',\n inline = false,\n onClick,\n showArrow = false,\n animated = false,\n className,\n ...props\n },\n ref\n ) => {\n const config = insightVariantConfig[variant];\n const displayEmoji = emoji ?? config.defaultEmoji;\n const isClickable = !!onClick;\n\n const handleClick = () => onClick?.();\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (isClickable && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick?.();\n }\n };\n\n const borderStyle: CSSProperties = !inline\n ? {\n borderColor: `var(${config.borderVar})`,\n }\n : {};\n\n const glowStyle: CSSProperties =\n isClickable && config.glowVar\n ? ({\n '--hover-glow': `var(${config.glowVar})`,\n } as CSSProperties)\n : {};\n\n // Inline variant\n if (inline) {\n return (\n <span\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm text-[var(--text-secondary)]',\n className\n )}\n {...props}\n >\n <span aria-hidden=\"true\">{displayEmoji}</span>\n <span>{text}</span>\n {detail && <span className=\"text-[var(--text-muted)]\">({detail})</span>}\n </span>\n );\n }\n\n // Card variant\n return (\n <div\n ref={ref}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onClick={isClickable ? handleClick : undefined}\n onKeyDown={isClickable ? handleKeyDown : undefined}\n className={cn(\n insightCardVariants({ inline, clickable: isClickable }),\n isClickable && config.glowVar && 'hover:shadow-[0_0_12px_var(--hover-glow)]',\n animated && 'animate-insight-fade-in',\n className\n )}\n style={{ ...borderStyle, ...glowStyle }}\n {...props}\n >\n <div className=\"flex items-start gap-2\">\n <span className=\"text-lg flex-shrink-0\" aria-hidden=\"true\">\n {displayEmoji}\n </span>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm text-[var(--text-primary)]\">{text}</p>\n {detail && <p className=\"text-xs text-[var(--text-muted)] mt-0.5\">{detail}</p>}\n </div>\n {showArrow && (\n <ChevronRight\n className=\"w-4 h-4 text-[var(--text-muted)] flex-shrink-0\"\n aria-hidden=\"true\"\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nInsightCardGlass.displayName = 'InsightCardGlass';\n"
14
21
  }
15
22
  ],
16
- "categories": ["atomic"]
17
- }
23
+ "categories": [
24
+ "atomic"
25
+ ]
26
+ }
@@ -4,8 +4,13 @@
4
4
  "type": "registry:lib",
5
5
  "title": "Interactive Card",
6
6
  "description": "Unified wrapper for card components with hover animations and glass effects.\n * Eliminates hover state duplication in MetricCardGlass, YearCardGlass,\n * AICardGlass, RepositoryCardGlass, and other card components.\n *\n * Features:",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn", "use-hover"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn",
12
+ "use-hover"
13
+ ],
9
14
  "files": [
10
15
  {
11
16
  "path": "components/glass/primitives/interactive-card.tsx",
@@ -13,5 +18,7 @@
13
18
  "content": "/**\n * InteractiveCard Component\n *\n * Unified wrapper for card components with hover animations and glass effects.\n * Eliminates hover state duplication in MetricCardGlass, YearCardGlass,\n * AICardGlass, RepositoryCardGlass, and other card components.\n *\n * Features:\n * - Hover lift animation (translateY -2px)\n * - Optional glow effects\n * - Glass surface with backdrop blur\n * - Configurable backgrounds and borders\n */\n\nimport { forwardRef, type HTMLAttributes, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\n\n/**\n * Props for the InteractiveCard component\n */\nexport interface InteractiveCardProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Enable hover lift effect (translateY -2px)\n * @default true\n */\n hoverLift?: boolean;\n\n /**\n * CSS variable for hover glow effect\n * @example 'var(--glow-primary)'\n */\n hoverGlow?: string;\n\n /**\n * CSS variable for hover background\n * @example 'var(--card-hover-bg)'\n */\n hoverBg?: string;\n\n /**\n * CSS variable for base background\n * @default 'var(--card-bg)'\n */\n baseBg?: string;\n\n /**\n * CSS variable for border color\n * @default 'var(--card-border)'\n */\n borderColor?: string;\n\n /**\n * CSS variable for hover border color\n */\n hoverBorderColor?: string;\n\n /**\n * Blur level for glass effect\n * @default 'sm'\n */\n blur?: 'sm' | 'md' | 'lg' | 'xl';\n\n /**\n * Disable all hover interactions\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Border radius class\n * @default 'rounded-2xl'\n */\n rounded?: 'rounded-xl' | 'rounded-2xl' | 'rounded-3xl';\n\n /**\n * Transition speed\n * @default 'var(--transition-slow)'\n */\n transition?: string;\n}\n\n/**\n * InteractiveCard component\n *\n * Provides consistent hover animations and glass effects for card components.\n * Replaces ~80 lines of duplicated hover state management across 4+ components.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <InteractiveCard>\n * <h3>Card Title</h3>\n * <p>Card content</p>\n * </InteractiveCard>\n *\n * // With hover effects\n * <InteractiveCard\n * hoverLift\n * hoverGlow=\"var(--glow-primary)\"\n * hoverBg=\"var(--card-hover-bg)\"\n * hoverBorderColor=\"var(--card-hover-border)\"\n * >\n * <MetricContent />\n * </InteractiveCard>\n *\n * // Custom blur and rounding\n * <InteractiveCard\n * blur=\"md\"\n * rounded=\"rounded-3xl\"\n * baseBg=\"var(--metric-success-bg)\"\n * >\n * <StatusCard />\n * </InteractiveCard>\n * ```\n */\nexport const InteractiveCard = forwardRef<HTMLDivElement, InteractiveCardProps>(\n (\n {\n hoverLift = true,\n hoverGlow,\n hoverBg,\n baseBg = 'var(--card-bg)',\n borderColor = 'var(--card-border)',\n hoverBorderColor,\n blur = 'sm',\n disabled = false,\n rounded = 'rounded-2xl',\n transition = 'var(--transition-slow)',\n className,\n style,\n children,\n ...props\n },\n ref\n ) => {\n const { isHovered, hoverProps } = useHover({ includeFocus: !disabled });\n\n const cardStyles: CSSProperties = {\n // Background\n background: isHovered && hoverBg ? hoverBg : baseBg,\n\n // Border\n border: `1px solid ${isHovered && hoverBorderColor ? hoverBorderColor : borderColor}`,\n\n // Glassmorphism\n backdropFilter: `blur(var(--blur-${blur}))`,\n WebkitBackdropFilter: `blur(var(--blur-${blur}))`,\n\n // Hover transform\n transform: hoverLift && isHovered && !disabled ? 'translateY(-2px)' : 'translateY(0)',\n\n // Glow effect\n boxShadow: isHovered && hoverGlow && !disabled ? hoverGlow : 'none',\n\n // Transition\n transition: `all ${transition}`,\n\n // User styles override\n ...style,\n };\n\n return (\n <div\n ref={ref}\n className={cn(rounded, className)}\n style={cardStyles}\n {...(disabled ? {} : hoverProps)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nInteractiveCard.displayName = 'InteractiveCard';\n"
14
19
  }
15
20
  ],
16
- "categories": ["primitives"]
17
- }
21
+ "categories": [
22
+ "primitives"
23
+ ]
24
+ }
@@ -4,8 +4,12 @@
4
4
  "type": "registry:component",
5
5
  "title": "Language Bar Glass",
6
6
  "description": "Language Bar Glass component with glass effects",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn"
12
+ ],
9
13
  "files": [
10
14
  {
11
15
  "path": "components/glass/specialized/language-bar-glass.tsx",
@@ -13,5 +17,7 @@
13
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"
14
18
  }
15
19
  ],
16
- "categories": ["specialized"]
17
- }
20
+ "categories": [
21
+ "specialized"
22
+ ]
23
+ }
@@ -4,8 +4,14 @@
4
4
  "type": "registry:block",
5
5
  "title": "Metric Card Glass",
6
6
  "description": "Metric variant system (following AlertGlass, BadgeGlass pattern)",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn",
13
+ "variants"
14
+ ],
9
15
  "files": [
10
16
  {
11
17
  "path": "components/glass/composite/metric-card-glass.tsx",
@@ -13,5 +19,7 @@
13
19
  "content": "// ========================================\n// METRIC CARD GLASS COMPONENT\n// Metric display card with progress, sparkline, and trend\n// Domain-specific composite component following shadcn/ui patterns\n// ========================================\n\nimport { forwardRef, type CSSProperties, type ReactNode } from 'react';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ProgressGlass } from '../specialized/progress-glass';\nimport { SparklineGlass } from '../specialized/sparkline-glass';\nimport { InteractiveCard } from '../primitives';\nimport '@/glass-theme.css';\n\nimport type { ProgressGradient } from '@/lib/variants/progress-glass-variants';\n\n// ========================================\n// TYPES\n// ========================================\n\n/**\n * Metric variant system (following AlertGlass, BadgeGlass pattern)\n * - default: Blue (primary metric)\n * - secondary: Gray (neutral metric)\n * - success: Green (positive metric)\n * - warning: Yellow (caution metric)\n * - destructive: Red (negative metric)\n */\nexport type MetricVariant =\n | 'default' // shadcn/ui base (blue)\n | 'secondary' // shadcn/ui base (gray)\n | 'success' // Glass UI extension (green)\n | 'warning' // Glass UI extension (yellow)\n | 'destructive'; // shadcn/ui base (red)\n\n/** @deprecated Use MetricVariant instead */\nexport type MetricColor = 'emerald' | 'amber' | 'blue' | 'red';\n\nexport type TrendDirection = 'up' | 'down' | 'neutral';\n\n/**\n * Detailed change object with trend information\n */\nexport interface MetricChange {\n /** Change value (e.g., 12.5 for +12.5%) */\n readonly value: number;\n /** Trend direction (auto-detected from value if not provided) */\n readonly direction?: TrendDirection;\n /** Optional period label (e.g., \"vs last month\") */\n readonly period?: string;\n}\n\n/** @deprecated Use MetricChange instead */\nexport interface MetricTrend {\n readonly value: number;\n readonly direction: TrendDirection;\n readonly label?: string;\n}\n\n/**\n * MetricCardGlass Props\n *\n * Follows shadcn/ui Card pattern with Glass UI extensions.\n * Compatible with AlertGlass, BadgeGlass, ButtonGlass variant system.\n *\n * @example Simple usage (shadcn/ui style)\n * ```tsx\n * <MetricCardGlass\n * title=\"Total Revenue\"\n * value=\"$45,231\"\n * change=\"+12.5%\"\n * variant=\"success\"\n * icon={<DollarSign />}\n * />\n * ```\n *\n * @example With progress and sparkline (Glass UI extensions)\n * ```tsx\n * <MetricCardGlass\n * title=\"Completion Rate\"\n * value=\"85%\"\n * description=\"Project milestones\"\n * change={{ value: 5.2, direction: 'up', period: 'vs last month' }}\n * variant=\"success\"\n * progress={85}\n * sparklineData={[70, 75, 78, 80, 82, 84, 85]}\n * showProgress\n * showSparkline\n * />\n * ```\n */\nexport interface MetricCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n // ========================================\n // CORE PROPS (shadcn/ui compatible)\n // ========================================\n\n /** Metric title (shadcn/ui Card: title) */\n readonly title: string;\n\n /** Display value (shadcn/ui Card: value) */\n readonly value: string | number;\n\n /** Optional description/subtitle (shadcn/ui Card: description) */\n readonly description?: string;\n\n /** Change indicator (shadcn/ui: change). Can be string \"+12.5%\" or detailed object */\n readonly change?: string | number | MetricChange;\n\n /** Semantic variant (follows AlertGlass, BadgeGlass pattern) */\n readonly variant?: MetricVariant;\n\n /** Icon to display */\n readonly icon?: ReactNode;\n\n // ========================================\n // GLASS UI EXTENSIONS\n // ========================================\n\n /** Data for sparkline visualization */\n readonly sparklineData?: readonly number[];\n\n /** Show sparkline chart */\n readonly showSparkline?: boolean;\n\n /** Show progress bar (requires progress prop) */\n readonly showProgress?: boolean;\n\n /** Progress percentage (0-100, separate from display value) */\n readonly progress?: number;\n\n // ========================================\n // DEPRECATED (backward compatibility)\n // ========================================\n\n /** @deprecated Use `title` instead. Will be removed in v2.0 */\n readonly label?: string;\n\n /** @deprecated Use `variant` instead. Mapping: emerald→success, amber→warning, blue→default, red→destructive. Will be removed in v2.0 */\n readonly color?: MetricColor;\n\n /** @deprecated Format value before passing. Use `value` prop directly. Will be removed in v2.0 */\n readonly valueFormatter?: (value: number) => string;\n\n /** @deprecated Use `description` instead. Will be removed in v2.0 */\n readonly valueSuffix?: string;\n\n /** @deprecated Use `change` instead. Will be removed in v2.0 */\n readonly trend?: MetricTrend;\n}\n\n// ========================================\n// VARIANT SYSTEM (following AlertGlass, BadgeGlass pattern)\n// ========================================\n\ntype VariantStyle = { bg: string; text: string; border: string; glow: string };\n\n// New variant-based system (shadcn/ui compatible)\nconst variantStyles: Record<MetricVariant, VariantStyle> = {\n default: {\n bg: 'var(--metric-default-bg)',\n text: 'var(--metric-default-text)',\n border: 'var(--metric-default-border)',\n glow: 'var(--metric-default-glow)',\n },\n secondary: {\n bg: 'var(--metric-secondary-bg)',\n text: 'var(--metric-secondary-text)',\n border: 'var(--metric-secondary-border)',\n glow: 'var(--metric-secondary-glow)',\n },\n success: {\n bg: 'var(--metric-success-bg)',\n text: 'var(--metric-success-text)',\n border: 'var(--metric-success-border)',\n glow: 'var(--metric-success-glow)',\n },\n warning: {\n bg: 'var(--metric-warning-bg)',\n text: 'var(--metric-warning-text)',\n border: 'var(--metric-warning-border)',\n glow: 'var(--metric-warning-glow)',\n },\n destructive: {\n bg: 'var(--metric-destructive-bg)',\n text: 'var(--metric-destructive-text)',\n border: 'var(--metric-destructive-border)',\n glow: 'var(--metric-destructive-glow)',\n },\n};\n\n// Map MetricVariant to ProgressGradient\nconst variantToGradient: Record<MetricVariant, ProgressGradient> = {\n default: 'blue',\n secondary: 'cyan',\n success: 'emerald',\n warning: 'amber',\n destructive: 'rose',\n};\n\n// ========================================\n// DEPRECATED: Old color system (backward compatibility)\n// ========================================\n\n/** @deprecated Use variantStyles instead */\nconst colorToVariant: Record<MetricColor, MetricVariant> = {\n emerald: 'success',\n amber: 'warning',\n blue: 'default',\n red: 'destructive',\n};\n\n// Trend direction colors - using existing alert CSS variables\nconst trendColors: Record<TrendDirection, string> = {\n up: 'text-[var(--alert-success-text)]',\n down: 'text-[var(--alert-destructive-text)]',\n neutral: 'text-[var(--text-muted)]',\n};\n\n// Trend icons\nconst TrendIcons: Record<TrendDirection, typeof TrendingUp> = {\n up: TrendingUp,\n down: TrendingDown,\n neutral: Minus,\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const MetricCardGlass = forwardRef<HTMLDivElement, MetricCardGlassProps>(\n (\n {\n // New API\n title,\n value,\n description,\n change,\n variant,\n progress,\n // Deprecated API (backward compatibility)\n label,\n color,\n valueFormatter,\n valueSuffix,\n trend,\n // Common props\n icon,\n sparklineData,\n showSparkline = true,\n showProgress = true,\n className,\n ...props\n },\n ref\n ) => {\n // ========================================\n // BACKWARD COMPATIBILITY LAYER\n // ========================================\n\n // Support old `label` prop\n const actualTitle = title || label;\n if (!actualTitle) {\n console.warn('[MetricCardGlass] Missing required prop: `title` (or deprecated `label`)');\n }\n if (label && !title) {\n console.warn(\n '[MetricCardGlass] Deprecated prop `label` used. Please use `title` instead. Will be removed in v2.0'\n );\n }\n\n // Support old `color` prop → `variant`\n const actualVariant: MetricVariant = variant || (color ? colorToVariant[color] : 'default');\n if (color && !variant) {\n console.warn(\n `[MetricCardGlass] Deprecated prop \\`color=\"${color}\"\\` used. Please use \\`variant=\"${colorToVariant[color]}\"\\` instead. Will be removed in v2.0`\n );\n }\n\n // Support old `valueSuffix` → `description`\n const actualDescription = description || valueSuffix;\n if (valueSuffix && !description) {\n console.warn(\n '[MetricCardGlass] Deprecated prop `valueSuffix` used. Please use `description` instead. Will be removed in v2.0'\n );\n }\n\n // Support old `trend` → `change`\n const actualChange =\n change ||\n (trend\n ? {\n value: trend.value,\n direction: trend.direction,\n period: trend.label,\n }\n : undefined);\n if (trend && !change) {\n console.warn(\n '[MetricCardGlass] Deprecated prop `trend` used. Please use `change` instead. Will be removed in v2.0'\n );\n }\n\n // Support old `valueFormatter`\n const displayValue =\n typeof value === 'number' && valueFormatter ? valueFormatter(value) : String(value);\n if (valueFormatter) {\n console.warn(\n '[MetricCardGlass] Deprecated prop `valueFormatter` used. Please format value before passing. Will be removed in v2.0'\n );\n }\n\n // Get actual progress value (use prop or infer from value if it's 0-100)\n const actualProgress =\n progress ?? (typeof value === 'number' && value >= 0 && value <= 100 ? value : undefined);\n\n // ========================================\n // COMPONENT LOGIC\n // ========================================\n\n const variantVars = variantStyles[actualVariant];\n const hasSparkline = showSparkline && sparklineData && sparklineData.length > 0;\n\n const valueStyles: CSSProperties = {\n color: variantVars.text,\n textShadow: variantVars.glow,\n };\n\n // Parse and render change indicator\n const renderChange = () => {\n if (!actualChange) return null;\n\n // Handle simple string or number\n if (typeof actualChange === 'string' || typeof actualChange === 'number') {\n const changeStr = String(actualChange);\n const isPositive =\n changeStr.startsWith('+') || (!changeStr.startsWith('-') && parseFloat(changeStr) > 0);\n const isNegative = changeStr.startsWith('-') || parseFloat(changeStr) < 0;\n const direction: TrendDirection = isPositive ? 'up' : isNegative ? 'down' : 'neutral';\n const TrendIcon = TrendIcons[direction];\n\n return (\n <div className={cn('flex items-center gap-1 text-xs', trendColors[direction])}>\n <TrendIcon className=\"w-3 h-3\" aria-hidden=\"true\" />\n <span className=\"font-medium\">{changeStr}</span>\n </div>\n );\n }\n\n // Handle detailed MetricChange object\n const changeValue = actualChange.value;\n const direction =\n actualChange.direction || (changeValue > 0 ? 'up' : changeValue < 0 ? 'down' : 'neutral');\n const TrendIcon = TrendIcons[direction];\n const displayChange = direction === 'down' ? `-${Math.abs(changeValue)}` : `+${changeValue}`;\n\n return (\n <div className={cn('flex items-center gap-1 text-xs', trendColors[direction])}>\n <TrendIcon className=\"w-3 h-3\" aria-hidden=\"true\" />\n <span className=\"font-medium\">{displayChange}%</span>\n {actualChange.period && (\n <span className=\"text-[var(--text-muted)] ml-0.5\">{actualChange.period}</span>\n )}\n </div>\n );\n };\n\n return (\n <InteractiveCard\n ref={ref}\n baseBg={variantVars.bg}\n borderColor={variantVars.border}\n hoverGlow={variantVars.glow}\n hoverLift\n blur=\"sm\"\n rounded=\"rounded-xl\"\n className={cn('p-3 md:p-4', className)}\n {...props}\n >\n {/* Header with icon and change indicator */}\n <div className=\"flex items-start justify-between mb-2\">\n <div className=\"flex items-center gap-2\">\n {icon && (\n <div className=\"text-(--text-muted)\" aria-hidden=\"true\">\n {icon}\n </div>\n )}\n <span className=\"text-(length:--font-size-2xs) sm:text-xs md:text-sm font-medium truncate text-(--text-secondary)\">\n {actualTitle}\n </span>\n </div>\n {renderChange()}\n </div>\n\n {/* Value display */}\n <div className=\"flex flex-col items-center mb-2 md:mb-3 gap-1\">\n <span\n className=\"font-bold text-lg sm:text-xl md:text-2xl whitespace-nowrap\"\n style={valueStyles}\n >\n {displayValue}\n </span>\n {actualDescription && (\n <span className=\"text-(length:--font-size-2xs) text-(--text-muted)\">\n {actualDescription}\n </span>\n )}\n </div>\n\n {/* Progress and Sparkline */}\n {hasSparkline ? (\n <div className=\"space-y-2\">\n {showProgress && actualProgress !== undefined && (\n <ProgressGlass\n value={actualProgress}\n gradient={variantToGradient[actualVariant]}\n size=\"sm\"\n />\n )}\n <SparklineGlass\n data={sparklineData}\n height=\"sm\"\n gap=\"sm\"\n className=\"w-full\"\n barColor={variantVars.text}\n highlightMax\n aria-label={`${actualTitle} trend`}\n />\n </div>\n ) : showProgress && actualProgress !== undefined ? (\n <ProgressGlass\n value={actualProgress}\n gradient={variantToGradient[actualVariant]}\n size=\"sm\"\n />\n ) : null}\n </InteractiveCard>\n );\n }\n);\n\nMetricCardGlass.displayName = 'MetricCardGlass';\n"
14
20
  }
15
21
  ],
16
- "categories": ["composite"]
17
- }
22
+ "categories": [
23
+ "composite"
24
+ ]
25
+ }
@@ -4,8 +4,12 @@
4
4
  "type": "registry:block",
5
5
  "title": "Metrics Grid Glass",
6
6
  "description": "Metrics Grid Glass component with glass effects",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn"
12
+ ],
9
13
  "files": [
10
14
  {
11
15
  "path": "components/glass/composite/metrics-grid-glass.tsx",
@@ -13,5 +17,7 @@
13
17
  "content": "// ========================================\n// METRICS GRID GLASS - COMPOSITE COMPONENT\n// Responsive grid of metric cards\n// Level 3: Composite (extracted from TrustScoreCardGlass)\n// ========================================\n\nimport { forwardRef, type HTMLAttributes } from 'react';\nimport { cn } from '@/lib/utils';\nimport { MetricCardGlass, type MetricVariant } from './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 MetricsGridGlassProps extends HTMLAttributes<HTMLDivElement> {\n /** Array of metrics to display */\n readonly metrics: readonly MetricData[];\n /** Number of columns on desktop (1-6) */\n readonly columns?: 1 | 2 | 3 | 4 | 5 | 6;\n /** Gap size */\n readonly gap?: 'sm' | 'md' | 'lg';\n}\n\nexport const MetricsGridGlass = forwardRef<HTMLDivElement, MetricsGridGlassProps>(\n ({ metrics, columns = 4, gap = 'md', className, ...props }, ref) => {\n const gapClasses = {\n sm: 'gap-2',\n md: 'gap-4',\n lg: 'gap-6',\n };\n\n const columnClasses = {\n 1: 'grid-cols-1',\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',\n 4: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',\n 5: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-5',\n 6: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-6',\n };\n\n if (metrics.length === 0) {\n return null;\n }\n\n return (\n <div\n ref={ref}\n className={cn('grid', columnClasses[columns], gapClasses[gap], className)}\n {...props}\n >\n {metrics.map((metric) => (\n <MetricCardGlass\n key={metric.title}\n title={metric.title}\n value={metric.value}\n variant={metric.variant}\n />\n ))}\n </div>\n );\n }\n);\n\nMetricsGridGlass.displayName = 'MetricsGridGlass';\n"
14
18
  }
15
19
  ],
16
- "categories": ["composite"]
17
- }
20
+ "categories": [
21
+ "composite"
22
+ ]
23
+ }
@@ -4,8 +4,16 @@
4
4
  "type": "registry:ui",
5
5
  "title": "Modal Glass",
6
6
  "description": "ModalGlass Component (Compound API only)",
7
- "dependencies": ["lucide-react"],
8
- "registryDependencies": ["cn", "primitives", "use-focus", "use-hover", "variants"],
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn",
12
+ "primitives",
13
+ "use-focus",
14
+ "use-hover",
15
+ "variants"
16
+ ],
9
17
  "files": [
10
18
  {
11
19
  "path": "components/glass/ui/modal-glass.tsx",
@@ -13,7 +21,9 @@
13
21
  "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * ModalGlass Component (Compound API only)\n *\n * Glass-themed modal with:\n * - Theme-aware styling (glass/light/aurora)\n * - Smooth open/close animations\n * - Escape key to close\n * - Click outside to close\n * - Body scroll lock\n * - Size variants\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Overlay />\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Confirm</ModalGlass.Title>\n * <ModalGlass.Description>Are you sure?</ModalGlass.Description>\n * <ModalGlass.Close />\n * </ModalGlass.Header>\n * <ModalGlass.Body>\n * <p>Body content</p>\n * </ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass onClick={() => setOpen(false)}>Cancel</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (isOpen/onClose/title props)\n */\n\nimport React, {\n useState,\n useEffect,\n useCallback,\n useMemo,\n forwardRef,\n createContext,\n useContext,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { modalSizes, type ModalSize } from '@/lib/variants/modal-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES & CONSTANTS\n// ========================================\n\nconst MODAL_ANIMATION_DURATION = 200;\n\n// ========================================\n// HELPERS\n// ========================================\n\nconst lockBodyScroll = (): void => {\n if (typeof document === 'undefined') return;\n document.body.style.overflow = 'hidden';\n};\n\nconst unlockBodyScroll = (): void => {\n if (typeof document === 'undefined') return;\n document.body.style.overflow = '';\n};\n\nconst delay = (ms: number): Promise<void> => {\n return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\n// ========================================\n// CONTEXT FOR COMPOUND COMPONENTS\n// ========================================\n\ninterface ModalContextValue {\n isOpen: boolean;\n onClose: () => void;\n size: ModalSize;\n isClosing: boolean;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\nconst useModalContext = () => {\n const context = useContext(ModalContext);\n if (!context) {\n throw new Error('Modal compound components must be used within ModalGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\n/**\n * Props for ModalGlass.Root component\n *\n * Root component that provides context and manages open/close state for the modal.\n * Handles keyboard events, body scroll lock, and accessibility attributes.\n *\n * @accessibility\n * - **Keyboard Navigation:** Escape key closes modal, Tab key traps focus within modal content\n * - **Focus Management:** Focus automatically moved to modal on open, returned to trigger on close (WCAG 2.4.3)\n * - **Screen Readers:** Uses `role=\"dialog\"` and `aria-modal=\"true\"` for proper modal semantics (WCAG 4.1.3)\n * - **Title Association:** Modal title automatically linked via `aria-labelledby=\"modal-title\"`\n * - **Description Association:** Optional description linked via `aria-describedby=\"modal-description\"`\n * - **Body Scroll Lock:** Prevents background scrolling when modal is open (improves UX and focus management)\n * - **Touch Targets:** All interactive elements (close button, action buttons) meet 44x44px minimum (WCAG 2.5.5)\n * - **Color Contrast:** Modal content and overlay meet WCAG AA contrast requirements\n * - **Motion:** Open/close animations respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic modal with title and description\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Overlay />\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Confirm Action</ModalGlass.Title>\n * <ModalGlass.Description>\n * This action cannot be undone.\n * </ModalGlass.Description>\n * <ModalGlass.Close />\n * </ModalGlass.Header>\n * <ModalGlass.Body>\n * <p>Are you sure you want to proceed?</p>\n * </ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass variant=\"ghost\" onClick={() => setOpen(false)}>\n * Cancel\n * </ButtonGlass>\n * <ButtonGlass variant=\"destructive\" onClick={handleConfirm}>\n * Confirm\n * </ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n *\n * // Different sizes\n * <ModalGlass.Root open={open} onOpenChange={setOpen} size=\"sm\">\n * {// Small modal content}\n * </ModalGlass.Root>\n * <ModalGlass.Root open={open} onOpenChange={setOpen} size=\"lg\">\n * {// Large modal content}\n * </ModalGlass.Root>\n *\n * // Form modal with proper focus management\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Overlay />\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Create Account</ModalGlass.Title>\n * <ModalGlass.Close />\n * </ModalGlass.Header>\n * <ModalGlass.Body>\n * <form id=\"signup-form\" onSubmit={handleSubmit}>\n * <InputGlass label=\"Email\" type=\"email\" required />\n * <InputGlass label=\"Password\" type=\"password\" required />\n * </form>\n * </ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass variant=\"ghost\" onClick={() => setOpen(false)}>\n * Cancel\n * </ButtonGlass>\n * <ButtonGlass type=\"submit\" form=\"signup-form\">\n * Sign Up\n * </ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n *\n * // Alert modal (no close button)\n * <ModalGlass.Root open={showAlert} onOpenChange={setShowAlert}>\n * <ModalGlass.Overlay />\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Session Expired</ModalGlass.Title>\n * </ModalGlass.Header>\n * <ModalGlass.Body>\n * Your session has expired. Please log in again.\n * </ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass onClick={handleLogin}>Log In</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n */\ninterface ModalRootProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Open state */\n open: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Size variant */\n size?: ModalSize;\n /** Child components */\n children: ReactNode;\n}\n\nconst ModalRoot: FC<ModalRootProps> = ({ open, onOpenChange, size = 'md', children, ...props }) => {\n const [isClosing, setIsClosing] = useState(false);\n\n const handleClose = useCallback(async () => {\n setIsClosing(true);\n await delay(MODAL_ANIMATION_DURATION);\n setIsClosing(false);\n onOpenChange?.(false);\n }, [onOpenChange]);\n\n useEffect(() => {\n if (open) {\n lockBodyScroll();\n } else {\n unlockBodyScroll();\n }\n return () => {\n unlockBodyScroll();\n };\n }, [open]);\n\n useEffect(() => {\n if (!open) return;\n\n const handleEscape = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') {\n handleClose();\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => {\n document.removeEventListener('keydown', handleEscape);\n };\n }, [open, handleClose]);\n\n if (!open) return null;\n\n return (\n <ModalContext.Provider value={{ isOpen: open, onClose: handleClose, size, isClosing }}>\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center p-2 sm:p-4\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n aria-describedby=\"modal-description\"\n {...props}\n >\n {children}\n </div>\n </ModalContext.Provider>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: OVERLAY\n// ========================================\n\ninterface ModalOverlayProps {\n className?: string;\n}\n\nconst ModalOverlay: FC<ModalOverlayProps> = ({ className }) => {\n const { onClose, isClosing } = useModalContext();\n\n const overlayStyles: CSSProperties = useMemo(\n () => ({\n background: 'var(--modal-overlay)',\n backdropFilter: 'blur(var(--blur-sm))',\n WebkitBackdropFilter: 'blur(var(--blur-sm))',\n opacity: isClosing ? 0 : 1,\n transition: 'all 0.3s',\n }),\n [isClosing]\n );\n\n return (\n <div\n className={cn('absolute inset-0 transition-all duration-300', className)}\n style={overlayStyles}\n onClick={onClose}\n aria-hidden=\"true\"\n />\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ninterface ModalContentProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(\n ({ children, className }, ref) => {\n const { size, isClosing } = useModalContext();\n\n const modalStyles: CSSProperties = useMemo(\n () => ({\n background: 'var(--modal-bg)',\n border: '1px solid var(--modal-border)',\n boxShadow: 'var(--modal-glow)',\n backdropFilter: 'blur(var(--blur-lg))',\n WebkitBackdropFilter: 'blur(var(--blur-lg))',\n transform: isClosing ? 'scale(0.95) translateY(10px)' : 'scale(1) translateY(0)',\n opacity: isClosing ? 0 : 1,\n animation: !isClosing ? 'modalFadeIn 0.3s ease-out' : 'none',\n }),\n [isClosing]\n );\n\n return (\n <div ref={ref} className={cn(modalSizes({ size }), className)} style={modalStyles}>\n {/* Glow effect */}\n <div\n className=\"absolute inset-0 rounded-3xl pointer-events-none\"\n style={{\n background: 'var(--modal-glow-effect)',\n }}\n />\n {children}\n </div>\n );\n }\n);\n\nModalContent.displayName = 'ModalContent';\n\n// ========================================\n// COMPOUND COMPONENT: HEADER\n// ========================================\n\ninterface ModalHeaderProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalHeader: FC<ModalHeaderProps> = ({ children, className }) => {\n return (\n <div className={cn('relative flex items-start justify-between mb-4 md:mb-5', className)}>\n {children}\n </div>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: BODY\n// ========================================\n\ninterface ModalBodyProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalBody: FC<ModalBodyProps> = ({ children, className }) => {\n return (\n <div className={cn('relative', className)} style={{ color: 'var(--text-secondary)' }}>\n {children}\n </div>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: FOOTER\n// ========================================\n\ninterface ModalFooterProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalFooter: FC<ModalFooterProps> = ({ children, className }) => {\n return (\n <div className={cn('relative flex gap-3 mt-4 md:mt-5 justify-end', className)}>{children}</div>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: TITLE\n// ========================================\n\ninterface ModalTitleProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalTitle: FC<ModalTitleProps> = ({ children, className }) => {\n return (\n <h3\n id=\"modal-title\"\n className={cn('text-lg md:text-xl font-semibold', className)}\n style={{ color: 'var(--text-primary)' }}\n >\n {children}\n </h3>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: DESCRIPTION\n// ========================================\n\ninterface ModalDescriptionProps {\n children: ReactNode;\n className?: string;\n}\n\nconst ModalDescription: FC<ModalDescriptionProps> = ({ children, className }) => {\n return (\n <p\n id=\"modal-description\"\n className={cn('text-sm md:text-base mt-1', className)}\n style={{ color: 'var(--text-muted)' }}\n >\n {children}\n </p>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: CLOSE\n// ========================================\n\ninterface ModalCloseProps {\n className?: string;\n}\n\nconst ModalClose: FC<ModalCloseProps> = ({ className }) => {\n const { onClose } = useModalContext();\n const { isHovered, hoverProps } = useHover();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n\n const closeButtonStyles: CSSProperties = useMemo(\n () => ({\n background: 'var(--modal-close-btn-bg)',\n border: 'var(--modal-close-btn-border)',\n color: 'var(--text-muted)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--modal-close-btn-hover-glow)'\n : 'none',\n outline: 'none',\n }),\n [isHovered, isFocusVisible]\n );\n\n return (\n <button\n onClick={onClose}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n className={cn('p-1.5 md:p-2 rounded-xl transition-all duration-300', className)}\n style={closeButtonStyles}\n type=\"button\"\n aria-label=\"Close modal\"\n >\n <X className={ICON_SIZES.md} />\n </button>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT (v1.0.0+)\n// ========================================\n\n/**\n * ModalGlass - Compound Component API\n *\n * @example\n * ```tsx\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Overlay />\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Confirm</ModalGlass.Title>\n * <ModalGlass.Description>Are you sure?</ModalGlass.Description>\n * <ModalGlass.Close />\n * </ModalGlass.Header>\n * <ModalGlass.Body>\n * <p>Body content</p>\n * </ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass>Cancel</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n */\nexport const ModalGlass = {\n Root: ModalRoot,\n Overlay: ModalOverlay,\n Content: ModalContent,\n Header: ModalHeader,\n Body: ModalBody,\n Footer: ModalFooter,\n Title: ModalTitle,\n Description: ModalDescription,\n Close: ModalClose,\n};\n"
14
22
  }
15
23
  ],
16
- "categories": ["ui"],
24
+ "categories": [
25
+ "ui"
26
+ ],
17
27
  "cssVars": {
18
28
  "light": {
19
29
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +40,4 @@
30
40
  "--blur-lg": "24px"
31
41
  }
32
42
  }
33
- }
43
+ }
@@ -4,8 +4,14 @@
4
4
  "type": "registry:component",
5
5
  "title": "Progress Glass",
6
6
  "description": "Glass-themed progress bar with:",
7
- "dependencies": ["class-variance-authority", "react"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "class-variance-authority",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn",
13
+ "variants"
14
+ ],
9
15
  "files": [
10
16
  {
11
17
  "path": "components/glass/specialized/progress-glass.tsx",
@@ -13,5 +19,7 @@
13
19
  "content": "/**\n * ProgressGlass Component\n *\n * Glass-themed progress bar with:\n * - Theme-aware styling (glass/light/aurora)\n * - Gradient fill with glow\n * - Size variants\n * - Optional label\n */\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { progressSizes, type ProgressGradient } from '@/lib/variants/progress-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface ProgressGlassProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>, VariantProps<typeof progressSizes> {\n readonly value: number;\n readonly gradient?: ProgressGradient;\n readonly showLabel?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\n// Map gradient to existing metric color variables\nconst getGradientColor = (\n gradient: ProgressGradient = 'violet'\n): { colorVar: string; glowVar: string } => {\n const gradients: Record<ProgressGradient, { colorVar: string; glowVar: string }> = {\n violet: { colorVar: '--metric-default-text', glowVar: '--progress-glow-violet' }, // Uses blue metric color\n blue: { colorVar: '--metric-default-text', glowVar: '--progress-glow-blue' },\n cyan: { colorVar: '--metric-secondary-text', glowVar: '--progress-glow-cyan' },\n amber: { colorVar: '--metric-warning-text', glowVar: '--progress-glow-amber' },\n emerald: { colorVar: '--metric-success-text', glowVar: '--progress-glow-emerald' },\n rose: { colorVar: '--metric-destructive-text', glowVar: '--progress-glow-rose' },\n };\n return gradients[gradient] || gradients.violet;\n};\n\nexport const ProgressGlass = forwardRef<HTMLDivElement, ProgressGlassProps>(\n ({ className, size = 'md', value = 0, gradient = 'violet', showLabel, ...props }, ref) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const { colorVar, glowVar } = getGradientColor(gradient);\n\n const trackStyles: CSSProperties = {\n background: 'var(--progress-bg)',\n };\n\n const fillStyles: CSSProperties = {\n width: `${clampedValue}%`,\n background: `var(${colorVar})`,\n boxShadow: `var(${glowVar})`,\n };\n\n return (\n <div ref={ref} className={cn('w-full', className)} {...props}>\n {showLabel && (\n <div className=\"flex justify-between mb-1 md:mb-1.5\">\n <span className=\"text-(length:--font-size-2xs) md:text-xs text-(--text-muted)\">\n Progress\n </span>\n <span className=\"text-(length:--font-size-2xs) md:text-xs font-medium text-(--text-secondary)\">\n {clampedValue}%\n </span>\n </div>\n )}\n <div className={cn(progressSizes({ size }))} style={trackStyles}>\n <div\n className=\"h-full rounded-full transition-all duration-700 ease-out\"\n style={fillStyles}\n role=\"progressbar\"\n aria-valuenow={clampedValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`Progress: ${clampedValue}%`}\n />\n </div>\n </div>\n );\n }\n);\n\nProgressGlass.displayName = 'ProgressGlass';\n"
14
20
  }
15
21
  ],
16
- "categories": ["specialized"]
17
- }
22
+ "categories": [
23
+ "specialized"
24
+ ]
25
+ }
@@ -352,4 +352,4 @@
352
352
  "description": "Expandable Header Glass component with glass effects"
353
353
  }
354
354
  ]
355
- }
355
+ }
@@ -4,8 +4,12 @@
4
4
  "type": "registry:component",
5
5
  "title": "Segmented Control Glass",
6
6
  "description": "Segmented Control Glass component with glass effects",
7
- "dependencies": ["react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn"
12
+ ],
9
13
  "files": [
10
14
  {
11
15
  "path": "components/glass/specialized/segmented-control-glass.tsx",
@@ -13,5 +17,7 @@
13
17
  "content": "// ========================================\n// SEGMENTED CONTROL GLASS COMPONENT\n// ========================================\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport interface SegmentOption {\n readonly value: string;\n readonly label: string;\n}\n\nexport interface SegmentedControlGlassProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange'\n> {\n readonly options: readonly SegmentOption[];\n readonly value: string;\n readonly onChange?: (value: string) => void;\n}\n\nexport const SegmentedControlGlass = forwardRef<HTMLDivElement, SegmentedControlGlassProps>(\n ({ options = [], value, onChange, className, ...props }, ref) => {\n const containerStyles: CSSProperties = {\n border: '1px solid var(--segmented-container-border)',\n background: 'var(--segmented-container-bg)',\n };\n\n // Early return if no options provided\n if (!options || options.length === 0) {\n return null;\n }\n\n return (\n <div\n ref={ref}\n className={cn('inline-flex rounded-xl overflow-hidden', className)}\n style={containerStyles}\n role=\"tablist\"\n {...props}\n >\n {options.map((opt) => {\n const isActive = value === opt.value;\n const buttonStyles: CSSProperties = {\n background: isActive ? 'var(--segmented-active-bg)' : 'transparent',\n color: isActive ? 'var(--segmented-active-text)' : 'var(--segmented-inactive-text)',\n };\n\n return (\n <button\n key={opt.value}\n onClick={() => onChange?.(opt.value)}\n className=\"px-3 py-1.5 md:px-4 md:py-2 text-xs md:text-sm font-medium transition-all duration-300\"\n style={buttonStyles}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n );\n }\n);\n\nSegmentedControlGlass.displayName = 'SegmentedControlGlass';\n"
14
18
  }
15
19
  ],
16
- "categories": ["specialized"]
17
- }
20
+ "categories": [
21
+ "specialized"
22
+ ]
23
+ }
@@ -4,8 +4,15 @@
4
4
  "type": "registry:component",
5
5
  "title": "Sparkline Glass",
6
6
  "description": "Sparkline Glass component with glass effects",
7
- "dependencies": ["class-variance-authority", "react", "recharts"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "class-variance-authority",
9
+ "react",
10
+ "recharts"
11
+ ],
12
+ "registryDependencies": [
13
+ "cn",
14
+ "variants"
15
+ ],
9
16
  "files": [
10
17
  {
11
18
  "path": "components/glass/specialized/sparkline-glass.tsx",
@@ -13,5 +20,7 @@
13
20
  "content": "// ========================================\n// SPARKLINE GLASS COMPONENT\n// Recharts-based sparkline following shadcn/ui Charts pattern\n// ========================================\n\nimport { forwardRef, useMemo } from 'react';\nimport { Bar, BarChart, Cell, ResponsiveContainer, Tooltip } from 'recharts';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { sparklineContainerVariants } from '@/lib/variants/sparkline-glass-variants';\nimport { type ChartConfig } from '@/components/ui/chart';\nimport '@/glass-theme.css';\n\n// ========================================\n// SPARKLINE CONFIG TYPE\n// ========================================\n\nexport type SparklineConfig = ChartConfig;\n\n// ========================================\n// DEFAULT CONFIG\n// ========================================\n\nconst defaultSparklineConfig = {\n value: {\n label: 'Value',\n // Use existing chart-1 color with fallback to primary accent\n color: 'var(--sparkline-bar-fill, hsl(var(--chart-1-base)))',\n },\n max: {\n label: 'Maximum',\n // Use existing success color with fallback\n color: 'var(--sparkline-bar-max, var(--alert-success-text))',\n },\n} satisfies SparklineConfig;\n\n// ========================================\n// SPARKLINE PROPS\n// ========================================\n\nexport interface SparklineGlassProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>,\n VariantProps<typeof sparklineContainerVariants> {\n /** Array of numeric values to display */\n readonly data: readonly number[];\n /** Labels for each bar (shown in tooltip) */\n readonly labels?: readonly string[];\n /** Show labels below bars */\n readonly showLabels?: boolean;\n /** Highlight the maximum value with different color */\n readonly highlightMax?: boolean;\n /** Color for regular bars (CSS variable or color) */\n readonly barColor?: string;\n /** Color for maximum bar */\n readonly maxBarColor?: string;\n /** Tooltips for each bar */\n readonly tooltips?: readonly string[];\n /** Enable animation */\n readonly animated?: boolean;\n /** Minimum bar height as percentage */\n readonly minBarHeightPercent?: number;\n /** Chart configuration (shadcn/ui pattern) */\n readonly config?: SparklineConfig;\n /** Value formatter for tooltip */\n readonly valueFormatter?: (value: number, index: number) => string;\n /** Callback when bar is clicked */\n readonly onBarClick?: (value: number, index: number) => void;\n /** Show tooltip on hover */\n readonly showTooltip?: boolean;\n}\n\n// ========================================\n// HEIGHT MAP\n// ========================================\n\nconst heightMap = {\n sm: 16,\n md: 24,\n lg: 32,\n} as const;\n\n// ========================================\n// CUSTOM TOOLTIP\n// ========================================\n\ninterface SparklineTooltipProps {\n active?: boolean;\n payload?: Array<{\n value: number;\n payload: { index: number; label?: string; value: number };\n }>;\n valueFormatter?: (value: number, index: number) => string;\n}\n\nconst SparklineTooltip = ({ active, payload, valueFormatter }: SparklineTooltipProps) => {\n if (!active || !payload?.length) {\n return null;\n }\n\n const data = payload[0];\n const value = data.value;\n const index = data.payload.index;\n const label = data.payload.label;\n\n const displayValue = valueFormatter ? valueFormatter(value, index) : value.toLocaleString();\n\n return (\n <div className=\"rounded-md border border-[var(--glass-border)] bg-[var(--glass-bg)] px-2 py-1 text-xs shadow-lg backdrop-blur-md\">\n {label && <div className=\"font-medium text-[var(--text-primary)]\">{label}</div>}\n <div className=\"text-[var(--text-secondary)]\">{displayValue}</div>\n </div>\n );\n};\n\n// ========================================\n// SPARKLINE COMPONENT\n// ========================================\n\nexport const SparklineGlass = forwardRef<HTMLDivElement, SparklineGlassProps>(\n (\n {\n data,\n labels,\n showLabels = false,\n highlightMax = false,\n barColor,\n maxBarColor,\n tooltips,\n height = 'md',\n gap = 'sm',\n animated = false,\n minBarHeightPercent = 4,\n config = defaultSparklineConfig,\n valueFormatter,\n onBarClick,\n showTooltip = true,\n className,\n ...props\n },\n ref\n ) => {\n // Transform data for Recharts\n const chartData = useMemo(() => {\n const maxValue = Math.max(...data, 0);\n const maxIndex = data.indexOf(maxValue);\n\n return data.map((value, index) => ({\n index,\n value,\n label: labels?.[index] || tooltips?.[index],\n isMax: highlightMax && index === maxIndex && value > 0,\n // Ensure minimum bar height\n displayValue:\n maxValue > 0\n ? Math.max(value, maxValue * (minBarHeightPercent / 100))\n : minBarHeightPercent,\n }));\n }, [data, labels, tooltips, highlightMax, minBarHeightPercent]);\n\n const { maxValue, maxIndex } = useMemo(() => {\n const max = Math.max(...data, 0);\n return { maxValue: max, maxIndex: data.indexOf(max) };\n }, [data]);\n\n const ariaLabel = useMemo(() => {\n if (data.length === 0) return 'Empty sparkline chart';\n return `Sparkline chart with ${data.length} data points, maximum value ${maxValue}${\n labels ? ` at ${labels[maxIndex] || `position ${maxIndex + 1}`}` : ''\n }`;\n }, [data.length, maxValue, maxIndex, labels]);\n\n // Resolve colors\n const resolvedBarColor = barColor || config.value?.color || 'var(--sparkline-bar-fill)';\n const resolvedMaxColor = maxBarColor || config.max?.color || 'var(--sparkline-bar-max)';\n\n // Gap map for bar spacing\n const gapMap = { none: 0, sm: 1, md: 2 };\n const barGap = gapMap[gap || 'sm'];\n\n const chartHeight = heightMap[height || 'md'];\n\n const handleClick = (dataPoint: (typeof chartData)[0]) => {\n if (onBarClick) {\n onBarClick(dataPoint.value, dataPoint.index);\n }\n };\n\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={ariaLabel}\n className={cn('flex flex-col', className)}\n {...props}\n >\n {/* Chart Container - following shadcn/ui pattern */}\n <div\n className={cn(sparklineContainerVariants({ height, gap }))}\n data-chart=\"sparkline\"\n style={\n {\n '--color-value': resolvedBarColor,\n '--color-max': resolvedMaxColor,\n } as React.CSSProperties\n }\n >\n <ResponsiveContainer width=\"100%\" height={chartHeight}>\n <BarChart\n data={chartData}\n margin={{ top: 0, right: 0, bottom: 0, left: 0 }}\n barGap={barGap}\n >\n {showTooltip && (\n <Tooltip\n content={<SparklineTooltip valueFormatter={valueFormatter} />}\n cursor={false}\n isAnimationActive={false}\n />\n )}\n <Bar\n dataKey=\"displayValue\"\n radius={[2, 2, 0, 0]}\n isAnimationActive={animated}\n animationDuration={500}\n animationEasing=\"ease-out\"\n onClick={(_, index) => handleClick(chartData[index])}\n style={{ cursor: onBarClick ? 'pointer' : 'default' }}\n >\n {chartData.map((entry, index) => (\n <Cell\n key={`cell-${index}`}\n fill={entry.isMax ? resolvedMaxColor : resolvedBarColor}\n className=\"transition-colors duration-200 hover:opacity-80\"\n />\n ))}\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n\n {/* Labels */}\n {showLabels && labels && labels.length > 0 && (\n <div className=\"flex mt-1\" aria-hidden=\"true\">\n {labels.map((label, index) => (\n <span\n key={index}\n className={cn(\n 'flex-1 text-center leading-none truncate text-xs',\n 'text-(--text-muted)'\n )}\n >\n {label}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n }\n);\n\nSparklineGlass.displayName = 'SparklineGlass';\n"
14
21
  }
15
22
  ],
16
- "categories": ["specialized"]
17
- }
23
+ "categories": [
24
+ "specialized"
25
+ ]
26
+ }
@@ -4,8 +4,14 @@
4
4
  "type": "registry:ui",
5
5
  "title": "Stepper Glass",
6
6
  "description": "StepperGlass Component (Compound API)",
7
- "dependencies": ["lucide-react"],
8
- "registryDependencies": ["cn", "use-focus", "variants"],
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn",
12
+ "use-focus",
13
+ "variants"
14
+ ],
9
15
  "files": [
10
16
  {
11
17
  "path": "components/glass/ui/stepper-glass.tsx",
@@ -13,7 +19,9 @@
13
19
  "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * StepperGlass Component (Compound API)\n *\n * Glass-themed step indicator with:\n * - Theme-aware styling (glass/light/aurora)\n * - Horizontal and vertical orientations\n * - Numbered, icon, and dots variants\n * - Linear mode (lock future steps)\n * - Animated connector lines\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <StepperGlass.Root value=\"step2\" onValueChange={setStep}>\n * <StepperGlass.List>\n * <StepperGlass.Step value=\"step1\" label=\"Account\" description=\"Create your account\" />\n * <StepperGlass.Step value=\"step2\" label=\"Profile\" description=\"Setup your profile\" />\n * <StepperGlass.Step value=\"step3\" label=\"Complete\" description=\"Finish setup\" />\n * </StepperGlass.List>\n * <StepperGlass.Content value=\"step1\">Step 1 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step2\">Step 2 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step3\">Step 3 content</StepperGlass.Content>\n * </StepperGlass.Root>\n * ```\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between steps (WCAG 2.1.1)\n * - **Focus Management:** Visible focus ring using `--focus-glow` (WCAG 2.4.7)\n * - **Screen Readers:** Uses `role=\"tablist\"`, `role=\"tab\"` (WCAG 4.1.3)\n * - **ARIA Attributes:** `aria-current=\"step\"`, `aria-disabled` for state\n * - **Touch Targets:** 44x44px minimum touch targets (WCAG 2.5.5)\n * - **Color Contrast:** All states meet WCAG AA 4.5:1 ratio\n * - **Motion:** Respects `prefers-reduced-motion`\n */\n\nimport {\n forwardRef,\n createContext,\n useContext,\n useMemo,\n useState,\n useCallback,\n useLayoutEffect,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { Check } from 'lucide-react';\nimport {\n stepperRootVariants,\n stepperListVariants,\n stepperStepContainerVariants,\n stepperIndicatorVariants,\n stepperConnectorVariants,\n stepperLabelVariants,\n stepperDescriptionVariants,\n stepperContentVariants,\n type StepperOrientation,\n type StepperVariant,\n type StepperSize,\n type StepStatus,\n} from '@/lib/variants/stepper-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// CONTEXT\n// ========================================\n\ninterface StepperContextValue {\n value: string;\n onValueChange?: (value: string) => void;\n orientation: StepperOrientation;\n variant: StepperVariant;\n size: StepperSize;\n linear: boolean;\n steps: string[];\n registerStep: (value: string, index: number) => void;\n unregisterStep: (value: string) => void;\n}\n\nconst StepperContext = createContext<StepperContextValue | null>(null);\n\nconst useStepperContext = () => {\n const context = useContext(StepperContext);\n if (!context) {\n throw new Error('Stepper compound components must be used within StepperGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// UTILITY: GET STEP STATUS\n// ========================================\n\nfunction getStepStatus(\n stepValue: string,\n currentValue: string,\n steps: string[],\n linear: boolean,\n disabled?: boolean\n): StepStatus {\n if (disabled) return 'disabled';\n\n const stepIndex = steps.indexOf(stepValue);\n const currentIndex = steps.indexOf(currentValue);\n\n if (stepIndex === -1 || currentIndex === -1) return 'pending';\n if (stepIndex === currentIndex) return 'active';\n if (stepIndex < currentIndex) return 'completed';\n if (linear && stepIndex > currentIndex) return 'disabled';\n return 'pending';\n}\n\n// ========================================\n// ROOT COMPONENT\n// ========================================\n\ninterface StepperRootProps {\n /** Current active step value */\n value: string;\n /** Callback when step value changes */\n onValueChange?: (value: string) => void;\n /** Orientation of the stepper */\n orientation?: StepperOrientation;\n /** Visual variant */\n variant?: StepperVariant;\n /** Size of step indicators */\n size?: StepperSize;\n /** Lock future steps (require sequential completion) */\n linear?: boolean;\n /** Child components */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperRoot: FC<StepperRootProps> = ({\n value,\n onValueChange,\n orientation = 'horizontal',\n variant = 'numbered',\n size = 'md',\n linear = false,\n children,\n className,\n}) => {\n const [steps, setSteps] = useState<string[]>([]);\n\n const registerStep = useCallback((stepValue: string, index: number) => {\n setSteps((prev) => {\n if (prev.includes(stepValue)) return prev;\n const newSteps = [...prev];\n // Insert at correct position to maintain order\n newSteps.splice(index, 0, stepValue);\n return newSteps;\n });\n }, []);\n\n const unregisterStep = useCallback((stepValue: string) => {\n setSteps((prev) => prev.filter((s) => s !== stepValue));\n }, []);\n\n const contextValue = useMemo(\n () => ({\n value,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n }),\n [value, onValueChange, orientation, variant, size, linear, steps, registerStep, unregisterStep]\n );\n\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n className={cn(stepperRootVariants({ orientation }), className)}\n aria-label=\"Progress steps\"\n >\n {children}\n </div>\n </StepperContext.Provider>\n );\n};\n\n// ========================================\n// LIST COMPONENT\n// ========================================\n\ninterface StepperListProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n className?: string;\n}\n\nconst StepperList = forwardRef<HTMLDivElement, StepperListProps>(\n ({ children, className, ...props }, ref) => {\n const { orientation } = useStepperContext();\n\n return (\n <div\n ref={ref}\n role=\"tablist\"\n aria-orientation={orientation}\n className={cn(stepperListVariants({ orientation }), className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nStepperList.displayName = 'StepperList';\n\n// ========================================\n// STEP COMPONENT\n// ========================================\n\ninterface StepperStepProps {\n /** Unique value for this step */\n value: string;\n /** Step label (required for accessibility) */\n label: string;\n /** Optional description */\n description?: string;\n /** Custom icon (for icon variant) */\n icon?: ReactNode;\n /** Completed icon override */\n completedIcon?: ReactNode;\n /** Force disabled state */\n disabled?: boolean;\n /** Optional className */\n className?: string;\n /** Step index for ordering (auto-detected) */\n index?: number;\n}\n\nconst StepperStep = forwardRef<HTMLButtonElement, StepperStepProps>(\n (\n {\n value: stepValue,\n label,\n description,\n icon,\n completedIcon,\n disabled: forcedDisabled,\n className,\n index: providedIndex,\n },\n ref\n ) => {\n const {\n value: currentValue,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n } = useStepperContext();\n\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n\n // Track mount order for step registration\n const [mountIndex] = useState(() => providedIndex ?? Date.now());\n\n // Register step on mount\n // Use useLayoutEffect to register before paint\n useLayoutEffect(() => {\n registerStep(stepValue, mountIndex);\n return () => unregisterStep(stepValue);\n }, [stepValue, mountIndex, registerStep, unregisterStep]);\n\n const status = getStepStatus(stepValue, currentValue, steps, linear, forcedDisabled);\n const stepIndex = steps.indexOf(stepValue);\n const isLast = stepIndex === steps.length - 1;\n const isClickable = status !== 'disabled';\n\n // Styles based on status\n const indicatorStyles: CSSProperties = {\n background:\n status === 'completed'\n ? 'var(--stepper-step-completed-bg)'\n : status === 'active'\n ? 'var(--stepper-step-active-bg)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-bg)'\n : 'var(--stepper-step-bg)',\n border: `2px solid ${\n status === 'completed'\n ? 'var(--stepper-step-completed-border)'\n : status === 'active'\n ? 'var(--stepper-step-active-border)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-border)'\n : 'var(--stepper-step-border)'\n }`,\n color:\n status === 'completed'\n ? 'var(--stepper-step-completed-text)'\n : status === 'active'\n ? 'var(--stepper-step-active-text)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-text)'\n : 'var(--stepper-step-text)',\n boxShadow:\n status === 'active'\n ? 'var(--stepper-step-active-glow)'\n : status === 'completed'\n ? 'var(--stepper-step-glow)'\n : isFocusVisible\n ? 'var(--focus-glow)'\n : 'none',\n backdropFilter: 'blur(var(--blur-sm))',\n };\n\n const connectorStyles: CSSProperties = {\n background:\n stepIndex < steps.indexOf(currentValue)\n ? 'var(--stepper-connector-active-bg)'\n : 'var(--stepper-connector-bg)',\n };\n\n const labelStyles: CSSProperties = {\n color:\n status === 'active' || status === 'completed'\n ? 'var(--stepper-label-text)'\n : 'var(--stepper-description-text)',\n };\n\n const descriptionStyles: CSSProperties = {\n color: 'var(--stepper-description-text)',\n };\n\n // Render indicator content\n const renderIndicatorContent = () => {\n if (status === 'completed') {\n if (completedIcon) return completedIcon;\n return <Check className=\"w-4 h-4\" />;\n }\n if (variant === 'icon' && icon) return icon;\n if (variant === 'dots') return null;\n // Numbered variant\n return stepIndex >= 0 ? stepIndex + 1 : '';\n };\n\n const handleClick = () => {\n if (isClickable && onValueChange) {\n onValueChange(stepValue);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!isClickable) return;\n\n const stepList = e.currentTarget.closest('[role=\"tablist\"]');\n if (!stepList) return;\n\n const allSteps = Array.from(\n stepList.querySelectorAll('[role=\"tab\"]:not([aria-disabled=\"true\"])')\n ) as HTMLButtonElement[];\n const currentIdx = allSteps.indexOf(e.currentTarget as HTMLButtonElement);\n\n let nextIdx = currentIdx;\n const isHorizontal = orientation === 'horizontal';\n\n switch (e.key) {\n case isHorizontal ? 'ArrowRight' : 'ArrowDown':\n e.preventDefault();\n nextIdx = (currentIdx + 1) % allSteps.length;\n break;\n case isHorizontal ? 'ArrowLeft' : 'ArrowUp':\n e.preventDefault();\n nextIdx = currentIdx - 1 < 0 ? allSteps.length - 1 : currentIdx - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIdx = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIdx = allSteps.length - 1;\n break;\n default:\n return;\n }\n\n const nextStep = allSteps[nextIdx];\n if (nextStep) {\n nextStep.focus();\n const nextValue = nextStep.getAttribute('data-value');\n if (nextValue && onValueChange) {\n onValueChange(nextValue);\n }\n }\n };\n\n // For horizontal, we need step + connector inline\n // For vertical, step is a row with connector below\n if (orientation === 'horizontal') {\n return (\n <>\n <div className={cn(stepperStepContainerVariants({ orientation }), className)}>\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n // Ensure minimum touch target\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col items-center\">\n <span\n className={cn(stepperLabelVariants({ size, orientation }))}\n style={labelStyles}\n >\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Connector line between steps */}\n {!isLast && (\n <div\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </>\n );\n }\n\n // Vertical orientation\n return (\n <div className=\"flex flex-col\">\n <div className={cn(stepperStepContainerVariants({ orientation }), className)}>\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col justify-center\">\n <span className={cn(stepperLabelVariants({ size, orientation }))} style={labelStyles}>\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Vertical connector */}\n {!isLast && (\n <div\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n }\n);\n\nStepperStep.displayName = 'StepperStep';\n\n// ========================================\n// CONTENT COMPONENT\n// ========================================\n\ninterface StepperContentProps {\n /** Value of the step this content belongs to */\n value: string;\n /** Content to display when step is active */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperContent: FC<StepperContentProps> = ({ value, children, className }) => {\n const { value: currentValue, orientation } = useStepperContext();\n const isActive = currentValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n role=\"tabpanel\"\n aria-hidden={!isActive}\n className={cn(stepperContentVariants({ orientation }), className)}\n >\n {children}\n </div>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT\n// ========================================\n\nexport const StepperGlass = {\n Root: StepperRoot,\n List: StepperList,\n Step: StepperStep,\n Content: StepperContent,\n};\n\n// Also export individual components for flexibility\nexport { StepperRoot, StepperList, StepperStep, StepperContent };\n\n// Re-export types\nexport type {\n StepperRootProps,\n StepperListProps,\n StepperStepProps,\n StepperContentProps,\n StepperOrientation,\n StepperVariant,\n StepperSize,\n StepStatus,\n};\n"
14
20
  }
15
21
  ],
16
- "categories": ["ui"],
22
+ "categories": [
23
+ "ui"
24
+ ],
17
25
  "cssVars": {
18
26
  "light": {
19
27
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +38,4 @@
30
38
  "--blur-lg": "24px"
31
39
  }
32
40
  }
33
- }
41
+ }
@@ -5,7 +5,10 @@
5
5
  "title": "Tabs Glass",
6
6
  "description": "TabsGlass Component (Compound API only)",
7
7
  "dependencies": [],
8
- "registryDependencies": ["cn", "use-focus"],
8
+ "registryDependencies": [
9
+ "cn",
10
+ "use-focus"
11
+ ],
9
12
  "files": [
10
13
  {
11
14
  "path": "components/glass/ui/tabs-glass.tsx",
@@ -13,7 +16,9 @@
13
16
  "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * TabsGlass Component (Compound API only)\n *\n * Glass-themed tab navigation with:\n * - Theme-aware styling (glass/light/aurora)\n * - Active tab indicator\n * - Smooth transitions\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"overview\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"analytics\">Analytics</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"settings\">Settings</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"overview\">\n * Overview content\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"analytics\">\n * Analytics content\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"settings\">\n * Settings content\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n */\n\nimport {\n forwardRef,\n createContext,\n useContext,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface TabItem {\n readonly id: string;\n readonly label: string;\n}\n\n// ========================================\n// CONTEXT FOR COMPOUND COMPONENTS\n// ========================================\n\ninterface TabsContextValue {\n value: string;\n onValueChange?: (value: string) => void;\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = () => {\n const context = useContext(TabsContext);\n if (!context) {\n throw new Error('Tabs compound components must be used within TabsGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\n/**\n * Props for TabsGlass.Root component\n *\n * Root component that manages tab state and provides context for child components.\n * Features accessible keyboard navigation and ARIA attributes.\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between tabs, Tab moves to tab panel content (WCAG 2.1.1)\n * - **Focus Management:** Visible focus ring on active tab using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Uses `role=\"tablist\"`, `role=\"tab\"`, `role=\"tabpanel\"` for proper tab semantics (WCAG 4.1.3)\n * - **ARIA Attributes:** Tabs marked with `aria-selected`, panels with `aria-hidden` for state announcement\n * - **Active State:** Visual indicator (underline) plus color change for multi-modal feedback\n * - **Touch Targets:** Tab triggers meet minimum 44x44px touch target (WCAG 2.5.5)\n * - **Color Contrast:** Active and inactive tab text meet WCAG AA contrast ratio 4.5:1\n * - **Motion:** Transitions and indicator animations respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic tabs\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"overview\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"analytics\">Analytics</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"settings\">Settings</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"overview\">\n * <h2>Overview</h2>\n * <p>Overview content here</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"analytics\">\n * <h2>Analytics</h2>\n * <p>Analytics content here</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"settings\">\n * <h2>Settings</h2>\n * <p>Settings content here</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Tabs with icons (ensure accessible labels)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"home\" aria-label=\"Home dashboard\">\n * <Home className=\"w-4 h-4\" />\n * </TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"search\" aria-label=\"Search\">\n * <Search className=\"w-4 h-4\" />\n * </TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"home\">Dashboard content</TabsGlass.Content>\n * <TabsGlass.Content value=\"search\">Search content</TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Disabled tab (announced to screen readers)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"tab1\">Available Tab</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"tab2\" disabled>\n * Locked Tab\n * </TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"tab1\">Content 1</TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Form tabs with proper focus management\n * <TabsGlass.Root value={currentStep} onValueChange={setCurrentStep}>\n * <TabsGlass.List aria-label=\"Registration steps\">\n * <TabsGlass.Trigger value=\"account\">Account Info</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"profile\">Profile Details</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"confirm\">Confirmation</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"account\">\n * <InputGlass label=\"Email\" type=\"email\" />\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"profile\">\n * <InputGlass label=\"Name\" />\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"confirm\">\n * <p>Review your information</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n * ```\n */\ninterface TabsRootProps {\n /** Current active tab value */\n value: string;\n /** Callback when tab value changes */\n onValueChange?: (value: string) => void;\n /** Child components */\n children: ReactNode;\n /** Optional className for container */\n className?: string;\n}\n\nconst TabsRoot: FC<TabsRootProps> = ({ value, onValueChange, children, className }) => {\n return (\n <TabsContext.Provider value={{ value, onValueChange }}>\n <div className={cn('tabs-glass-root', className)}>{children}</div>\n </TabsContext.Provider>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: LIST\n// ========================================\n\ninterface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n className?: string;\n}\n\nconst TabsList = forwardRef<HTMLDivElement, TabsListProps>(\n ({ children, className, ...props }, ref) => {\n const containerStyles: CSSProperties = {\n background: 'var(--tab-container-bg)',\n border: '1px solid var(--tab-container-border)',\n };\n\n return (\n <div\n ref={ref}\n className={cn('inline-flex gap-0.5 md:gap-1 p-0.5 md:p-1 rounded-xl', className)}\n style={containerStyles}\n role=\"tablist\"\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nTabsList.displayName = 'TabsList';\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\ninterface TabsTriggerProps {\n /** Value of this tab */\n value: string;\n /** Tab label/content */\n children: ReactNode;\n /** Optional className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n}\n\nconst TabsTrigger = forwardRef<HTMLButtonElement, TabsTriggerProps>(\n ({ value, children, className, disabled }, ref) => {\n const { value: activeValue, onValueChange } = useTabsContext();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const isActive = activeValue === value;\n\n const tabStyles: CSSProperties = {\n background: isActive ? 'var(--tab-active-bg)' : 'var(--tab-bg)',\n color: isActive ? 'var(--tab-active-text)' : 'var(--text-secondary)',\n boxShadow: isFocusVisible && !disabled ? 'var(--focus-glow)' : 'none',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const tablist = e.currentTarget.closest('[role=\"tablist\"]');\n if (!tablist) return;\n\n const tabs = Array.from(\n tablist.querySelectorAll('[role=\"tab\"]:not([disabled])')\n ) as HTMLButtonElement[];\n const currentIndex = tabs.indexOf(e.currentTarget);\n\n let nextIndex = currentIndex;\n\n switch (e.key) {\n case 'ArrowRight':\n e.preventDefault();\n nextIndex = currentIndex + 1 >= tabs.length ? 0 : currentIndex + 1;\n break;\n case 'ArrowLeft':\n e.preventDefault();\n nextIndex = currentIndex - 1 < 0 ? tabs.length - 1 : currentIndex - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIndex = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIndex = tabs.length - 1;\n break;\n default:\n return;\n }\n\n const nextTab = tabs[nextIndex];\n if (nextTab) {\n nextTab.focus();\n // Get the value from the button's data attribute or find it in context\n const nextValue = nextTab.getAttribute('data-value');\n if (nextValue && onValueChange) {\n onValueChange(nextValue);\n }\n }\n };\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n disabled={disabled}\n data-value={value}\n className={cn(\n 'relative px-2.5 py-1.5 md:px-4 md:py-2 rounded-lg text-xs md:text-sm font-medium transition-[background-color,color,opacity] duration-300',\n disabled && 'opacity-50 cursor-not-allowed',\n className\n )}\n style={tabStyles}\n onClick={() => !disabled && onValueChange?.(value)}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n >\n {children}\n {isActive && (\n <div\n className=\"absolute bottom-0 left-1/2 -translate-x-1/2 w-6 md:w-8 h-0.5 rounded-full\"\n style={{ background: 'var(--tab-indicator)' }}\n />\n )}\n </button>\n );\n }\n);\n\nTabsTrigger.displayName = 'TabsTrigger';\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ninterface TabsContentProps {\n /** Value of the tab this content belongs to */\n value: string;\n /** Content to display when tab is active */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst TabsContent: FC<TabsContentProps> = ({ value, children, className }) => {\n const { value: activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n role=\"tabpanel\"\n aria-hidden={!isActive}\n className={cn('animate-in fade-in-0 duration-200', className)}\n >\n {children}\n </div>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT (v1.0.0+)\n// ========================================\n\n/**\n * TabsGlass - Compound Component API\n *\n * @example\n * ```tsx\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"tab1\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"tab2\">Analytics</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"tab1\">\n * <p>Overview content</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"tab2\">\n * <p>Analytics content</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n */\nexport const TabsGlass = {\n Root: TabsRoot,\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n};\n"
14
17
  }
15
18
  ],
16
- "categories": ["ui"],
19
+ "categories": [
20
+ "ui"
21
+ ],
17
22
  "cssVars": {
18
23
  "light": {
19
24
  "--glass-bg": "rgba(255, 255, 255, 0.1)",
@@ -30,4 +35,4 @@
30
35
  "--blur-lg": "24px"
31
36
  }
32
37
  }
33
- }
38
+ }
@@ -4,8 +4,13 @@
4
4
  "type": "registry:block",
5
5
  "title": "Trust Score Card Glass",
6
6
  "description": "Trust Score Card Glass component with glass effects",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn"
13
+ ],
9
14
  "files": [
10
15
  {
11
16
  "path": "components/glass/sections/trust-score-card-glass.tsx",
@@ -13,5 +18,7 @@
13
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"
14
19
  }
15
20
  ],
16
- "categories": ["sections"]
17
- }
21
+ "categories": [
22
+ "sections"
23
+ ]
24
+ }
@@ -4,8 +4,14 @@
4
4
  "type": "registry:block",
5
5
  "title": "Year Card Glass",
6
6
  "description": "Year Card Glass component with glass effects",
7
- "dependencies": ["lucide-react", "react"],
8
- "registryDependencies": ["cn", "variants"],
7
+ "dependencies": [
8
+ "lucide-react",
9
+ "react"
10
+ ],
11
+ "registryDependencies": [
12
+ "cn",
13
+ "variants"
14
+ ],
9
15
  "files": [
10
16
  {
11
17
  "path": "components/glass/composite/year-card-glass.tsx",
@@ -13,5 +19,7 @@
13
19
  "content": "// ========================================\n// YEAR CARD GLASS COMPONENT\n// Year card for career timeline / analytics\n// Domain-specific composite component following shadcn/ui patterns\n// ========================================\n\nimport { forwardRef, type CSSProperties, type ReactNode } from 'react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { BadgeGlass } from '../ui/badge-glass';\nimport { ProgressGlass } from '../specialized/progress-glass';\nimport { SparklineGlass } from '../specialized/sparkline-glass';\nimport { ButtonGlass } from '../ui/button-glass';\nimport { InteractiveCard } from '../primitives';\nimport { InsightCardGlass } from '../atomic/insight-card-glass';\nimport type { ProgressGradient } from '@/lib/variants/progress-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface YearCardGlassInsight {\n readonly variant?: 'default' | 'tip' | 'highlight' | 'warning' | 'stat' | 'growth' | 'decline';\n readonly emoji?: string;\n readonly text: string;\n readonly detail?: string;\n}\n\nexport interface YearCardGlassStat {\n readonly label: string;\n readonly value: string | number;\n readonly icon?: ReactNode;\n}\n\nexport interface YearCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Year to display (string or number) */\n readonly year: string | number;\n /** Emoji for the badge */\n readonly emoji: string;\n /** Label for the badge (e.g., \"Peak Year\") */\n readonly label: string;\n /** Primary metric value (e.g., \"629 commits\") */\n readonly commits: string;\n /** Progress percentage (0-100) */\n readonly progress: number;\n /** Whether the card is expanded to show details */\n readonly isExpanded?: boolean;\n /** Progress bar gradient color */\n readonly gradient?: ProgressGradient;\n /** Number of PRs (shown in expanded view) */\n readonly prs?: number;\n /** Number of repos (shown in expanded view) */\n readonly repos?: number;\n /** Callback when \"Show repos\" button is clicked */\n readonly onShowYear?: () => void;\n /** Monthly activity data for sparkline */\n readonly sparklineData?: readonly number[];\n /** Labels for sparkline (e.g., month names) */\n readonly sparklineLabels?: readonly string[];\n /** Insights to display in expanded view */\n readonly insights?: readonly YearCardGlassInsight[];\n /** Custom stats for expanded view (replaces default commits/prs/repos grid) */\n readonly stats?: readonly YearCardGlassStat[];\n /** Custom action button text */\n readonly actionLabel?: string;\n /** Show sparkline in collapsed view */\n readonly showSparklineCollapsed?: boolean;\n /** Custom value formatter for commits display */\n readonly valueFormatter?: (commits: string) => string;\n /** Additional content for expanded section */\n readonly children?: ReactNode;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const YearCardGlass = forwardRef<HTMLDivElement, YearCardGlassProps>(\n (\n {\n year,\n emoji,\n label,\n commits,\n progress,\n isExpanded = false,\n gradient = 'blue',\n prs = 0,\n repos = 0,\n onShowYear,\n sparklineData,\n sparklineLabels,\n insights,\n stats,\n actionLabel,\n showSparklineCollapsed = true,\n valueFormatter,\n children,\n className,\n onClick,\n ...props\n },\n ref\n ) => {\n const expandedStyles: CSSProperties = {\n background: 'var(--expanded-bg)',\n borderColor: 'var(--expanded-border)',\n };\n\n const metricCardStyles: CSSProperties = {\n background: 'var(--card-bg)',\n borderColor: 'var(--card-border)',\n };\n\n // Format commits display\n const displayCommits = valueFormatter ? valueFormatter(commits) : commits;\n\n // Default stats if none provided\n const displayStats = stats || [\n { label: 'Commits', value: commits },\n { label: 'PRs', value: prs },\n { label: 'Repos', value: repos },\n ];\n\n // Default action label\n const buttonLabel = actionLabel || `Show repos from ${year}`;\n\n return (\n <InteractiveCard\n ref={ref}\n baseBg=\"var(--year-card-bg)\"\n hoverBg=\"var(--card-hover-bg)\"\n borderColor=\"var(--year-card-border)\"\n hoverGlow=\"var(--year-card-hover-glow)\"\n hoverLift\n rounded=\"rounded-xl\"\n className={cn('w-full max-w-2xl p-2.5 md:p-3 cursor-pointer', className)}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.(e as unknown as React.MouseEvent<HTMLDivElement>);\n }\n }}\n aria-expanded={isExpanded}\n aria-label={`${year} year: ${label}, ${commits} commits. ${isExpanded ? 'Collapse' : 'Expand'} details`}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between mb-1.5 md:mb-2\">\n <div className=\"flex items-center gap-1.5 md:gap-2\">\n <span\n className=\"font-semibold text-sm md:text-base\"\n style={{ color: 'var(--text-primary)' }}\n >\n {year}\n </span>\n <BadgeGlass>\n {emoji} {label}\n </BadgeGlass>\n </div>\n <span\n className=\"text-xs md:text-sm flex items-center gap-0.5 md:gap-1\"\n style={{ color: 'var(--text-secondary)' }}\n >\n {displayCommits}\n {isExpanded ? (\n <ChevronUp className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n ) : (\n <ChevronDown className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n )}\n </span>\n </div>\n\n {/* Progress + Sparkline (collapsed view) */}\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1\">\n <ProgressGlass value={progress} gradient={gradient} size=\"sm\" />\n </div>\n {showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n height=\"sm\"\n gap=\"sm\"\n className=\"w-16 md:w-20\"\n aria-label={`Activity trend for ${year}`}\n />\n )}\n </div>\n\n {/* Expanded Section */}\n {isExpanded && (\n <div className=\"mt-3 pt-3 border-t space-y-3\" style={expandedStyles}>\n {/* Stats Grid */}\n <div\n className=\"grid gap-2 md:gap-3\"\n style={{ gridTemplateColumns: `repeat(${Math.min(displayStats.length, 4)}, 1fr)` }}\n >\n {displayStats.map((stat, index) => (\n <div\n key={index}\n className=\"p-2 md:p-2.5 rounded-lg border text-center\"\n style={metricCardStyles}\n >\n {stat.icon && (\n <div className=\"mb-1 flex justify-center text-[var(--text-muted)]\">\n {stat.icon}\n </div>\n )}\n <div\n className=\"text-base md:text-xl font-bold\"\n style={{ color: 'var(--text-primary)' }}\n >\n {stat.value}\n </div>\n <div className=\"text-(length:--font-size-2xs) md:text-xs text-(--text-muted)\">\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n\n {/* Expanded Sparkline with labels (if not shown in collapsed) */}\n {!showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n showLabels={!!sparklineLabels}\n height=\"md\"\n gap=\"sm\"\n highlightMax\n className=\"w-full\"\n aria-label={`Monthly activity for ${year}`}\n />\n )}\n\n {/* Insights */}\n {insights && insights.length > 0 && (\n <div className=\"space-y-2\">\n {insights.map((insight, index) => (\n <InsightCardGlass\n key={index}\n variant={insight.variant}\n emoji={insight.emoji}\n text={insight.text}\n detail={insight.detail}\n inline={false}\n />\n ))}\n </div>\n )}\n\n {/* Custom children content */}\n {children}\n\n {/* Show Year Button */}\n {onShowYear && (\n <ButtonGlass\n variant=\"primary\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation();\n onShowYear();\n }}\n className=\"w-full\"\n >\n {buttonLabel}\n </ButtonGlass>\n )}\n </div>\n )}\n </InteractiveCard>\n );\n }\n);\n\nYearCardGlass.displayName = 'YearCardGlass';\n"
14
20
  }
15
21
  ],
16
- "categories": ["composite"]
17
- }
22
+ "categories": [
23
+ "composite"
24
+ ]
25
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shadcn-glass-ui",
3
3
  "private": false,
4
- "version": "2.0.0",
4
+ "version": "2.0.2",
5
5
  "description": "Glassmorphism UI library for React - AI-friendly with 58 components, strict TypeScript, and comprehensive docs",
6
6
  "author": "Yhooi2",
7
7
  "license": "MIT",
@@ -32,6 +32,10 @@
32
32
  "type": "git",
33
33
  "url": "git+https://github.com/Yhooi2/shadcn-glass-ui-library.git"
34
34
  },
35
+ "publishConfig": {
36
+ "registry": "https://registry.npmjs.org/",
37
+ "access": "public"
38
+ },
35
39
  "homepage": "https://yhooi2.github.io/shadcn-glass-ui-library/",
36
40
  "bugs": "https://github.com/Yhooi2/shadcn-glass-ui-library/issues",
37
41
  "type": "module",