shadcn-glass-ui 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +0 -51
  2. package/README.md +11 -12
  3. package/context7.json +4 -15
  4. package/dist/cli/index.cjs +1 -1
  5. package/dist/components.cjs +4 -4
  6. package/dist/components.d.ts +89 -160
  7. package/dist/components.js +1 -1
  8. package/dist/hooks.cjs +2 -2
  9. package/dist/index.cjs +966 -2289
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.js +965 -2253
  12. package/dist/index.js.map +1 -1
  13. package/dist/r/alert-glass.json +1 -1
  14. package/dist/r/badge-glass.json +1 -1
  15. package/dist/r/button-glass.json +1 -1
  16. package/dist/r/card-glass.json +40 -0
  17. package/dist/r/input-glass.json +1 -1
  18. package/dist/r/modal-glass.json +5 -5
  19. package/dist/r/registry.json +7 -1
  20. package/dist/r/tooltip-glass.json +1 -1
  21. package/dist/shadcn-glass-ui.css +1 -1
  22. package/dist/{theme-context-D_cb9KzA.cjs → theme-context-BEA8K_rq.cjs} +2 -2
  23. package/dist/{theme-context-D_cb9KzA.cjs.map → theme-context-BEA8K_rq.cjs.map} +1 -1
  24. package/dist/themes.cjs +1 -1
  25. package/dist/{trust-score-card-glass-CTsEVRD3.cjs → trust-score-card-glass-DTS1RdIt.cjs} +189 -341
  26. package/dist/trust-score-card-glass-DTS1RdIt.cjs.map +1 -0
  27. package/dist/{trust-score-card-glass-CUStm4o_.js → trust-score-card-glass-Dg4_b_g_.js} +158 -238
  28. package/dist/trust-score-card-glass-Dg4_b_g_.js.map +1 -0
  29. package/dist/{use-focus--Hw2nevi.cjs → use-focus-CdoUzFQ8.cjs} +2 -2
  30. package/dist/{use-focus--Hw2nevi.cjs.map → use-focus-CdoUzFQ8.cjs.map} +1 -1
  31. package/dist/{use-wallpaper-tint-B4oMQsXQ.cjs → use-wallpaper-tint-Rq5UgY9L.cjs} +2 -2
  32. package/dist/{use-wallpaper-tint-B4oMQsXQ.cjs.map → use-wallpaper-tint-Rq5UgY9L.cjs.map} +1 -1
  33. package/dist/{utils-BqeJ4aco.cjs → utils-NLnOCttr.cjs} +2 -2
  34. package/dist/{utils-BqeJ4aco.cjs.map → utils-NLnOCttr.cjs.map} +1 -1
  35. package/dist/utils.cjs +1 -1
  36. package/docs/ADVANCED_PATTERNS.md +7 -5
  37. package/docs/AI_USAGE.md +0 -1
  38. package/docs/BEST_PRACTICES.md +0 -2
  39. package/docs/BREAKING_CHANGES.md +0 -1
  40. package/docs/COMPONENTS_CATALOG.md +1 -4
  41. package/docs/COMPONENT_PATTERNS.md +325 -0
  42. package/docs/GETTING_STARTED.md +52 -28
  43. package/docs/api/README.md +0 -2
  44. package/docs/api/variables/ModalGlass.md +5 -4
  45. package/package.json +2 -4
  46. package/dist/trust-score-card-glass-CTsEVRD3.cjs.map +0 -1
  47. package/dist/trust-score-card-glass-CUStm4o_.js.map +0 -1
@@ -18,7 +18,7 @@
18
18
  {
19
19
  "path": "components/glass/ui/alert-glass.tsx",
20
20
  "type": "registry:component",
21
- "content": "/**\n * AlertGlass Component\n *\n * Glass-themed alert with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - shadcn/ui compatible variants (default, destructive)\n * - Extended Glass UI variants (success, warning)\n * - Compound component API for flexible composition\n * - Dismissible option\n * - Backdrop blur effect\n *\n * @example Compound API (recommended)\n * ```tsx\n * <AlertGlass variant=\"default\">\n * <AlertGlassTitle>Heads up!</AlertGlassTitle>\n * <AlertGlassDescription>\n * You can add components to your app using the cli.\n * </AlertGlassDescription>\n * </AlertGlass>\n * ```\n *\n * @example With dismiss button\n * ```tsx\n * <AlertGlass variant=\"destructive\" dismissible onDismiss={() => setShow(false)}>\n * <AlertGlassTitle>Error</AlertGlassTitle>\n * <AlertGlassDescription>\n * Your session has expired.\n * </AlertGlassDescription>\n * </AlertGlass>\n * ```\n */\n\n'use client';\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { Info, CheckCircle, AlertTriangle, AlertCircle, X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { alertVariants } from '@/lib/variants/alert-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\nimport type { AlertVariant } from '@/lib/variants/alert-glass-variants';\n\n// ========================================\n// ICON MAP\n// ========================================\n\nconst iconMap: Record<AlertVariant, typeof Info> = {\n default: Info,\n destructive: AlertCircle,\n success: CheckCircle,\n warning: AlertTriangle,\n // Aliases\n info: Info,\n error: AlertCircle,\n};\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\ntype AlertStyleVars = { bg: string; border: string; text: string };\n\nconst variantStyles: Record<AlertVariant, AlertStyleVars> = {\n // shadcn/ui compatible variants\n default: {\n bg: 'var(--alert-default-bg)',\n border: 'var(--alert-default-border)',\n text: 'var(--alert-default-text)',\n },\n destructive: {\n bg: 'var(--alert-destructive-bg)',\n border: 'var(--alert-destructive-border)',\n text: 'var(--alert-destructive-text)',\n },\n // Glass UI extended variants\n success: {\n bg: 'var(--alert-success-bg)',\n border: 'var(--alert-success-border)',\n text: 'var(--alert-success-text)',\n },\n warning: {\n bg: 'var(--alert-warning-bg)',\n border: 'var(--alert-warning-border)',\n text: 'var(--alert-warning-text)',\n },\n // Backward compatibility aliases\n info: {\n bg: 'var(--alert-default-bg)',\n border: 'var(--alert-default-border)',\n text: 'var(--alert-default-text)',\n },\n error: {\n bg: 'var(--alert-destructive-bg)',\n border: 'var(--alert-destructive-border)',\n text: 'var(--alert-destructive-text)',\n },\n};\n\nconst getAlertStyles = (variant: AlertVariant): CSSProperties => {\n const config = variantStyles[variant];\n return {\n background: config.bg,\n border: `1px solid ${config.border}`,\n color: config.text,\n };\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\nexport interface AlertGlassProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>, VariantProps<typeof alertVariants> {\n dismissible?: boolean;\n onDismiss?: () => void;\n}\n\nconst AlertGlassRoot = forwardRef<HTMLDivElement, AlertGlassProps>(\n ({ className, variant = 'default', dismissible, onDismiss, children, ...props }, ref) => {\n const effectiveVariant: AlertVariant = variant ?? 'default';\n const Icon = iconMap[effectiveVariant];\n const config = variantStyles[effectiveVariant];\n\n return (\n <div\n ref={ref}\n className={cn(alertVariants({ variant: effectiveVariant }), className)}\n style={getAlertStyles(effectiveVariant)}\n role=\"alert\"\n {...props}\n >\n <Icon\n className=\"w-4 h-4 md:w-5 md:h-5 shrink-0 mt-0.5\"\n style={{ color: config.text }}\n aria-hidden=\"true\"\n />\n <div className=\"flex-1\">{children}</div>\n {dismissible && (\n <button\n onClick={onDismiss}\n className=\"p-0.5 md:p-1 rounded transition-colors duration-200 hover:bg-black/5 shrink-0\"\n aria-label=\"Dismiss alert\"\n >\n <X className={ICON_SIZES.md} style={{ color: config.text }} />\n </button>\n )}\n </div>\n );\n }\n);\n\nAlertGlassRoot.displayName = 'AlertGlass';\n\n// ========================================\n// COMPOUND COMPONENT: TITLE\n// ========================================\n\nexport type AlertGlassTitleProps = React.HTMLAttributes<HTMLParagraphElement>;\n\nconst AlertGlassTitle = forwardRef<HTMLParagraphElement, AlertGlassTitleProps>(\n ({ className, style, ...props }, ref) => {\n return (\n <p\n ref={ref}\n className={cn('font-medium text-xs md:text-sm mb-0.5 md:mb-1', className)}\n style={{ color: 'inherit', ...style }}\n {...props}\n />\n );\n }\n);\n\nAlertGlassTitle.displayName = 'AlertGlassTitle';\n\n// ========================================\n// COMPOUND COMPONENT: DESCRIPTION\n// ========================================\n\nexport type AlertGlassDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>;\n\nconst AlertGlassDescription = forwardRef<HTMLParagraphElement, AlertGlassDescriptionProps>(\n ({ className, style, ...props }, ref) => {\n return (\n <p\n ref={ref}\n className={cn('text-xs md:text-sm opacity-80', className)}\n style={{ color: 'inherit', ...style }}\n {...props}\n />\n );\n }\n);\n\nAlertGlassDescription.displayName = 'AlertGlassDescription';\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const AlertGlass = AlertGlassRoot;\nexport { AlertGlassTitle, AlertGlassDescription };\n"
21
+ "content": "/**\n * AlertGlass Component\n *\n * Glass-themed alert with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - shadcn/ui compatible variants (default, destructive)\n * - Extended Glass UI variants (success, warning)\n * - Compound component API for flexible composition\n * - Dismissible option\n * - Backdrop blur effect\n *\n * @example Compound API (recommended)\n * ```tsx\n * <AlertGlass variant=\"default\">\n * <AlertGlassTitle>Heads up!</AlertGlassTitle>\n * <AlertGlassDescription>\n * You can add components to your app using the cli.\n * </AlertGlassDescription>\n * </AlertGlass>\n * ```\n *\n * @example With dismiss button\n * ```tsx\n * <AlertGlass variant=\"destructive\" dismissible onDismiss={() => setShow(false)}>\n * <AlertGlassTitle>Error</AlertGlassTitle>\n * <AlertGlassDescription>\n * Your session has expired.\n * </AlertGlassDescription>\n * </AlertGlass>\n * ```\n */\n\n'use client';\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { Info, CheckCircle, AlertTriangle, AlertCircle, X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { alertVariants } from '@/lib/variants/alert-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\nimport type { AlertVariant } from '@/lib/variants/alert-glass-variants';\n\n// ========================================\n// ICON MAP\n// ========================================\n\nconst iconMap: Record<AlertVariant, typeof Info> = {\n default: Info,\n destructive: AlertCircle,\n success: CheckCircle,\n warning: AlertTriangle,\n // Aliases\n info: Info,\n error: AlertCircle,\n};\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\ntype AlertStyleVars = { bg: string; border: string; text: string };\n\nconst variantStyles: Record<AlertVariant, AlertStyleVars> = {\n // shadcn/ui compatible variants\n default: {\n bg: 'var(--alert-default-bg)',\n border: 'var(--alert-default-border)',\n text: 'var(--alert-default-text)',\n },\n destructive: {\n bg: 'var(--alert-destructive-bg)',\n border: 'var(--alert-destructive-border)',\n text: 'var(--alert-destructive-text)',\n },\n // Glass UI extended variants\n success: {\n bg: 'var(--alert-success-bg)',\n border: 'var(--alert-success-border)',\n text: 'var(--alert-success-text)',\n },\n warning: {\n bg: 'var(--alert-warning-bg)',\n border: 'var(--alert-warning-border)',\n text: 'var(--alert-warning-text)',\n },\n // Backward compatibility aliases\n info: {\n bg: 'var(--alert-default-bg)',\n border: 'var(--alert-default-border)',\n text: 'var(--alert-default-text)',\n },\n error: {\n bg: 'var(--alert-destructive-bg)',\n border: 'var(--alert-destructive-border)',\n text: 'var(--alert-destructive-text)',\n },\n};\n\nconst getAlertStyles = (variant: AlertVariant): CSSProperties => {\n const config = variantStyles[variant];\n return {\n background: config.bg,\n border: `1px solid ${config.border}`,\n color: config.text,\n };\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\nexport interface AlertGlassProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style'>, VariantProps<typeof alertVariants> {\n dismissible?: boolean;\n onDismiss?: () => void;\n}\n\nconst AlertGlassRoot = forwardRef<HTMLDivElement, AlertGlassProps>(\n ({ className, variant = 'default', dismissible, onDismiss, children, ...props }, ref) => {\n const effectiveVariant: AlertVariant = variant ?? 'default';\n const Icon = iconMap[effectiveVariant];\n const config = variantStyles[effectiveVariant];\n\n return (\n <div\n ref={ref}\n data-slot=\"alert\"\n className={cn(alertVariants({ variant: effectiveVariant }), className)}\n style={getAlertStyles(effectiveVariant)}\n role=\"alert\"\n {...props}\n >\n <Icon\n className=\"w-4 h-4 md:w-5 md:h-5 shrink-0 mt-0.5\"\n style={{ color: config.text }}\n aria-hidden=\"true\"\n />\n <div className=\"flex-1\">{children}</div>\n {dismissible && (\n <button\n onClick={onDismiss}\n className=\"p-0.5 md:p-1 rounded transition-colors duration-200 hover:bg-black/5 shrink-0\"\n aria-label=\"Dismiss alert\"\n >\n <X className={ICON_SIZES.md} style={{ color: config.text }} />\n </button>\n )}\n </div>\n );\n }\n);\n\nAlertGlassRoot.displayName = 'AlertGlass';\n\n// ========================================\n// COMPOUND COMPONENT: TITLE\n// ========================================\n\nexport type AlertGlassTitleProps = React.HTMLAttributes<HTMLDivElement>;\n\nconst AlertGlassTitle = forwardRef<HTMLDivElement, AlertGlassTitleProps>(\n ({ className, style, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-slot=\"alert-title\"\n className={cn('font-medium text-xs md:text-sm mb-0.5 md:mb-1', className)}\n style={{ color: 'inherit', ...style }}\n {...props}\n />\n );\n }\n);\n\nAlertGlassTitle.displayName = 'AlertGlassTitle';\n\n// ========================================\n// COMPOUND COMPONENT: DESCRIPTION\n// ========================================\n\nexport type AlertGlassDescriptionProps = React.HTMLAttributes<HTMLDivElement>;\n\nconst AlertGlassDescription = forwardRef<HTMLDivElement, AlertGlassDescriptionProps>(\n ({ className, style, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-slot=\"alert-description\"\n className={cn('text-xs md:text-sm opacity-80', className)}\n style={{ color: 'inherit', ...style }}\n {...props}\n />\n );\n }\n);\n\nAlertGlassDescription.displayName = 'AlertGlassDescription';\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const AlertGlass = AlertGlassRoot;\nexport { AlertGlassTitle, AlertGlassDescription };\n"
22
22
  }
