@hyperpackai/hyperui 0.2.0 → 0.3.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.
- package/README.md +13 -0
- package/dist/components/Accordion/index.d.ts +6 -0
- package/dist/components/Accordion/index.d.ts.map +1 -1
- package/dist/components/Accordion/index.js +65 -9
- package/dist/components/Autocomplete/index.d.ts +12 -2
- package/dist/components/Autocomplete/index.d.ts.map +1 -1
- package/dist/components/Autocomplete/index.js +148 -24
- package/dist/components/Backdrop/index.d.ts +2 -1
- package/dist/components/Backdrop/index.d.ts.map +1 -1
- package/dist/components/Backdrop/index.js +6 -3
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +6 -2
- package/dist/components/DashboardLayout/index.d.ts +13 -0
- package/dist/components/DashboardLayout/index.d.ts.map +1 -1
- package/dist/components/DashboardLayout/index.js +50 -7
- package/dist/components/DataTable/index.d.ts +43 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +126 -21
- package/dist/components/Dialog/index.d.ts +9 -3
- package/dist/components/Dialog/index.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +46 -30
- package/dist/components/Drawer/index.d.ts +11 -3
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +66 -11
- package/dist/components/DropdownMenu/index.d.ts +5 -3
- package/dist/components/DropdownMenu/index.d.ts.map +1 -1
- package/dist/components/DropdownMenu/index.js +56 -13
- package/dist/components/FocusTrap/index.d.ts.map +1 -1
- package/dist/components/FocusTrap/index.js +34 -32
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.d.ts.map +1 -1
- package/dist/components/Input/index.js +18 -4
- package/dist/components/Menu/index.d.ts +6 -2
- package/dist/components/Menu/index.d.ts.map +1 -1
- package/dist/components/Menu/index.js +50 -15
- package/dist/components/Modal/index.d.ts +3 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/Modal/index.js +27 -9
- package/dist/components/NestedNavbar/index.d.ts +33 -0
- package/dist/components/NestedNavbar/index.d.ts.map +1 -0
- package/dist/components/NestedNavbar/index.js +435 -0
- package/dist/components/NestedSidebar/index.d.ts +48 -0
- package/dist/components/NestedSidebar/index.d.ts.map +1 -0
- package/dist/components/NestedSidebar/index.js +368 -0
- package/dist/components/Popover/index.d.ts +11 -3
- package/dist/components/Popover/index.d.ts.map +1 -1
- package/dist/components/Popover/index.js +45 -9
- package/dist/components/Radio/index.d.ts +26 -1
- package/dist/components/Radio/index.d.ts.map +1 -1
- package/dist/components/Radio/index.js +61 -2
- package/dist/components/Select/index.d.ts +5 -0
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +22 -5
- package/dist/components/Sheet/index.d.ts +9 -3
- package/dist/components/Sheet/index.d.ts.map +1 -1
- package/dist/components/Sheet/index.js +48 -23
- package/dist/components/Sidebar/index.d.ts +20 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +285 -8
- package/dist/components/SpeedDial/index.d.ts +10 -0
- package/dist/components/SpeedDial/index.d.ts.map +1 -1
- package/dist/components/SpeedDial/index.js +61 -11
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.d.ts.map +1 -1
- package/dist/components/Switch/index.js +6 -2
- package/dist/components/Tabs/index.d.ts +3 -0
- package/dist/components/Tabs/index.d.ts.map +1 -1
- package/dist/components/Tabs/index.js +47 -8
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.d.ts.map +1 -1
- package/dist/components/TextField/index.js +12 -4
- package/dist/components/Textarea/index.d.ts +5 -0
- package/dist/components/Textarea/index.d.ts.map +1 -1
- package/dist/components/Textarea/index.js +21 -4
- package/dist/components/Transition/index.d.ts +14 -0
- package/dist/components/Transition/index.d.ts.map +1 -0
- package/dist/components/Transition/index.js +49 -0
- package/dist/components/TransitionGroup/index.d.ts +16 -0
- package/dist/components/TransitionGroup/index.d.ts.map +1 -0
- package/dist/components/TransitionGroup/index.js +95 -0
- package/dist/components/data.d.ts +81 -16
- package/dist/components/data.d.ts.map +1 -1
- package/dist/components/data.js +163 -31
- package/dist/components/enterprise.d.ts +85 -26
- package/dist/components/enterprise.d.ts.map +1 -1
- package/dist/components/enterprise.js +211 -36
- package/dist/components/index.d.ts +21 -13
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -2
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +3 -0
- package/dist/theme/index.d.ts +5 -6
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +30 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +11 -0
- package/package.json +6 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { signal } from "@hyperpackai/hyperion";
|
|
2
|
+
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
3
|
+
import { FocusTrap } from "../FocusTrap/index.js";
|
|
4
|
+
let sidebarCounter = 0;
|
|
5
|
+
const NESTED_SIDEBAR_CSS = `
|
|
6
|
+
/* ── Base ───────────────────────────────────────────────────────────── */
|
|
7
|
+
.hu-nested-sidebar {
|
|
8
|
+
--hu-ns-width: 260px;
|
|
9
|
+
--hu-ns-top: 0px;
|
|
10
|
+
--hu-ns-height: 100vh;
|
|
11
|
+
display: flex; flex-direction: column;
|
|
12
|
+
width: var(--hu-ns-width); min-width: var(--hu-ns-width);
|
|
13
|
+
height: var(--hu-ns-height); overflow: hidden;
|
|
14
|
+
background: linear-gradient(180deg, var(--hu-bg), color-mix(in srgb, var(--hu-bg-2) 64%, var(--hu-bg)));
|
|
15
|
+
border-right: 1px solid color-mix(in srgb, var(--hu-border) 82%, transparent);
|
|
16
|
+
position: sticky; top: var(--hu-ns-top);
|
|
17
|
+
}
|
|
18
|
+
.hu-nested-sidebar--sm { --hu-ns-width: 220px; }
|
|
19
|
+
.hu-nested-sidebar--md { --hu-ns-width: 260px; }
|
|
20
|
+
.hu-nested-sidebar--lg { --hu-ns-width: 300px; }
|
|
21
|
+
.hu-nested-sidebar--static { position: static; }
|
|
22
|
+
.hu-nested-sidebar--sticky { position: sticky; top: var(--hu-ns-top); }
|
|
23
|
+
.hu-nested-sidebar--fixed { position: fixed; top: var(--hu-ns-top); left: 0; bottom: 0; }
|
|
24
|
+
|
|
25
|
+
.hu-nested-sidebar__header {
|
|
26
|
+
padding: var(--hu-space-4) var(--hu-space-4) var(--hu-space-3);
|
|
27
|
+
border-bottom: 1px solid var(--hu-border); flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
.hu-nested-sidebar__nav { flex: 1; overflow-y: auto; padding: var(--hu-space-2); scrollbar-width: thin; }
|
|
30
|
+
.hu-nested-sidebar__footer { padding: var(--hu-space-3) var(--hu-space-4); border-top: 1px solid var(--hu-border); flex-shrink: 0; }
|
|
31
|
+
|
|
32
|
+
/* Section */
|
|
33
|
+
.hu-nested-sidebar__section { margin-bottom: var(--hu-space-4); }
|
|
34
|
+
.hu-nested-sidebar__section-label {
|
|
35
|
+
padding: var(--hu-space-2) var(--hu-space-3); font-size: 11px;
|
|
36
|
+
font-weight: var(--hu-font-weight-semibold); color: var(--hu-text-3);
|
|
37
|
+
text-transform: uppercase; letter-spacing: .06em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Flat item */
|
|
41
|
+
.hu-nested-sidebar__item {
|
|
42
|
+
display: flex; align-items: center; gap: var(--hu-space-3); width: 100%;
|
|
43
|
+
min-height: 38px; padding: 8px var(--hu-space-3); border-radius: var(--hu-radius-md);
|
|
44
|
+
font-size: var(--hu-font-size-sm); font-family: inherit;
|
|
45
|
+
color: var(--hu-text-2); text-decoration: none;
|
|
46
|
+
border: none; background: transparent; cursor: pointer;
|
|
47
|
+
font-weight: var(--hu-font-weight-medium); text-align: left;
|
|
48
|
+
transition: background var(--hu-duration) var(--hu-ease), color var(--hu-duration) var(--hu-ease), transform var(--hu-duration-fast) var(--hu-ease);
|
|
49
|
+
}
|
|
50
|
+
.hu-nested-sidebar__item:hover { background: var(--hu-bg-3); color: var(--hu-text); transform: translateX(1px); }
|
|
51
|
+
.hu-nested-sidebar__item:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
52
|
+
.hu-nested-sidebar__item--active { background: var(--hu-primary-bg); color: var(--hu-primary); box-shadow: inset 3px 0 0 var(--hu-primary); }
|
|
53
|
+
.hu-nested-sidebar__item-icon { width: 18px; height: 18px; flex-shrink: 0; }
|
|
54
|
+
.hu-nested-sidebar__item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
55
|
+
.hu-nested-sidebar__item-badge { margin-left: auto; flex-shrink: 0; }
|
|
56
|
+
|
|
57
|
+
/* Nested group trigger */
|
|
58
|
+
.hu-nested-sidebar__group-trigger {
|
|
59
|
+
display: flex; align-items: center; gap: var(--hu-space-3); width: 100%;
|
|
60
|
+
min-height: 38px; padding: 8px var(--hu-space-3); border-radius: var(--hu-radius-md);
|
|
61
|
+
font-size: var(--hu-font-size-sm); font-family: inherit;
|
|
62
|
+
color: var(--hu-text-2); background: transparent; border: none;
|
|
63
|
+
cursor: pointer; font-weight: var(--hu-font-weight-medium); text-align: left;
|
|
64
|
+
transition: background var(--hu-duration) var(--hu-ease), color var(--hu-duration) var(--hu-ease);
|
|
65
|
+
}
|
|
66
|
+
.hu-nested-sidebar__group-trigger:hover { background: var(--hu-bg-3); color: var(--hu-text); }
|
|
67
|
+
.hu-nested-sidebar__group-trigger:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
68
|
+
.hu-nested-sidebar__group-trigger--active { color: var(--hu-primary); }
|
|
69
|
+
.hu-nested-sidebar__group-chevron {
|
|
70
|
+
width: 14px; height: 14px; margin-left: auto; flex-shrink: 0;
|
|
71
|
+
transition: transform var(--hu-duration) var(--hu-ease);
|
|
72
|
+
}
|
|
73
|
+
.hu-nested-sidebar__group-trigger[aria-expanded="true"] .hu-nested-sidebar__group-chevron { transform: rotate(90deg); }
|
|
74
|
+
|
|
75
|
+
/* Child items */
|
|
76
|
+
.hu-nested-sidebar__children {
|
|
77
|
+
padding-left: var(--hu-space-4); margin-top: 2px; margin-bottom: 2px;
|
|
78
|
+
border-left: 2px solid var(--hu-border);
|
|
79
|
+
margin-left: calc(var(--hu-space-3) + 9px);
|
|
80
|
+
}
|
|
81
|
+
.hu-nested-sidebar__group:not([data-expanded="true"]) .hu-nested-sidebar__children { display: none !important; }
|
|
82
|
+
.hu-nested-sidebar__child {
|
|
83
|
+
display: flex; align-items: center; gap: var(--hu-space-2); width: 100%;
|
|
84
|
+
min-height: 34px; padding: 6px var(--hu-space-2); border-radius: var(--hu-radius-sm);
|
|
85
|
+
font-size: var(--hu-font-size-sm); font-family: inherit;
|
|
86
|
+
color: var(--hu-text-2); text-decoration: none;
|
|
87
|
+
border: none; background: transparent; cursor: pointer;
|
|
88
|
+
font-weight: var(--hu-font-weight-medium); text-align: left;
|
|
89
|
+
transition: background var(--hu-duration) var(--hu-ease), color var(--hu-duration) var(--hu-ease);
|
|
90
|
+
}
|
|
91
|
+
.hu-nested-sidebar__child:hover { background: var(--hu-bg-3); color: var(--hu-text); }
|
|
92
|
+
.hu-nested-sidebar__child:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
93
|
+
.hu-nested-sidebar__child--active { color: var(--hu-primary); background: var(--hu-primary-bg); }
|
|
94
|
+
|
|
95
|
+
/* ── Mobile Bar & Toggle ────────────────────────────────────────────── */
|
|
96
|
+
.hu-nested-sidebar__root { display: contents; }
|
|
97
|
+
.hu-nested-sidebar__mobile-shell { display: none; }
|
|
98
|
+
.hu-nested-sidebar__mobile-bar {
|
|
99
|
+
width: 100%; padding: var(--hu-space-2) var(--hu-space-3);
|
|
100
|
+
background: color-mix(in srgb, var(--hu-bg) 92%, transparent);
|
|
101
|
+
border-bottom: 1px solid var(--hu-border);
|
|
102
|
+
}
|
|
103
|
+
.hu-nested-sidebar__menu-btn {
|
|
104
|
+
display: inline-flex; align-items: center; gap: var(--hu-space-2);
|
|
105
|
+
min-height: 44px; padding: 0 var(--hu-space-3); max-width: 100%;
|
|
106
|
+
background: var(--hu-bg); border: 1px solid var(--hu-border);
|
|
107
|
+
border-radius: var(--hu-radius-md); box-shadow: var(--hu-shadow-xs);
|
|
108
|
+
font: inherit; font-size: var(--hu-font-size-sm);
|
|
109
|
+
font-weight: var(--hu-font-weight-semibold); color: var(--hu-text); cursor: pointer;
|
|
110
|
+
transition: border-color var(--hu-duration) var(--hu-ease), background var(--hu-duration) var(--hu-ease);
|
|
111
|
+
}
|
|
112
|
+
.hu-nested-sidebar__menu-btn:hover { background: var(--hu-bg-2); border-color: var(--hu-border-2); }
|
|
113
|
+
.hu-nested-sidebar__menu-btn:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
114
|
+
.hu-nested-sidebar__menu-btn-icon { display: inline-flex; align-items: center; width: 20px; height: 20px; flex-shrink: 0; }
|
|
115
|
+
|
|
116
|
+
/* ── Mobile Backdrop ────────────────────────────────────────────────── */
|
|
117
|
+
.hu-nested-sidebar__backdrop {
|
|
118
|
+
position: fixed; inset: 0; z-index: var(--hu-z-backdrop);
|
|
119
|
+
background: var(--hu-overlay);
|
|
120
|
+
animation: hu-ns-fade-in var(--hu-duration-fast) var(--hu-ease);
|
|
121
|
+
}
|
|
122
|
+
@keyframes hu-ns-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
123
|
+
|
|
124
|
+
/* ── Mobile Drawer ──────────────────────────────────────────────────── */
|
|
125
|
+
.hu-nested-sidebar__drawer {
|
|
126
|
+
position: fixed; inset-y: 0; left: 0; z-index: var(--hu-z-modal);
|
|
127
|
+
width: min(82vw, 320px);
|
|
128
|
+
display: flex; flex-direction: column;
|
|
129
|
+
background: linear-gradient(180deg, var(--hu-bg), color-mix(in srgb, var(--hu-bg-2) 74%, var(--hu-bg)));
|
|
130
|
+
border-right: 1px solid var(--hu-border);
|
|
131
|
+
box-shadow: var(--hu-shadow-xl);
|
|
132
|
+
animation: hu-ns-drawer-in var(--hu-duration-slow) var(--hu-ease-out);
|
|
133
|
+
}
|
|
134
|
+
@keyframes hu-ns-drawer-in { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
|
135
|
+
.hu-nested-sidebar__drawer-header {
|
|
136
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
137
|
+
padding: var(--hu-space-4); border-bottom: 1px solid var(--hu-border);
|
|
138
|
+
}
|
|
139
|
+
.hu-nested-sidebar__drawer-title { font-weight: var(--hu-font-weight-semibold); color: var(--hu-text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
140
|
+
.hu-nested-sidebar__drawer-close {
|
|
141
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
142
|
+
width: 34px; height: 34px; padding: 0;
|
|
143
|
+
background: var(--hu-bg); border: 1px solid var(--hu-border);
|
|
144
|
+
border-radius: var(--hu-radius-md); cursor: pointer; color: var(--hu-text-2);
|
|
145
|
+
}
|
|
146
|
+
.hu-nested-sidebar__drawer-close:hover { color: var(--hu-text); background: var(--hu-bg-3); }
|
|
147
|
+
.hu-nested-sidebar__drawer-close:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
148
|
+
.hu-nested-sidebar__drawer-body { flex: 1; overflow-y: auto; padding: var(--hu-space-2); }
|
|
149
|
+
|
|
150
|
+
/* The drawer items use the same .hu-nested-sidebar__item / .hu-nested-sidebar__child styles */
|
|
151
|
+
.hu-nested-sidebar__drawer .hu-nested-sidebar__item { min-height: 44px; }
|
|
152
|
+
.hu-nested-sidebar__drawer .hu-nested-sidebar__child { min-height: 40px; }
|
|
153
|
+
|
|
154
|
+
/* ── Mobile Bottom Sheet ────────────────────────────────────────────── */
|
|
155
|
+
.hu-nested-sidebar__sheet {
|
|
156
|
+
position: fixed; inset-x: 0; bottom: 0; z-index: var(--hu-z-modal);
|
|
157
|
+
max-height: 85vh; display: flex; flex-direction: column;
|
|
158
|
+
background: var(--hu-bg); border-top: 1px solid var(--hu-border);
|
|
159
|
+
border-radius: var(--hu-radius-xl) var(--hu-radius-xl) 0 0;
|
|
160
|
+
box-shadow: var(--hu-shadow-xl);
|
|
161
|
+
animation: hu-ns-sheet-in var(--hu-duration-slow) var(--hu-ease-out);
|
|
162
|
+
}
|
|
163
|
+
@keyframes hu-ns-sheet-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
|
|
164
|
+
.hu-nested-sidebar__sheet-handle {
|
|
165
|
+
width: 36px; height: 4px; border-radius: 2px;
|
|
166
|
+
background: var(--hu-border); margin: var(--hu-space-3) auto var(--hu-space-1); flex-shrink: 0;
|
|
167
|
+
}
|
|
168
|
+
.hu-nested-sidebar__sheet-header {
|
|
169
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
170
|
+
padding: var(--hu-space-2) var(--hu-space-4) var(--hu-space-3);
|
|
171
|
+
border-bottom: 1px solid var(--hu-border);
|
|
172
|
+
}
|
|
173
|
+
.hu-nested-sidebar__sheet-title { font-weight: var(--hu-font-weight-semibold); color: var(--hu-text); font-size: var(--hu-font-size-md); }
|
|
174
|
+
.hu-nested-sidebar__sheet-close {
|
|
175
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
176
|
+
width: 34px; height: 34px; padding: 0;
|
|
177
|
+
background: var(--hu-bg); border: 1px solid var(--hu-border);
|
|
178
|
+
border-radius: var(--hu-radius-md); cursor: pointer; color: var(--hu-text-2);
|
|
179
|
+
}
|
|
180
|
+
.hu-nested-sidebar__sheet-close:hover { background: var(--hu-bg-3); color: var(--hu-text); }
|
|
181
|
+
.hu-nested-sidebar__sheet-close:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
182
|
+
.hu-nested-sidebar__sheet-body { flex: 1; overflow-y: auto; padding: var(--hu-space-2); }
|
|
183
|
+
.hu-nested-sidebar__sheet .hu-nested-sidebar__item { min-height: 44px; }
|
|
184
|
+
.hu-nested-sidebar__sheet .hu-nested-sidebar__child { min-height: 40px; }
|
|
185
|
+
|
|
186
|
+
/* ── Responsive Breakpoint ──────────────────────────────────────────── */
|
|
187
|
+
@media (max-width: 1024px) {
|
|
188
|
+
.hu-nested-sidebar--responsive { display: none; }
|
|
189
|
+
.hu-nested-sidebar__root { display: block; width: 100%; }
|
|
190
|
+
.hu-nested-sidebar__mobile-shell { display: block; }
|
|
191
|
+
.hu-nested-sidebar__mobile-bar { position: sticky; top: var(--hu-ns-top); z-index: var(--hu-z-sticky); }
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
const nestedSidebarState = new Map();
|
|
195
|
+
function getSidebarState(key, sections, defaultOpen) {
|
|
196
|
+
let state = nestedSidebarState.get(key);
|
|
197
|
+
const defaultExpanded = collectDefaultExpanded(sections);
|
|
198
|
+
if (!state) {
|
|
199
|
+
state = {
|
|
200
|
+
expandedItems: signal(defaultExpanded),
|
|
201
|
+
uncontrolledOpen: signal(defaultOpen),
|
|
202
|
+
seededDefaults: defaultExpanded
|
|
203
|
+
};
|
|
204
|
+
nestedSidebarState.set(key, state);
|
|
205
|
+
return state;
|
|
206
|
+
}
|
|
207
|
+
for (const itemId of defaultExpanded) {
|
|
208
|
+
if (!state.seededDefaults.has(itemId)) {
|
|
209
|
+
const next = new Set(state.expandedItems.value);
|
|
210
|
+
next.add(itemId);
|
|
211
|
+
state.expandedItems.value = next;
|
|
212
|
+
state.seededDefaults.add(itemId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return state;
|
|
216
|
+
}
|
|
217
|
+
function collectDefaultExpanded(sections) {
|
|
218
|
+
const expanded = new Set();
|
|
219
|
+
for (const section of sections) {
|
|
220
|
+
for (const item of section.items) {
|
|
221
|
+
if (item.defaultExpanded && item.children?.length)
|
|
222
|
+
expanded.add(item.id);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return expanded;
|
|
226
|
+
}
|
|
227
|
+
function getSidebarKey(props) {
|
|
228
|
+
return props.id ?? props.class ?? props.drawerTitle ?? props.menuLabel ?? props.sections.map((section) => section.items.map((item) => item.id).join(",")).join("|") ?? `nested-sidebar-${sidebarCounter + 1}`;
|
|
229
|
+
}
|
|
230
|
+
// ── SVG helpers ───────────────────────────────────────────────────────────────
|
|
231
|
+
const chevronRight = h("svg", { class: "hu-nested-sidebar__group-chevron", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "2", "aria-hidden": "true" }, h("path", { d: "M6 4l4 4-4 4" }));
|
|
232
|
+
const closeIcon = h("svg", { viewBox: "0 0 16 16", width: "16", height: "16", fill: "none", stroke: "currentColor", "stroke-width": "2", "aria-hidden": "true" }, h("path", { d: "M2 2l12 12M14 2L2 14" }));
|
|
233
|
+
const menuHamburger = h("svg", { viewBox: "0 0 20 20", width: "18", height: "18", fill: "none", stroke: "currentColor", "stroke-width": "1.75", "aria-hidden": "true" }, h("path", { d: "M3 5h14M3 10h14M3 15h14" }));
|
|
234
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
235
|
+
function isItemActive(active) {
|
|
236
|
+
if (typeof active === "function")
|
|
237
|
+
return active();
|
|
238
|
+
if (typeof active === "object" && active != null && "peek" in active)
|
|
239
|
+
return active.value;
|
|
240
|
+
return active === true;
|
|
241
|
+
}
|
|
242
|
+
function formatCssSize(value) {
|
|
243
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
244
|
+
}
|
|
245
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
246
|
+
export function NestedSidebar(props) {
|
|
247
|
+
injectCSS("hu-nested-sidebar", NESTED_SIDEBAR_CSS);
|
|
248
|
+
const { sections, width = "md", position = "sticky", responsive = true, mobileVariant = "drawer" } = props;
|
|
249
|
+
const sid = props.id ?? `hu-ns-${++sidebarCounter}`;
|
|
250
|
+
const state = getSidebarState(getSidebarKey(props), sections, props.defaultOpen ?? false);
|
|
251
|
+
const { expandedItems, uncontrolledOpen } = state;
|
|
252
|
+
// Controlled / uncontrolled mobile open state
|
|
253
|
+
const isControlled = () => props.open !== undefined;
|
|
254
|
+
const isOpen = () => {
|
|
255
|
+
if (typeof props.open === "boolean")
|
|
256
|
+
return props.open;
|
|
257
|
+
if (typeof props.open === "object" && props.open != null && "peek" in props.open)
|
|
258
|
+
return props.open.value;
|
|
259
|
+
return uncontrolledOpen.value;
|
|
260
|
+
};
|
|
261
|
+
const setOpen = (next) => {
|
|
262
|
+
if (!isControlled())
|
|
263
|
+
uncontrolledOpen.value = next;
|
|
264
|
+
if (typeof props.open === "object" && props.open != null && "peek" in props.open)
|
|
265
|
+
props.open.value = next;
|
|
266
|
+
props.onOpenChange?.(next);
|
|
267
|
+
};
|
|
268
|
+
const closeMobile = () => setOpen(false);
|
|
269
|
+
const syncGroup = (groupEl, expanded) => {
|
|
270
|
+
groupEl?.setAttribute("data-expanded", String(expanded));
|
|
271
|
+
groupEl?.querySelector(".hu-nested-sidebar__group-trigger")?.setAttribute("aria-expanded", String(expanded));
|
|
272
|
+
groupEl?.querySelectorAll(".hu-nested-sidebar__children").forEach((panel) => {
|
|
273
|
+
panel.hidden = !expanded;
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
const toggleGroup = (event, id) => {
|
|
277
|
+
const next = new Set(expandedItems.value);
|
|
278
|
+
const groupEl = event ? event.currentTarget.closest(".hu-nested-sidebar__group") : null;
|
|
279
|
+
const expanded = !(groupEl?.dataset.expanded === "true" || next.has(id));
|
|
280
|
+
if (expanded)
|
|
281
|
+
next.add(id);
|
|
282
|
+
else
|
|
283
|
+
next.delete(id);
|
|
284
|
+
expandedItems.value = next;
|
|
285
|
+
syncGroup(groupEl, expanded);
|
|
286
|
+
};
|
|
287
|
+
const styleValue = [
|
|
288
|
+
props.topOffset !== undefined ? `--hu-ns-top:${formatCssSize(props.topOffset)}` : "",
|
|
289
|
+
props.height !== undefined ? `--hu-ns-height:${formatCssSize(props.height)}` : "",
|
|
290
|
+
].filter(Boolean).join(";");
|
|
291
|
+
// ── Nav content renderer ───────────────────────────────────────────────────
|
|
292
|
+
const renderNav = (onClose) => h("nav", { class: "hu-nested-sidebar__nav", "aria-label": "Sidebar navigation" }, ...sections.map((section, si) => h("div", { key: si, class: "hu-nested-sidebar__section" }, section.label && h("div", { class: "hu-nested-sidebar__section-label" }, section.label), ...section.items.map((item) => {
|
|
293
|
+
const active = () => isItemActive(item.active);
|
|
294
|
+
if (!item.children?.length) {
|
|
295
|
+
return h(item.href ? "a" : "button", {
|
|
296
|
+
key: item.id,
|
|
297
|
+
class: () => cn("hu-nested-sidebar__item", active() && "hu-nested-sidebar__item--active"),
|
|
298
|
+
href: item.href,
|
|
299
|
+
type: item.href ? undefined : "button",
|
|
300
|
+
"aria-current": () => active() ? "page" : undefined,
|
|
301
|
+
onClick: () => { item.onClick?.(); onClose?.(); },
|
|
302
|
+
}, item.icon && h("span", { class: "hu-nested-sidebar__item-icon", "aria-hidden": "true" }, item.icon), h("span", { class: "hu-nested-sidebar__item-label" }, item.label), item.badge && h("span", { class: "hu-nested-sidebar__item-badge" }, item.badge));
|
|
303
|
+
}
|
|
304
|
+
// Group with children
|
|
305
|
+
const isExpanded = () => expandedItems.value.has(item.id);
|
|
306
|
+
return h("div", {
|
|
307
|
+
key: item.id,
|
|
308
|
+
class: "hu-nested-sidebar__group",
|
|
309
|
+
"data-expanded": () => String(isExpanded())
|
|
310
|
+
}, h("button", {
|
|
311
|
+
type: "button",
|
|
312
|
+
class: () => cn("hu-nested-sidebar__group-trigger", active() && "hu-nested-sidebar__group-trigger--active"),
|
|
313
|
+
"aria-expanded": () => String(isExpanded()),
|
|
314
|
+
onClick: (event) => toggleGroup(event, item.id),
|
|
315
|
+
}, item.icon && h("span", { class: "hu-nested-sidebar__item-icon", "aria-hidden": "true" }, item.icon), h("span", { class: "hu-nested-sidebar__item-label" }, item.label), item.badge && h("span", { class: "hu-nested-sidebar__item-badge" }, item.badge), chevronRight), h("div", {
|
|
316
|
+
class: "hu-nested-sidebar__children",
|
|
317
|
+
hidden: () => !isExpanded()
|
|
318
|
+
}, ...item.children.map((child) => {
|
|
319
|
+
const childActive = () => isItemActive(child.active);
|
|
320
|
+
return h(child.href ? "a" : "button", {
|
|
321
|
+
key: child.id,
|
|
322
|
+
class: () => cn("hu-nested-sidebar__child", childActive() && "hu-nested-sidebar__child--active"),
|
|
323
|
+
href: child.href,
|
|
324
|
+
type: child.href ? undefined : "button",
|
|
325
|
+
"aria-current": () => childActive() ? "page" : undefined,
|
|
326
|
+
onClick: () => { child.onClick?.(); onClose?.(); },
|
|
327
|
+
}, child.icon && h("span", { class: "hu-nested-sidebar__item-icon", "aria-hidden": "true" }, child.icon), child.label);
|
|
328
|
+
})));
|
|
329
|
+
}))));
|
|
330
|
+
// ── Mobile overlay ─────────────────────────────────────────────────────────
|
|
331
|
+
const renderOverlay = () => {
|
|
332
|
+
if (!isOpen())
|
|
333
|
+
return null;
|
|
334
|
+
const drawerTitle = props.drawerTitle ?? props.menuLabel ?? "Menu";
|
|
335
|
+
const closeBtn = (cls) => h("button", {
|
|
336
|
+
type: "button", class: cls, "aria-label": "Close menu", onClick: closeMobile,
|
|
337
|
+
}, closeIcon);
|
|
338
|
+
const overlayEl = mobileVariant === "bottom-sheet"
|
|
339
|
+
? h("div", {
|
|
340
|
+
class: "hu-nested-sidebar__sheet",
|
|
341
|
+
role: "dialog", "aria-modal": "true", "aria-label": drawerTitle,
|
|
342
|
+
}, h("div", { class: "hu-nested-sidebar__sheet-handle" }), h("div", { class: "hu-nested-sidebar__sheet-header" }, h("span", { class: "hu-nested-sidebar__sheet-title" }, drawerTitle), closeBtn("hu-nested-sidebar__sheet-close")), h("div", { class: "hu-nested-sidebar__sheet-body" }, renderNav(closeMobile)))
|
|
343
|
+
: h("aside", {
|
|
344
|
+
class: "hu-nested-sidebar__drawer",
|
|
345
|
+
role: "dialog", "aria-modal": "true", "aria-label": drawerTitle,
|
|
346
|
+
}, h("div", { class: "hu-nested-sidebar__drawer-header" }, h("div", { class: "hu-nested-sidebar__drawer-title" }, drawerTitle), closeBtn("hu-nested-sidebar__drawer-close")), h("div", { class: "hu-nested-sidebar__drawer-body" }, props.header && h("div", { class: "hu-nested-sidebar__header" }, props.header), renderNav(closeMobile), props.footer && h("div", { class: "hu-nested-sidebar__footer" }, props.footer)));
|
|
347
|
+
return h("div", {}, h("div", { class: "hu-nested-sidebar__backdrop", onClick: closeMobile, "aria-hidden": "true" }), FocusTrap({ active: true, restoreFocus: true, autoFocus: true, onEscape: closeMobile, children: overlayEl }));
|
|
348
|
+
};
|
|
349
|
+
// ── Desktop sidebar VNode ──────────────────────────────────────────────────
|
|
350
|
+
const sidebarEl = h("aside", {
|
|
351
|
+
class: cn("hu-nested-sidebar", `hu-nested-sidebar--${width}`, `hu-nested-sidebar--${position}`, responsive && "hu-nested-sidebar--responsive", props.class),
|
|
352
|
+
style: styleValue || undefined,
|
|
353
|
+
id: sid,
|
|
354
|
+
}, props.header && h("div", { class: "hu-nested-sidebar__header" }, props.header), renderNav(), props.footer && h("div", { class: "hu-nested-sidebar__footer" }, props.footer));
|
|
355
|
+
if (!responsive)
|
|
356
|
+
return sidebarEl;
|
|
357
|
+
// ── Responsive root: sidebar + mobile bar + overlay ────────────────────────
|
|
358
|
+
const menuIcon = props.menuIcon ?? menuHamburger;
|
|
359
|
+
const menuLabel = props.menuLabel ?? "Menu";
|
|
360
|
+
return h("div", { class: "hu-nested-sidebar__root", style: styleValue || undefined }, sidebarEl, h("div", { class: "hu-nested-sidebar__mobile-shell" }, h("div", { class: "hu-nested-sidebar__mobile-bar" }, h("button", {
|
|
361
|
+
type: "button",
|
|
362
|
+
class: "hu-nested-sidebar__menu-btn",
|
|
363
|
+
"aria-label": menuLabel,
|
|
364
|
+
"aria-haspopup": "dialog",
|
|
365
|
+
"aria-expanded": () => String(isOpen()),
|
|
366
|
+
onClick: () => setOpen(true),
|
|
367
|
+
}, h("span", { class: "hu-nested-sidebar__menu-btn-icon", "aria-hidden": "true" }, menuIcon), menuLabel)), renderOverlay));
|
|
368
|
+
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { type Signal } from "@hyperpackai/hyperion";
|
|
2
2
|
import { type VNode } from "../../theme/index.js";
|
|
3
3
|
export interface PopoverProps {
|
|
4
|
-
open
|
|
4
|
+
open?: boolean | Signal<boolean>;
|
|
5
|
+
defaultOpen?: boolean;
|
|
5
6
|
side?: "top" | "bottom" | "left" | "right";
|
|
6
7
|
trigger?: unknown;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
closeOnEscape?: boolean;
|
|
9
|
+
closeOnClickAway?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
role?: "dialog" | "menu" | "listbox" | "tooltip";
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
"aria-labelledby"?: string;
|
|
14
|
+
onClose?: (reason?: PopoverCloseReason) => void;
|
|
15
|
+
onOpenChange?: (open: boolean, reason?: PopoverCloseReason) => void;
|
|
9
16
|
children?: unknown;
|
|
10
17
|
class?: string;
|
|
11
18
|
}
|
|
19
|
+
export type PopoverCloseReason = "click-away" | "escape" | "programmatic" | "trigger";
|
|
12
20
|
export declare function Popover(props: PopoverProps): VNode;
|
|
13
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Popover/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Popover/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAiBpE,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,QAAQ,GAAG,cAAc,GAAG,SAAS,CAAC;AAItF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,CA2DlD"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { signal } from "@hyperpackai/hyperion";
|
|
1
2
|
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
3
|
+
import { ClickAwayListener } from "../ClickAwayListener/index.js";
|
|
2
4
|
const POPOVER_CSS = `
|
|
3
5
|
.hu-popover-wrap { position: relative; display: inline-flex; }
|
|
4
6
|
.hu-popover-content {
|
|
@@ -12,23 +14,57 @@ const POPOVER_CSS = `
|
|
|
12
14
|
.hu-popover-content--left { right: calc(100% + 6px); top: 0; }
|
|
13
15
|
.hu-popover-content--right { left: calc(100% + 6px); top: 0; }
|
|
14
16
|
`;
|
|
17
|
+
let popoverId = 0;
|
|
15
18
|
export function Popover(props) {
|
|
16
19
|
injectCSS("hu-popover", POPOVER_CSS);
|
|
17
|
-
const { side = "bottom", trigger, children } = props;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const { side = "bottom", trigger, children, closeOnEscape = true, closeOnClickAway = true, role = "dialog" } = props;
|
|
21
|
+
const id = props.id ?? `hu-popover-${++popoverId}`;
|
|
22
|
+
const signalOpen = getSignalProp(props.open);
|
|
23
|
+
const internalOpen = signal(props.defaultOpen ?? false);
|
|
24
|
+
const isControlled = typeof props.open === "boolean" || !!signalOpen;
|
|
25
|
+
const isOpen = () => signalOpen ? signalOpen.value : typeof props.open === "boolean" ? props.open : internalOpen.value;
|
|
26
|
+
const setOpen = (next, reason = "programmatic") => {
|
|
27
|
+
if (signalOpen)
|
|
28
|
+
signalOpen.value = next;
|
|
29
|
+
else if (!isControlled)
|
|
30
|
+
internalOpen.value = next;
|
|
31
|
+
props.onOpenChange?.(next, reason);
|
|
32
|
+
if (!next)
|
|
33
|
+
props.onClose?.(reason);
|
|
34
|
+
};
|
|
35
|
+
const close = (reason = "programmatic") => setOpen(false, reason);
|
|
36
|
+
const toggle = () => setOpen(!isOpen(), "trigger");
|
|
22
37
|
const overlay = () => isOpen()
|
|
23
38
|
? h("div", {
|
|
39
|
+
id,
|
|
24
40
|
class: cn("hu-popover-content", `hu-popover-content--${side}`),
|
|
41
|
+
role,
|
|
42
|
+
tabindex: "-1",
|
|
43
|
+
"aria-label": props["aria-label"],
|
|
44
|
+
"aria-labelledby": props["aria-labelledby"],
|
|
25
45
|
onKeyDown: (e) => {
|
|
26
|
-
if (e.key === "Escape") {
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
if (closeOnEscape && e.key === "Escape") {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
close("escape");
|
|
29
49
|
}
|
|
30
50
|
}
|
|
31
51
|
}, children)
|
|
32
52
|
: null;
|
|
33
|
-
|
|
53
|
+
const content = h("div", {
|
|
54
|
+
class: cn("hu-popover-wrap", props.class),
|
|
55
|
+
onBlur: closeOnClickAway ? (event) => {
|
|
56
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
57
|
+
close("click-away");
|
|
58
|
+
} : undefined
|
|
59
|
+
}, trigger && (isControlled
|
|
60
|
+
? trigger
|
|
61
|
+
: h("div", { onClick: toggle, style: "display:contents;", "aria-controls": id, "aria-expanded": isOpen() ? "true" : "false" }, trigger)), typeof props.open === "boolean" ? overlay() : overlay);
|
|
62
|
+
return ClickAwayListener({
|
|
63
|
+
disabled: !closeOnClickAway || !isOpen(),
|
|
64
|
+
onClickAway: () => close("click-away"),
|
|
65
|
+
children: content
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function getSignalProp(value) {
|
|
69
|
+
return typeof value === "object" && value != null && "peek" in value ? value : undefined;
|
|
34
70
|
}
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
import { type Signal } from "@hyperpackai/hyperion";
|
|
1
2
|
import { type VNode } from "../../theme/index.js";
|
|
2
3
|
import type { CheckboxProps } from "../Checkbox/index.js";
|
|
3
|
-
export interface RadioProps extends Omit<CheckboxProps, "indeterminate"
|
|
4
|
+
export interface RadioProps extends Omit<CheckboxProps, "indeterminate"> {
|
|
4
5
|
value: string;
|
|
6
|
+
onValueChange?: (value: string, e: Event) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface RadioGroupOption {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface RadioGroupProps {
|
|
15
|
+
options: RadioGroupOption[];
|
|
16
|
+
value?: string | Signal<string>;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
helper?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
direction?: "vertical" | "horizontal";
|
|
25
|
+
id?: string;
|
|
26
|
+
class?: string;
|
|
27
|
+
onChange?: (e: Event) => void;
|
|
28
|
+
onValueChange?: (value: string, e: Event) => void;
|
|
5
29
|
}
|
|
6
30
|
export declare function Radio(props: RadioProps): VNode;
|
|
31
|
+
export declare function RadioGroup(props: RadioGroupProps): VNode;
|
|
7
32
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Radio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Radio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8B1D,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAgB9C;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CA4DxD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { signal } from "@hyperpackai/hyperion";
|
|
1
2
|
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
2
3
|
const RADIO_CSS = `
|
|
3
4
|
.hu-radio-wrap { display: inline-flex; align-items: center; gap: var(--hu-space-2); cursor: pointer; min-height: 28px; max-width: 100%; }
|
|
@@ -20,9 +21,67 @@ const RADIO_CSS = `
|
|
|
20
21
|
.hu-radio:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: 2px; }
|
|
21
22
|
.hu-radio-group { display: flex; flex-direction: column; gap: var(--hu-space-2); }
|
|
22
23
|
.hu-radio-group--horizontal { flex-direction: row; flex-wrap: wrap; }
|
|
24
|
+
.hu-radio-group-wrap { display: flex; flex-direction: column; gap: 6px; }
|
|
25
|
+
.hu-radio-group__label { color: var(--hu-text); font-size: var(--hu-font-size-sm); font-weight: var(--hu-font-weight-semibold); line-height: 1.35; }
|
|
26
|
+
.hu-radio-group__label--required::after { content: " *"; color: var(--hu-error); }
|
|
27
|
+
.hu-radio-group__helper { font-size: var(--hu-font-size-xs); color: var(--hu-text-2); line-height: 1.45; }
|
|
28
|
+
.hu-radio-group__error { font-size: var(--hu-font-size-xs); color: var(--hu-error); line-height: 1.45; }
|
|
23
29
|
`;
|
|
24
30
|
export function Radio(props) {
|
|
25
31
|
injectCSS("hu-radio", RADIO_CSS);
|
|
26
|
-
const { label, description, disabled, id, ...rest } = props;
|
|
27
|
-
|
|
32
|
+
const { label, description, disabled, id, onChange, onCheckedChange, onValueChange, ...rest } = props;
|
|
33
|
+
const handleChange = (event) => {
|
|
34
|
+
onChange?.(event);
|
|
35
|
+
onCheckedChange?.(event.target.checked, event);
|
|
36
|
+
if (event.target.checked)
|
|
37
|
+
onValueChange?.(props.value, event);
|
|
38
|
+
};
|
|
39
|
+
return h("label", { class: cn("hu-radio-wrap", disabled && "hu-radio-wrap--disabled", props.class) }, h("input", { ...rest, type: "radio", id, disabled, onChange: handleChange, class: "hu-radio" }), (label || description) && h("span", { class: "hu-checkbox-text" }, label && h("span", { class: "hu-checkbox-label" }, label), description && h("span", { class: "hu-checkbox-desc" }, description)));
|
|
40
|
+
}
|
|
41
|
+
let radioGroupId = 0;
|
|
42
|
+
export function RadioGroup(props) {
|
|
43
|
+
injectCSS("hu-radio", RADIO_CSS);
|
|
44
|
+
const { options, label, helper, error, required, disabled, direction = "vertical", onChange, onValueChange } = props;
|
|
45
|
+
const generatedId = props.id ?? `hu-radio-group-${++radioGroupId}`;
|
|
46
|
+
const labelId = label ? `${generatedId}-label` : undefined;
|
|
47
|
+
const helperId = helper && !error ? `${generatedId}-helper` : undefined;
|
|
48
|
+
const errorId = error ? `${generatedId}-error` : undefined;
|
|
49
|
+
const describedBy = [errorId, helperId].filter(Boolean).join(" ") || undefined;
|
|
50
|
+
const name = props.name ?? generatedId;
|
|
51
|
+
const valueSignal = getSignalProp(props.value);
|
|
52
|
+
const isControlled = typeof props.value === "string" || !!valueSignal;
|
|
53
|
+
const internalValue = signal(typeof props.value === "string" ? props.value : props.defaultValue ?? "");
|
|
54
|
+
const currentValue = () => valueSignal ? valueSignal.value : typeof props.value === "string" ? props.value : internalValue.value;
|
|
55
|
+
const handleValueChange = (value, event) => {
|
|
56
|
+
if (valueSignal)
|
|
57
|
+
valueSignal.value = value;
|
|
58
|
+
else if (!isControlled)
|
|
59
|
+
internalValue.value = value;
|
|
60
|
+
onChange?.(event);
|
|
61
|
+
onValueChange?.(value, event);
|
|
62
|
+
};
|
|
63
|
+
return h("div", { class: cn("hu-radio-group-wrap", props.class) }, label && h("div", {
|
|
64
|
+
id: labelId,
|
|
65
|
+
class: cn("hu-radio-group__label", required && "hu-radio-group__label--required")
|
|
66
|
+
}, label), h("div", {
|
|
67
|
+
class: cn("hu-radio-group", direction === "horizontal" && "hu-radio-group--horizontal"),
|
|
68
|
+
role: "radiogroup",
|
|
69
|
+
"aria-labelledby": labelId,
|
|
70
|
+
"aria-describedby": describedBy,
|
|
71
|
+
"aria-invalid": error ? "true" : undefined,
|
|
72
|
+
"aria-required": required ? "true" : undefined
|
|
73
|
+
}, ...options.map((option, index) => Radio({
|
|
74
|
+
id: `${generatedId}-${index}`,
|
|
75
|
+
name,
|
|
76
|
+
value: option.value,
|
|
77
|
+
label: option.label,
|
|
78
|
+
description: option.description,
|
|
79
|
+
checked: currentValue() === option.value,
|
|
80
|
+
required,
|
|
81
|
+
disabled: disabled || option.disabled,
|
|
82
|
+
onValueChange: handleValueChange
|
|
83
|
+
}))), error && h("span", { id: errorId, class: "hu-radio-group__error", role: "alert" }, error), !error && helper && h("span", { id: helperId, class: "hu-radio-group__helper" }, helper));
|
|
84
|
+
}
|
|
85
|
+
function getSignalProp(value) {
|
|
86
|
+
return typeof value === "object" && value != null && "peek" in value ? value : undefined;
|
|
28
87
|
}
|
|
@@ -7,17 +7,22 @@ export interface SelectOption {
|
|
|
7
7
|
}
|
|
8
8
|
export interface SelectProps {
|
|
9
9
|
value?: string;
|
|
10
|
+
defaultValue?: string;
|
|
10
11
|
options: SelectOption[];
|
|
11
12
|
placeholder?: string;
|
|
12
13
|
disabled?: boolean;
|
|
13
14
|
required?: boolean;
|
|
14
15
|
size?: "sm" | "md" | "lg";
|
|
15
16
|
error?: string;
|
|
17
|
+
helper?: string;
|
|
16
18
|
label?: string;
|
|
17
19
|
id?: string;
|
|
18
20
|
name?: string;
|
|
19
21
|
class?: string;
|
|
22
|
+
"aria-label"?: string;
|
|
23
|
+
"aria-describedby"?: string;
|
|
20
24
|
onChange?: (e: Event) => void;
|
|
25
|
+
onValueChange?: (value: string, e: Event) => void;
|
|
21
26
|
}
|
|
22
27
|
export declare function Select(props: SelectProps): VNode;
|
|
23
28
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0BpE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0BpE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAkEhD"}
|