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
@@ -1,4 +1,4 @@
1
- const require_trust_score_card_glass = require("./trust-score-card-glass-CTsEVRD3.cjs");
1
+ const require_trust_score_card_glass = require("./trust-score-card-glass-DTS1RdIt.cjs");
2
2
  let react = require("react");
3
3
  function useHover(options = {}) {
4
4
  const { enterDelay = 0, leaveDelay = 0, includeFocus = false, onHoverChange } = options;
@@ -127,4 +127,4 @@ Object.defineProperty(exports, "useHover", {
127
127
  }
128
128
  });
129
129
 
130
- //# sourceMappingURL=use-focus--Hw2nevi.cjs.map
130
+ //# sourceMappingURL=use-focus-CdoUzFQ8.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-focus--Hw2nevi.cjs","names":["hoverProps: UseHoverReturn['hoverProps']","focusProps: UseFocusReturn['focusProps']"],"sources":["../src/lib/hooks/use-hover.ts","../src/lib/hooks/use-focus.ts"],"sourcesContent":["/**\n * useHover Hook\n *\n * Replaces the repeated hover state pattern found in 13+ components:\n *\n * ```tsx\n * // BEFORE (duplicated in every component)\n * const [isHovered, setIsHovered] = useState(false);\n * <div\n * onMouseEnter={() => setIsHovered(true)}\n * onMouseLeave={() => setIsHovered(false)}\n * />\n *\n * // AFTER\n * const { isHovered, hoverProps } = useHover();\n * <div {...hoverProps} />\n * ```\n */\n\nimport { useState, useCallback, type MouseEvent, type FocusEvent } from 'react';\n\nexport interface UseHoverOptions {\n /** Delay before hover state becomes true (ms) */\n enterDelay?: number;\n /** Delay before hover state becomes false (ms) */\n leaveDelay?: number;\n /** Include focus events for accessibility */\n includeFocus?: boolean;\n /** Callback when hover state changes */\n onHoverChange?: (isHovered: boolean) => void;\n}\n\nexport interface UseHoverReturn {\n /** Current hover state */\n isHovered: boolean;\n /** Props to spread on the target element */\n hoverProps: {\n onMouseEnter: (e: MouseEvent) => void;\n onMouseLeave: (e: MouseEvent) => void;\n onFocus?: (e: FocusEvent) => void;\n onBlur?: (e: FocusEvent) => void;\n };\n /** Manually set hover state */\n setIsHovered: (value: boolean) => void;\n}\n\n/**\n * Hook for managing hover state with optional delays and focus support.\n *\n * @example\n * ```tsx\n * const { isHovered, hoverProps } = useHover();\n *\n * return (\n * <div\n * {...hoverProps}\n * style={{ opacity: isHovered ? 1 : 0.8 }}\n * >\n * Hover me\n * </div>\n * );\n * ```\n *\n * @example With options\n * ```tsx\n * const { isHovered, hoverProps } = useHover({\n * enterDelay: 100,\n * leaveDelay: 200,\n * includeFocus: true,\n * onHoverChange: (hover) => console.log('Hovered:', hover),\n * });\n * ```\n */\nexport function useHover(options: UseHoverOptions = {}): UseHoverReturn {\n const {\n enterDelay = 0,\n leaveDelay = 0,\n includeFocus = false,\n onHoverChange,\n } = options;\n\n const [isHovered, setIsHoveredState] = useState(false);\n const [enterTimeout, setEnterTimeout] = useState<ReturnType<typeof setTimeout> | null>(null);\n const [leaveTimeout, setLeaveTimeout] = useState<ReturnType<typeof setTimeout> | null>(null);\n\n const setIsHovered = useCallback(\n (value: boolean) => {\n setIsHoveredState(value);\n onHoverChange?.(value);\n },\n [onHoverChange]\n );\n\n const handleMouseEnter = useCallback(\n () => {\n // Clear any pending leave timeout\n if (leaveTimeout) {\n clearTimeout(leaveTimeout);\n setLeaveTimeout(null);\n }\n\n if (enterDelay > 0) {\n const timeout = setTimeout(() => {\n setIsHovered(true);\n }, enterDelay);\n setEnterTimeout(timeout);\n } else {\n setIsHovered(true);\n }\n },\n [enterDelay, leaveTimeout, setIsHovered]\n );\n\n const handleMouseLeave = useCallback(\n () => {\n // Clear any pending enter timeout\n if (enterTimeout) {\n clearTimeout(enterTimeout);\n setEnterTimeout(null);\n }\n\n if (leaveDelay > 0) {\n const timeout = setTimeout(() => {\n setIsHovered(false);\n }, leaveDelay);\n setLeaveTimeout(timeout);\n } else {\n setIsHovered(false);\n }\n },\n [leaveDelay, enterTimeout, setIsHovered]\n );\n\n const handleFocus = useCallback(\n () => {\n if (includeFocus) {\n setIsHovered(true);\n }\n },\n [includeFocus, setIsHovered]\n );\n\n const handleBlur = useCallback(\n () => {\n if (includeFocus) {\n setIsHovered(false);\n }\n },\n [includeFocus, setIsHovered]\n );\n\n const hoverProps: UseHoverReturn['hoverProps'] = {\n onMouseEnter: handleMouseEnter,\n onMouseLeave: handleMouseLeave,\n ...(includeFocus && {\n onFocus: handleFocus,\n onBlur: handleBlur,\n }),\n };\n\n return {\n isHovered,\n hoverProps,\n setIsHovered,\n };\n}\n\nexport default useHover;\n","/**\n * useFocus Hook\n *\n * Manages focus state for form elements with keyboard navigation support.\n * Similar to useHover but for focus events.\n *\n * Implements proper :focus-visible behavior by tracking keyboard vs mouse interaction\n * at the document level, ensuring focus rings only appear for keyboard navigation.\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n useEffect,\n type FocusEvent,\n type KeyboardEvent,\n} from 'react';\n\n// Global state to track whether the last user interaction was via keyboard\nlet hadKeyboardEvent = false;\nlet isInitialized = false;\n\n/**\n * Initialize global keyboard/mouse tracking for focus-visible behavior.\n * This ensures focus rings only appear when navigating via keyboard (Tab key),\n * not when clicking with a mouse.\n */\nfunction initializeKeyboardTracking() {\n if (isInitialized || typeof window === 'undefined') return;\n\n isInitialized = true;\n\n // Track keyboard events (Tab, Shift, Arrow keys, etc.)\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n // Only consider keyboard navigation keys\n if (e.key === 'Tab' || e.key.startsWith('Arrow') || e.key === 'Enter' || e.key === ' ') {\n hadKeyboardEvent = true;\n }\n };\n\n // Track mouse/pointer events - clear keyboard flag\n const handlePointerDown = () => {\n hadKeyboardEvent = false;\n };\n\n // Use capture phase to detect events before they reach components\n document.addEventListener('keydown', handleKeyDown, true);\n document.addEventListener('mousedown', handlePointerDown, true);\n document.addEventListener('pointerdown', handlePointerDown, true);\n document.addEventListener('touchstart', handlePointerDown, true);\n\n // Cleanup not needed - these are global listeners that persist for the app lifecycle\n}\n\nexport interface UseFocusOptions {\n /** Callback when focus state changes */\n onFocusChange?: (isFocused: boolean) => void;\n /** Include focus-visible behavior (keyboard focus only) */\n focusVisible?: boolean;\n /** Callback for keyboard events while focused */\n onKeyDown?: (e: KeyboardEvent) => void;\n}\n\nexport interface UseFocusReturn {\n /** Current focus state */\n isFocused: boolean;\n /** True only when focused via keyboard (if focusVisible enabled) */\n isFocusVisible: boolean;\n /** Props to spread on the target element */\n focusProps: {\n onFocus: (e: FocusEvent) => void;\n onBlur: (e: FocusEvent) => void;\n onKeyDown?: (e: KeyboardEvent) => void;\n };\n /** Manually set focus state */\n setIsFocused: (value: boolean) => void;\n /** Reference to track if last focus was from keyboard */\n focusRef: React.RefObject<boolean>;\n}\n\n/**\n * Hook for managing focus state with optional focus-visible support.\n *\n * @example Basic usage\n * ```tsx\n * const { isFocused, focusProps } = useFocus();\n *\n * return (\n * <input\n * {...focusProps}\n * style={{\n * borderColor: isFocused ? 'violet' : 'gray',\n * }}\n * />\n * );\n * ```\n *\n * @example Focus-visible for keyboard navigation\n * ```tsx\n * const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n *\n * return (\n * <button\n * {...focusProps}\n * style={{\n * outline: isFocusVisible ? '2px solid violet' : 'none',\n * }}\n * >\n * Click or Tab to me\n * </button>\n * );\n * ```\n */\nexport function useFocus(options: UseFocusOptions = {}): UseFocusReturn {\n const { onFocusChange, focusVisible = false, onKeyDown } = options;\n\n const [isFocused, setIsFocusedState] = useState(false);\n const [isFocusVisible, setIsFocusVisible] = useState(false);\n const hadKeyboardEventRef = useRef(false);\n const focusRef = useRef(false);\n\n // Initialize global keyboard tracking on mount (runs once per app)\n useEffect(() => {\n if (focusVisible) {\n initializeKeyboardTracking();\n }\n }, [focusVisible]);\n\n const setIsFocused = useCallback(\n (value: boolean) => {\n setIsFocusedState(value);\n focusRef.current = value;\n onFocusChange?.(value);\n },\n [onFocusChange]\n );\n\n const handleFocus = useCallback(\n () => {\n setIsFocused(true);\n\n if (focusVisible) {\n // Use global keyboard tracking state for accurate focus-visible detection\n const isKeyboardFocus = hadKeyboardEvent;\n setIsFocusVisible(isKeyboardFocus);\n hadKeyboardEventRef.current = isKeyboardFocus;\n }\n },\n [setIsFocused, focusVisible]\n );\n\n const handleBlur = useCallback(\n () => {\n setIsFocused(false);\n setIsFocusVisible(false);\n },\n [setIsFocused]\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n hadKeyboardEventRef.current = true;\n onKeyDown?.(e);\n },\n [onKeyDown]\n );\n\n const focusProps: UseFocusReturn['focusProps'] = {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...(onKeyDown && { onKeyDown: handleKeyDown }),\n };\n\n return {\n isFocused,\n isFocusVisible,\n focusProps,\n setIsFocused,\n focusRef,\n };\n}\n\nexport default useFocus;\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-focus-CdoUzFQ8.cjs","names":["hoverProps: UseHoverReturn['hoverProps']","focusProps: UseFocusReturn['focusProps']"],"sources":["../src/lib/hooks/use-hover.ts","../src/lib/hooks/use-focus.ts"],"sourcesContent":["/**\n * useHover Hook\n *\n * Replaces the repeated hover state pattern found in 13+ components:\n *\n * ```tsx\n * // BEFORE (duplicated in every component)\n * const [isHovered, setIsHovered] = useState(false);\n * <div\n * onMouseEnter={() => setIsHovered(true)}\n * onMouseLeave={() => setIsHovered(false)}\n * />\n *\n * // AFTER\n * const { isHovered, hoverProps } = useHover();\n * <div {...hoverProps} />\n * ```\n */\n\nimport { useState, useCallback, type MouseEvent, type FocusEvent } from 'react';\n\nexport interface UseHoverOptions {\n /** Delay before hover state becomes true (ms) */\n enterDelay?: number;\n /** Delay before hover state becomes false (ms) */\n leaveDelay?: number;\n /** Include focus events for accessibility */\n includeFocus?: boolean;\n /** Callback when hover state changes */\n onHoverChange?: (isHovered: boolean) => void;\n}\n\nexport interface UseHoverReturn {\n /** Current hover state */\n isHovered: boolean;\n /** Props to spread on the target element */\n hoverProps: {\n onMouseEnter: (e: MouseEvent) => void;\n onMouseLeave: (e: MouseEvent) => void;\n onFocus?: (e: FocusEvent) => void;\n onBlur?: (e: FocusEvent) => void;\n };\n /** Manually set hover state */\n setIsHovered: (value: boolean) => void;\n}\n\n/**\n * Hook for managing hover state with optional delays and focus support.\n *\n * @example\n * ```tsx\n * const { isHovered, hoverProps } = useHover();\n *\n * return (\n * <div\n * {...hoverProps}\n * style={{ opacity: isHovered ? 1 : 0.8 }}\n * >\n * Hover me\n * </div>\n * );\n * ```\n *\n * @example With options\n * ```tsx\n * const { isHovered, hoverProps } = useHover({\n * enterDelay: 100,\n * leaveDelay: 200,\n * includeFocus: true,\n * onHoverChange: (hover) => console.log('Hovered:', hover),\n * });\n * ```\n */\nexport function useHover(options: UseHoverOptions = {}): UseHoverReturn {\n const {\n enterDelay = 0,\n leaveDelay = 0,\n includeFocus = false,\n onHoverChange,\n } = options;\n\n const [isHovered, setIsHoveredState] = useState(false);\n const [enterTimeout, setEnterTimeout] = useState<ReturnType<typeof setTimeout> | null>(null);\n const [leaveTimeout, setLeaveTimeout] = useState<ReturnType<typeof setTimeout> | null>(null);\n\n const setIsHovered = useCallback(\n (value: boolean) => {\n setIsHoveredState(value);\n onHoverChange?.(value);\n },\n [onHoverChange]\n );\n\n const handleMouseEnter = useCallback(\n () => {\n // Clear any pending leave timeout\n if (leaveTimeout) {\n clearTimeout(leaveTimeout);\n setLeaveTimeout(null);\n }\n\n if (enterDelay > 0) {\n const timeout = setTimeout(() => {\n setIsHovered(true);\n }, enterDelay);\n setEnterTimeout(timeout);\n } else {\n setIsHovered(true);\n }\n },\n [enterDelay, leaveTimeout, setIsHovered]\n );\n\n const handleMouseLeave = useCallback(\n () => {\n // Clear any pending enter timeout\n if (enterTimeout) {\n clearTimeout(enterTimeout);\n setEnterTimeout(null);\n }\n\n if (leaveDelay > 0) {\n const timeout = setTimeout(() => {\n setIsHovered(false);\n }, leaveDelay);\n setLeaveTimeout(timeout);\n } else {\n setIsHovered(false);\n }\n },\n [leaveDelay, enterTimeout, setIsHovered]\n );\n\n const handleFocus = useCallback(\n () => {\n if (includeFocus) {\n setIsHovered(true);\n }\n },\n [includeFocus, setIsHovered]\n );\n\n const handleBlur = useCallback(\n () => {\n if (includeFocus) {\n setIsHovered(false);\n }\n },\n [includeFocus, setIsHovered]\n );\n\n const hoverProps: UseHoverReturn['hoverProps'] = {\n onMouseEnter: handleMouseEnter,\n onMouseLeave: handleMouseLeave,\n ...(includeFocus && {\n onFocus: handleFocus,\n onBlur: handleBlur,\n }),\n };\n\n return {\n isHovered,\n hoverProps,\n setIsHovered,\n };\n}\n\nexport default useHover;\n","/**\n * useFocus Hook\n *\n * Manages focus state for form elements with keyboard navigation support.\n * Similar to useHover but for focus events.\n *\n * Implements proper :focus-visible behavior by tracking keyboard vs mouse interaction\n * at the document level, ensuring focus rings only appear for keyboard navigation.\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n useEffect,\n type FocusEvent,\n type KeyboardEvent,\n} from 'react';\n\n// Global state to track whether the last user interaction was via keyboard\nlet hadKeyboardEvent = false;\nlet isInitialized = false;\n\n/**\n * Initialize global keyboard/mouse tracking for focus-visible behavior.\n * This ensures focus rings only appear when navigating via keyboard (Tab key),\n * not when clicking with a mouse.\n */\nfunction initializeKeyboardTracking() {\n if (isInitialized || typeof window === 'undefined') return;\n\n isInitialized = true;\n\n // Track keyboard events (Tab, Shift, Arrow keys, etc.)\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n // Only consider keyboard navigation keys\n if (e.key === 'Tab' || e.key.startsWith('Arrow') || e.key === 'Enter' || e.key === ' ') {\n hadKeyboardEvent = true;\n }\n };\n\n // Track mouse/pointer events - clear keyboard flag\n const handlePointerDown = () => {\n hadKeyboardEvent = false;\n };\n\n // Use capture phase to detect events before they reach components\n document.addEventListener('keydown', handleKeyDown, true);\n document.addEventListener('mousedown', handlePointerDown, true);\n document.addEventListener('pointerdown', handlePointerDown, true);\n document.addEventListener('touchstart', handlePointerDown, true);\n\n // Cleanup not needed - these are global listeners that persist for the app lifecycle\n}\n\nexport interface UseFocusOptions {\n /** Callback when focus state changes */\n onFocusChange?: (isFocused: boolean) => void;\n /** Include focus-visible behavior (keyboard focus only) */\n focusVisible?: boolean;\n /** Callback for keyboard events while focused */\n onKeyDown?: (e: KeyboardEvent) => void;\n}\n\nexport interface UseFocusReturn {\n /** Current focus state */\n isFocused: boolean;\n /** True only when focused via keyboard (if focusVisible enabled) */\n isFocusVisible: boolean;\n /** Props to spread on the target element */\n focusProps: {\n onFocus: (e: FocusEvent) => void;\n onBlur: (e: FocusEvent) => void;\n onKeyDown?: (e: KeyboardEvent) => void;\n };\n /** Manually set focus state */\n setIsFocused: (value: boolean) => void;\n /** Reference to track if last focus was from keyboard */\n focusRef: React.RefObject<boolean>;\n}\n\n/**\n * Hook for managing focus state with optional focus-visible support.\n *\n * @example Basic usage\n * ```tsx\n * const { isFocused, focusProps } = useFocus();\n *\n * return (\n * <input\n * {...focusProps}\n * style={{\n * borderColor: isFocused ? 'violet' : 'gray',\n * }}\n * />\n * );\n * ```\n *\n * @example Focus-visible for keyboard navigation\n * ```tsx\n * const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n *\n * return (\n * <button\n * {...focusProps}\n * style={{\n * outline: isFocusVisible ? '2px solid violet' : 'none',\n * }}\n * >\n * Click or Tab to me\n * </button>\n * );\n * ```\n */\nexport function useFocus(options: UseFocusOptions = {}): UseFocusReturn {\n const { onFocusChange, focusVisible = false, onKeyDown } = options;\n\n const [isFocused, setIsFocusedState] = useState(false);\n const [isFocusVisible, setIsFocusVisible] = useState(false);\n const hadKeyboardEventRef = useRef(false);\n const focusRef = useRef(false);\n\n // Initialize global keyboard tracking on mount (runs once per app)\n useEffect(() => {\n if (focusVisible) {\n initializeKeyboardTracking();\n }\n }, [focusVisible]);\n\n const setIsFocused = useCallback(\n (value: boolean) => {\n setIsFocusedState(value);\n focusRef.current = value;\n onFocusChange?.(value);\n },\n [onFocusChange]\n );\n\n const handleFocus = useCallback(\n () => {\n setIsFocused(true);\n\n if (focusVisible) {\n // Use global keyboard tracking state for accurate focus-visible detection\n const isKeyboardFocus = hadKeyboardEvent;\n setIsFocusVisible(isKeyboardFocus);\n hadKeyboardEventRef.current = isKeyboardFocus;\n }\n },\n [setIsFocused, focusVisible]\n );\n\n const handleBlur = useCallback(\n () => {\n setIsFocused(false);\n setIsFocusVisible(false);\n },\n [setIsFocused]\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n hadKeyboardEventRef.current = true;\n onKeyDown?.(e);\n },\n [onKeyDown]\n );\n\n const focusProps: UseFocusReturn['focusProps'] = {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...(onKeyDown && { onKeyDown: handleKeyDown }),\n };\n\n return {\n isFocused,\n isFocusVisible,\n focusProps,\n setIsFocused,\n focusRef,\n };\n}\n\nexport default useFocus;\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const require_trust_score_card_glass = require("./trust-score-card-glass-CTsEVRD3.cjs");
1
+ const require_trust_score_card_glass = require("./trust-score-card-glass-DTS1RdIt.cjs");
2
2
  let react = require("react");