23
23
  ],
24
24
  "categories": [
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "path": "components/glass/ui/badge-glass.tsx",
18
18
  "type": "registry:component",
19
- "content": "/**\n * BadgeGlass Component\n *\n * Glass-themed badge with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - shadcn/ui compatible variants (default, secondary, destructive, outline)\n * - Extended Glass UI variants (success, warning, info)\n * - Size options\n * - Optional animated dot\n */\n\nimport { forwardRef, type ReactNode, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { badgeVariants, type BadgeVariant } from '@/lib/variants/badge-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\ntype BadgeStyleVars = { bg: string; text: string; border: string };\n\nconst variantStyles: Record<BadgeVariant, BadgeStyleVars> = {\n // shadcn/ui compatible variants\n default: {\n bg: 'var(--badge-default-bg)',\n text: 'var(--badge-default-text)',\n border: 'var(--badge-default-border)',\n },\n secondary: {\n bg: 'var(--badge-secondary-bg)',\n text: 'var(--badge-secondary-text)',\n border: 'var(--badge-secondary-border)',\n },\n destructive: {\n bg: 'var(--badge-destructive-bg)',\n text: 'var(--badge-destructive-text)',\n border: 'var(--badge-destructive-border)',\n },\n outline: {\n bg: 'var(--badge-outline-bg)',\n text: 'var(--badge-outline-text)',\n border: 'var(--badge-outline-border)',\n },\n // Glass UI extended variants\n success: {\n bg: 'var(--badge-success-bg)',\n text: 'var(--badge-success-text)',\n border: 'var(--badge-success-border)',\n },\n warning: {\n bg: 'var(--badge-warning-bg)',\n text: 'var(--badge-warning-text)',\n border: 'var(--badge-warning-border)',\n },\n info: {\n bg: 'var(--badge-info-bg)',\n text: 'var(--badge-info-text)',\n border: 'var(--badge-info-border)',\n },\n};\n\nconst getBadgeStyles = (variant: BadgeVariant): CSSProperties => {\n const v = variantStyles[variant] || variantStyles.default;\n return {\n background: v.bg,\n color: v.text,\n border: `1px solid ${v.border}`,\n };\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the BadgeGlass component\n *\n * A glass-themed badge with semantic variants and optional animated status dot.\n * Features shadcn/ui compatible variants plus extended Glass UI variants.\n *\n * @accessibility\n * - **Keyboard Navigation:** Badges are non-interactive by default (display-only)\n * - **Focus Management:** N/A - badges do not receive focus unless wrapped in interactive elements\n * - **Screen Readers:** Semantic `<span>` element, content announced naturally\n * - **Status Indicators:** Use `aria-label` to provide context for status badges (e.g., \"Status: Active\")\n * - **Animated Dot:** Pulse animation respects `prefers-reduced-motion` settings\n * - **Touch Targets:** N/A for display badges, ensure 44x44px if wrapping in button/link (WCAG 2.5.5)\n * - **Color Contrast:** All variant text meets WCAG AA contrast ratio 4.5:1 against badge background\n * - **Motion:** Dot pulse animation can be disabled for users with motion sensitivity\n *\n * @example\n * ```tsx\n * // Basic badge with variant\n * <BadgeGlass variant=\"default\">New</BadgeGlass>\n *\n * // Status badge with aria-label for screen readers\n * <BadgeGlass variant=\"success\" aria-label=\"Status: Active\">\n * Active\n * </BadgeGlass>\n *\n * // Different variants (shadcn/ui compatible)\n * <BadgeGlass variant=\"default\">Default</BadgeGlass>\n * <BadgeGlass variant=\"secondary\">Secondary</BadgeGlass>\n * <BadgeGlass variant=\"destructive\">Error</BadgeGlass>\n * <BadgeGlass variant=\"outline\">Outline</BadgeGlass>\n *\n * // Extended Glass UI variants\n * <BadgeGlass variant=\"success\">Success</BadgeGlass>\n * <BadgeGlass variant=\"warning\">Warning</BadgeGlass>\n * <BadgeGlass variant=\"info\">Info</BadgeGlass>\n *\n * // With animated status dot\n * <BadgeGlass variant=\"success\" dot aria-label=\"Status: Online\">\n * Online\n * </BadgeGlass>\n * <BadgeGlass variant=\"destructive\" dot aria-label=\"Status: Offline\">\n * Offline\n * </BadgeGlass>\n *\n * // Size variants\n * <BadgeGlass size=\"sm\">Small</BadgeGlass>\n * <BadgeGlass size=\"default\">Medium</BadgeGlass>\n * <BadgeGlass size=\"lg\">Large</BadgeGlass>\n *\n * // Inside interactive elements (ensure accessible labels)\n * <button aria-label=\"Filter by active status\">\n * Filter: <BadgeGlass variant=\"success\">Active</BadgeGlass>\n * </button>\n *\n * // Count badge with semantic meaning\n * <div>\n * <span>Notifications</span>\n * <BadgeGlass variant=\"destructive\" aria-label=\"3 unread notifications\">\n * 3\n * </BadgeGlass>\n * </div>\n * ```\n */\nexport interface BadgeGlassProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'style'>, VariantProps<typeof badgeVariants> {\n readonly children: ReactNode;\n readonly variant?: BadgeVariant;\n readonly dot?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const BadgeGlass = forwardRef<HTMLSpanElement, BadgeGlassProps>(\n ({ children, className, variant = 'default', size = 'md', dot, ...props }, ref) => {\n const v = variantStyles[variant];\n\n return (\n <span\n ref={ref}\n className={cn(badgeVariants({ size }), className)}\n style={getBadgeStyles(variant)}\n {...props}\n >\n {dot && (\n <span\n className=\"w-1 h-1 md:w-1.5 md:h-1.5 rounded-full animate-pulse\"\n style={{ background: v.text }}\n />\n )}\n {children}\n </span>\n );\n }\n);\n\nBadgeGlass.displayName = 'BadgeGlass';\n"
19
+ "content": "/**\n * BadgeGlass Component\n *\n * Glass-themed badge with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - shadcn/ui compatible variants (default, secondary, destructive, outline)\n * - Extended Glass UI variants (success, warning, info)\n * - Size options\n * - Optional animated dot\n */\n\nimport { forwardRef, type ReactNode, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { badgeVariants, type BadgeVariant } from '@/lib/variants/badge-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\ntype BadgeStyleVars = { bg: string; text: string; border: string };\n\nconst variantStyles: Record<BadgeVariant, BadgeStyleVars> = {\n // shadcn/ui compatible variants\n default: {\n bg: 'var(--badge-default-bg)',\n text: 'var(--badge-default-text)',\n border: 'var(--badge-default-border)',\n },\n secondary: {\n bg: 'var(--badge-secondary-bg)',\n text: 'var(--badge-secondary-text)',\n border: 'var(--badge-secondary-border)',\n },\n destructive: {\n bg: 'var(--badge-destructive-bg)',\n text: 'var(--badge-destructive-text)',\n border: 'var(--badge-destructive-border)',\n },\n outline: {\n bg: 'var(--badge-outline-bg)',\n text: 'var(--badge-outline-text)',\n border: 'var(--badge-outline-border)',\n },\n // Glass UI extended variants\n success: {\n bg: 'var(--badge-success-bg)',\n text: 'var(--badge-success-text)',\n border: 'var(--badge-success-border)',\n },\n warning: {\n bg: 'var(--badge-warning-bg)',\n text: 'var(--badge-warning-text)',\n border: 'var(--badge-warning-border)',\n },\n info: {\n bg: 'var(--badge-info-bg)',\n text: 'var(--badge-info-text)',\n border: 'var(--badge-info-border)',\n },\n};\n\nconst getBadgeStyles = (variant: BadgeVariant): CSSProperties => {\n const v = variantStyles[variant] || variantStyles.default;\n return {\n background: v.bg,\n color: v.text,\n border: `1px solid ${v.border}`,\n };\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the BadgeGlass component\n *\n * A glass-themed badge with semantic variants and optional animated status dot.\n * Features shadcn/ui compatible variants plus extended Glass UI variants.\n *\n * @accessibility\n * - **Keyboard Navigation:** Badges are non-interactive by default (display-only)\n * - **Focus Management:** N/A - badges do not receive focus unless wrapped in interactive elements\n * - **Screen Readers:** Semantic `<span>` element, content announced naturally\n * - **Status Indicators:** Use `aria-label` to provide context for status badges (e.g., \"Status: Active\")\n * - **Animated Dot:** Pulse animation respects `prefers-reduced-motion` settings\n * - **Touch Targets:** N/A for display badges, ensure 44x44px if wrapping in button/link (WCAG 2.5.5)\n * - **Color Contrast:** All variant text meets WCAG AA contrast ratio 4.5:1 against badge background\n * - **Motion:** Dot pulse animation can be disabled for users with motion sensitivity\n *\n * @example\n * ```tsx\n * // Basic badge with variant\n * <BadgeGlass variant=\"default\">New</BadgeGlass>\n *\n * // Status badge with aria-label for screen readers\n * <BadgeGlass variant=\"success\" aria-label=\"Status: Active\">\n * Active\n * </BadgeGlass>\n *\n * // Different variants (shadcn/ui compatible)\n * <BadgeGlass variant=\"default\">Default</BadgeGlass>\n * <BadgeGlass variant=\"secondary\">Secondary</BadgeGlass>\n * <BadgeGlass variant=\"destructive\">Error</BadgeGlass>\n * <BadgeGlass variant=\"outline\">Outline</BadgeGlass>\n *\n * // Extended Glass UI variants\n * <BadgeGlass variant=\"success\">Success</BadgeGlass>\n * <BadgeGlass variant=\"warning\">Warning</BadgeGlass>\n * <BadgeGlass variant=\"info\">Info</BadgeGlass>\n *\n * // With animated status dot\n * <BadgeGlass variant=\"success\" dot aria-label=\"Status: Online\">\n * Online\n * </BadgeGlass>\n * <BadgeGlass variant=\"destructive\" dot aria-label=\"Status: Offline\">\n * Offline\n * </BadgeGlass>\n *\n * // Size variants\n * <BadgeGlass size=\"sm\">Small</BadgeGlass>\n * <BadgeGlass size=\"default\">Medium</BadgeGlass>\n * <BadgeGlass size=\"lg\">Large</BadgeGlass>\n *\n * // Inside interactive elements (ensure accessible labels)\n * <button aria-label=\"Filter by active status\">\n * Filter: <BadgeGlass variant=\"success\">Active</BadgeGlass>\n * </button>\n *\n * // Count badge with semantic meaning\n * <div>\n * <span>Notifications</span>\n * <BadgeGlass variant=\"destructive\" aria-label=\"3 unread notifications\">\n * 3\n * </BadgeGlass>\n * </div>\n * ```\n */\nexport interface BadgeGlassProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'style'>, VariantProps<typeof badgeVariants> {\n readonly children: ReactNode;\n readonly variant?: BadgeVariant;\n readonly dot?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const BadgeGlass = forwardRef<HTMLSpanElement, BadgeGlassProps>(\n ({ children, className, variant = 'default', size = 'md', dot, ...props }, ref) => {\n const v = variantStyles[variant];\n\n return (\n <span\n ref={ref}\n data-slot=\"badge\"\n className={cn(badgeVariants({ size }), className)}\n style={getBadgeStyles(variant)}\n {...props}\n >\n {dot && (\n <span\n className=\"w-1 h-1 md:w-1.5 md:h-1.5 rounded-full animate-pulse\"\n style={{ background: v.text }}\n />\n )}\n {children}\n </span>\n );\n }\n);\n\nBadgeGlass.displayName = 'BadgeGlass';\n"
20
20
  }
