@rdna/radiants 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +125 -0
  3. package/animations.css +68 -0
  4. package/assets/scrollbar-background.svg +9 -0
  5. package/base.css +144 -0
  6. package/dark.css +117 -0
  7. package/dist/chunk-SR2T7OEJ.mjs +46 -0
  8. package/dist/chunk-SR2T7OEJ.mjs.map +1 -0
  9. package/dist/components/core/index.d.mts +911 -0
  10. package/dist/components/core/index.mjs +2475 -0
  11. package/dist/components/core/index.mjs.map +1 -0
  12. package/dist/hooks/index.d.mts +22 -0
  13. package/dist/hooks/index.mjs +3 -0
  14. package/dist/hooks/index.mjs.map +1 -0
  15. package/dist/remotion/index.d.mts +252 -0
  16. package/dist/remotion/index.mjs +170 -0
  17. package/dist/remotion/index.mjs.map +1 -0
  18. package/dna.config.json +8 -0
  19. package/fonts/Joystix.woff2 +0 -0
  20. package/fonts/PixelCode-Black-Italic.woff2 +0 -0
  21. package/fonts/PixelCode-Black.woff2 +0 -0
  22. package/fonts/PixelCode-Bold-Italic.woff2 +0 -0
  23. package/fonts/PixelCode-Bold.woff2 +0 -0
  24. package/fonts/PixelCode-DemiBold-Italic.woff2 +0 -0
  25. package/fonts/PixelCode-DemiBold.woff2 +0 -0
  26. package/fonts/PixelCode-ExtraBlack-Italic.woff2 +0 -0
  27. package/fonts/PixelCode-ExtraBlack.woff2 +0 -0
  28. package/fonts/PixelCode-ExtraBold-Italic.woff2 +0 -0
  29. package/fonts/PixelCode-ExtraBold.woff2 +0 -0
  30. package/fonts/PixelCode-ExtraLight-Italic.woff2 +0 -0
  31. package/fonts/PixelCode-ExtraLight.woff2 +0 -0
  32. package/fonts/PixelCode-Italic.woff2 +0 -0
  33. package/fonts/PixelCode-Light-Italic.woff2 +0 -0
  34. package/fonts/PixelCode-Light.woff2 +0 -0
  35. package/fonts/PixelCode-Medium-Italic.woff2 +0 -0
  36. package/fonts/PixelCode-Medium.woff2 +0 -0
  37. package/fonts/PixelCode-Thin-Italic.woff2 +0 -0
  38. package/fonts/PixelCode-Thin.woff2 +0 -0
  39. package/fonts/PixelCode.woff2 +0 -0
  40. package/fonts.css +115 -0
  41. package/index.css +25 -0
  42. package/package.json +88 -0
  43. package/tokens.css +202 -0
  44. package/typography.css +175 -0
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # @rdna/radiants
2
+
3
+ Radiants theme package for DNA (Design Nexus Architecture) — a retro pixel aesthetic design system.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @rdna/radiants
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### CSS Tokens
14
+
15
+ Import the theme tokens in your CSS:
16
+
17
+ ```css
18
+ /* Import all tokens + base styles */
19
+ @import '@rdna/radiants';
20
+
21
+ /* Import dark mode support */
22
+ @import '@rdna/radiants/dark';
23
+ ```
24
+
25
+ Or import individual parts:
26
+
27
+ ```css
28
+ @import '@rdna/radiants/tokens'; /* Tokens only */
29
+ @import '@rdna/radiants/typography'; /* Typography styles */
30
+ @import '@rdna/radiants/fonts'; /* Font declarations */
31
+ @import '@rdna/radiants/animations'; /* Animation utilities */
32
+ @import '@rdna/radiants/base'; /* Base element styles */
33
+ ```
34
+
35
+ ### React Components
36
+
37
+ ```tsx
38
+ import { Button, Card, Badge } from '@rdna/radiants/components/core';
39
+ import { useToast } from '@rdna/radiants/components/core';
40
+
41
+ function App() {
42
+ return (
43
+ <Card>
44
+ <Badge variant="success">New</Badge>
45
+ <Button variant="primary">Click me</Button>
46
+ </Card>
47
+ );
48
+ }
49
+ ```
50
+
51
+ ### Available Components
52
+
53
+ - **Layout**: Card, Divider
54
+ - **Actions**: Button, ContextMenu, DropdownMenu
55
+ - **Forms**: Input, TextArea, Select, Checkbox, Radio, Switch, Slider
56
+ - **Feedback**: Alert, Badge, Progress, Spinner, Toast, Tooltip
57
+ - **Overlays**: Dialog, Popover, Sheet, HelpPanel
58
+ - **Navigation**: Tabs, Breadcrumbs, Accordion
59
+ - **Specialty**: CountdownTimer, Web3ActionBar
60
+
61
+ ### Hooks
62
+
63
+ ```tsx
64
+ import { useMotion } from '@rdna/radiants/hooks';
65
+
66
+ const { duration, easing } = useMotion();
67
+ ```
68
+
69
+ ## Semantic Tokens
70
+
71
+ Use semantic token classes instead of hardcoded colors:
72
+
73
+ ```tsx
74
+ // ✅ Do this
75
+ <div className="bg-surface-primary text-content-primary border-edge-primary">
76
+
77
+ // ❌ Not this
78
+ <div className="bg-[#FEF8E2] text-[#0F0E0C]">
79
+ ```
80
+
81
+ ### Token Categories
82
+
83
+ | Category | Examples | Purpose |
84
+ |----------|----------|---------|
85
+ | `surface-*` | `bg-surface-primary` | Backgrounds |
86
+ | `content-*` | `text-content-primary` | Text/foreground |
87
+ | `edge-*` | `border-edge-primary` | Borders/outlines |
88
+ | `action-*` | `bg-action-primary` | Interactive elements |
89
+ | `status-*` | `bg-status-success` | Feedback states |
90
+
91
+ ## Dark Mode
92
+
93
+ Dark mode is automatic with `prefers-color-scheme`, or manually toggle with classes:
94
+
95
+ ```html
96
+ <!-- Force dark -->
97
+ <html class="dark">
98
+
99
+ <!-- Force light -->
100
+ <html class="light">
101
+ ```
102
+
103
+ ## Fonts
104
+
105
+ This package includes:
106
+ - **Joystix Monospace** — Heading font
107
+ - **PixelCode** — Monospace/code font
108
+
109
+ **Mondwest** (body font) must be downloaded separately due to licensing:
110
+
111
+ 1. Purchase/download from [Pangram Pangram](https://pangrampangram.com/products/bitmap-mondwest)
112
+ 2. Place `Mondwest.woff2` and `Mondwest-Bold.woff2` in your project's fonts directory
113
+ 3. The theme will fall back to system fonts if Mondwest is not available
114
+
115
+ ## Requirements
116
+
117
+ - React 18+ or 19
118
+ - Tailwind CSS 4
119
+ - Next.js 14+ (optional)
120
+
121
+ ## License
122
+
123
+ GPL-3.0 — See [LICENSE](./LICENSE) for details.
124
+
125
+ Note: The DNA specification itself is MIT licensed. This theme implementation is GPL-3.0.
package/animations.css ADDED
@@ -0,0 +1,68 @@
1
+ /* =============================================================================
2
+ @dna/radiants - Animations
3
+ Animation keyframes and utility classes for the Radiants theme
4
+ ============================================================================= */
5
+
6
+ /* ============================================================================
7
+ Keyframe Animations
8
+ ============================================================================ */
9
+
10
+ @keyframes slide-in-right {
11
+ from {
12
+ transform: translateX(100%);
13
+ }
14
+ to {
15
+ transform: translateX(0);
16
+ }
17
+ }
18
+
19
+ @keyframes fadeIn {
20
+ from {
21
+ opacity: 0;
22
+ }
23
+ to {
24
+ opacity: 1;
25
+ }
26
+ }
27
+
28
+ @keyframes scaleIn {
29
+ from {
30
+ opacity: 0;
31
+ transform: scale(0.95);
32
+ }
33
+ to {
34
+ opacity: 1;
35
+ transform: scale(1);
36
+ }
37
+ }
38
+
39
+ @keyframes slideIn {
40
+ from {
41
+ opacity: 0;
42
+ transform: translateX(100%);
43
+ }
44
+ to {
45
+ opacity: 1;
46
+ transform: translateX(0);
47
+ }
48
+ }
49
+
50
+ /* ============================================================================
51
+ Animation Utility Classes
52
+ ============================================================================ */
53
+
54
+ .animate-slide-in-right {
55
+ animation: slide-in-right 0.2s ease-out forwards;
56
+ }
57
+
58
+ .animate-fadeIn {
59
+ animation: fadeIn 0.15s ease-out forwards;
60
+ }
61
+
62
+ .animate-scaleIn {
63
+ animation: scaleIn 0.15s ease-out forwards;
64
+ }
65
+
66
+ .animate-slideIn {
67
+ animation: slideIn 0.2s ease-out forwards;
68
+ }
@@ -0,0 +1,9 @@
1
+ <svg width="8" height="341" viewBox="0 0 8 341" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <rect width="7" height="340.706" transform="translate(0.5 0.294922)" fill="url(#pattern0_162_293)"/>
3
+ <defs>
4
+ <pattern id="pattern0_162_293" patternContentUnits="objectBoundingBox" width="1" height="0.610498">
5
+ <use xlink:href="#image0_162_293" transform="scale(0.0714286 0.00146754)"/>
6
+ </pattern>
7
+ <image id="image0_162_293" width="14" height="416" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAGgAQMAAACg58S4AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMATX7+8BUAAAAaSURBVDjLYzhwAIR4eIBolD3KHmWPsocTGwCp5UuQtb1x6QAAAABJRU5ErkJggg=="/>
8
+ </defs>
9
+ </svg>
package/base.css ADDED
@@ -0,0 +1,144 @@
1
+ /* =============================================================================
2
+ @dna/radiants - Base Styles
3
+ Box-sizing reset, html/body defaults, selection styles, and scrollbar CSS
4
+ ============================================================================= */
5
+
6
+ /* ============================================================================
7
+ Box-Sizing Reset
8
+ ============================================================================ */
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ /* ============================================================================
15
+ HTML/Body Base Styles
16
+ ============================================================================ */
17
+
18
+ html,
19
+ body {
20
+ margin: 0;
21
+ padding: 0;
22
+ width: 100%;
23
+ height: 100%;
24
+ }
25
+
26
+ body {
27
+ font-family: 'Joystix Monospace', monospace;
28
+ font-size: clamp(1rem, 1vw, 1.125rem);
29
+ background-color: var(--color-sun-yellow);
30
+ color: var(--color-black);
31
+ -webkit-font-smoothing: antialiased;
32
+ -moz-osx-font-smoothing: grayscale;
33
+ }
34
+
35
+ /* ============================================================================
36
+ Text Selection
37
+ ============================================================================ */
38
+
39
+ ::selection {
40
+ background: var(--color-sun-yellow);
41
+ color: var(--color-black);
42
+ }
43
+
44
+ ::-moz-selection {
45
+ background: var(--color-sun-yellow);
46
+ color: var(--color-black);
47
+ }
48
+
49
+ /* ============================================================================
50
+ Custom Scrollbar Styles - Mac Compatible
51
+
52
+ Retro-style webkit scrollbar matching the design system.
53
+ Uses CSS variables from tokens.css for consistency.
54
+ ============================================================================ */
55
+
56
+ /* Force scrollbars to always be visible - make wider for padding effect */
57
+ ::-webkit-scrollbar {
58
+ width: 1.75rem; /* Wider invisible container for padding effect */
59
+ height: 0px;
60
+ -webkit-appearance: none;
61
+ }
62
+
63
+ /* Only show horizontal scrollbar when actually needed */
64
+ ::-webkit-scrollbar:horizontal {
65
+ height: 0.5rem;
66
+ display: none !important;
67
+ }
68
+
69
+ /* Track - narrow and centered within wider scrollbar */
70
+ ::-webkit-scrollbar-track {
71
+ background: url('./assets/scrollbar-background.svg') center center / 0.5rem auto repeat-y;
72
+ background-position: bottom;
73
+ background-size: 45%;
74
+ -webkit-appearance: none;
75
+ }
76
+
77
+ /* Hide horizontal scrollbar track completely */
78
+ ::-webkit-scrollbar-track:horizontal {
79
+ }
80
+
81
+ /* Thumb - using border/background-clip method for inset effect */
82
+ ::-webkit-scrollbar-thumb {
83
+ background: var(--color-warm-cloud);
84
+ border: 0.375rem solid transparent; /* Transparent border creates padding */
85
+ border-radius: var(--radius-md);
86
+ -webkit-appearance: none;
87
+ min-height: 2.25rem;
88
+ background-clip: padding-box; /* This is the magic - clips background to content area only */
89
+ /* Add outline around the green area using inset box-shadow */
90
+ box-shadow: inset 0 0 0 1px var(--color-black);
91
+ }
92
+
93
+ /* Thumb hover state */
94
+ ::-webkit-scrollbar-thumb:hover {
95
+ background: var(--color-sun-yellow);
96
+ border: 0.375rem solid transparent;
97
+ background-clip: padding-box;
98
+ box-shadow: inset 0 0 0 1px var(--color-black);
99
+ }
100
+
101
+ /* Thumb active state */
102
+ ::-webkit-scrollbar-thumb:active {
103
+ background: var(--color-sun-yellow);
104
+ border: 0.375rem solid transparent;
105
+ background-clip: padding-box;
106
+ box-shadow: inset 0 0 0 1px var(--color-black);
107
+ }
108
+
109
+ /* Scrollbar buttons (arrows) - Hide all buttons */
110
+ ::-webkit-scrollbar-button {
111
+ display: none !important; /* Hide all arrow buttons */
112
+ height: 0 !important;
113
+ width: 0 !important;
114
+ }
115
+
116
+ /* Corner where horizontal and vertical scrollbars meet */
117
+ ::-webkit-scrollbar-corner {
118
+ display: none !important; /* Hide the corner completely */
119
+ }
120
+
121
+ /* ============================================================================
122
+ Scrollable Container Utility Classes
123
+
124
+ Apply .custom-scrollbar to any scrollable element for styled scrollbars
125
+ ============================================================================ */
126
+
127
+ .custom-scrollbar {
128
+ scrollbar-width: thin;
129
+ scrollbar-color: var(--color-warm-cloud) var(--color-sun-yellow);
130
+ }
131
+
132
+ /* For elements that need a dark-themed scrollbar */
133
+ .custom-scrollbar-dark::-webkit-scrollbar-track {
134
+ background: var(--color-black);
135
+ }
136
+
137
+ .custom-scrollbar-dark::-webkit-scrollbar-thumb {
138
+ background: var(--color-warm-cloud);
139
+ box-shadow: inset 0 0 0 1px var(--color-sun-yellow);
140
+ }
141
+
142
+ .custom-scrollbar-dark::-webkit-scrollbar-thumb:hover {
143
+ background: var(--color-sun-yellow);
144
+ }
package/dark.css ADDED
@@ -0,0 +1,117 @@
1
+ /* =============================================================================
2
+ @dna/radiants - Dark Mode Overrides
3
+ Inverted semantic tokens for dark theme
4
+ ============================================================================= */
5
+
6
+ /* =============================================================================
7
+ Dark Mode Class Selector
8
+ Apply via: <html class="dark"> or <body class="dark">
9
+ ============================================================================= */
10
+
11
+ .dark {
12
+ /* ============================================
13
+ SURFACE TOKENS - Inverted for dark backgrounds
14
+ ============================================ */
15
+
16
+ --color-surface-primary: var(--color-black);
17
+ --color-surface-secondary: var(--color-warm-cloud);
18
+ --color-surface-tertiary: #3D2E1A; /* Muted sunset-fuzz for dark - WCAG AA compliant */
19
+ --color-surface-elevated: #1A1918; /* Slightly lighter than black for depth */
20
+ --color-surface-muted: #252422; /* Muted dark surface */
21
+
22
+ /* ============================================
23
+ CONTENT TOKENS - Inverted for dark backgrounds
24
+ ============================================ */
25
+
26
+ --color-content-primary: var(--color-warm-cloud);
27
+ --color-content-secondary: var(--color-warm-cloud); /* Base for opacity modifiers: text-content-secondary/70 */
28
+ --color-content-inverted: var(--color-black);
29
+ --color-content-muted: rgba(254, 248, 226, 0.6); /* warm-cloud at 60% */
30
+ --color-content-link: var(--color-sky-blue); /* Links stay sky-blue for contrast */
31
+
32
+ /* ============================================
33
+ EDGE TOKENS - Adjusted for dark backgrounds
34
+ ============================================ */
35
+
36
+ --color-edge-primary: var(--color-warm-cloud);
37
+ --color-edge-muted: rgba(254, 248, 226, 0.2); /* warm-cloud at 20% */
38
+ --color-edge-focus: var(--color-sun-yellow); /* Focus stays yellow for visibility */
39
+
40
+ /* ============================================
41
+ ACTION TOKENS - Preserved for retro aesthetic
42
+ High-contrast action colors work on dark backgrounds
43
+ ============================================ */
44
+
45
+ --color-action-primary: var(--color-sun-yellow);
46
+ --color-action-secondary: var(--color-warm-cloud);
47
+ --color-action-destructive: var(--color-sun-red); /* Same in dark - high contrast on dark bg */
48
+ --color-action-accent: var(--color-sunset-fuzz);
49
+
50
+ /* ============================================
51
+ BOX SHADOWS - Adjusted for dark mode
52
+ Using warm-cloud for retro lift effect on dark
53
+ ============================================ */
54
+
55
+ --shadow-btn: 0 1px 0 0 var(--color-warm-cloud);
56
+ --shadow-btn-hover: 0 3px 0 0 var(--color-warm-cloud);
57
+ --shadow-card: 2px 2px 0 0 rgba(254, 248, 226, 0.3);
58
+ --shadow-card-lg: 4px 4px 0 0 rgba(254, 248, 226, 0.3);
59
+ --shadow-inner: inset 0 0 0 1px var(--color-warm-cloud);
60
+ }
61
+
62
+ /* =============================================================================
63
+ System Preference Fallback
64
+ Auto-applies dark mode when user prefers dark color scheme
65
+ The :not(.light) allows explicit light mode override
66
+ ============================================================================= */
67
+
68
+ @media (prefers-color-scheme: dark) {
69
+ :root:not(.light) {
70
+ /* ============================================
71
+ SURFACE TOKENS - Inverted for dark backgrounds
72
+ ============================================ */
73
+
74
+ --color-surface-primary: var(--color-black);
75
+ --color-surface-secondary: var(--color-warm-cloud);
76
+ --color-surface-tertiary: #3D2E1A; /* Muted sunset-fuzz for dark - WCAG AA compliant */
77
+ --color-surface-elevated: #1A1918;
78
+ --color-surface-muted: #252422;
79
+
80
+ /* ============================================
81
+ CONTENT TOKENS - Inverted for dark backgrounds
82
+ ============================================ */
83
+
84
+ --color-content-primary: var(--color-warm-cloud);
85
+ --color-content-secondary: var(--color-warm-cloud); /* Base for opacity modifiers: text-content-secondary/70 */
86
+ --color-content-inverted: var(--color-black);
87
+ --color-content-muted: rgba(254, 248, 226, 0.6);
88
+ --color-content-link: var(--color-sky-blue);
89
+
90
+ /* ============================================
91
+ EDGE TOKENS - Adjusted for dark backgrounds
92
+ ============================================ */
93
+
94
+ --color-edge-primary: var(--color-warm-cloud);
95
+ --color-edge-muted: rgba(254, 248, 226, 0.2);
96
+ --color-edge-focus: var(--color-sun-yellow);
97
+
98
+ /* ============================================
99
+ ACTION TOKENS - Preserved for retro aesthetic
100
+ ============================================ */
101
+
102
+ --color-action-primary: var(--color-sun-yellow);
103
+ --color-action-secondary: var(--color-warm-cloud);
104
+ --color-action-destructive: var(--color-sun-red); /* Same in dark - high contrast on dark bg */
105
+ --color-action-accent: var(--color-sunset-fuzz);
106
+
107
+ /* ============================================
108
+ BOX SHADOWS - Adjusted for dark mode
109
+ ============================================ */
110
+
111
+ --shadow-btn: 0 1px 0 0 var(--color-warm-cloud);
112
+ --shadow-btn-hover: 0 3px 0 0 var(--color-warm-cloud);
113
+ --shadow-card: 2px 2px 0 0 rgba(254, 248, 226, 0.3);
114
+ --shadow-card-lg: 4px 4px 0 0 rgba(254, 248, 226, 0.3);
115
+ --shadow-inner: inset 0 0 0 1px var(--color-warm-cloud);
116
+ }
117
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+
3
+ // hooks/useModalBehavior.ts
4
+ function useEscapeKey(isActive, onEscape) {
5
+ useEffect(() => {
6
+ if (!isActive) return;
7
+ const handleEscape = (e) => {
8
+ if (e.key === "Escape") {
9
+ onEscape();
10
+ }
11
+ };
12
+ document.addEventListener("keydown", handleEscape);
13
+ return () => document.removeEventListener("keydown", handleEscape);
14
+ }, [isActive, onEscape]);
15
+ }
16
+ function useClickOutside(isActive, refs, onClickOutside) {
17
+ useEffect(() => {
18
+ if (!isActive) return;
19
+ const handleClickOutside = (e) => {
20
+ const clickedOutsideAll = refs.every(
21
+ (ref) => ref.current && !ref.current.contains(e.target)
22
+ );
23
+ if (clickedOutsideAll) {
24
+ onClickOutside();
25
+ }
26
+ };
27
+ document.addEventListener("mousedown", handleClickOutside);
28
+ return () => document.removeEventListener("mousedown", handleClickOutside);
29
+ }, [isActive, refs, onClickOutside]);
30
+ }
31
+ function useLockBodyScroll(isActive) {
32
+ useEffect(() => {
33
+ if (isActive) {
34
+ document.body.style.overflow = "hidden";
35
+ } else {
36
+ document.body.style.overflow = "";
37
+ }
38
+ return () => {
39
+ document.body.style.overflow = "";
40
+ };
41
+ }, [isActive]);
42
+ }
43
+
44
+ export { useClickOutside, useEscapeKey, useLockBodyScroll };
45
+ //# sourceMappingURL=chunk-SR2T7OEJ.mjs.map
46
+ //# sourceMappingURL=chunk-SR2T7OEJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../hooks/useModalBehavior.ts"],"names":[],"mappings":";;;AAOO,SAAS,YAAA,CAAa,UAAmB,QAAA,EAA4B;AAC1E,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAAqB;AACzC,MAAA,IAAI,CAAA,CAAE,QAAQ,QAAA,EAAU;AACtB,QAAA,QAAA,EAAS;AAAA,MACX;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,YAAY,CAAA;AACjD,IAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,SAAA,EAAW,YAAY,CAAA;AAAA,EACnE,CAAA,EAAG,CAAC,QAAA,EAAU,QAAQ,CAAC,CAAA;AACzB;AAQO,SAAS,eAAA,CACd,QAAA,EACA,IAAA,EACA,cAAA,EACM;AACN,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,kBAAA,GAAqB,CAAC,CAAA,KAAkB;AAC5C,MAAA,MAAM,oBAAoB,IAAA,CAAK,KAAA;AAAA,QAC7B,CAAC,QAAQ,GAAA,CAAI,OAAA,IAAW,CAAC,GAAA,CAAI,OAAA,CAAQ,QAAA,CAAS,CAAA,CAAE,MAAc;AAAA,OAChE;AACA,MAAA,IAAI,iBAAA,EAAmB;AACrB,QAAA,cAAA,EAAe;AAAA,MACjB;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,kBAAkB,CAAA;AACzD,IAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,WAAA,EAAa,kBAAkB,CAAA;AAAA,EAC3E,CAAA,EAAG,CAAC,QAAA,EAAU,IAAA,EAAM,cAAc,CAAC,CAAA;AACrC;AAMO,SAAS,kBAAkB,QAAA,EAAyB;AACzD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,IACjC,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,EAAA;AAAA,IACjC;AACA,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,EAAA;AAAA,IACjC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACf","file":"chunk-SR2T7OEJ.mjs","sourcesContent":["import { useEffect, RefObject } from 'react';\n\n/**\n * Hook to handle escape key press to close modals/overlays\n * @param isActive - Whether the modal is currently open\n * @param onEscape - Callback to close the modal\n */\nexport function useEscapeKey(isActive: boolean, onEscape: () => void): void {\n useEffect(() => {\n if (!isActive) return;\n\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onEscape();\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isActive, onEscape]);\n}\n\n/**\n * Hook to detect clicks outside of specified element(s)\n * @param isActive - Whether to listen for clicks\n * @param refs - Array of refs to elements that should NOT trigger the callback\n * @param onClickOutside - Callback when clicking outside all refs\n */\nexport function useClickOutside(\n isActive: boolean,\n refs: RefObject<HTMLElement | null>[],\n onClickOutside: () => void\n): void {\n useEffect(() => {\n if (!isActive) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n const clickedOutsideAll = refs.every(\n (ref) => ref.current && !ref.current.contains(e.target as Node)\n );\n if (clickedOutsideAll) {\n onClickOutside();\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [isActive, refs, onClickOutside]);\n}\n\n/**\n * Hook to prevent body scroll when modal is open\n * @param isActive - Whether to lock body scroll\n */\nexport function useLockBodyScroll(isActive: boolean): void {\n useEffect(() => {\n if (isActive) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [isActive]);\n}\n"]}