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.
- package/CHANGELOG.md +0 -51
- package/README.md +11 -12
- package/context7.json +4 -15
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.d.ts +89 -160
- package/dist/components.js +1 -1
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +966 -2289
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +965 -2253
- package/dist/index.js.map +1 -1
- package/dist/r/alert-glass.json +1 -1
- package/dist/r/badge-glass.json +1 -1
- package/dist/r/button-glass.json +1 -1
- package/dist/r/card-glass.json +40 -0
- package/dist/r/input-glass.json +1 -1
- package/dist/r/modal-glass.json +5 -5
- package/dist/r/registry.json +7 -1
- package/dist/r/tooltip-glass.json +1 -1
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-D_cb9KzA.cjs → theme-context-BEA8K_rq.cjs} +2 -2
- package/dist/{theme-context-D_cb9KzA.cjs.map → theme-context-BEA8K_rq.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-CTsEVRD3.cjs → trust-score-card-glass-DTS1RdIt.cjs} +189 -341
- package/dist/trust-score-card-glass-DTS1RdIt.cjs.map +1 -0
- package/dist/{trust-score-card-glass-CUStm4o_.js → trust-score-card-glass-Dg4_b_g_.js} +158 -238
- package/dist/trust-score-card-glass-Dg4_b_g_.js.map +1 -0
- package/dist/{use-focus--Hw2nevi.cjs → use-focus-CdoUzFQ8.cjs} +2 -2
- package/dist/{use-focus--Hw2nevi.cjs.map → use-focus-CdoUzFQ8.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-B4oMQsXQ.cjs → use-wallpaper-tint-Rq5UgY9L.cjs} +2 -2
- package/dist/{use-wallpaper-tint-B4oMQsXQ.cjs.map → use-wallpaper-tint-Rq5UgY9L.cjs.map} +1 -1
- package/dist/{utils-BqeJ4aco.cjs → utils-NLnOCttr.cjs} +2 -2
- package/dist/{utils-BqeJ4aco.cjs.map → utils-NLnOCttr.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/ADVANCED_PATTERNS.md +7 -5
- package/docs/AI_USAGE.md +0 -1
- package/docs/BEST_PRACTICES.md +0 -2
- package/docs/BREAKING_CHANGES.md +0 -1
- package/docs/COMPONENTS_CATALOG.md +1 -4
- package/docs/COMPONENT_PATTERNS.md +325 -0
- package/docs/GETTING_STARTED.md +52 -28
- package/docs/api/README.md +0 -2
- package/docs/api/variables/ModalGlass.md +5 -4
- package/package.json +2 -4
- package/dist/trust-score-card-glass-CTsEVRD3.cjs.map +0 -1
- 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-
|
|
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
|
|
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":";;AAyEA,SAAgB,SAAS,UAA2B,EAAE,EAAkB;CACtE,MAAM,EACJ,aAAa,GACb,aAAa,GACb,eAAe,OACf,kBACE;CAEJ,MAAM,CAAC,WAAW,sBAAA,GAAA,MAAA,UAA8B,MAAM;CACtD,MAAM,CAAC,cAAc,oBAAA,GAAA,MAAA,UAAkE,KAAK;CAC5F,MAAM,CAAC,cAAc,oBAAA,GAAA,MAAA,UAAkE,KAAK;CAE5F,MAAM,gBAAA,GAAA,MAAA,cACH,UAAmB;AAClB,oBAAkB,MAAM;AACxB,kBAAgB,MAAM;IAExB,CAAC,cAAc,CAChB;CAED,MAAM,oBAAA,GAAA,MAAA,mBACE;AAEJ,MAAI,cAAc;AAChB,gBAAa,aAAa;AAC1B,mBAAgB,KAAK;;AAGvB,MAAI,aAAa,EAIf,iBAHgB,iBAAiB;AAC/B,gBAAa,KAAK;KACjB,WAAW,CACU;MAExB,cAAa,KAAK;IAGtB;EAAC;EAAY;EAAc;EAAa,CACzC;CAED,MAAM,oBAAA,GAAA,MAAA,mBACE;AAEJ,MAAI,cAAc;AAChB,gBAAa,aAAa;AAC1B,mBAAgB,KAAK;;AAGvB,MAAI,aAAa,EAIf,iBAHgB,iBAAiB;AAC/B,gBAAa,MAAM;KAClB,WAAW,CACU;MAExB,cAAa,MAAM;IAGvB;EAAC;EAAY;EAAc;EAAa,CACzC;CAED,MAAM,eAAA,GAAA,MAAA,mBACE;AACJ,MAAI,aACF,cAAa,KAAK;IAGtB,CAAC,cAAc,aAAa,CAC7B;CAED,MAAM,cAAA,GAAA,MAAA,mBACE;AACJ,MAAI,aACF,cAAa,MAAM;IAGvB,CAAC,cAAc,aAAa,CAC7B;AAWD,QAAO;EACL;EACA,YAX+C;GAC/C,cAAc;GACd,cAAc;GACd,GAAI,gBAAgB;IAClB,SAAS;IACT,QAAQ;IACT;GACF;EAKC;EACD;;AChJH,IAAI,mBAAmB;AACvB,IAAI,gBAAgB;AAOpB,SAAS,6BAA6B;AACpC,KAAI,iBAAiB,OAAO,WAAW,YAAa;AAEpD,iBAAgB;CAGhB,MAAM,iBAAiB,MAAgC;AAErD,MAAI,EAAE,QAAQ,SAAS,EAAE,IAAI,WAAW,QAAQ,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IACjF,oBAAmB;;CAKvB,MAAM,0BAA0B;AAC9B,qBAAmB;;AAIrB,UAAS,iBAAiB,WAAW,eAAe,KAAK;AACzD,UAAS,iBAAiB,aAAa,mBAAmB,KAAK;AAC/D,UAAS,iBAAiB,eAAe,mBAAmB,KAAK;AACjE,UAAS,iBAAiB,cAAc,mBAAmB,KAAK;;AAgElE,SAAgB,SAAS,UAA2B,EAAE,EAAkB;CACtE,MAAM,EAAE,eAAe,eAAe,OAAO,cAAc;CAE3D,MAAM,CAAC,WAAW,sBAAA,GAAA,MAAA,UAA8B,MAAM;CACtD,MAAM,CAAC,gBAAgB,sBAAA,GAAA,MAAA,UAA8B,MAAM;CAC3D,MAAM,uBAAA,GAAA,MAAA,QAA6B,MAAM;CACzC,MAAM,YAAA,GAAA,MAAA,QAAkB,MAAM;AAG9B,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,aACF,6BAA4B;IAE7B,CAAC,aAAa,CAAC;CAElB,MAAM,gBAAA,GAAA,MAAA,cACH,UAAmB;AAClB,oBAAkB,MAAM;AACxB,WAAS,UAAU;AACnB,kBAAgB,MAAM;IAExB,CAAC,cAAc,CAChB;CAED,MAAM,eAAA,GAAA,MAAA,mBACE;AACJ,eAAa,KAAK;AAElB,MAAI,cAAc;GAEhB,MAAM,kBAAkB;AACxB,qBAAkB,gBAAgB;AAClC,uBAAoB,UAAU;;IAGlC,CAAC,cAAc,aAAa,CAC7B;CAED,MAAM,cAAA,GAAA,MAAA,mBACE;AACJ,eAAa,MAAM;AACnB,oBAAkB,MAAM;IAE1B,CAAC,aAAa,CACf;CAED,MAAM,iBAAA,GAAA,MAAA,cACH,MAAqB;AACpB,sBAAoB,UAAU;AAC9B,cAAY,EAAE;IAEhB,CAAC,UAAU,CACZ;AAQD,QAAO;EACL;EACA;EACA,YAT+C;GAC/C,SAAS;GACT,QAAQ;GACR,GAAI,aAAa,EAAE,WAAW,eAAe;GAC9C;EAMC;EACA;EACD"}
|
|
@@ -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-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-
|
|
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-
|
|
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-
|
|
14
|
+
//# sourceMappingURL=utils-NLnOCttr.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils-
|
|
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-
|
|
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.
|
|
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>
|
package/docs/BEST_PRACTICES.md
CHANGED
|
@@ -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>
|
package/docs/BREAKING_CHANGES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component Catalog
|
|
2
2
|
|
|
3
|
-
Searchable index of all **
|
|
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
|
+
```
|