3
3
  var BREAKPOINTS = {
4
4
  xs: 0,
@@ -159,4 +159,4 @@ Object.defineProperty(exports, "useWallpaperTint", {
159
159
  }
160
160
  });
161
161
 
162
- //# sourceMappingURL=use-wallpaper-tint-B4oMQsXQ.cjs.map
162
+ //# sourceMappingURL=use-wallpaper-tint-Rq5UgY9L.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-wallpaper-tint-B4oMQsXQ.cjs","names":["colors: { r: number; g: number; b: number }[]"],"sources":["../src/lib/hooks/use-responsive.ts","../src/lib/hooks/use-wallpaper-tint.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\n\n/**\n * Tailwind CSS breakpoints\n * @see https://tailwindcss.com/docs/responsive-design\n */\nconst BREAKPOINTS = {\n xs: 0,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const;\n\nexport type Breakpoint = keyof typeof BREAKPOINTS;\n\nexport interface UseResponsiveReturn {\n /** Window width is less than 768px (mobile) */\n isMobile: boolean;\n /** Window width is >= 768px and < 1024px (tablet) */\n isTablet: boolean;\n /** Window width is >= 1024px (desktop) */\n isDesktop: boolean;\n /** Current active breakpoint */\n currentBreakpoint: Breakpoint;\n /** Current window width in pixels */\n width: number;\n}\n\n/**\n * Hook to detect current responsive breakpoint\n *\n * @returns Responsive state with current breakpoint and device type flags\n *\n * @example\n * ```tsx\n * const { isMobile, isTablet, isDesktop, currentBreakpoint } = useResponsive();\n *\n * return (\n * <div className={isMobile ? 'flex-col' : 'flex-row'}>\n * {currentBreakpoint === 'lg' && <Sidebar />}\n * </div>\n * );\n * ```\n */\nexport function useResponsive(): UseResponsiveReturn {\n const [width, setWidth] = useState<number>(\n typeof window !== 'undefined' ? window.innerWidth : BREAKPOINTS.lg\n );\n\n useEffect(() => {\n // Server-side rendering guard\n if (typeof window === 'undefined') return;\n\n const handleResize = () => {\n setWidth(window.innerWidth);\n };\n\n // Set initial value\n handleResize();\n\n // Add event listener\n window.addEventListener('resize', handleResize);\n\n // Cleanup\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n // Calculate current breakpoint\n const getCurrentBreakpoint = (): Breakpoint => {\n if (width >= BREAKPOINTS['2xl']) return '2xl';\n if (width >= BREAKPOINTS.xl) return 'xl';\n if (width >= BREAKPOINTS.lg) return 'lg';\n if (width >= BREAKPOINTS.md) return 'md';\n if (width >= BREAKPOINTS.sm) return 'sm';\n return 'xs';\n };\n\n const currentBreakpoint = getCurrentBreakpoint();\n\n return {\n isMobile: width < BREAKPOINTS.md,\n isTablet: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,\n isDesktop: width >= BREAKPOINTS.lg,\n currentBreakpoint,\n width,\n };\n}\n","// ========================================\n// WALLPAPER TINT HOOK\n// Extract dominant color from background image\n// ========================================\n\nimport { useState, useEffect, useCallback } from \"react\";\n\nexport interface WallpaperTintOptions {\n /**\n * The image URL to sample for tint color\n */\n imageUrl?: string;\n\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * Number of sample points to take from the image\n * @default 10\n */\n sampleSize?: number;\n\n /**\n * Whether to enable the tint extraction\n * @default true\n */\n enabled?: boolean;\n}\n\nexport interface WallpaperTintResult {\n /**\n * The extracted tint color in RGB format\n * Example: \"120, 80, 200\"\n */\n tintColor: string | null;\n\n /**\n * Whether the tint extraction is in progress\n */\n isLoading: boolean;\n\n /**\n * Error message if extraction failed\n */\n error: string | null;\n\n /**\n * Re-extract the tint color from the current image\n */\n refresh: () => void;\n}\n\n/**\n * Converts RGB values to a luminance value (0-255)\n */\nconst getLuminance = (r: number, g: number, b: number): number => {\n // Use standard luminance formula\n return 0.299 * r + 0.587 * g + 0.114 * b;\n};\n\n/**\n * Extracts the dominant color from an image using canvas sampling\n */\nconst extractDominantColor = async (\n imageUrl: string,\n sampleSize: number = 10\n): Promise<string> => {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = \"Anonymous\"; // Enable CORS\n\n img.onload = () => {\n try {\n // Create canvas for sampling\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n reject(new Error(\"Failed to get canvas context\"));\n return;\n }\n\n // Set canvas size to image size\n canvas.width = img.width;\n canvas.height = img.height;\n\n // Draw image to canvas\n ctx.drawImage(img, 0, 0);\n\n // Sample colors from grid\n const colors: { r: number; g: number; b: number }[] = [];\n const stepX = Math.floor(img.width / sampleSize);\n const stepY = Math.floor(img.height / sampleSize);\n\n for (let y = stepY / 2; y < img.height; y += stepY) {\n for (let x = stepX / 2; x < img.width; x += stepX) {\n const pixel = ctx.getImageData(x, y, 1, 1).data;\n colors.push({\n r: pixel[0],\n g: pixel[1],\n b: pixel[2],\n });\n }\n }\n\n // Calculate average color (simple approach)\n const avgColor = colors.reduce(\n (acc, color) => ({\n r: acc.r + color.r,\n g: acc.g + color.g,\n b: acc.b + color.b,\n }),\n { r: 0, g: 0, b: 0 }\n );\n\n const count = colors.length;\n avgColor.r = Math.round(avgColor.r / count);\n avgColor.g = Math.round(avgColor.g / count);\n avgColor.b = Math.round(avgColor.b / count);\n\n // Adjust color based on luminance for better glass effect\n const luminance = getLuminance(avgColor.r, avgColor.g, avgColor.b);\n\n // If too dark, lighten it\n if (luminance < 80) {\n const factor = 1.5;\n avgColor.r = Math.min(255, Math.round(avgColor.r * factor));\n avgColor.g = Math.min(255, Math.round(avgColor.g * factor));\n avgColor.b = Math.min(255, Math.round(avgColor.b * factor));\n }\n\n // If too bright, darken it slightly\n if (luminance > 200) {\n const factor = 0.7;\n avgColor.r = Math.round(avgColor.r * factor);\n avgColor.g = Math.round(avgColor.g * factor);\n avgColor.b = Math.round(avgColor.b * factor);\n }\n\n resolve(`${avgColor.r}, ${avgColor.g}, ${avgColor.b}`);\n } catch (error) {\n reject(error);\n }\n };\n\n img.onerror = () => {\n reject(new Error(\"Failed to load image\"));\n };\n\n img.src = imageUrl;\n });\n};\n\n/**\n * Hook to extract and use wallpaper tint color\n *\n * @example\n * ```tsx\n * const { tintColor, isLoading } = useWallpaperTint({\n * imageUrl: '/path/to/background.jpg',\n * });\n *\n * // Use tintColor in CSS variables\n * <div style={{ '--wallpaper-tint': tintColor }}>\n * <GlassCard />\n * </div>\n * ```\n */\nexport const useWallpaperTint = (\n options: WallpaperTintOptions = {}\n): WallpaperTintResult => {\n const {\n imageUrl,\n debounceMs = 300,\n sampleSize = 10,\n enabled = true,\n } = options;\n\n const [tintColor, setTintColor] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const extractTint = useCallback(async () => {\n if (!imageUrl || !enabled) {\n setTintColor(null);\n setError(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const color = await extractDominantColor(imageUrl, sampleSize);\n setTintColor(color);\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Failed to extract tint color\";\n setError(errorMessage);\n setTintColor(null);\n } finally {\n setIsLoading(false);\n }\n }, [imageUrl, sampleSize, enabled]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const timeoutId = setTimeout(() => {\n extractTint();\n }, debounceMs);\n\n return () => clearTimeout(timeoutId);\n }, [extractTint, debounceMs, enabled]);\n\n return {\n tintColor,\n isLoading,\n error,\n refresh: extractTint,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-wallpaper-tint-Rq5UgY9L.cjs","names":["colors: { r: number; g: number; b: number }[]"],"sources":["../src/lib/hooks/use-responsive.ts","../src/lib/hooks/use-wallpaper-tint.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\n\n/**\n * Tailwind CSS breakpoints\n * @see https://tailwindcss.com/docs/responsive-design\n */\nconst BREAKPOINTS = {\n xs: 0,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const;\n\nexport type Breakpoint = keyof typeof BREAKPOINTS;\n\nexport interface UseResponsiveReturn {\n /** Window width is less than 768px (mobile) */\n isMobile: boolean;\n /** Window width is >= 768px and < 1024px (tablet) */\n isTablet: boolean;\n /** Window width is >= 1024px (desktop) */\n isDesktop: boolean;\n /** Current active breakpoint */\n currentBreakpoint: Breakpoint;\n /** Current window width in pixels */\n width: number;\n}\n\n/**\n * Hook to detect current responsive breakpoint\n *\n * @returns Responsive state with current breakpoint and device type flags\n *\n * @example\n * ```tsx\n * const { isMobile, isTablet, isDesktop, currentBreakpoint } = useResponsive();\n *\n * return (\n * <div className={isMobile ? 'flex-col' : 'flex-row'}>\n * {currentBreakpoint === 'lg' && <Sidebar />}\n * </div>\n * );\n * ```\n */\nexport function useResponsive(): UseResponsiveReturn {\n const [width, setWidth] = useState<number>(\n typeof window !== 'undefined' ? window.innerWidth : BREAKPOINTS.lg\n );\n\n useEffect(() => {\n // Server-side rendering guard\n if (typeof window === 'undefined') return;\n\n const handleResize = () => {\n setWidth(window.innerWidth);\n };\n\n // Set initial value\n handleResize();\n\n // Add event listener\n window.addEventListener('resize', handleResize);\n\n // Cleanup\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n // Calculate current breakpoint\n const getCurrentBreakpoint = (): Breakpoint => {\n if (width >= BREAKPOINTS['2xl']) return '2xl';\n if (width >= BREAKPOINTS.xl) return 'xl';\n if (width >= BREAKPOINTS.lg) return 'lg';\n if (width >= BREAKPOINTS.md) return 'md';\n if (width >= BREAKPOINTS.sm) return 'sm';\n return 'xs';\n };\n\n const currentBreakpoint = getCurrentBreakpoint();\n\n return {\n isMobile: width < BREAKPOINTS.md,\n isTablet: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,\n isDesktop: width >= BREAKPOINTS.lg,\n currentBreakpoint,\n width,\n };\n}\n","// ========================================\n// WALLPAPER TINT HOOK\n// Extract dominant color from background image\n// ========================================\n\nimport { useState, useEffect, useCallback } from \"react\";\n\nexport interface WallpaperTintOptions {\n /**\n * The image URL to sample for tint color\n */\n imageUrl?: string;\n\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * Number of sample points to take from the image\n * @default 10\n */\n sampleSize?: number;\n\n /**\n * Whether to enable the tint extraction\n * @default true\n */\n enabled?: boolean;\n}\n\nexport interface WallpaperTintResult {\n /**\n * The extracted tint color in RGB format\n * Example: \"120, 80, 200\"\n */\n tintColor: string | null;\n\n /**\n * Whether the tint extraction is in progress\n */\n isLoading: boolean;\n\n /**\n * Error message if extraction failed\n */\n error: string | null;\n\n /**\n * Re-extract the tint color from the current image\n */\n refresh: () => void;\n}\n\n/**\n * Converts RGB values to a luminance value (0-255)\n */\nconst getLuminance = (r: number, g: number, b: number): number => {\n // Use standard luminance formula\n return 0.299 * r + 0.587 * g + 0.114 * b;\n};\n\n/**\n * Extracts the dominant color from an image using canvas sampling\n */\nconst extractDominantColor = async (\n imageUrl: string,\n sampleSize: number = 10\n): Promise<string> => {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = \"Anonymous\"; // Enable CORS\n\n img.onload = () => {\n try {\n // Create canvas for sampling\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n reject(new Error(\"Failed to get canvas context\"));\n return;\n }\n\n // Set canvas size to image size\n canvas.width = img.width;\n canvas.height = img.height;\n\n // Draw image to canvas\n ctx.drawImage(img, 0, 0);\n\n // Sample colors from grid\n const colors: { r: number; g: number; b: number }[] = [];\n const stepX = Math.floor(img.width / sampleSize);\n const stepY = Math.floor(img.height / sampleSize);\n\n for (let y = stepY / 2; y < img.height; y += stepY) {\n for (let x = stepX / 2; x < img.width; x += stepX) {\n const pixel = ctx.getImageData(x, y, 1, 1).data;\n colors.push({\n r: pixel[0],\n g: pixel[1],\n b: pixel[2],\n });\n }\n }\n\n // Calculate average color (simple approach)\n const avgColor = colors.reduce(\n (acc, color) => ({\n r: acc.r + color.r,\n g: acc.g + color.g,\n b: acc.b + color.b,\n }),\n { r: 0, g: 0, b: 0 }\n );\n\n const count = colors.length;\n avgColor.r = Math.round(avgColor.r / count);\n avgColor.g = Math.round(avgColor.g / count);\n avgColor.b = Math.round(avgColor.b / count);\n\n // Adjust color based on luminance for better glass effect\n const luminance = getLuminance(avgColor.r, avgColor.g, avgColor.b);\n\n // If too dark, lighten it\n if (luminance < 80) {\n const factor = 1.5;\n avgColor.r = Math.min(255, Math.round(avgColor.r * factor));\n avgColor.g = Math.min(255, Math.round(avgColor.g * factor));\n avgColor.b = Math.min(255, Math.round(avgColor.b * factor));\n }\n\n // If too bright, darken it slightly\n if (luminance > 200) {\n const factor = 0.7;\n avgColor.r = Math.round(avgColor.r * factor);\n avgColor.g = Math.round(avgColor.g * factor);\n avgColor.b = Math.round(avgColor.b * factor);\n }\n\n resolve(`${avgColor.r}, ${avgColor.g}, ${avgColor.b}`);\n } catch (error) {\n reject(error);\n }\n };\n\n img.onerror = () => {\n reject(new Error(\"Failed to load image\"));\n };\n\n img.src = imageUrl;\n });\n};\n\n/**\n * Hook to extract and use wallpaper tint color\n *\n * @example\n * ```tsx\n * const { tintColor, isLoading } = useWallpaperTint({\n * imageUrl: '/path/to/background.jpg',\n * });\n *\n * // Use tintColor in CSS variables\n * <div style={{ '--wallpaper-tint': tintColor }}>\n * <GlassCard />\n * </div>\n * ```\n */\nexport const useWallpaperTint = (\n options: WallpaperTintOptions = {}\n): WallpaperTintResult => {\n const {\n imageUrl,\n debounceMs = 300,\n sampleSize = 10,\n enabled = true,\n } = options;\n\n const [tintColor, setTintColor] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const extractTint = useCallback(async () => {\n if (!imageUrl || !enabled) {\n setTintColor(null);\n setError(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const color = await extractDominantColor(imageUrl, sampleSize);\n setTintColor(color);\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Failed to extract tint color\";\n setError(errorMessage);\n setTintColor(null);\n } finally {\n setIsLoading(false);\n }\n }, [imageUrl, sampleSize, enabled]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const timeoutId = setTimeout(() => {\n extractTint();\n }, debounceMs);\n\n return () => clearTimeout(timeoutId);\n }, [extractTint, debounceMs, enabled]);\n\n return {\n tintColor,\n isLoading,\n error,\n refresh: extractTint,\n };\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const require_trust_score_card_glass = require("./trust-score-card-glass-CTsEVRD3.cjs");
1
+ const require_trust_score_card_glass = require("./trust-score-card-glass-DTS1RdIt.cjs");
2
2
  let clsx = require("clsx");