21
21
  ],
22
22
  "categories": [
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "path": "components/glass/ui/button-glass.tsx",
22
22
  "type": "registry:component",
23
- "content": "/**\n * ButtonGlass Component\n *\n * Glass-themed button with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - Glow effects on hover\n * - Ripple effect on click (JS)\n * - Shine animation for primary variant (JS)\n * - Loading state with spinner\n * - Icon support (left/right position)\n */\n\nimport {\n forwardRef,\n useState,\n useCallback,\n useEffect,\n useRef,\n type MouseEvent,\n type CSSProperties,\n} from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { type VariantProps } from 'class-variance-authority';\nimport { RefreshCw, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { buttonGlassVariants, type ButtonGlassVariant } from '@/lib/variants/button-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE STYLE MAPS\n// ========================================\n\nconst getVariantStyles = (\n variant: ButtonGlassVariant,\n isHovered: boolean,\n isFocusVisible: boolean\n): CSSProperties => {\n const baseStyles: Record<ButtonGlassVariant, CSSProperties> = {\n default: {\n background: isHovered ? 'var(--btn-primary-hover-bg)' : 'var(--btn-primary-bg)',\n color: 'var(--btn-primary-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-primary-glow)'\n : 'var(--btn-primary-shadow)',\n },\n secondary: {\n background: isHovered ? 'var(--btn-secondary-hover-bg)' : 'var(--btn-secondary-bg)',\n color: 'var(--btn-secondary-text)',\n border: '1px solid var(--btn-secondary-border)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-secondary-glow)'\n : 'none',\n },\n ghost: {\n background: isHovered ? 'var(--btn-ghost-hover-bg)' : 'var(--btn-ghost-bg)',\n color: 'var(--btn-ghost-text)',\n border: 'none',\n boxShadow: isFocusVisible ? 'var(--focus-glow)' : 'none',\n },\n destructive: {\n background: 'var(--btn-destructive-bg)',\n color: 'var(--btn-destructive-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-destructive-glow)'\n : 'var(--btn-destructive-shadow)',\n },\n outline: {\n background: isHovered ? 'var(--btn-outline-hover-bg)' : 'transparent',\n color: 'var(--btn-outline-text)',\n border: '1px solid var(--btn-outline-border)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-outline-glow)'\n : 'none',\n },\n success: {\n background: 'var(--btn-success-bg)',\n color: 'var(--btn-success-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-success-glow)'\n : 'var(--btn-success-shadow)',\n },\n link: {\n background: 'transparent',\n color: 'var(--btn-link-text)',\n border: 'none',\n boxShadow: isFocusVisible ? 'var(--focus-glow)' : 'none',\n textDecoration: isHovered ? 'underline' : 'none',\n },\n };\n\n return baseStyles[variant];\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the ButtonGlass component\n *\n * A glass-themed button with ripple effects, loading states, and icon support.\n * Features theme-aware styling and hover animations.\n *\n * **shadcn/ui compatible variants:**\n * - `default` - Primary action button with glow effects\n * - `secondary` - Secondary action with border\n * - `ghost` - Minimal visual presence\n * - `destructive` - Dangerous/delete actions (red)\n * - `outline` - Border with transparent background\n * - `link` - Text-only button with underline on hover\n * - `success` - Positive feedback (glass-ui extension)\n *\n * @accessibility\n * - **Keyboard Navigation:** Fully keyboard accessible with native `<button>` element\n * - **Focus Management:** Visible focus ring using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Semantic `<button>` element, disabled state announced automatically\n * - **Loading State:** When loading=true, button is disabled and loading spinner is visible\n * - **Touch Targets:** Minimum 44x44px touch target (WCAG 2.5.5) via size variants\n * - **Color Contrast:** All variants meet WCAG AA contrast ratio 4.5:1 minimum\n * - **Motion:** Respects `prefers-reduced-motion` for ripple/shine animations\n *\n * @example\n * ```tsx\n * // Basic button (default variant)\n * <ButtonGlass>Click me</ButtonGlass>\n * <ButtonGlass variant=\"default\">Primary action</ButtonGlass>\n *\n * // With icon and aria-label for icon-only buttons\n * <ButtonGlass icon={Check} iconPosition=\"left\">Save</ButtonGlass>\n * <ButtonGlass icon={X} size=\"icon\" aria-label=\"Close dialog\" />\n *\n * // Loading state (automatically disables and shows spinner)\n * <ButtonGlass loading aria-live=\"polite\">Processing...</ButtonGlass>\n *\n * // Different variants (shadcn/ui compatible)\n * <ButtonGlass variant=\"secondary\">Secondary</ButtonGlass>\n * <ButtonGlass variant=\"ghost\">Cancel</ButtonGlass>\n * <ButtonGlass variant=\"outline\">Outline</ButtonGlass>\n * <ButtonGlass variant=\"destructive\">Delete</ButtonGlass>\n * <ButtonGlass variant=\"link\">Learn more</ButtonGlass>\n * <ButtonGlass variant=\"success\">Confirm</ButtonGlass>\n *\n * // As a link (asChild pattern) - maintains semantic HTML\n * <ButtonGlass asChild>\n * <a href=\"/dashboard\">Go to Dashboard</a>\n * </ButtonGlass>\n *\n * // With Next.js Link\n * <ButtonGlass asChild variant=\"ghost\">\n * <Link href=\"/settings\">Settings</Link>\n * </ButtonGlass>\n *\n * // Form submit button\n * <ButtonGlass type=\"submit\">\n * Submit Form\n * </ButtonGlass>\n * ```\n */\nexport interface ButtonGlassProps\n extends\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'style'>,\n VariantProps<typeof buttonGlassVariants> {\n /**\n * Render as child element instead of button (polymorphic rendering).\n * Useful for rendering buttons as links or other interactive elements.\n *\n * **Note:** When using `asChild`, decorative effects (ripple, shine, glow)\n * are disabled to maintain compatibility with Radix UI Slot.\n * Only styles and event handlers are passed to the child element.\n *\n * @default false\n * @example\n * ```tsx\n * <ButtonGlass asChild>\n * <a href=\"/about\">About Us</a>\n * </ButtonGlass>\n * ```\n */\n readonly asChild?: boolean;\n\n /**\n * Visual style variant of the button (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly variant?: ButtonGlassVariant;\n\n /**\n * Show loading spinner and disable interactions\n * @default false\n */\n readonly loading?: boolean;\n\n /**\n * Icon component from lucide-react to display\n * @example icon={Check}\n */\n readonly icon?: LucideIcon;\n\n /**\n * Position of the icon relative to button text\n * @default \"left\"\n */\n readonly iconPosition?: 'left' | 'right';\n\n /**\n * Size variant of the button (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly size?: 'default' | 'sm' | 'lg' | 'xl' | 'icon';\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ButtonGlass = forwardRef<HTMLButtonElement, ButtonGlassProps>(\n (\n {\n asChild = false,\n className,\n variant = 'default',\n size = 'default',\n children,\n loading = false,\n disabled,\n icon: Icon,\n iconPosition = 'left',\n onClick,\n ...props\n },\n ref\n ) => {\n const { isHovered, hoverProps } = useHover();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const [ripple, setRipple] = useState<{ x: number; y: number } | null>(null);\n\n const isDisabled = disabled || loading;\n const rippleTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n // Cleanup ripple timeout on unmount\n useEffect(() => {\n return () => {\n if (rippleTimeoutRef.current) {\n clearTimeout(rippleTimeoutRef.current);\n }\n };\n }, []);\n\n // Ripple effect handler\n const handleClick = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n if (isDisabled) return;\n\n // Create ripple effect\n const rect = e.currentTarget.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n setRipple({ x, y });\n\n // Clear previous timeout if exists\n if (rippleTimeoutRef.current) {\n clearTimeout(rippleTimeoutRef.current);\n }\n\n rippleTimeoutRef.current = setTimeout(() => {\n setRipple(null);\n rippleTimeoutRef.current = null;\n }, 600);\n\n onClick?.(e);\n },\n [isDisabled, onClick]\n );\n\n // Polymorphic component - render as Slot when asChild is true\n const Comp = asChild ? Slot : 'button';\n\n return (\n <Comp\n ref={ref}\n className={cn(\n buttonGlassVariants({ variant, size }),\n isHovered && !isDisabled && 'scale-[1.02]',\n className\n )}\n style={{\n ...getVariantStyles(variant, isHovered && !isDisabled, isFocusVisible && !isDisabled),\n outline: 'none',\n }}\n type={asChild ? undefined : 'button'}\n disabled={isDisabled}\n onClick={handleClick}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n {...props}\n >\n {/* When asChild is true, only render children (Slot expects a single child) */}\n {asChild ? (\n children\n ) : (\n <>\n {/* Shine effect on hover for default variant */}\n {isHovered && variant === 'default' && !isDisabled && (\n <div\n className=\"absolute inset-0 overflow-hidden pointer-events-none\"\n style={{ borderRadius: 'inherit' }}\n >\n <div\n className=\"absolute top-0 h-full w-1/3 bg-linear-to-r from-transparent via-white/20 to-transparent\"\n style={{ animation: 'btn-shine 1.5s ease-in-out infinite' }}\n />\n </div>\n )}\n\n {/* Ripple effect */}\n {ripple && (\n <span\n className=\"absolute rounded-full bg-white/30 pointer-events-none\"\n style={{\n left: ripple.x,\n top: ripple.y,\n width: 10,\n height: 10,\n transform: 'translate(-50%, -50%)',\n animation: 'ripple 0.6s ease-out',\n }}\n />\n )}\n\n {/* Pulsing glow on hover */}\n {isHovered && variant === 'default' && !isDisabled && (\n <div\n className=\"absolute inset-0 rounded-xl animate-glow-pulse pointer-events-none\"\n style={{\n background: 'var(--btn-glow-radial)',\n }}\n />\n )}\n\n {/* Loading spinner */}\n {loading && <RefreshCw className={cn(ICON_SIZES.md, 'animate-spin')} />}\n\n {/* Icon left */}\n {!loading && Icon && iconPosition === 'left' && <Icon className={ICON_SIZES.md} />}\n\n {/* Content */}\n {!loading && children}\n\n {/* Icon right */}\n {!loading && Icon && iconPosition === 'right' && <Icon className={ICON_SIZES.md} />}\n </>\n )}\n </Comp>\n );\n }\n);\n\nButtonGlass.displayName = 'ButtonGlass';\n"
23
+ "content": "/**\n * ButtonGlass Component\n *\n * Glass-themed button with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - Glow effects on hover\n * - Ripple effect on click (JS)\n * - Shine animation for primary variant (JS)\n * - Loading state with spinner\n * - Icon support (left/right position)\n */\n\nimport {\n forwardRef,\n useState,\n useCallback,\n useEffect,\n useRef,\n type MouseEvent,\n type CSSProperties,\n} from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { type VariantProps } from 'class-variance-authority';\nimport { RefreshCw, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { buttonGlassVariants, type ButtonGlassVariant } from '@/lib/variants/button-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE STYLE MAPS\n// ========================================\n\nconst getVariantStyles = (\n variant: ButtonGlassVariant,\n isHovered: boolean,\n isFocusVisible: boolean\n): CSSProperties => {\n const baseStyles: Record<ButtonGlassVariant, CSSProperties> = {\n default: {\n background: isHovered ? 'var(--btn-primary-hover-bg)' : 'var(--btn-primary-bg)',\n color: 'var(--btn-primary-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-primary-glow)'\n : 'var(--btn-primary-shadow)',\n },\n secondary: {\n background: isHovered ? 'var(--btn-secondary-hover-bg)' : 'var(--btn-secondary-bg)',\n color: 'var(--btn-secondary-text)',\n border: '1px solid var(--btn-secondary-border)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-secondary-glow)'\n : 'none',\n },\n ghost: {\n background: isHovered ? 'var(--btn-ghost-hover-bg)' : 'var(--btn-ghost-bg)',\n color: 'var(--btn-ghost-text)',\n border: 'none',\n boxShadow: isFocusVisible ? 'var(--focus-glow)' : 'none',\n },\n destructive: {\n background: 'var(--btn-destructive-bg)',\n color: 'var(--btn-destructive-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-destructive-glow)'\n : 'var(--btn-destructive-shadow)',\n },\n outline: {\n background: isHovered ? 'var(--btn-outline-hover-bg)' : 'transparent',\n color: 'var(--btn-outline-text)',\n border: '1px solid var(--btn-outline-border)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-outline-glow)'\n : 'none',\n },\n success: {\n background: 'var(--btn-success-bg)',\n color: 'var(--btn-success-text)',\n border: 'none',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered\n ? 'var(--btn-success-glow)'\n : 'var(--btn-success-shadow)',\n },\n link: {\n background: 'transparent',\n color: 'var(--btn-link-text)',\n border: 'none',\n boxShadow: isFocusVisible ? 'var(--focus-glow)' : 'none',\n textDecoration: isHovered ? 'underline' : 'none',\n },\n };\n\n return baseStyles[variant];\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the ButtonGlass component\n *\n * A glass-themed button with ripple effects, loading states, and icon support.\n * Features theme-aware styling and hover animations.\n *\n * **shadcn/ui compatible variants:**\n * - `default` - Primary action button with glow effects\n * - `secondary` - Secondary action with border\n * - `ghost` - Minimal visual presence\n * - `destructive` - Dangerous/delete actions (red)\n * - `outline` - Border with transparent background\n * - `link` - Text-only button with underline on hover\n * - `success` - Positive feedback (glass-ui extension)\n *\n * @accessibility\n * - **Keyboard Navigation:** Fully keyboard accessible with native `<button>` element\n * - **Focus Management:** Visible focus ring using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Semantic `<button>` element, disabled state announced automatically\n * - **Loading State:** When loading=true, button is disabled and loading spinner is visible\n * - **Touch Targets:** Minimum 44x44px touch target (WCAG 2.5.5) via size variants\n * - **Color Contrast:** All variants meet WCAG AA contrast ratio 4.5:1 minimum\n * - **Motion:** Respects `prefers-reduced-motion` for ripple/shine animations\n *\n * @example\n * ```tsx\n * // Basic button (default variant)\n * <ButtonGlass>Click me</ButtonGlass>\n * <ButtonGlass variant=\"default\">Primary action</ButtonGlass>\n *\n * // With icon and aria-label for icon-only buttons\n * <ButtonGlass icon={Check} iconPosition=\"left\">Save</ButtonGlass>\n * <ButtonGlass icon={X} size=\"icon\" aria-label=\"Close dialog\" />\n *\n * // Loading state (automatically disables and shows spinner)\n * <ButtonGlass loading aria-live=\"polite\">Processing...</ButtonGlass>\n *\n * // Different variants (shadcn/ui compatible)\n * <ButtonGlass variant=\"secondary\">Secondary</ButtonGlass>\n * <ButtonGlass variant=\"ghost\">Cancel</ButtonGlass>\n * <ButtonGlass variant=\"outline\">Outline</ButtonGlass>\n * <ButtonGlass variant=\"destructive\">Delete</ButtonGlass>\n * <ButtonGlass variant=\"link\">Learn more</ButtonGlass>\n * <ButtonGlass variant=\"success\">Confirm</ButtonGlass>\n *\n * // As a link (asChild pattern) - maintains semantic HTML\n * <ButtonGlass asChild>\n * <a href=\"/dashboard\">Go to Dashboard</a>\n * </ButtonGlass>\n *\n * // With Next.js Link\n * <ButtonGlass asChild variant=\"ghost\">\n * <Link href=\"/settings\">Settings</Link>\n * </ButtonGlass>\n *\n * // Form submit button\n * <ButtonGlass type=\"submit\">\n * Submit Form\n * </ButtonGlass>\n * ```\n */\nexport interface ButtonGlassProps\n extends\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'style'>,\n VariantProps<typeof buttonGlassVariants> {\n /**\n * Render as child element instead of button (polymorphic rendering).\n * Useful for rendering buttons as links or other interactive elements.\n *\n * **Note:** When using `asChild`, decorative effects (ripple, shine, glow)\n * are disabled to maintain compatibility with Radix UI Slot.\n * Only styles and event handlers are passed to the child element.\n *\n * @default false\n * @example\n * ```tsx\n * <ButtonGlass asChild>\n * <a href=\"/about\">About Us</a>\n * </ButtonGlass>\n * ```\n */\n readonly asChild?: boolean;\n\n /**\n * Visual style variant of the button (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly variant?: ButtonGlassVariant;\n\n /**\n * Show loading spinner and disable interactions\n * @default false\n */\n readonly loading?: boolean;\n\n /**\n * Icon component from lucide-react to display\n * @example icon={Check}\n */\n readonly icon?: LucideIcon;\n\n /**\n * Position of the icon relative to button text\n * @default \"left\"\n */\n readonly iconPosition?: 'left' | 'right';\n\n /**\n * Size variant of the button (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly size?: 'default' | 'sm' | 'lg' | 'xl' | 'icon';\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ButtonGlass = forwardRef<HTMLButtonElement, ButtonGlassProps>(\n (\n {\n asChild = false,\n className,\n variant = 'default',\n size = 'default',\n children,\n loading = false,\n disabled,\n icon: Icon,\n iconPosition = 'left',\n onClick,\n ...props\n },\n ref\n ) => {\n const { isHovered, hoverProps } = useHover();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const [ripple, setRipple] = useState<{ x: number; y: number } | null>(null);\n\n const isDisabled = disabled || loading;\n const rippleTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n // Cleanup ripple timeout on unmount\n useEffect(() => {\n return () => {\n if (rippleTimeoutRef.current) {\n clearTimeout(rippleTimeoutRef.current);\n }\n };\n }, []);\n\n // Ripple effect handler\n const handleClick = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n if (isDisabled) return;\n\n // Create ripple effect\n const rect = e.currentTarget.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n setRipple({ x, y });\n\n // Clear previous timeout if exists\n if (rippleTimeoutRef.current) {\n clearTimeout(rippleTimeoutRef.current);\n }\n\n rippleTimeoutRef.current = setTimeout(() => {\n setRipple(null);\n rippleTimeoutRef.current = null;\n }, 600);\n\n onClick?.(e);\n },\n [isDisabled, onClick]\n );\n\n // Polymorphic component - render as Slot when asChild is true\n const Comp = asChild ? Slot : 'button';\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button\"\n className={cn(\n buttonGlassVariants({ variant, size }),\n isHovered && !isDisabled && 'scale-[1.02]',\n className\n )}\n style={{\n ...getVariantStyles(variant, isHovered && !isDisabled, isFocusVisible && !isDisabled),\n outline: 'none',\n }}\n type={asChild ? undefined : 'button'}\n disabled={isDisabled}\n onClick={handleClick}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n {...props}\n >\n {/* When asChild is true, only render children (Slot expects a single child) */}\n {asChild ? (\n children\n ) : (\n <>\n {/* Shine effect on hover for default variant */}\n {isHovered && variant === 'default' && !isDisabled && (\n <div\n className=\"absolute inset-0 overflow-hidden pointer-events-none\"\n style={{ borderRadius: 'inherit' }}\n >\n <div\n className=\"absolute top-0 h-full w-1/3 bg-linear-to-r from-transparent via-white/20 to-transparent\"\n style={{ animation: 'btn-shine 1.5s ease-in-out infinite' }}\n />\n </div>\n )}\n\n {/* Ripple effect */}\n {ripple && (\n <span\n className=\"absolute rounded-full bg-white/30 pointer-events-none\"\n style={{\n left: ripple.x,\n top: ripple.y,\n width: 10,\n height: 10,\n transform: 'translate(-50%, -50%)',\n animation: 'ripple 0.6s ease-out',\n }}\n />\n )}\n\n {/* Pulsing glow on hover */}\n {isHovered && variant === 'default' && !isDisabled && (\n <div\n className=\"absolute inset-0 rounded-xl animate-glow-pulse pointer-events-none\"\n style={{\n background: 'var(--btn-glow-radial)',\n }}\n />\n )}\n\n {/* Loading spinner */}\n {loading && <RefreshCw className={cn(ICON_SIZES.md, 'animate-spin')} />}\n\n {/* Icon left */}\n {!loading && Icon && iconPosition === 'left' && <Icon className={ICON_SIZES.md} />}\n\n {/* Content */}\n {!loading && children}\n\n {/* Icon right */}\n {!loading && Icon && iconPosition === 'right' && <Icon className={ICON_SIZES.md} />}\n </>\n )}\n </Comp>\n );\n }\n);\n\nButtonGlass.displayName = 'ButtonGlass';\n"
24
24
  }
