shadcn-glass-ui 2.1.2 → 2.1.5
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 +5 -5
- package/context7.json +17 -2
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.js +1 -1
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +1659 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1651 -4
- package/dist/index.js.map +1 -1
- package/dist/r/registry.json +36 -0
- package/dist/r/sidebar-context.json +35 -0
- package/dist/r/sidebar-glass.json +40 -0
- package/dist/r/sidebar-menu.json +39 -0
- package/dist/r/split-layout-accordion.json +24 -0
- package/dist/r/split-layout-context.json +21 -0
- package/dist/r/split-layout-glass.json +25 -0
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-BHXYJ4RE.cjs → theme-context-Y98bGvcm.cjs} +2 -2
- package/dist/{theme-context-BHXYJ4RE.cjs.map → theme-context-Y98bGvcm.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-CGXmOIfq.cjs → trust-score-card-glass-2rjz00d_.cjs} +47 -5
- package/dist/trust-score-card-glass-2rjz00d_.cjs.map +1 -0
- package/dist/{trust-score-card-glass-L9g0qamo.js → trust-score-card-glass-zjkx4OC2.js} +3 -3
- package/dist/trust-score-card-glass-zjkx4OC2.js.map +1 -0
- package/dist/{use-focus-CeNHOiBa.cjs → use-focus-DbpBEuee.cjs} +2 -2
- package/dist/{use-focus-CeNHOiBa.cjs.map → use-focus-DbpBEuee.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-Bt2G3g1v.cjs → use-wallpaper-tint-DbawS9zh.cjs} +2 -2
- package/dist/{use-wallpaper-tint-Bt2G3g1v.cjs.map → use-wallpaper-tint-DbawS9zh.cjs.map} +1 -1
- package/dist/{utils-LYxxWvUn.cjs → utils-XlyXIhuP.cjs} +2 -2
- package/dist/{utils-LYxxWvUn.cjs.map → utils-XlyXIhuP.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/AI_USAGE.md +5 -5
- package/docs/BEST_PRACTICES.md +1 -1
- package/docs/COMPONENTS_CATALOG.md +215 -0
- package/docs/EXPORTS_MAP.json +140 -14
- package/docs/EXPORTS_STRUCTURE.md +43 -9
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/REGISTRY_USAGE.md +1 -1
- package/docs/api/README.md +1 -1
- package/docs/components/SIDEBAR_GLASS.md +555 -0
- package/docs/components/SPLIT_LAYOUT_GLASS.md +546 -0
- package/package.json +3 -3
- package/dist/trust-score-card-glass-CGXmOIfq.cjs.map +0 -1
- package/dist/trust-score-card-glass-L9g0qamo.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-2rjz00d_.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-DbpBEuee.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-focus-CeNHOiBa.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
|
+
{"version":3,"file":"use-focus-DbpBEuee.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-2rjz00d_.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-DbawS9zh.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-wallpaper-tint-Bt2G3g1v.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-DbawS9zh.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-2rjz00d_.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-XlyXIhuP.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils-
|
|
1
|
+
{"version":3,"file":"utils-XlyXIhuP.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-XlyXIhuP.cjs");
|
|
2
2
|
exports.cn = require_utils.cn;
|
package/docs/AI_USAGE.md
CHANGED
|
@@ -138,7 +138,7 @@ export default {
|
|
|
138
138
|
In your main CSS file (`src/index.css` or `src/globals.css`):
|
|
139
139
|
|
|
140
140
|
```css
|
|
141
|
-
@import 'shadcn-glass-ui/
|
|
141
|
+
@import 'shadcn-glass-ui/styles.css';
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
**5. Wrap app with theme provider**
|
|
@@ -203,7 +203,7 @@ export function Test() {
|
|
|
203
203
|
**Issue: "Styles not applied"**
|
|
204
204
|
|
|
205
205
|
- **Cause:** CSS not imported
|
|
206
|
-
- **Fix:** Add `@import 'shadcn-glass-ui/
|
|
206
|
+
- **Fix:** Add `@import 'shadcn-glass-ui/styles.css';` to main CSS
|
|
207
207
|
|
|
208
208
|
---
|
|
209
209
|
|
|
@@ -541,7 +541,7 @@ npm install shadcn-glass-ui
|
|
|
541
541
|
|
|
542
542
|
```css
|
|
543
543
|
/* src/index.css */
|
|
544
|
-
@import 'shadcn-glass-ui/
|
|
544
|
+
@import 'shadcn-glass-ui/styles.css';
|
|
545
545
|
```
|
|
546
546
|
|
|
547
547
|
```typescript
|
|
@@ -658,7 +658,7 @@ mcp__context7__get-library-docs /yhooi2/shadcn-glass-ui-library --topic="token a
|
|
|
658
658
|
```tsx
|
|
659
659
|
import { ButtonGlass, InputGlass } from 'shadcn-glass-ui';
|
|
660
660
|
import { ThemeProvider, useTheme } from 'shadcn-glass-ui';
|
|
661
|
-
import 'shadcn-glass-ui/
|
|
661
|
+
import 'shadcn-glass-ui/styles.css';
|
|
662
662
|
```
|
|
663
663
|
|
|
664
664
|
**shadcn CLI:**
|
|
@@ -756,7 +756,7 @@ import { ThemeProvider } from '@/lib/theme-context';
|
|
|
756
756
|
@tailwind base;
|
|
757
757
|
@tailwind components;
|
|
758
758
|
@tailwind utilities;
|
|
759
|
-
@import 'shadcn-glass-ui/
|
|
759
|
+
@import 'shadcn-glass-ui/styles.css';
|
|
760
760
|
```
|
|
761
761
|
|
|
762
762
|
---
|
package/docs/BEST_PRACTICES.md
CHANGED
|
@@ -57,6 +57,8 @@ hardcoded OKLCH values across all components.
|
|
|
57
57
|
| **RepositoryMetadataGlass** | Composite | `src/components/glass/composite/repository-metadata-glass.tsx` | 6 | Repo metadata display |
|
|
58
58
|
| **UserInfoGlass** | Composite | `src/components/glass/composite/user-info-glass.tsx` | 5 | User card, avatar, stats |
|
|
59
59
|
| **UserStatsLineGlass** | Composite | `src/components/glass/composite/user-stats-line-glass.tsx` | 4 | Horizontal stats line |
|
|
60
|
+
| **SplitLayoutGlass** | Composite | `src/components/glass/composite/split-layout-glass/` | - | Compound API, sticky scroll, master-detail |
|
|
61
|
+
| **SidebarGlass** | Core UI | `src/components/glass/ui/sidebar-glass/` | - | Compound API, shadcn/ui compatible, rail |
|
|
60
62
|
| **HeaderNavGlass** | Section | `src/components/glass/sections/header-nav-glass.tsx` | 6 | Navigation, search, theme |
|
|
61
63
|
| **ProfileHeaderGlass** | Section | `src/components/glass/sections/profile-header-glass.tsx` | 7 | Profile, avatar, stats, langs |
|
|
62
64
|
| **CareerStatsGlass** | Section | `src/components/glass/sections/career-stats-glass.tsx` | 5 | Career stats, year cards |
|
|
@@ -857,6 +859,91 @@ content, anchor **API:** Compound component + Legacy wrapper
|
|
|
857
859
|
</PopoverGlassLegacy>
|
|
858
860
|
```
|
|
859
861
|
|
|
862
|
+
#### SidebarGlass (Compound API)
|
|
863
|
+
|
|
864
|
+
**File:** `src/components/glass/ui/sidebar-glass/sidebar-glass.tsx` **Components:** Provider, Root,
|
|
865
|
+
Header, Content, Footer, Rail, Inset, Trigger, Separator, Group, GroupLabel, GroupAction,
|
|
866
|
+
GroupContent, Menu, MenuItem, MenuButton, MenuAction, MenuBadge, MenuSkeleton, MenuSub, MenuSubItem,
|
|
867
|
+
MenuSubButton **Features:** 100% shadcn/ui compatible, mobile drawer, desktop collapsible
|
|
868
|
+
(offcanvas/icon/none), glassmorphism
|
|
869
|
+
|
|
870
|
+
**Basic Sidebar:**
|
|
871
|
+
|
|
872
|
+
```tsx
|
|
873
|
+
<SidebarGlass.Provider>
|
|
874
|
+
<SidebarGlass.Root>
|
|
875
|
+
<SidebarGlass.Header>
|
|
876
|
+
<Logo />
|
|
877
|
+
</SidebarGlass.Header>
|
|
878
|
+
<SidebarGlass.Content>
|
|
879
|
+
<SidebarGlass.Menu>
|
|
880
|
+
<SidebarGlass.MenuItem>
|
|
881
|
+
<SidebarGlass.MenuButton isActive>
|
|
882
|
+
<Home className="w-4 h-4" />
|
|
883
|
+
Dashboard
|
|
884
|
+
</SidebarGlass.MenuButton>
|
|
885
|
+
</SidebarGlass.MenuItem>
|
|
886
|
+
<SidebarGlass.MenuItem>
|
|
887
|
+
<SidebarGlass.MenuButton>
|
|
888
|
+
<Settings className="w-4 h-4" />
|
|
889
|
+
Settings
|
|
890
|
+
</SidebarGlass.MenuButton>
|
|
891
|
+
</SidebarGlass.MenuItem>
|
|
892
|
+
</SidebarGlass.Menu>
|
|
893
|
+
</SidebarGlass.Content>
|
|
894
|
+
<SidebarGlass.Footer>
|
|
895
|
+
<UserInfo />
|
|
896
|
+
</SidebarGlass.Footer>
|
|
897
|
+
</SidebarGlass.Root>
|
|
898
|
+
<SidebarGlass.Inset>
|
|
899
|
+
<main>Main Content</main>
|
|
900
|
+
</SidebarGlass.Inset>
|
|
901
|
+
</SidebarGlass.Provider>
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
**With Groups and Submenus:**
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
<SidebarGlass.Provider collapsible="icon" side="left">
|
|
908
|
+
<SidebarGlass.Root>
|
|
909
|
+
<SidebarGlass.Content>
|
|
910
|
+
<SidebarGlass.Group>
|
|
911
|
+
<SidebarGlass.GroupLabel>Main</SidebarGlass.GroupLabel>
|
|
912
|
+
<SidebarGlass.GroupContent>
|
|
913
|
+
<SidebarGlass.Menu>
|
|
914
|
+
<SidebarGlass.MenuItem>
|
|
915
|
+
<SidebarGlass.MenuButton tooltip="Dashboard">
|
|
916
|
+
<Home /> Dashboard
|
|
917
|
+
</SidebarGlass.MenuButton>
|
|
918
|
+
</SidebarGlass.MenuItem>
|
|
919
|
+
</SidebarGlass.Menu>
|
|
920
|
+
</SidebarGlass.GroupContent>
|
|
921
|
+
</SidebarGlass.Group>
|
|
922
|
+
</SidebarGlass.Content>
|
|
923
|
+
<SidebarGlass.Rail />
|
|
924
|
+
</SidebarGlass.Root>
|
|
925
|
+
</SidebarGlass.Provider>
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
**Props (Provider):**
|
|
929
|
+
|
|
930
|
+
| Prop | Type | Default | Description |
|
|
931
|
+
| ---------------- | ---------------------------------- | --------------- | ---------------------------------- |
|
|
932
|
+
| side | 'left' \| 'right' | 'left' | Sidebar position |
|
|
933
|
+
| variant | 'sidebar' \| 'floating' \| 'inset' | 'sidebar' | Visual style |
|
|
934
|
+
| collapsible | 'offcanvas' \| 'icon' \| 'none' | 'offcanvas' | Collapse behavior |
|
|
935
|
+
| open | boolean | - | Controlled open state |
|
|
936
|
+
| onOpenChange | (open: boolean) => void | - | Open state change handler |
|
|
937
|
+
| defaultOpen | boolean | true | Initial open state |
|
|
938
|
+
| cookieName | string | 'sidebar:state' | Cookie name for persistence |
|
|
939
|
+
| keyboardShortcut | string \| false | 'b' | Keyboard shortcut (Cmd/Ctrl + key) |
|
|
940
|
+
|
|
941
|
+
**Hook: useSidebar()**
|
|
942
|
+
|
|
943
|
+
```tsx
|
|
944
|
+
const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } = useSidebar();
|
|
945
|
+
```
|
|
946
|
+
|
|
860
947
|
---
|
|
861
948
|
|
|
862
949
|
### Level 2 - Atomic (7)
|
|
@@ -1016,6 +1103,122 @@ feature list **Usage:**
|
|
|
1016
1103
|
/>
|
|
1017
1104
|
```
|
|
1018
1105
|
|
|
1106
|
+
#### SplitLayoutGlass (Compound API)
|
|
1107
|
+
|
|
1108
|
+
**File:** `src/components/glass/composite/split-layout-glass/split-layout-glass.tsx` **Components:**
|
|
1109
|
+
Provider, Root, Sidebar, SidebarHeader, SidebarContent, SidebarFooter, Main, MainHeader,
|
|
1110
|
+
MainContent, MainFooter, Trigger, Accordion **Features:** Sticky scroll, responsive
|
|
1111
|
+
(stack/main-only/sidebar-only), master-detail pattern, keyboard shortcut (Cmd+B)
|
|
1112
|
+
|
|
1113
|
+
**Basic Two-Column Layout:**
|
|
1114
|
+
|
|
1115
|
+
```tsx
|
|
1116
|
+
<SplitLayoutGlass.Provider>
|
|
1117
|
+
<SplitLayoutGlass.Root>
|
|
1118
|
+
<SplitLayoutGlass.Sidebar>
|
|
1119
|
+
<SplitLayoutGlass.SidebarHeader>
|
|
1120
|
+
<h3>Navigation</h3>
|
|
1121
|
+
</SplitLayoutGlass.SidebarHeader>
|
|
1122
|
+
<SplitLayoutGlass.SidebarContent>
|
|
1123
|
+
<nav>
|
|
1124
|
+
<a href="#section1">Section 1</a>
|
|
1125
|
+
<a href="#section2">Section 2</a>
|
|
1126
|
+
</nav>
|
|
1127
|
+
</SplitLayoutGlass.SidebarContent>
|
|
1128
|
+
</SplitLayoutGlass.Sidebar>
|
|
1129
|
+
<SplitLayoutGlass.Main>
|
|
1130
|
+
<SplitLayoutGlass.MainContent>
|
|
1131
|
+
<article>
|
|
1132
|
+
<h1>Main Content</h1>
|
|
1133
|
+
<p>Your content here...</p>
|
|
1134
|
+
</article>
|
|
1135
|
+
</SplitLayoutGlass.MainContent>
|
|
1136
|
+
</SplitLayoutGlass.Main>
|
|
1137
|
+
</SplitLayoutGlass.Root>
|
|
1138
|
+
</SplitLayoutGlass.Provider>
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
**With Master-Detail Pattern:**
|
|
1142
|
+
|
|
1143
|
+
```tsx
|
|
1144
|
+
<SplitLayoutGlass.Provider
|
|
1145
|
+
defaultSelectedKey="item-1"
|
|
1146
|
+
breakpoint="lg"
|
|
1147
|
+
mobileMode="drawer"
|
|
1148
|
+
intensity="medium"
|
|
1149
|
+
>
|
|
1150
|
+
<SplitLayoutGlass.Root ratio={{ sidebar: 1, main: 2 }} minSidebarWidth="300px">
|
|
1151
|
+
<SplitLayoutGlass.Sidebar>
|
|
1152
|
+
<SplitLayoutGlass.SidebarHeader>
|
|
1153
|
+
<h3>Items List</h3>
|
|
1154
|
+
<SplitLayoutGlass.Trigger variant="menu" />
|
|
1155
|
+
</SplitLayoutGlass.SidebarHeader>
|
|
1156
|
+
<SplitLayoutGlass.SidebarContent scrollable>
|
|
1157
|
+
{items.map((item) => (
|
|
1158
|
+
<ItemRow key={item.id} item={item} />
|
|
1159
|
+
))}
|
|
1160
|
+
</SplitLayoutGlass.SidebarContent>
|
|
1161
|
+
</SplitLayoutGlass.Sidebar>
|
|
1162
|
+
<SplitLayoutGlass.Main>
|
|
1163
|
+
<SplitLayoutGlass.MainHeader>
|
|
1164
|
+
<Breadcrumbs />
|
|
1165
|
+
</SplitLayoutGlass.MainHeader>
|
|
1166
|
+
<SplitLayoutGlass.MainContent>
|
|
1167
|
+
<ItemDetail />
|
|
1168
|
+
</SplitLayoutGlass.MainContent>
|
|
1169
|
+
<SplitLayoutGlass.MainFooter>
|
|
1170
|
+
<Actions />
|
|
1171
|
+
</SplitLayoutGlass.MainFooter>
|
|
1172
|
+
</SplitLayoutGlass.Main>
|
|
1173
|
+
</SplitLayoutGlass.Root>
|
|
1174
|
+
</SplitLayoutGlass.Provider>
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Props (Provider):**
|
|
1178
|
+
|
|
1179
|
+
| Prop | Type | Default | Description |
|
|
1180
|
+
| ------------------- | ------------------------------------- | -------- | ---------------------------------------- |
|
|
1181
|
+
| selectedKey | string \| null | - | Controlled selected key (master-detail) |
|
|
1182
|
+
| onSelectedKeyChange | (key: string \| null) => void | - | Selection change handler |
|
|
1183
|
+
| defaultSelectedKey | string \| null | - | Initial selected key |
|
|
1184
|
+
| open | boolean | - | Controlled sidebar open state |
|
|
1185
|
+
| onOpenChange | (open: boolean) => void | - | Open state change handler |
|
|
1186
|
+
| defaultOpen | boolean | true | Initial sidebar open state |
|
|
1187
|
+
| breakpoint | 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' | 'md' | Desktop layout breakpoint |
|
|
1188
|
+
| mobileMode | 'stack' \| 'accordion' \| 'drawer' | 'stack' | Mobile layout behavior |
|
|
1189
|
+
| intensity | 'subtle' \| 'medium' \| 'strong' | 'medium' | Glass blur intensity |
|
|
1190
|
+
| stickyOffset | number | 24 | Sticky offset in pixels |
|
|
1191
|
+
| urlParamName | string | - | URL param name for selection persistence |
|
|
1192
|
+
| keyboardShortcut | string \| false | 'b' | Keyboard shortcut (Cmd/Ctrl + key) |
|
|
1193
|
+
|
|
1194
|
+
**Props (Root):**
|
|
1195
|
+
|
|
1196
|
+
| Prop | Type | Default | Description |
|
|
1197
|
+
| --------------- | ----------------------------------------------- | --------------------------- | ----------------------------- |
|
|
1198
|
+
| ratio | { sidebar: number; main: number } | { sidebar: 1, main: 2 } | Column ratio (1:2 = 33%/67%) |
|
|
1199
|
+
| minSidebarWidth | string | '300px' | Minimum sidebar width |
|
|
1200
|
+
| maxSidebarWidth | string | - | Maximum sidebar width |
|
|
1201
|
+
| gap | number \| { mobile?: number; desktop?: number } | { mobile: 16, desktop: 24 } | Gap between panels |
|
|
1202
|
+
| breakpoint | Breakpoint | - | Overrides Provider breakpoint |
|
|
1203
|
+
| mobileLayout | 'stack' \| 'main-only' \| 'sidebar-only' | 'stack' | Mobile layout mode |
|
|
1204
|
+
|
|
1205
|
+
**Hook: useSplitLayout()**
|
|
1206
|
+
|
|
1207
|
+
```tsx
|
|
1208
|
+
const {
|
|
1209
|
+
selectedKey,
|
|
1210
|
+
setSelectedKey,
|
|
1211
|
+
isOpen,
|
|
1212
|
+
setIsOpen,
|
|
1213
|
+
isMobileOpen,
|
|
1214
|
+
setMobileOpen,
|
|
1215
|
+
isMobile,
|
|
1216
|
+
toggle,
|
|
1217
|
+
state,
|
|
1218
|
+
toggleSidebar, // shadcn/ui alias
|
|
1219
|
+
} = useSplitLayout();
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1019
1222
|
---
|
|
1020
1223
|
|
|
1021
1224
|
### Level 5 - Sections (7)
|
|
@@ -1060,6 +1263,14 @@ avatar, stats, languages **Usage:**
|
|
|
1060
1263
|
- **DropdownGlass** - Dropdown menus
|
|
1061
1264
|
- **ButtonGlass** (asChild) - Link buttons
|
|
1062
1265
|
- **SegmentedControlGlass** - Segmented buttons
|
|
1266
|
+
- **SidebarGlass** - Collapsible sidebar navigation (compound API)
|
|
1267
|
+
|
|
1268
|
+
### Layouts
|
|
1269
|
+
|
|
1270
|
+
- **SplitLayoutGlass** - Two-column responsive layout (compound API)
|
|
1271
|
+
- **SidebarGlass** - Sidebar with collapsible rail
|
|
1272
|
+
- **GlassCard** - Card containers
|
|
1273
|
+
- **ModalGlass** - Modal dialogs (compound API)
|
|
1063
1274
|
|
|
1064
1275
|
### Feedback
|
|
1065
1276
|
|
|
@@ -1105,6 +1316,10 @@ For AI assistants: Use Ctrl+F to search by keyword.
|
|
|
1105
1316
|
- "avatar, profile, user" → AvatarGlass, ProfileAvatarGlass, ProfileHeaderGlass, UserInfoGlass
|
|
1106
1317
|
- "badge, tag, label, status" → BadgeGlass, StatusIndicatorGlass
|
|
1107
1318
|
- "tabs, navigation, switch" → TabsGlass, HeaderNavGlass, SegmentedControlGlass
|
|
1319
|
+
- "sidebar, drawer, collapsible, rail" → SidebarGlass
|
|
1320
|
+
- "split, two-column, layout, master-detail" → SplitLayoutGlass
|
|
1321
|
+
- "compound, compound component" → ModalGlass, TabsGlass, SidebarGlass, SplitLayoutGlass,
|
|
1322
|
+
StepperGlass
|
|
1108
1323
|
- "theme, dark mode" → ThemeProvider, ThemeToggleGlass, useTheme
|
|
1109
1324
|
- "tooltip, hint, help" → TooltipGlass
|
|
1110
1325
|
- "slider, range" → SliderGlass
|