3
3
  let tailwind_merge = require("tailwind-merge");
4
4
  const cn = (...inputs) => {
@@ -11,4 +11,4 @@ Object.defineProperty(exports, "cn", {
11
11
  }
12
12
  });
13
13
 
14
- //# sourceMappingURL=utils-BqeJ4aco.cjs.map
14
+ //# sourceMappingURL=utils-NLnOCttr.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils-BqeJ4aco.cjs","names":[],"sources":["../src/lib/utils.ts"],"sourcesContent":["// ========================================\n// GLASS THEME UTILITIES\n// ========================================\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combine class names with Tailwind merge support (shadcn standard)\n * @param inputs - Array of class values\n * @returns Merged class string\n */\nexport const cn = (...inputs: ClassValue[]): string => {\n return twMerge(clsx(inputs));\n};\n"],"mappings":";;;AAYA,MAAa,MAAM,GAAG,WAAiC;AACrD,SAAA,GAAA,eAAA,UAAA,GAAA,KAAA,MAAoB,OAAO,CAAC"}
1
+ {"version":3,"file":"utils-NLnOCttr.cjs","names":[],"sources":["../src/lib/utils.ts"],"sourcesContent":["// ========================================\n// GLASS THEME UTILITIES\n// ========================================\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combine class names with Tailwind merge support (shadcn standard)\n * @param inputs - Array of class values\n * @returns Merged class string\n */\nexport const cn = (...inputs: ClassValue[]): string => {\n return twMerge(clsx(inputs));\n};\n"],"mappings":";;;AAYA,MAAa,MAAM,GAAG,WAAiC;AACrD,SAAA,GAAA,eAAA,UAAA,GAAA,KAAA,MAAoB,OAAO,CAAC"}
package/dist/utils.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const require_utils = require("./utils-BqeJ4aco.cjs");
1
+ const require_utils = require("./utils-NLnOCttr.cjs");
2
2
  exports.cn = require_utils.cn;
