shadcn-glass-ui 2.0.8 → 2.0.9
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.
- package/README.md +153 -109
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.d.ts +63 -322
- package/dist/components.js +2 -2
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +43 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/r/alert-glass.json +2 -1
- package/dist/r/avatar-glass.json +2 -3
- package/dist/r/button-glass.json +1 -1
- package/dist/r/circular-progress-glass.json +1 -1
- package/dist/r/combobox-glass.json +1 -1
- package/dist/r/dropdown-glass.json +3 -5
- package/dist/r/dropdown-menu-glass.json +42 -0
- package/dist/r/language-bar-glass.json +2 -2
- package/dist/r/popover-glass.json +1 -1
- package/dist/r/profile-avatar-glass.json +5 -3
- package/dist/r/rainbow-progress-glass.json +1 -1
- package/dist/r/registry.json +10 -4
- package/dist/r/sort-dropdown-glass.json +3 -4
- package/dist/r/tooltip-glass.json +3 -5
- package/dist/r/trust-score-card-glass.json +1 -1
- package/dist/r/trust-score-display-glass.json +1 -1
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-CVW50BKW.cjs → theme-context-DNe_2vWJ.cjs} +2 -2
- package/dist/theme-context-DNe_2vWJ.cjs.map +1 -0
- package/dist/{theme-context-BZoCplcU.js → theme-context-_T5r1KG4.js} +1 -1
- package/dist/theme-context-_T5r1KG4.js.map +1 -0
- package/dist/themes.cjs +1 -1
- package/dist/themes.d.ts +89 -1
- package/dist/themes.js +1 -1
- package/dist/{trust-score-card-glass-BcZbul0P.js → trust-score-card-glass-A7kas5OS.js} +353 -279
- package/dist/trust-score-card-glass-A7kas5OS.js.map +1 -0
- package/dist/{trust-score-card-glass-r3qM09Jp.cjs → trust-score-card-glass-Dgu46oWI.cjs} +551 -313
- package/dist/trust-score-card-glass-Dgu46oWI.cjs.map +1 -0
- package/dist/{use-focus-ZE8ozmZE.cjs → use-focus-BRkQtQCj.cjs} +2 -2
- package/dist/{use-focus-ZE8ozmZE.cjs.map → use-focus-BRkQtQCj.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-BuS80tbN.cjs → use-wallpaper-tint-CfShPBo2.cjs} +2 -2
- package/dist/{use-wallpaper-tint-BuS80tbN.cjs.map → use-wallpaper-tint-CfShPBo2.cjs.map} +1 -1
- package/dist/{utils-DLXayspX.cjs → utils-BXN7AcRu.cjs} +2 -2
- package/dist/{utils-DLXayspX.cjs.map → utils-BXN7AcRu.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/AI_USAGE.md +1 -32
- package/docs/API_PATTERNS_COMPARISON.md +10 -9
- package/docs/COMPONENTS_CATALOG.md +140 -45
- package/docs/DROPDOWN_ARCHITECTURE.md +290 -0
- package/docs/GETTING_STARTED.md +6 -5
- package/docs/TOKEN_ARCHITECTURE.md +100 -0
- package/docs/api/README.md +3 -3
- package/docs/migration/compound-components-v2.md +384 -0
- package/docs/visual-testing-guide.md +50 -12
- package/package.json +2 -2
- package/dist/theme-context-BZoCplcU.js.map +0 -1
- package/dist/theme-context-CVW50BKW.cjs.map +0 -1
- package/dist/trust-score-card-glass-BcZbul0P.js.map +0 -1
- package/dist/trust-score-card-glass-r3qM09Jp.cjs.map +0 -1
- package/docs/ADVANCED_PATTERNS.md +0 -408
- package/docs/BREAKING_CHANGES.md +0 -213
- package/docs/announcements/v1.0.5-devto.md +0 -363
- package/docs/announcements/v1.0.5-reddit.md +0 -115
- package/docs/announcements/v1.0.5-twitter.md +0 -70
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_trust_score_card_glass = require("./trust-score-card-glass-
|
|
1
|
+
const require_trust_score_card_glass = require("./trust-score-card-glass-Dgu46oWI.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-
|
|
130
|
+
//# sourceMappingURL=use-focus-BRkQtQCj.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-focus-ZE8ozmZE.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-BRkQtQCj.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-
|
|
1
|
+
const require_trust_score_card_glass = require("./trust-score-card-glass-Dgu46oWI.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-
|
|
162
|
+
//# sourceMappingURL=use-wallpaper-tint-CfShPBo2.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-wallpaper-tint-BuS80tbN.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-CfShPBo2.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-
|
|
1
|
+
const require_trust_score_card_glass = require("./trust-score-card-glass-Dgu46oWI.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-
|
|
14
|
+
//# sourceMappingURL=utils-BXN7AcRu.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils-
|
|
1
|
+
{"version":3,"file":"utils-BXN7AcRu.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-
|
|
1
|
+
const require_utils = require("./utils-BXN7AcRu.cjs");
|
|
2
2
|
exports.cn = require_utils.cn;
|
package/docs/AI_USAGE.md
CHANGED
|
@@ -3,37 +3,6 @@
|
|
|
3
3
|
This guide is specifically for **AI assistants** (Claude Code, GitHub Copilot, GPT-based tools)
|
|
4
4
|
helping users integrate shadcn-glass-ui into their projects.
|
|
5
5
|
|
|
6
|
-
## Badges & Integrations
|
|
7
|
-
|
|
8
|
-
[](https://github.com/Yhooi2/shadcn-glass-ui-library/actions)
|
|
9
|
-
[](https://bundlephobia.com/package/shadcn-glass-ui)
|
|
10
|
-
[](https://reactjs.org/)
|
|
11
|
-
[](https://tailwindcss.com/)
|
|
12
|
-
[](docs/AI_USAGE.md)
|
|
13
|
-
[](docs/AI_USAGE.md)
|
|
14
|
-
[](https://context7.com/yhooi2/shadcn-glass-ui-library)
|
|
15
|
-
|
|
16
|
-
### AI Tool Integration Status
|
|
17
|
-
|
|
18
|
-
| Tool | Status | Integration |
|
|
19
|
-
| ------------------ | --------- | ------------------------------------------------------------ |
|
|
20
|
-
| **Claude Code** | Optimized | [CLAUDE.md](../CLAUDE.md) with 500+ lines of project context |
|
|
21
|
-
| **GitHub Copilot** | Supported | TypeScript strict mode + comprehensive JSDoc |
|
|
22
|
-
| **ChatGPT/GPT-4** | Supported | [EXPORTS_MAP.json](EXPORTS_MAP.json) with component metadata |
|
|
23
|
-
| **Context7 MCP** | Indexed | [59 rules](../context7.json) covering breaking changes, APIs |
|
|
24
|
-
| **Cursor** | Supported | Full TypeScript inference |
|
|
25
|
-
|
|
26
|
-
### Library Identifiers
|
|
27
|
-
|
|
28
|
-
| Identifier | Value |
|
|
29
|
-
| ------------------ | --------------------------------- |
|
|
30
|
-
| npm package | `shadcn-glass-ui` |
|
|
31
|
-
| Registry namespace | `@shadcn-glass-ui` |
|
|
32
|
-
| Context7 ID | `/yhooi2/shadcn-glass-ui-library` |
|
|
33
|
-
| GitHub | `Yhooi2/shadcn-glass-ui-library` |
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
6
|
## 🎯 Quick Decision Tree
|
|
38
7
|
|
|
39
8
|
### When a user asks "I want to use shadcn-glass-ui"
|
|
@@ -1079,7 +1048,7 @@ for details.
|
|
|
1079
1048
|
- **Context7 Library ID:** `/yhooi2/shadcn-glass-ui-library`
|
|
1080
1049
|
- **Component count:** 58 (includes StepperGlass in v2.0.0+)
|
|
1081
1050
|
- **React version:** 18.0+ or 19.0+
|
|
1082
|
-
- **Tailwind version:** 4.
|
|
1051
|
+
- **Tailwind version:** 4.1+
|
|
1083
1052
|
- **Node version:** 20.16+, 22.19+, or 24+
|
|
1084
1053
|
|
|
1085
1054
|
### Import Patterns
|
|
@@ -9,19 +9,20 @@ new `MetricCardGlass` API.
|
|
|
9
9
|
|
|
10
10
|
## 📊 Comparison Table: Core UI Components
|
|
11
11
|
|
|
12
|
-
| Component | Title/Label Prop
|
|
13
|
-
| --------------------- |
|
|
14
|
-
| **shadcn/ui Card** | `CardTitle` (subcomponent)
|
|
15
|
-
| **AlertGlass** | `
|
|
16
|
-
| **BadgeGlass** | —
|
|
17
|
-
| **ButtonGlass** | —
|
|
18
|
-
| **GlassCard** | —
|
|
19
|
-
| **NotificationGlass** | `title: string` (required)
|
|
12
|
+
| Component | Title/Label Prop | Value Prop | Variant System | Children | Icon Support | Notes |
|
|
13
|
+
| --------------------- | -------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------- | -------------- | -------------------------- | ------------------------ |
|
|
14
|
+
| **shadcn/ui Card** | `CardTitle` (subcomponent) | — | — | ✅ Composition | — | Compound component |
|
|
15
|
+
| **AlertGlass** | `AlertGlassTitle` (subcomponent) | — | `variant: 'default' \| 'destructive' \| 'success' \| 'warning'` | ✅ Composition | ✅ Auto (variant-based) | **Compound component** |
|
|
16
|
+
| **BadgeGlass** | — | — | `variant: 'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning' \| 'info'` | ✅ `children` | ❌ | **shadcn/ui + extended** |
|
|
17
|
+
| **ButtonGlass** | — | — | `variant: 'primary' \| 'secondary' \| 'ghost' \| 'destructive' \| 'success' \| 'text'` | ✅ `children` | ✅ `iconLeft`, `iconRight` | **shadcn/ui-like** |
|
|
18
|
+
| **GlassCard** | — | — | `intensity: 'low' \| 'medium' \| 'high'` | ✅ `children` | ❌ | Custom Glass prop |
|
|
19
|
+
| **NotificationGlass** | `title: string` (required) | `message: string` | `variant: 'default' \| 'destructive' \| 'success' \| 'warning'` | ❌ | ✅ Auto (variant-based) | **shadcn/ui compatible** |
|
|
20
20
|
|
|
21
21
|
### Insights from Core UI:
|
|
22
22
|
|
|
23
23
|
1. **✅ Variant System** - all use `variant` prop (except GlassCard with `intensity`)
|
|
24
|
-
2. **✅
|
|
24
|
+
2. **✅ Compound Components** - AlertGlass follows shadcn/ui pattern with subcomponents
|
|
25
|
+
(AlertGlassTitle, AlertGlassDescription)
|
|
25
26
|
3. **✅ shadcn/ui variants** - base variants: `default`, `secondary`, `destructive`, `outline`
|
|
26
27
|
4. **✅ Glass UI extensions** - extended variants: `success`, `warning`, `info`
|
|
27
28
|
5. **✅ Icon handling** - automatic icons based on `variant` (AlertGlass, NotificationGlass)
|
|
@@ -380,76 +380,130 @@ onValueChange | (value: string) => void | - | Change handler |
|
|
|
380
380
|
|
|
381
381
|
#### DropdownGlass
|
|
382
382
|
|
|
383
|
-
**File:** `src/components/glass/ui/dropdown-glass.tsx` **Features:**
|
|
384
|
-
support, keyboard navigation **
|
|
383
|
+
**File:** `src/components/glass/ui/dropdown-glass.tsx` + `dropdown-menu-glass.tsx` **Features:**
|
|
384
|
+
Radix UI based, submenu support, keyboard navigation **API:** Simple API (items prop) + Compound
|
|
385
|
+
components (DropdownMenuGlass.\*)
|
|
386
|
+
|
|
387
|
+
**Simple API (recommended for basic dropdowns):**
|
|
385
388
|
|
|
386
389
|
```tsx
|
|
387
390
|
<DropdownGlass
|
|
388
391
|
trigger={<ButtonGlass>Menu</ButtonGlass>}
|
|
389
392
|
items={[
|
|
390
|
-
{ label: 'Profile', onClick: () => {} },
|
|
391
|
-
{ label: 'Settings', onClick: () => {} },
|
|
392
|
-
{
|
|
393
|
-
{ label: 'Logout', onClick: () => {} },
|
|
393
|
+
{ label: 'Profile', icon: User, onClick: () => {} },
|
|
394
|
+
{ label: 'Settings', icon: Settings, onClick: () => {} },
|
|
395
|
+
{ divider: true },
|
|
396
|
+
{ label: 'Logout', icon: LogOut, onClick: () => {}, danger: true },
|
|
394
397
|
]}
|
|
395
398
|
/>
|
|
396
399
|
```
|
|
397
400
|
|
|
401
|
+
**Compound API (for complex dropdowns with full control):**
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
<DropdownMenuGlass>
|
|
405
|
+
<DropdownMenuGlassTrigger asChild>
|
|
406
|
+
<ButtonGlass>Open Menu</ButtonGlass>
|
|
407
|
+
</DropdownMenuGlassTrigger>
|
|
408
|
+
<DropdownMenuGlassContent>
|
|
409
|
+
<DropdownMenuGlassLabel>My Account</DropdownMenuGlassLabel>
|
|
410
|
+
<DropdownMenuGlassSeparator />
|
|
411
|
+
<DropdownMenuGlassGroup>
|
|
412
|
+
<DropdownMenuGlassItem>
|
|
413
|
+
<User className="mr-2 h-4 w-4" />
|
|
414
|
+
Profile
|
|
415
|
+
</DropdownMenuGlassItem>
|
|
416
|
+
<DropdownMenuGlassItem>
|
|
417
|
+
<Settings className="mr-2 h-4 w-4" />
|
|
418
|
+
Settings
|
|
419
|
+
</DropdownMenuGlassItem>
|
|
420
|
+
</DropdownMenuGlassGroup>
|
|
421
|
+
<DropdownMenuGlassSeparator />
|
|
422
|
+
<DropdownMenuGlassItem variant="destructive">
|
|
423
|
+
<LogOut className="mr-2 h-4 w-4" />
|
|
424
|
+
Logout
|
|
425
|
+
</DropdownMenuGlassItem>
|
|
426
|
+
</DropdownMenuGlassContent>
|
|
427
|
+
</DropdownMenuGlass>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Available Compound Components:**
|
|
431
|
+
|
|
432
|
+
- `DropdownMenuGlass` - Root container
|
|
433
|
+
- `DropdownMenuGlassTrigger` - Trigger button (use `asChild`)
|
|
434
|
+
- `DropdownMenuGlassContent` - Menu content container
|
|
435
|
+
- `DropdownMenuGlassItem` - Menu item (supports `variant="destructive"`)
|
|
436
|
+
- `DropdownMenuGlassLabel` - Section label
|
|
437
|
+
- `DropdownMenuGlassSeparator` - Divider
|
|
438
|
+
- `DropdownMenuGlassGroup` - Group items
|
|
439
|
+
- `DropdownMenuGlassCheckboxItem` - Checkbox menu item
|
|
440
|
+
- `DropdownMenuGlassRadioGroup` + `DropdownMenuGlassRadioItem` - Radio menu items
|
|
441
|
+
- `DropdownMenuGlassSub` + `DropdownMenuGlassSubTrigger` + `DropdownMenuGlassSubContent` - Submenu
|
|
442
|
+
|
|
398
443
|
#### AlertGlass
|
|
399
444
|
|
|
400
445
|
**File:** `src/components/glass/ui/alert-glass.tsx` **Variants:** default, destructive, success,
|
|
401
|
-
warning **Features:**
|
|
446
|
+
warning **Features:** Compound component (Title, Description), icon, dismissable **API:** Compound
|
|
447
|
+
component pattern (shadcn/ui compatible)
|
|
402
448
|
|
|
403
449
|
**Default Alert:**
|
|
404
450
|
|
|
405
451
|
```tsx
|
|
406
|
-
<AlertGlass variant="default"
|
|
407
|
-
|
|
452
|
+
<AlertGlass variant="default">
|
|
453
|
+
<AlertGlassTitle>Information</AlertGlassTitle>
|
|
454
|
+
<AlertGlassDescription>This is an informational message.</AlertGlassDescription>
|
|
408
455
|
</AlertGlass>
|
|
409
456
|
```
|
|
410
457
|
|
|
411
458
|
**Destructive Alert:**
|
|
412
459
|
|
|
413
460
|
```tsx
|
|
414
|
-
<AlertGlass variant="destructive"
|
|
415
|
-
|
|
461
|
+
<AlertGlass variant="destructive">
|
|
462
|
+
<AlertGlassTitle>Error</AlertGlassTitle>
|
|
463
|
+
<AlertGlassDescription>Something went wrong. Please try again.</AlertGlassDescription>
|
|
416
464
|
</AlertGlass>
|
|
417
465
|
```
|
|
418
466
|
|
|
419
467
|
**Success Alert:**
|
|
420
468
|
|
|
421
469
|
```tsx
|
|
422
|
-
<AlertGlass variant="success"
|
|
423
|
-
|
|
470
|
+
<AlertGlass variant="success">
|
|
471
|
+
<AlertGlassTitle>Success!</AlertGlassTitle>
|
|
472
|
+
<AlertGlassDescription>Your changes have been saved successfully.</AlertGlassDescription>
|
|
424
473
|
</AlertGlass>
|
|
425
474
|
```
|
|
426
475
|
|
|
427
476
|
**Warning Alert:**
|
|
428
477
|
|
|
429
478
|
```tsx
|
|
430
|
-
<AlertGlass variant="warning"
|
|
431
|
-
|
|
479
|
+
<AlertGlass variant="warning">
|
|
480
|
+
<AlertGlassTitle>Warning</AlertGlassTitle>
|
|
481
|
+
<AlertGlassDescription>This action may have unintended consequences.</AlertGlassDescription>
|
|
432
482
|
</AlertGlass>
|
|
433
483
|
```
|
|
434
484
|
|
|
435
485
|
**Dismissable Alert:**
|
|
436
486
|
|
|
437
487
|
```tsx
|
|
438
|
-
<AlertGlass
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
dismissable
|
|
442
|
-
onDismiss={() => console.log('dismissed')}
|
|
443
|
-
>
|
|
444
|
-
Your session will expire in 5 minutes.
|
|
488
|
+
<AlertGlass variant="warning" dismissable onDismiss={() => console.log('dismissed')}>
|
|
489
|
+
<AlertGlassTitle>Session Expiring</AlertGlassTitle>
|
|
490
|
+
<AlertGlassDescription>Your session will expire in 5 minutes.</AlertGlassDescription>
|
|
445
491
|
</AlertGlass>
|
|
446
492
|
```
|
|
447
493
|
|
|
448
|
-
**Props:**
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
494
|
+
**Props (AlertGlass):**
|
|
495
|
+
|
|
496
|
+
| Prop | Type | Default | Description |
|
|
497
|
+
| ----------- | -------------------------------------------- | ------- | --------------------------------------- |
|
|
498
|
+
| variant | default \| destructive \| success \| warning | default | Visual style variant |
|
|
499
|
+
| children | ReactNode | - | AlertGlassTitle + AlertGlassDescription |
|
|
500
|
+
| dismissable | boolean | false | Show dismiss button |
|
|
501
|
+
| onDismiss | () => void | - | Dismiss callback |
|
|
502
|
+
|
|
503
|
+
**Sub-components:**
|
|
504
|
+
|
|
505
|
+
- `AlertGlassTitle` - Alert title (inherits color from variant)
|
|
506
|
+
- `AlertGlassDescription` - Alert description text (inherits color, 80% opacity)
|
|
453
507
|
|
|
454
508
|
#### BadgeGlass
|
|
455
509
|
|
|
@@ -497,10 +551,22 @@ className | string | - | Additional styles |
|
|
|
497
551
|
#### AvatarGlass
|
|
498
552
|
|
|
499
553
|
**File:** `src/components/glass/ui/avatar-glass.tsx` **Sizes:** sm (32px), md (48px), lg (64px), xl
|
|
500
|
-
(96px) **Status:** online, offline, busy, away **Features:**
|
|
554
|
+
(96px) **Status:** online, offline, busy, away **Features:** Compound component (Radix UI),
|
|
555
|
+
fallback, image **API:** Compound component + Simple wrapper
|
|
556
|
+
|
|
557
|
+
**Compound API (recommended):**
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
<AvatarGlass size="lg" status="online">
|
|
561
|
+
<AvatarGlassImage src="/avatar.jpg" alt="John Doe" />
|
|
562
|
+
<AvatarGlassFallback>JD</AvatarGlassFallback>
|
|
563
|
+
</AvatarGlass>
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Simple API (backward compatible):**
|
|
501
567
|
|
|
502
568
|
```tsx
|
|
503
|
-
<
|
|
569
|
+
<AvatarGlassSimple name="John Doe" size="lg" status="online" />
|
|
504
570
|
```
|
|
505
571
|
|
|
506
572
|
#### GlassCard
|
|
@@ -713,24 +779,43 @@ button |
|
|
|
713
779
|
#### TooltipGlass
|
|
714
780
|
|
|
715
781
|
**File:** `src/components/glass/ui/tooltip-glass.tsx` **Sides:** top, bottom, left, right
|
|
716
|
-
**Features:**
|
|
782
|
+
**Features:** Radix UI based, accessible, delay config **API:** Compound component + Simple wrapper
|
|
717
783
|
|
|
718
|
-
**
|
|
784
|
+
**Compound API (recommended):**
|
|
719
785
|
|
|
720
786
|
```tsx
|
|
721
|
-
<
|
|
722
|
-
<
|
|
723
|
-
|
|
787
|
+
<TooltipGlassProvider>
|
|
788
|
+
<TooltipGlass>
|
|
789
|
+
<TooltipGlassTrigger asChild>
|
|
790
|
+
<ButtonGlass>Hover me</ButtonGlass>
|
|
791
|
+
</TooltipGlassTrigger>
|
|
792
|
+
<TooltipGlassContent side="top">
|
|
793
|
+
<p>This is a tooltip</p>
|
|
794
|
+
</TooltipGlassContent>
|
|
795
|
+
</TooltipGlass>
|
|
796
|
+
</TooltipGlassProvider>
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**Simple API (backward compatible):**
|
|
800
|
+
|
|
801
|
+
```tsx
|
|
802
|
+
<TooltipGlassProvider>
|
|
803
|
+
<TooltipGlassSimple content="This is a tooltip" side="top">
|
|
804
|
+
<ButtonGlass>Hover me</ButtonGlass>
|
|
805
|
+
</TooltipGlassSimple>
|
|
806
|
+
</TooltipGlassProvider>
|
|
724
807
|
```
|
|
725
808
|
|
|
726
809
|
**Tooltip on Icon Button (Accessibility):**
|
|
727
810
|
|
|
728
811
|
```tsx
|
|
729
|
-
<
|
|
730
|
-
<
|
|
731
|
-
<
|
|
732
|
-
|
|
733
|
-
</
|
|
812
|
+
<TooltipGlassProvider>
|
|
813
|
+
<TooltipGlassSimple content="Delete item" side="bottom">
|
|
814
|
+
<ButtonGlass variant="ghost" size="icon" aria-label="Delete item">
|
|
815
|
+
<Trash className="h-4 w-4" />
|
|
816
|
+
</ButtonGlass>
|
|
817
|
+
</TooltipGlassSimple>
|
|
818
|
+
</TooltipGlassProvider>
|
|
734
819
|
```
|
|
735
820
|
|
|
736
821
|
**Tooltip with Custom Delay:**
|
|
@@ -748,20 +833,30 @@ delayDuration | number | 200 | Show delay (ms) | | sideOffset | number | 4 | Dis
|
|
|
748
833
|
|
|
749
834
|
#### PopoverGlass
|
|
750
835
|
|
|
751
|
-
**File:** `src/components/glass/ui/popover-glass.tsx` **Features:**
|
|
752
|
-
**
|
|
836
|
+
**File:** `src/components/glass/ui/popover-glass.tsx` **Features:** Radix UI based, trigger,
|
|
837
|
+
content, anchor **API:** Compound component + Legacy wrapper
|
|
838
|
+
|
|
839
|
+
**Compound API (recommended):**
|
|
753
840
|
|
|
754
841
|
```tsx
|
|
755
842
|
<PopoverGlass>
|
|
756
|
-
<
|
|
843
|
+
<PopoverGlassTrigger asChild>
|
|
757
844
|
<ButtonGlass>Open</ButtonGlass>
|
|
758
|
-
</
|
|
759
|
-
<
|
|
845
|
+
</PopoverGlassTrigger>
|
|
846
|
+
<PopoverGlassContent side="bottom" align="center">
|
|
760
847
|
<p>Popover content</p>
|
|
761
|
-
</
|
|
848
|
+
</PopoverGlassContent>
|
|
762
849
|
</PopoverGlass>
|
|
763
850
|
```
|
|
764
851
|
|
|
852
|
+
**Legacy API (backward compatible):**
|
|
853
|
+
|
|
854
|
+
```tsx
|
|
855
|
+
<PopoverGlassLegacy trigger={<ButtonGlass>Open</ButtonGlass>} side="bottom" align="center">
|
|
856
|
+
<p>Popover content</p>
|
|
857
|
+
</PopoverGlassLegacy>
|
|
858
|
+
```
|
|
859
|
+
|
|
765
860
|
---
|
|
766
861
|
|
|
767
862
|
### Level 2 - Atomic (7)
|