25
25
  ],
26
26
  "categories": [
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "card-glass",
4
+ "type": "registry:ui",
5
+ "title": "Card Glass",
6
+ "description": "CardGlass Compound Component",
7
+ "dependencies": [
8
+ "react"
9
+ ],
10
+ "registryDependencies": [
11
+ "cn",
12
+ "variants"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "components/glass/ui/card-glass.tsx",
17
+ "type": "registry:component",
18
+ "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * CardGlass Compound Component\n *\n * A glass-themed card with compound sub-components matching shadcn/ui Card API.\n * Provides structure (Header, Content, Footer) with glass visual effects.\n *\n * @example\n * ```tsx\n * // Object pattern (recommended)\n * <CardGlass.Root intensity=\"medium\" glow=\"blue\">\n * <CardGlass.Header>\n * <CardGlass.Title>Card Title</CardGlass.Title>\n * <CardGlass.Description>Card description</CardGlass.Description>\n * <CardGlass.Action>\n * <ButtonGlass size=\"sm\">Action</ButtonGlass>\n * </CardGlass.Action>\n * </CardGlass.Header>\n * <CardGlass.Content>\n * <p>Main content goes here</p>\n * </CardGlass.Content>\n * <CardGlass.Footer>\n * <ButtonGlass variant=\"ghost\">Cancel</ButtonGlass>\n * <ButtonGlass>Save</ButtonGlass>\n * </CardGlass.Footer>\n * </CardGlass.Root>\n *\n * // Named exports (shadcn/ui compatible)\n * import { CardGlassRoot, CardGlassHeader, CardGlassTitle } from '@/components/glass/ui';\n * ```\n *\n * @see GlassCard - Simple glass container without structure\n * @see Card (shadcn/ui) - Reference implementation\n */\n\nimport * as React from 'react';\nimport { type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport type { GlowType, IntensityType } from '@/lib/variants/glass-card-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface CardGlassRootProps extends React.ComponentProps<'div'> {\n /**\n * Glass blur intensity\n * @default 'medium'\n */\n intensity?: IntensityType;\n /**\n * Glow effect color\n * @default null\n */\n glow?: GlowType;\n /**\n * Enable hover effects\n * @default false\n */\n hover?: boolean;\n}\n\n// ========================================\n// STYLE MAPS (CSS Variables)\n// ========================================\n\nconst blurMap: Record<IntensityType, string> = {\n subtle: 'var(--blur-sm)', // 8px\n medium: 'var(--blur-md)', // 16px\n strong: 'var(--blur-lg)', // 24px\n};\n\nconst bgVarMap: Record<IntensityType, string> = {\n subtle: 'var(--card-subtle-bg)',\n medium: 'var(--card-medium-bg)',\n strong: 'var(--card-strong-bg)',\n};\n\nconst borderVarMap: Record<IntensityType, string> = {\n subtle: 'var(--card-subtle-border)',\n medium: 'var(--card-medium-border)',\n strong: 'var(--card-strong-border)',\n};\n\nconst glowVarMap: Record<string, string> = {\n blue: 'var(--glow-blue)',\n violet: 'var(--glow-violet)',\n cyan: 'var(--glow-cyan)',\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\n/**\n * CardGlass Root - Glass-themed card container\n *\n * @accessibility\n * - Uses semantic div element\n * - Color contrast meets WCAG AA\n * - Hover effects are decorative only\n */\nfunction CardGlassRoot({\n className,\n intensity = 'medium',\n glow = null,\n hover = false,\n style,\n ...props\n}: CardGlassRootProps) {\n const cardStyles: CSSProperties = {\n background: bgVarMap[intensity],\n borderColor: borderVarMap[intensity],\n backdropFilter: `blur(${blurMap[intensity]})`,\n WebkitBackdropFilter: `blur(${blurMap[intensity]})`,\n boxShadow: glow ? glowVarMap[glow] : 'var(--glow-neutral)',\n ...style,\n };\n\n return (\n <div\n data-slot=\"card\"\n className={cn(\n 'flex flex-col gap-6 rounded-xl border py-6 transition-all duration-300',\n 'text-[var(--text-primary)]',\n hover && 'hover:shadow-lg hover:border-[var(--card-hover-border)] cursor-pointer',\n className\n )}\n style={cardStyles}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: HEADER\n// ========================================\n\n/**\n * CardGlass Header - Container for title, description, and action\n *\n * Uses CSS Grid for layout with automatic action positioning.\n */\nfunction CardGlassHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6',\n 'has-data-[slot=card-action]:grid-cols-[1fr_auto]',\n '[.border-b]:pb-6',\n className\n )}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: TITLE\n// ========================================\n\n/**\n * CardGlass Title - Card title text\n */\nfunction CardGlassTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"card-title\"\n className={cn('leading-none font-semibold text-[var(--text-primary)]', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: DESCRIPTION\n// ========================================\n\n/**\n * CardGlass Description - Card description/subtitle text\n */\nfunction CardGlassDescription({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"card-description\"\n className={cn('text-sm text-[var(--text-muted)]', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: ACTION\n// ========================================\n\n/**\n * CardGlass Action - Positioned action slot in header\n *\n * Automatically positioned to the right of title/description.\n */\nfunction CardGlassAction({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"card-action\"\n className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\n/**\n * CardGlass Content - Main content area\n */\nfunction CardGlassContent({ className, ...props }: React.ComponentProps<'div'>) {\n return <div data-slot=\"card-content\" className={cn('px-6', className)} {...props} />;\n}\n\n// ========================================\n// COMPOUND COMPONENT: FOOTER\n// ========================================\n\n/**\n * CardGlass Footer - Footer area with flex layout\n */\nfunction CardGlassFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn('flex items-center gap-2 px-6 [.border-t]:pt-6', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// EXPORTS: OBJECT PATTERN\n// ========================================\n\n/**\n * CardGlass - Compound Component API\n *\n * @example\n * ```tsx\n * <CardGlass.Root intensity=\"medium\">\n * <CardGlass.Header>\n * <CardGlass.Title>Title</CardGlass.Title>\n * <CardGlass.Description>Description</CardGlass.Description>\n * </CardGlass.Header>\n * <CardGlass.Content>Content</CardGlass.Content>\n * <CardGlass.Footer>Footer</CardGlass.Footer>\n * </CardGlass.Root>\n * ```\n */\nexport const CardGlass = {\n Root: CardGlassRoot,\n Header: CardGlassHeader,\n Title: CardGlassTitle,\n Description: CardGlassDescription,\n Action: CardGlassAction,\n Content: CardGlassContent,\n Footer: CardGlassFooter,\n};\n\n// ========================================\n// EXPORTS: NAMED (shadcn/ui compatible)\n// ========================================\n\nexport {\n CardGlassRoot,\n CardGlassHeader,\n CardGlassTitle,\n CardGlassDescription,\n CardGlassAction,\n CardGlassContent,\n CardGlassFooter,\n};\n"
19
+ }
20
+ ],
21
+ "categories": [
22
+ "ui"
23
+ ],
24
+ "cssVars": {
25
+ "light": {
26
+ "--glass-bg": "rgba(255, 255, 255, 0.1)",
27
+ "--glass-border": "rgba(255, 255, 255, 0.2)",
28
+ "--blur-sm": "8px",
29
+ "--blur-md": "16px",
30
+ "--blur-lg": "24px"
31
+ },
32
+ "dark": {
33
+ "--glass-bg": "rgba(255, 255, 255, 0.05)",
34
+ "--glass-border": "rgba(255, 255, 255, 0.1)",
35
+ "--blur-sm": "8px",
36
+ "--blur-md": "16px",
37
+ "--blur-lg": "24px"
38
+ }
39
+ }
40
+ }
@@ -18,7 +18,7 @@
18
18
  {
19
19
  "path": "components/glass/ui/input-glass.tsx",
20
20
  "type": "registry:component",
21
- "content": "/**\n * InputGlass Component\n *\n * Glass-themed input with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - Focus glow effects\n * - Error/success states\n * - Icon support (left/right position)\n * - Backdrop blur effect\n */\n\nimport {\n forwardRef,\n useCallback,\n type InputHTMLAttributes,\n type CSSProperties,\n type FocusEvent,\n} from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { inputVariants } from '@/lib/variants/input-glass-variants';\nimport { ICON_SIZES, FormFieldWrapper } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\nconst getInputStyles = (\n isFocused: boolean,\n error?: string,\n success?: string\n): CSSProperties => {\n // Determine border color based on state\n let borderColor = 'var(--input-border)';\n if (error) {\n borderColor = 'var(--alert-danger-text)';\n } else if (success) {\n borderColor = 'var(--alert-success-text)';\n } else if (isFocused) {\n borderColor = 'var(--input-focus-border)';\n }\n\n return {\n background: 'var(--input-bg)',\n border: `1px solid ${borderColor}`,\n color: 'var(--input-text)',\n boxShadow: isFocused ? 'var(--focus-glow)' : 'none',\n };\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the InputGlass component\n *\n * A glass-themed input field with labels, validation states, and icon support.\n * Features focus glow effects and theme-aware styling.\n *\n * @accessibility\n * - **Keyboard Navigation:** Full keyboard support with native `<input>` element, standard tab navigation\n * - **Focus Management:** Visible focus ring using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Semantic `<label>` elements properly associated via `htmlFor`, error/success messages announced with `aria-describedby`\n * - **Error State:** Red border and error message displayed below input, programmatically associated for screen readers\n * - **Success State:** Green border and success message displayed below input, programmatically associated for screen readers\n * - **Touch Targets:** Minimum 44x44px touch target on mobile devices (WCAG 2.5.5)\n * - **Color Contrast:** All text meets WCAG AA contrast ratio 4.5:1 minimum against backgrounds\n * - **Motion:** Icon color transitions respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic input with label\n * <InputGlass label=\"Email\" placeholder=\"you@example.com\" />\n *\n * // With aria-describedby for additional context\n * <InputGlass\n * label=\"Username\"\n * placeholder=\"Enter username\"\n * aria-describedby=\"username-help\"\n * />\n * <span id=\"username-help\">Must be 3-20 characters</span>\n *\n * // With validation states (automatically announced to screen readers)\n * <InputGlass label=\"Username\" error=\"Username is required\" />\n * <InputGlass label=\"Password\" success=\"Strong password\" type=\"password\" />\n *\n * // With icon and accessible label\n * <InputGlass\n * icon={Search}\n * placeholder=\"Search...\"\n * aria-label=\"Search products\"\n * />\n * <InputGlass icon={Mail} iconPosition=\"right\" type=\"email\" />\n *\n * // Disabled state (automatically announced)\n * <InputGlass label=\"Email\" disabled value=\"locked@example.com\" />\n *\n * // Form integration with accessible error handling\n * <form onSubmit={handleSubmit}>\n * <InputGlass\n * label=\"Email\"\n * type=\"email\"\n * required\n * error={errors.email}\n * aria-invalid={!!errors.email}\n * />\n * <InputGlass\n * label=\"Password\"\n * type=\"password\"\n * icon={Lock}\n * error={errors.password}\n * />\n * <ButtonGlass type=\"submit\">Sign In</ButtonGlass>\n * </form>\n * ```\n */\nexport interface InputGlassProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>,\n VariantProps<typeof inputVariants> {\n /**\n * Label text displayed above the input field\n */\n readonly label?: string;\n\n /**\n * Error message to display below the input (red styling)\n */\n readonly error?: string;\n\n /**\n * Success message to display below the input (green styling)\n */\n readonly success?: string;\n\n /**\n * Icon component from lucide-react to display\n * @example icon={Search}\n */\n readonly icon?: LucideIcon;\n\n /**\n * Position of the icon relative to input text\n * @default \"left\"\n */\n readonly iconPosition?: 'left' | 'right';\n\n /**\n * @deprecated Use `size` prop instead. Will be removed in v4.0\n * @default \"md\"\n */\n readonly inputSize?: 'sm' | 'md' | 'lg';\n\n // Note: size prop comes from VariantProps<typeof inputVariants>\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const InputGlass = forwardRef<HTMLInputElement, InputGlassProps>(\n (\n {\n className,\n size,\n inputSize,\n label,\n error,\n success,\n icon: Icon,\n iconPosition = 'left',\n disabled,\n onFocus,\n onBlur,\n ...props\n },\n ref\n ) => {\n // Determine size value with fallback to deprecated inputSize prop\n const sizeValue = size ?? inputSize ?? 'md';\n\n // Deprecation warning in development mode\n if (process.env.NODE_ENV !== 'production' && inputSize !== undefined) {\n console.warn(\n '[InputGlass] The `inputSize` prop is deprecated and will be removed in v4.0. Use `size` instead.'\n );\n }\n\n const { isFocused, focusProps } = useFocus();\n\n // Wrap focus handlers to call both internal and external callbacks\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n focusProps.onFocus(e);\n onFocus?.(e);\n },\n [focusProps, onFocus]\n );\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n focusProps.onBlur(e);\n onBlur?.(e);\n },\n [focusProps, onBlur]\n );\n\n const hasIcon = Boolean(Icon);\n const paddingLeft = hasIcon && iconPosition === 'left' ? 'pl-10' : '';\n const paddingRight = hasIcon && iconPosition === 'right' ? 'pr-10' : '';\n\n return (\n <FormFieldWrapper\n label={label}\n error={error}\n success={success}\n htmlFor={props.id}\n className={className}\n >\n <div className=\"relative\">\n {Icon && iconPosition === 'left' && (\n <Icon\n className={cn(\n 'absolute left-2.5 md:left-3 top-1/2 -translate-y-1/2 transition-colors duration-300 z-10',\n ICON_SIZES.md\n )}\n style={{\n color: isFocused ? 'var(--text-accent)' : 'var(--text-muted)',\n }}\n />\n )}\n <input\n ref={ref}\n className={cn(\n inputVariants({ size: sizeValue }),\n paddingLeft,\n paddingRight\n )}\n style={getInputStyles(isFocused, error, success)}\n disabled={disabled}\n onFocus={handleFocus}\n onBlur={handleBlur}\n {...props}\n />\n {Icon && iconPosition === 'right' && (\n <Icon\n className={cn(\n 'absolute right-2.5 md:right-3 top-1/2 -translate-y-1/2 transition-colors duration-300 z-10',\n ICON_SIZES.md\n )}\n style={{\n color: isFocused ? 'var(--text-accent)' : 'var(--text-muted)',\n }}\n />\n )}\n </div>\n </FormFieldWrapper>\n );\n }\n);\n\nInputGlass.displayName = 'InputGlass';\n"
21
+ "content": "/**\n * InputGlass Component\n *\n * Glass-themed input with:\n * - Theme-aware styling via CSS variables (glass/light/aurora)\n * - Focus glow effects\n * - Error/success states\n * - Icon support (left/right position)\n * - Backdrop blur effect\n */\n\nimport {\n forwardRef,\n useCallback,\n type InputHTMLAttributes,\n type CSSProperties,\n type FocusEvent,\n} from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { inputVariants } from '@/lib/variants/input-glass-variants';\nimport { ICON_SIZES, FormFieldWrapper } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// CSS VARIABLE HELPERS\n// ========================================\n\nconst getInputStyles = (isFocused: boolean, error?: string, success?: string): CSSProperties => {\n // Determine border color based on state\n let borderColor = 'var(--input-border)';\n if (error) {\n borderColor = 'var(--alert-danger-text)';\n } else if (success) {\n borderColor = 'var(--alert-success-text)';\n } else if (isFocused) {\n borderColor = 'var(--input-focus-border)';\n }\n\n return {\n background: 'var(--input-bg)',\n border: `1px solid ${borderColor}`,\n color: 'var(--input-text)',\n boxShadow: isFocused ? 'var(--focus-glow)' : 'none',\n };\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for the InputGlass component\n *\n * A glass-themed input field with labels, validation states, and icon support.\n * Features focus glow effects and theme-aware styling.\n *\n * @accessibility\n * - **Keyboard Navigation:** Full keyboard support with native `<input>` element, standard tab navigation\n * - **Focus Management:** Visible focus ring using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Semantic `<label>` elements properly associated via `htmlFor`, error/success messages announced with `aria-describedby`\n * - **Error State:** Red border and error message displayed below input, programmatically associated for screen readers\n * - **Success State:** Green border and success message displayed below input, programmatically associated for screen readers\n * - **Touch Targets:** Minimum 44x44px touch target on mobile devices (WCAG 2.5.5)\n * - **Color Contrast:** All text meets WCAG AA contrast ratio 4.5:1 minimum against backgrounds\n * - **Motion:** Icon color transitions respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic input with label\n * <InputGlass label=\"Email\" placeholder=\"you@example.com\" />\n *\n * // With aria-describedby for additional context\n * <InputGlass\n * label=\"Username\"\n * placeholder=\"Enter username\"\n * aria-describedby=\"username-help\"\n * />\n * <span id=\"username-help\">Must be 3-20 characters</span>\n *\n * // With validation states (automatically announced to screen readers)\n * <InputGlass label=\"Username\" error=\"Username is required\" />\n * <InputGlass label=\"Password\" success=\"Strong password\" type=\"password\" />\n *\n * // With icon and accessible label\n * <InputGlass\n * icon={Search}\n * placeholder=\"Search...\"\n * aria-label=\"Search products\"\n * />\n * <InputGlass icon={Mail} iconPosition=\"right\" type=\"email\" />\n *\n * // Disabled state (automatically announced)\n * <InputGlass label=\"Email\" disabled value=\"locked@example.com\" />\n *\n * // Form integration with accessible error handling\n * <form onSubmit={handleSubmit}>\n * <InputGlass\n * label=\"Email\"\n * type=\"email\"\n * required\n * error={errors.email}\n * aria-invalid={!!errors.email}\n * />\n * <InputGlass\n * label=\"Password\"\n * type=\"password\"\n * icon={Lock}\n * error={errors.password}\n * />\n * <ButtonGlass type=\"submit\">Sign In</ButtonGlass>\n * </form>\n * ```\n */\nexport interface InputGlassProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>, VariantProps<typeof inputVariants> {\n /**\n * Label text displayed above the input field\n */\n readonly label?: string;\n\n /**\n * Error message to display below the input (red styling)\n */\n readonly error?: string;\n\n /**\n * Success message to display below the input (green styling)\n */\n readonly success?: string;\n\n /**\n * Icon component from lucide-react to display\n * @example icon={Search}\n */\n readonly icon?: LucideIcon;\n\n /**\n * Position of the icon relative to input text\n * @default \"left\"\n */\n readonly iconPosition?: 'left' | 'right';\n\n /**\n * @deprecated Use `size` prop instead. Will be removed in v4.0\n * @default \"md\"\n */\n readonly inputSize?: 'sm' | 'md' | 'lg';\n\n // Note: size prop comes from VariantProps<typeof inputVariants>\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const InputGlass = forwardRef<HTMLInputElement, InputGlassProps>(\n (\n {\n className,\n size,\n inputSize,\n label,\n error,\n success,\n icon: Icon,\n iconPosition = 'left',\n disabled,\n onFocus,\n onBlur,\n ...props\n },\n ref\n ) => {\n // Determine size value with fallback to deprecated inputSize prop\n const sizeValue = size ?? inputSize ?? 'md';\n\n // Deprecation warning in development mode\n if (process.env.NODE_ENV !== 'production' && inputSize !== undefined) {\n console.warn(\n '[InputGlass] The `inputSize` prop is deprecated and will be removed in v4.0. Use `size` instead.'\n );\n }\n\n const { isFocused, focusProps } = useFocus();\n\n // Wrap focus handlers to call both internal and external callbacks\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n focusProps.onFocus(e);\n onFocus?.(e);\n },\n [focusProps, onFocus]\n );\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n focusProps.onBlur(e);\n onBlur?.(e);\n },\n [focusProps, onBlur]\n );\n\n const hasIcon = Boolean(Icon);\n const paddingLeft = hasIcon && iconPosition === 'left' ? 'pl-10' : '';\n const paddingRight = hasIcon && iconPosition === 'right' ? 'pr-10' : '';\n\n return (\n <FormFieldWrapper\n label={label}\n error={error}\n success={success}\n htmlFor={props.id}\n className={className}\n >\n <div className=\"relative\">\n {Icon && iconPosition === 'left' && (\n <Icon\n className={cn(\n 'absolute left-2.5 md:left-3 top-1/2 -translate-y-1/2 transition-colors duration-300 z-10',\n ICON_SIZES.md\n )}\n style={{\n color: isFocused ? 'var(--text-accent)' : 'var(--text-muted)',\n }}\n />\n )}\n <input\n ref={ref}\n data-slot=\"input\"\n className={cn(inputVariants({ size: sizeValue }), paddingLeft, paddingRight)}\n style={getInputStyles(isFocused, error, success)}\n disabled={disabled}\n onFocus={handleFocus}\n onBlur={handleBlur}\n {...props}\n />\n {Icon && iconPosition === 'right' && (\n <Icon\n className={cn(\n 'absolute right-2.5 md:right-3 top-1/2 -translate-y-1/2 transition-colors duration-300 z-10',\n ICON_SIZES.md\n )}\n style={{\n color: isFocused ? 'var(--text-accent)' : 'var(--text-muted)',\n }}\n />\n )}\n </div>\n </FormFieldWrapper>\n );\n }\n);\n\nInputGlass.displayName = 'InputGlass';\n"
22
22
  }