@@ -31,14 +31,19 @@ handles a specific concern.
31
31
  **Sub-components:**
32
32
 
33
33
  - `ModalGlass.Root` - Context provider with open/close state
34
- - `ModalGlass.Overlay` - Backdrop with blur effect
35
- - `ModalGlass.Content` - Main modal container
34
+ - `ModalGlass.Trigger` - Opens modal when clicked (supports asChild)
35
+ - `ModalGlass.Content` - Main modal container (**includes Portal + Overlay automatically**)
36
36
  - `ModalGlass.Header` - Header layout
37
37
  - `ModalGlass.Body` - Content area
38
38
  - `ModalGlass.Footer` - Footer with actions
39
39
  - `ModalGlass.Title` - Accessible title (ARIA)
40
40
  - `ModalGlass.Description` - Accessible description (ARIA)
41
41
  - `ModalGlass.Close` - Close button
42
+ - `ModalGlass.Portal` - Portal for rendering (optional, Content includes it)
43
+ - `ModalGlass.Overlay` - Backdrop with blur effect (optional, Content includes it)
44
+
45
+ > **Important:** `ModalGlass.Content` automatically includes the overlay backdrop. You don't need to
46
+ > add `<ModalGlass.Overlay />` separately.
42
47
 
43
48
  #### Basic Usage