23
23
  ],
24
24
  "categories": [
@@ -3,22 +3,22 @@
3
3
  "name": "modal-glass",
4
4
  "type": "registry:ui",
5
5
  "title": "Modal Glass",
6
- "description": "ModalGlass Component (Compound API only)",
6
+ "description": "ModalGlass Component (Radix UI Dialog-based)",
7
7
  "dependencies": [
8
- "lucide-react"
8
+ "@radix-ui/react-dialog",
9
+ "lucide-react",
10
+ "react"
9
11
  ],
10
12
  "registryDependencies": [
11
13
  "cn",
12
14
  "primitives",
13
- "use-focus",
14
- "use-hover",
15
15
  "variants"
16
16
  ],
17
17
  "files": [
18
18
  {
19
19
  "path": "components/glass/ui/modal-glass.tsx",
20
20
  "type": "registry:component",
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"
21
+ "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * ModalGlass Component (Radix UI Dialog-based)\n *\n * Glass-themed modal/dialog with full shadcn/ui Dialog API compatibility.\n * Built on @radix-ui/react-dialog for accessibility and state management.\n *\n * @example Uncontrolled with Trigger (shadcn/ui pattern)\n * ```tsx\n * <ModalGlass.Root>\n * <ModalGlass.Trigger asChild>\n * <ButtonGlass>Open Dialog</ButtonGlass>\n * </ModalGlass.Trigger>\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Dialog Title</ModalGlass.Title>\n * <ModalGlass.Description>Dialog description</ModalGlass.Description>\n * </ModalGlass.Header>\n * <ModalGlass.Body>Content here</ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ModalGlass.Close asChild>\n * <ButtonGlass variant=\"ghost\">Cancel</ButtonGlass>\n * </ModalGlass.Close>\n * <ButtonGlass>Confirm</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n *\n * @example Controlled mode (programmatic)\n * ```tsx\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Confirm Action</ModalGlass.Title>\n * </ModalGlass.Header>\n * <ModalGlass.Body>Are you sure?</ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ButtonGlass onClick={() => setOpen(false)}>Cancel</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n *\n * @accessibility\n * - Built on Radix UI Dialog with full WCAG 2.1 AA compliance\n * - Keyboard: Escape to close, Tab for focus trap\n * - Screen Readers: role=\"dialog\", aria-modal, aria-labelledby, aria-describedby\n * - Focus Management: Auto-focus on open, return focus on close\n *\n * @since v2.0.0 - Migrated to Radix UI Dialog, added Trigger and Portal\n */\n\nimport * as React from 'react';\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { modalSizes, type ModalSize } from '@/lib/variants/modal-glass-variants';\nimport { ICON_SIZES } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// CONTEXT FOR SIZE (optional enhancement)\n// ========================================\n\ninterface ModalContextValue {\n size: ModalSize;\n}\n\nconst ModalContext = React.createContext<ModalContextValue>({ size: 'sm' });\n\nconst useModalContext = () => React.useContext(ModalContext);\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\ninterface ModalRootProps extends React.ComponentProps<typeof DialogPrimitive.Root> {\n /** Size variant for the modal */\n size?: ModalSize;\n}\n\n/**\n * ModalGlass.Root - Dialog root component\n *\n * Supports both controlled (open/onOpenChange) and uncontrolled (with Trigger) modes.\n */\nfunction ModalRoot({ size = 'sm', children, ...props }: ModalRootProps) {\n return (\n <ModalContext.Provider value={{ size }}>\n <DialogPrimitive.Root data-slot=\"dialog\" {...props}>\n {children}\n </DialogPrimitive.Root>\n </ModalContext.Provider>\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\n/**\n * ModalGlass.Trigger - Opens the modal when clicked\n *\n * Use `asChild` to render as the child element instead of a button.\n */\nfunction ModalTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\n// ========================================\n// COMPOUND COMPONENT: PORTAL\n// ========================================\n\n/**\n * ModalGlass.Portal - Renders children into a portal\n */\nfunction ModalPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\n// ========================================\n// COMPOUND COMPONENT: OVERLAY\n// ========================================\n\n/**\n * ModalGlass.Overlay - Backdrop with glass blur effect\n */\nconst ModalOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n data-slot=\"dialog-overlay\"\n className={cn(\n 'fixed inset-0 z-50',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n className\n )}\n style={{\n background: 'var(--modal-overlay)',\n backdropFilter: 'blur(var(--blur-sm))',\n WebkitBackdropFilter: 'blur(var(--blur-sm))',\n }}\n {...props}\n />\n));\nModalOverlay.displayName = 'ModalOverlay';\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ninterface ModalContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /** Show close button in top-right corner */\n showCloseButton?: boolean;\n /** Override size from Root */\n size?: ModalSize;\n}\n\n/**\n * ModalGlass.Content - Main modal container with glass styling\n *\n * Automatically renders Portal and Overlay.\n */\nconst ModalContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n ModalContentProps\n>(({ className, children, showCloseButton = true, size: sizeProp, ...props }, ref) => {\n const { size: contextSize } = useModalContext();\n const size = sizeProp ?? contextSize;\n\n return (\n <ModalPortal>\n <ModalOverlay />\n <DialogPrimitive.Content\n ref={ref}\n data-slot=\"dialog-content\"\n className={cn(\n modalSizes({ size }),\n 'fixed top-[50%] left-[50%] z-50',\n 'translate-x-[-50%] translate-y-[-50%]',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',\n 'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',\n 'duration-200',\n className\n )}\n style={{\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 }}\n {...props}\n >\n {/* Glow effect layer */}\n <div\n className=\"absolute inset-0 rounded-3xl pointer-events-none\"\n style={{ background: 'var(--modal-glow-effect)' }}\n aria-hidden=\"true\"\n />\n {/* Content */}\n <div className=\"relative\">{children}</div>\n {/* Close button */}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className={cn(\n 'absolute top-4 right-4',\n 'p-1.5 md:p-2 rounded-xl',\n 'transition-all duration-300',\n 'ring-offset-background',\n 'focus:outline-none focus:ring-2 focus:ring-(--semantic-primary) focus:ring-offset-2',\n 'hover:opacity-100 opacity-70',\n 'disabled:pointer-events-none'\n )}\n style={{\n background: 'var(--modal-close-btn-bg)',\n border: 'var(--modal-close-btn-border)',\n color: 'var(--text-muted)',\n }}\n >\n <X className={ICON_SIZES.md} />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </ModalPortal>\n );\n});\nModalContent.displayName = 'ModalContent';\n\n// ========================================\n// COMPOUND COMPONENT: HEADER\n// ========================================\n\n/**\n * ModalGlass.Header - Header section with flex layout\n */\nfunction ModalHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn('flex flex-col gap-2 text-center sm:text-left mb-4', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: BODY\n// ========================================\n\n/**\n * ModalGlass.Body - Main content area\n *\n * Note: shadcn/ui Dialog doesn't have DialogBody, but we keep it for convenience.\n */\nfunction ModalBody({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-body\"\n className={cn('relative', className)}\n style={{ color: 'var(--text-secondary)' }}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: FOOTER\n// ========================================\n\n/**\n * ModalGlass.Footer - Footer section with flex layout for actions\n */\nfunction ModalFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end mt-4', className)}\n {...props}\n />\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: TITLE\n// ========================================\n\n/**\n * ModalGlass.Title - Modal title with proper accessibility\n */\nconst ModalTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n data-slot=\"dialog-title\"\n className={cn('text-lg md:text-xl font-semibold leading-none tracking-tight', className)}\n style={{ color: 'var(--text-primary)' }}\n {...props}\n />\n));\nModalTitle.displayName = 'ModalTitle';\n\n// ========================================\n// COMPOUND COMPONENT: DESCRIPTION\n// ========================================\n\n/**\n * ModalGlass.Description - Modal description text\n */\nconst ModalDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n data-slot=\"dialog-description\"\n className={cn('text-sm', className)}\n style={{ color: 'var(--text-muted)' }}\n {...props}\n />\n));\nModalDescription.displayName = 'ModalDescription';\n\n// ========================================\n// COMPOUND COMPONENT: CLOSE\n// ========================================\n\n/**\n * ModalGlass.Close - Closes the modal when clicked\n *\n * Use `asChild` to render as the child element.\n *\n * @example\n * ```tsx\n * <ModalGlass.Close asChild>\n * <ButtonGlass variant=\"ghost\">Cancel</ButtonGlass>\n * </ModalGlass.Close>\n * ```\n */\nfunction ModalClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\n// ========================================\n// EXPORT COMPOUND COMPONENT\n// ========================================\n\n/**\n * ModalGlass - Glass-themed Dialog with shadcn/ui API compatibility\n *\n * Built on @radix-ui/react-dialog for full accessibility support.\n *\n * @example Uncontrolled (with Trigger)\n * ```tsx\n * <ModalGlass.Root>\n * <ModalGlass.Trigger asChild>\n * <ButtonGlass>Open</ButtonGlass>\n * </ModalGlass.Trigger>\n * <ModalGlass.Content>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Title</ModalGlass.Title>\n * </ModalGlass.Header>\n * <ModalGlass.Body>Content</ModalGlass.Body>\n * <ModalGlass.Footer>\n * <ModalGlass.Close asChild>\n * <ButtonGlass>Close</ButtonGlass>\n * </ModalGlass.Close>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n *\n * @example Controlled\n * ```tsx\n * <ModalGlass.Root open={open} onOpenChange={setOpen}>\n * <ModalGlass.Content showCloseButton={false}>\n * <ModalGlass.Header>\n * <ModalGlass.Title>Confirm</ModalGlass.Title>\n * </ModalGlass.Header>\n * <ModalGlass.Footer>\n * <ButtonGlass onClick={() => setOpen(false)}>OK</ButtonGlass>\n * </ModalGlass.Footer>\n * </ModalGlass.Content>\n * </ModalGlass.Root>\n * ```\n */\nexport const ModalGlass = {\n Root: ModalRoot,\n Trigger: ModalTrigger,\n Portal: ModalPortal,\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\n// Named exports for direct imports\nexport {\n ModalRoot,\n ModalTrigger,\n ModalPortal,\n ModalOverlay,\n ModalContent,\n ModalHeader,\n ModalBody,\n ModalFooter,\n ModalTitle,\n ModalDescription,\n ModalClose,\n};\n\nexport type { ModalRootProps, ModalContentProps };\n"
22
22
  }
23
23
  ],
24
24
  "categories": [
@@ -55,7 +55,7 @@
55
55
  "name": "modal-glass",
56
56
  "type": "registry:ui",
57
57
  "title": "Modal Glass",
58
- "description": "ModalGlass Component (Compound API only)"
58
+ "description": "ModalGlass Component (Radix UI Dialog-based)"
59
59
  },
60
60
  {
61
61
  "name": "input-glass",
@@ -99,6 +99,12 @@
99
99
  "title": "Checkbox Glass",
100
100
  "description": "Glass-themed checkbox with:"
101
101
  },
102
+ {
103
+ "name": "card-glass",
104
+ "type": "registry:ui",
105
+ "title": "Card Glass",
106
+ "description": "CardGlass Compound Component"
107
+ },
102
108
  {
103
109
  "name": "button-glass",
104
110
  "type": "registry:ui",
@@ -15,7 +15,7 @@
15
15
  {
16
16
  "path": "components/glass/ui/tooltip-glass.tsx",
17
17
  "type": "registry:component",
18
- "content": "/**\n * TooltipGlass Component\n *\n * Glass-themed tooltip with:\n * - Unified dark design (consistent UX across themes)\n * - Position variants (top/bottom/left/right)\n * - Smooth animation\n * - Built on Radix UI primitives\n *\n * @example Compound API (recommended)\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlass>\n * <TooltipGlassTrigger asChild>\n * <Button>Hover</Button>\n * </TooltipGlassTrigger>\n * <TooltipGlassContent>\n * <p>Add to library</p>\n * </TooltipGlassContent>\n * </TooltipGlass>\n * </TooltipGlassProvider>\n * ```\n *\n * @example Simple wrapper\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlassSimple content=\"Click to edit\">\n * <button>Edit</button>\n * </TooltipGlassSimple>\n * </TooltipGlassProvider>\n * ```\n */\n\n'use client';\n\nimport * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// COMPOUND COMPONENT: PROVIDER\n// ========================================\n\ntype TooltipGlassProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\nconst TooltipGlassProvider: React.FC<TooltipGlassProviderProps> = ({\n delayDuration = 0,\n ...props\n}) => {\n return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\nconst TooltipGlassRoot = TooltipPrimitive.Root;\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\nconst TooltipGlassTrigger = TooltipPrimitive.Trigger;\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ntype TooltipGlassContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>;\n\nconst TooltipGlassContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n TooltipGlassContentProps\n>(({ className, sideOffset = 4, ...props }, ref) => {\n const tooltipStyles: React.CSSProperties = {\n background: 'var(--tooltip-bg)',\n color: 'var(--tooltip-text)',\n border: '1px solid var(--tooltip-border)',\n boxShadow: 'var(--tooltip-shadow)',\n backdropFilter: 'blur(var(--blur-xl))',\n WebkitBackdropFilter: 'blur(var(--blur-xl))',\n };\n\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs text-balance',\n 'animate-in fade-in-0 zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n className\n )}\n style={tooltipStyles}\n {...props}\n />\n </TooltipPrimitive.Portal>\n );\n});\n\nTooltipGlassContent.displayName = 'TooltipGlassContent';\n\n// ========================================\n// SIMPLE WRAPPER (convenience)\n// ========================================\n\ninterface TooltipGlassSimpleProps {\n content: string;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n className?: string;\n}\n\nconst TooltipGlassSimple: React.FC<TooltipGlassSimpleProps> = ({\n content,\n children,\n side = 'top',\n className,\n}) => {\n return (\n <TooltipGlassRoot>\n <TooltipGlassTrigger asChild>{children}</TooltipGlassTrigger>\n <TooltipGlassContent side={side} className={className}>\n {content}\n </TooltipGlassContent>\n </TooltipGlassRoot>\n );\n};\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const TooltipGlass = TooltipGlassRoot;\nexport { TooltipGlassProvider, TooltipGlassTrigger, TooltipGlassContent, TooltipGlassSimple };\n"
18
+ "content": "/**\n * TooltipGlass Component\n *\n * Glass-themed tooltip with:\n * - Unified dark design (consistent UX across themes)\n * - Position variants (top/bottom/left/right)\n * - Smooth animation\n * - Built on Radix UI primitives\n *\n * @example Compound API (recommended)\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlass>\n * <TooltipGlassTrigger asChild>\n * <Button>Hover</Button>\n * </TooltipGlassTrigger>\n * <TooltipGlassContent>\n * <p>Add to library</p>\n * </TooltipGlassContent>\n * </TooltipGlass>\n * </TooltipGlassProvider>\n * ```\n *\n * @example Simple wrapper\n * ```tsx\n * <TooltipGlassProvider>\n * <TooltipGlassSimple content=\"Click to edit\">\n * <button>Edit</button>\n * </TooltipGlassSimple>\n * </TooltipGlassProvider>\n * ```\n */\n\n'use client';\n\nimport * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// COMPOUND COMPONENT: PROVIDER\n// ========================================\n\ntype TooltipGlassProviderProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>;\n\n/**\n * TooltipGlassProvider - Context provider for all tooltips\n * Must wrap TooltipGlass components at the app level.\n */\nconst TooltipGlassProvider: React.FC<TooltipGlassProviderProps> = ({\n delayDuration = 0,\n ...props\n}) => {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n );\n};\n\nTooltipGlassProvider.displayName = 'TooltipGlassProvider';\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\n/**\n * TooltipGlassRoot - Individual tooltip instance\n * Wraps Radix TooltipPrimitive.Root with auto-included Provider (shadcn/ui pattern)\n */\nfunction TooltipGlassRoot({\n ...props\n}: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>) {\n return (\n <TooltipGlassProvider>\n <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n </TooltipGlassProvider>\n );\n}\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\n/**\n * TooltipGlassTrigger - Element that triggers the tooltip\n * Supports asChild pattern for custom trigger elements.\n */\nconst TooltipGlassTrigger = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>\n>(({ ...props }, ref) => (\n <TooltipPrimitive.Trigger ref={ref} data-slot=\"tooltip-trigger\" {...props} />\n));\n\nTooltipGlassTrigger.displayName = 'TooltipGlassTrigger';\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\ntype TooltipGlassContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>;\n\n/**\n * TooltipGlassContent - Tooltip content with glass styling\n * Rendered in a portal for proper z-index handling.\n */\nconst TooltipGlassContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n TooltipGlassContentProps\n>(({ className, sideOffset = 0, children, ...props }, ref) => {\n const tooltipStyles: React.CSSProperties = {\n background: 'var(--tooltip-bg)',\n color: 'var(--tooltip-text)',\n border: '1px solid var(--tooltip-border)',\n boxShadow: 'var(--tooltip-shadow)',\n backdropFilter: 'blur(var(--blur-xl))',\n WebkitBackdropFilter: 'blur(var(--blur-xl))',\n };\n\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n data-slot=\"tooltip-content\"\n className={cn(\n 'z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n 'animate-in fade-in-0 zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n className\n )}\n style={tooltipStyles}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow\n className=\"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-xs\"\n style={{\n fill: 'var(--tooltip-bg)',\n background: 'var(--tooltip-bg)',\n }}\n />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n});\n\nTooltipGlassContent.displayName = 'TooltipGlassContent';\n\n// ========================================\n// SIMPLE WRAPPER (convenience)\n// ========================================\n\ninterface TooltipGlassSimpleProps {\n content: string;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n className?: string;\n}\n\nconst TooltipGlassSimple: React.FC<TooltipGlassSimpleProps> = ({\n content,\n children,\n side = 'top',\n className,\n}) => {\n return (\n <TooltipGlassRoot>\n <TooltipGlassTrigger asChild>{children}</TooltipGlassTrigger>\n <TooltipGlassContent side={side} className={className}>\n {content}\n </TooltipGlassContent>\n </TooltipGlassRoot>\n );\n};\n\n// ========================================\n// EXPORTS\n// ========================================\n\n// Compound API (shadcn/ui pattern)\nexport const TooltipGlass = TooltipGlassRoot;\nexport { TooltipGlassProvider, TooltipGlassTrigger, TooltipGlassContent, TooltipGlassSimple };\n"
19
19
  }
20
20
  ],
21
21
  "categories": [