44
49
 
@@ -53,7 +58,6 @@ function MyModal() {
53
58
  <ButtonGlass onClick={() => setOpen(true)}>Open Modal</ButtonGlass>
54
59
 
55
60
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
56
- <ModalGlass.Overlay />
57
61
  <ModalGlass.Content>
58
62
  <ModalGlass.Header>
59
63
  <ModalGlass.Title>Confirm Action</ModalGlass.Title>
@@ -81,7 +85,6 @@ function MyModal() {
81
85
 
82
86
  ```tsx
83
87
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
84
- <ModalGlass.Overlay />
85
88
  <ModalGlass.Content size="lg">
86
89
  <ModalGlass.Header>
87
90
  <ModalGlass.Title>Create Account</ModalGlass.Title>
@@ -490,7 +493,6 @@ function SettingsModal({ open, onOpenChange }) {
490
493
 
491
494
  return (
492
495
  <ModalGlass.Root open={open} onOpenChange={onOpenChange}>
493
- <ModalGlass.Overlay />
494
496
  <ModalGlass.Content size="lg">
495
497
  <ModalGlass.Header>
496
498
  <ModalGlass.Title>Settings</ModalGlass.Title>
package/docs/AI_USAGE.md CHANGED
@@ -415,7 +415,6 @@ v2.0.0 achieves **complete API compatibility with shadcn/ui**.
415
415
 
416
416
  ```tsx
417
417
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
418
- <ModalGlass.Overlay />
419
418
  <ModalGlass.Content>
420
419
  <ModalGlass.Header>
421
420
  <ModalGlass.Title>My Modal</ModalGlass.Title>
@@ -348,7 +348,6 @@ import { ModalGlass, ButtonGlass } from 'shadcn-glass-ui';
348
348
  function ConfirmDialog({ open, onConfirm, onCancel, title, message }: ConfirmDialogProps) {
349
349
  return (
350
350
  <ModalGlass.Root open={open} onOpenChange={onCancel}>
351
- <ModalGlass.Overlay />
352
351
  <ModalGlass.Content size="sm">
353
352
  <ModalGlass.Header>
354
353
  <ModalGlass.Title>{title}</ModalGlass.Title>
@@ -389,7 +388,6 @@ function CreateProjectModal({ open, onClose, onSubmit }) {
389
388
 
390
389
  return (
391
390
  <ModalGlass.Root open={open} onOpenChange={onClose}>
392
- <ModalGlass.Overlay />
393
391
  <ModalGlass.Content>
394
392
  <ModalGlass.Header>
395
393
  <ModalGlass.Title>Create Project</ModalGlass.Title>
@@ -111,7 +111,6 @@ ModalGlass and TabsGlass require compound component API:
111
111
 
112
112
  ```tsx
113
113
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
114
- <ModalGlass.Overlay />
115
114
  <ModalGlass.Content>
116
115
  <ModalGlass.Header>
117
116
  <ModalGlass.Title>Title</ModalGlass.Title>
@@ -1,6 +1,6 @@
1
1
  # Component Catalog
2
2
 
3
- Searchable index of all **57 components** in shadcn-glass-ui.
3
+ Searchable index of all **59 components** in shadcn-glass-ui.
4
4
 
5
5
  **v2.0.0 Update:** All components now use the 3-layer token system with semantic CSS variables. Zero
6
6
  hardcoded OKLCH values across all components.
@@ -252,7 +252,6 @@ trap, scroll lock, click-outside-to-close
252
252
 
253
253
  ```tsx
254
254
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
255
- <ModalGlass.Overlay />
256
255
  <ModalGlass.Content>
257
256
  <ModalGlass.Header>
258
257
  <ModalGlass.Title>Confirm</ModalGlass.Title>
@@ -270,7 +269,6 @@ trap, scroll lock, click-outside-to-close
270
269
 
271
270
  ```tsx
272
271
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
273
- <ModalGlass.Overlay />
274
272
  <ModalGlass.Content size="sm">
275
273
  <ModalGlass.Header>
276
274
  <ModalGlass.Title>Delete Item</ModalGlass.Title>
@@ -294,7 +292,6 @@ trap, scroll lock, click-outside-to-close
294
292
 
295
293
  ```tsx
296
294
  <ModalGlass.Root open={open} onOpenChange={setOpen}>
297
- <ModalGlass.Overlay />
298
295
  <ModalGlass.Content size="default">
299
296
  <ModalGlass.Header>
300
297
  <ModalGlass.Title>Create New Project</ModalGlass.Title>
@@ -0,0 +1,325 @@
1
+ # Component Patterns Guide
2
+
3
+ This document defines the established patterns for Glass UI components to ensure consistency across
4
+ the library.
5
+
6
+ ## Naming Convention
7
+
8
+ ### Standard Pattern: `{Feature}Glass`
9
+
10
+ All Glass UI components follow a consistent naming pattern: **`{Feature}Glass`**
11
+
12
+ **Examples:**
13
+
14
+ - `ButtonGlass` - Button component
15
+ - `InputGlass` - Input component
16
+ - `AlertGlass` - Alert component
17
+ - `ModalGlass` - Modal/Dialog component
18
+ - `TabsGlass` - Tabs component
19
+ - `SidebarGlass` - Sidebar component
20
+ - `CardGlass` - Card component (compound)
21
+
22
+ ### Exception: `GlassCard`
23
+
24
+ The `GlassCard` component is the only exception to this pattern. It uses `Glass{Feature}` instead of
25
+ `{Feature}Glass`. This is intentional and documented:
26
+
27
+ - **`GlassCard`** - A simple glass container with visual effects (intensity, glow, hover)
28
+ - **`CardGlass`** - A structured card with compound sub-components (shadcn/ui pattern)
29
+
30
+ Both serve different purposes:
31
+
32
+ - `GlassCard` = "Glass Container" (visual effects focus)
33
+ - `CardGlass` = "Structured Card" (layout/composition focus)
34
+
35
+ ---
36
+
37
+ ## Export Patterns
38
+
39
+ ### Pattern 1: Object Pattern (Complex Compound Components)
40
+
41
+ **When to use:** Components with 5+ sub-components
42
+
43
+ **Structure:**
44
+
45
+ ```tsx
46
+ export const ComponentGlass = {
47
+ Root: ComponentRoot,
48
+ Header: ComponentHeader,
49
+ Content: ComponentContent,
50
+ Footer: ComponentFooter,
51
+ // ... more sub-components
52
+ };
53
+ ```
54
+
55
+ **Usage:**
56
+
57
+ ```tsx
58
+ <ComponentGlass.Root>
59
+ <ComponentGlass.Header>
60
+ <ComponentGlass.Title>Title</ComponentGlass.Title>
61
+ </ComponentGlass.Header>
62
+ <ComponentGlass.Content>Content</ComponentGlass.Content>
63
+ <ComponentGlass.Footer>Footer</ComponentGlass.Footer>
64
+ </ComponentGlass.Root>
65
+ ```
66
+
67
+ **Examples:**
68
+
69
+ - `ModalGlass` (10 sub-components)
70
+ - `TabsGlass` (4 sub-components)
71
+ - `SidebarGlass` (20+ sub-components)
72
+ - `SplitLayoutGlass` (10+ sub-components)
73
+ - `CardGlass` (7 sub-components)
74
+
75
+ ### Pattern 2: Named Exports (Simple Compound Components)
76
+
77
+ **When to use:** Components with 2-4 sub-components
78
+
79
+ **Structure:**
80
+
81
+ ```tsx
82
+ export const AlertGlass = AlertGlassRoot;
83
+ export { AlertGlassTitle, AlertGlassDescription };
84
+ ```
85
+
86
+ **Usage:**
87
+
88
+ ```tsx
89
+ <AlertGlass variant="destructive">
90
+ <AlertGlassTitle>Error</AlertGlassTitle>
91
+ <AlertGlassDescription>Something went wrong</AlertGlassDescription>
92
+ </AlertGlass>
93
+ ```
94
+
95
+ **Examples:**
96
+
97
+ - `AlertGlass` (3 exports)
98
+ - `PopoverGlass` (4 exports)
99
+ - `TooltipGlass` (5 exports)
100
+ - `AvatarGlass` (4 exports)
101
+
102
+ ### Pattern 3: Single Export (Atomic Components)
103
+
104
+ **When to use:** Self-contained components without sub-components
105
+
106
+ **Structure:**
107
+
108
+ ```tsx
109
+ export const ButtonGlass = forwardRef<HTMLButtonElement, ButtonGlassProps>(
110
+ ({ variant, size, className, ...props }, ref) => { ... }
111
+ );
112
+ ```
113
+
114
+ **Examples:**
115
+
116
+ - `ButtonGlass`
117
+ - `BadgeGlass`
118
+ - `InputGlass`
119
+ - `CheckboxGlass`
120
+ - `SliderGlass`
121
+ - `ToggleGlass`
122
+
123
+ ---
124
+
125
+ ## Compound Component Structure
126
+
127
+ ### Sub-component Naming
128
+
129
+ For compound components using Object Pattern:
130
+
131
+ - Root component: `ComponentRoot` (internal), `ComponentGlass.Root` (exported)
132
+ - Sub-components: `ComponentHeader`, `ComponentTitle`, etc.
133
+
134
+ ### TypeScript Types
135
+
136
+ ```tsx
137
+ // Props interface
138
+ interface ComponentGlassProps extends React.ComponentProps<'div'> {
139
+ // Component-specific props
140
+ }
141
+
142
+ // For Radix-based components
143
+ type ComponentGlassProps = React.ComponentProps<typeof Primitive.Root>;
144
+ ```
145
+
146
+ ### Data Slot Attributes
147
+
148
+ All sub-components should include `data-slot` attributes for styling hooks:
149
+
150
+ ```tsx
151
+ function CardGlassHeader({ className, ...props }: React.ComponentProps<'div'>) {
152
+ return <div data-slot="card-header" className={cn('...', className)} {...props} />;
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## CSS Variable Naming
159
+
160
+ ### 3-Layer Token Architecture
161
+
162
+ Glass UI uses a 3-layer token system:
163
+
164
+ 1. **Layer 1 - Primitives:** `--oklch-*` (color values)
165
+ 2. **Layer 2 - Semantic:** `--semantic-*` (role-based)
166
+ 3. **Layer 3 - Component:** `--{component}-*` (component-specific)
167
+
168
+ ### Component Token Pattern
169
+
170
+ ```css
171
+ /* Card Component Tokens */
172
+ --card-subtle-bg: var(--semantic-surface);
173
+ --card-medium-bg: var(--semantic-surface-elevated);
174
+ --card-header-gap: 0.5rem;
175
+
176
+ /* Modal Component Tokens */
177
+ --modal-bg: var(--semantic-surface-elevated);
178
+ --modal-overlay: var(--semantic-overlay);
179
+ --modal-border: var(--semantic-border);
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Radix UI Integration
185
+
186
+ ### When to Use Radix Primitives
187
+
188
+ Use `@radix-ui/react-*` primitives for:
189
+
190
+ - Components requiring complex accessibility (Dialog, Tooltip, Popover)
191
+ - Components with trigger/portal patterns
192
+ - Components needing state management (open/close, selected)
193
+
194
+ ### Wrapping Pattern
195
+
196
+ ```tsx
197
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
198
+
199
+ function ModalGlassRoot({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
200
+ return <DialogPrimitive.Root data-slot="modal" {...props} />;
201
+ }
202
+
203
+ function ModalGlassTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
204
+ return <DialogPrimitive.Trigger data-slot="modal-trigger" {...props} />;
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## File Organization
211
+
212
+ ### Component Files
213
+
214
+ ```
215
+ src/components/glass/ui/
216
+ ├── button-glass.tsx # Single component
217
+ ├── button-glass.stories.tsx # Colocated stories
218
+ ├── alert-glass.tsx # Named exports compound
219
+ ├── modal-glass.tsx # Object pattern compound
220
+ ├── card-glass.tsx # Object pattern compound
221
+ └── __tests__/
222
+ ├── button-glass.test.tsx
223
+ ├── alert-glass.test.tsx
224
+ └── modal-glass.test.tsx
225
+ ```
226
+
227
+ ### Complex Components (Nested Directory)
228
+
229
+ ```
230
+ src/components/glass/ui/sidebar-glass/
231
+ ├── index.ts # Barrel export
232
+ ├── sidebar-context.tsx # Context provider
233
+ ├── sidebar-glass.tsx # Main components
234
+ └── sidebar-menu.tsx # Menu sub-components
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Accessibility Requirements
240
+
241
+ All Glass components must meet WCAG 2.1 AA standards:
242
+
243
+ 1. **Keyboard Navigation:** All interactive elements accessible via keyboard
244
+ 2. **Focus Management:** Visible focus indicators, focus trapping where appropriate
245
+ 3. **Screen Readers:** Proper ARIA attributes, semantic HTML
246
+ 4. **Color Contrast:** Meet minimum contrast ratios
247
+ 5. **Motion:** Respect `prefers-reduced-motion`
248
+
249
+ ### JSDoc Accessibility Documentation
250
+
251
+ ```tsx
252
+ /**
253
+ * ModalGlass Component
254
+ *
255
+ * @accessibility
256
+ * - **Keyboard Navigation:** Escape key closes modal, Tab traps focus
257
+ * - **Focus Management:** Focus moved to modal on open, returned on close
258
+ * - **Screen Readers:** Uses role="dialog" and aria-modal="true"
259
+ */
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Testing Requirements
265
+
266
+ ### Unit Tests
267
+
268
+ Every component must have unit tests covering:
269
+
270
+ - Rendering with default props
271
+ - All variant combinations
272
+ - Accessibility attributes
273
+ - Event handlers
274
+
275
+ ### Visual Regression Tests
276
+
277
+ All components need visual tests for:
278
+
279
+ - All 3 themes (glass, light, aurora)
280
+ - All size variants
281
+ - All state variations (hover, focus, disabled)
282
+
283
+ ### Storybook Stories
284
+
285
+ Required stories:
286
+
287
+ - Default usage
288
+ - All variants
289
+ - All sizes
290
+ - Interactive examples
291
+ - Accessibility showcase
292
+
293
+ ---
294
+
295
+ ## Migration Guide
296
+
297
+ ### From shadcn/ui to Glass UI
298
+
299
+ Replace shadcn/ui imports with Glass UI equivalents:
300
+
301
+ ```tsx
302
+ // Before (shadcn/ui)
303
+ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
304
+
305
+ // After (Glass UI)
306
+ import { CardGlass } from '@/components/glass/ui';
307
+
308
+ // Usage
309
+ <CardGlass.Root>
310
+ <CardGlass.Header>
311
+ <CardGlass.Title>Title</CardGlass.Title>
312
+ </CardGlass.Header>
313
+ <CardGlass.Content>Content</CardGlass.Content>
314
+ </CardGlass.Root>;
315
+ ```
316
+
317
+ For named export pattern (simpler components):
318
+
319
+ ```tsx
320
+ // Before (shadcn/ui)
321
+ import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
322
+
323
+ // After (Glass UI)
324
+ import { AlertGlass, AlertGlassTitle, AlertGlassDescription } from '@/components/glass/ui';
325
+ ```