@scripso-homepad/ui 0.3.9 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-C7GHBVMM.js +614 -0
- package/dist/chunk-C7GHBVMM.js.map +1 -0
- package/dist/index.js +29 -635
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +1189 -0
- package/dist/web/index.cjs.map +1 -0
- package/dist/web/index.d.cts +156 -0
- package/dist/web/index.d.ts +156 -0
- package/dist/web/index.js +739 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +26 -2
- package/src/web/hooks/useMediaQuery.ts +23 -0
- package/src/web/hooks/useOnClickOutside.ts +30 -0
- package/src/web/icons/BellIcon.tsx +27 -0
- package/src/web/icons/BuildingIcon.tsx +55 -0
- package/src/web/index.ts +37 -0
- package/src/web/layout/AppHeader.stories.tsx +85 -0
- package/src/web/layout/AppHeader.tsx +115 -0
- package/src/web/layout/BuildingSelect.stories.tsx +60 -0
- package/src/web/layout/BuildingSelect.tsx +208 -0
- package/src/web/layout/DashboardLayout.stories.tsx +87 -0
- package/src/web/layout/DashboardLayout.tsx +37 -0
- package/src/web/layout/Sidebar.stories.tsx +80 -0
- package/src/web/layout/Sidebar.tsx +244 -0
- package/src/web/layout/SidebarMobileHeader.stories.tsx +47 -0
- package/src/web/layout/SidebarMobileHeader.tsx +48 -0
- package/src/web/layout/SidebarNavItem.tsx +60 -0
- package/src/web/layout/SidebarUserCard.tsx +67 -0
- package/src/web/layout/story-fixtures.tsx +93 -0
- package/src/web/layout/story-helpers.tsx +5 -0
- package/src/web/layout/types.ts +48 -0
- package/src/web/utils/cn.ts +6 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import { Input } from '../chunk-C7GHBVMM.js';
|
|
2
|
+
import { ChevronsUpDown, Search, LogOut, PanelLeftClose, PanelLeftOpen, X, Menu } from 'lucide-react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
import { useEffect, useState, useRef, useId, useCallback } from 'react';
|
|
7
|
+
import { NavLink, useLocation } from 'react-router-dom';
|
|
8
|
+
|
|
9
|
+
function cn(...inputs) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
function BellIcon({ className }) {
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
"svg",
|
|
15
|
+
{
|
|
16
|
+
width: "20",
|
|
17
|
+
height: "20",
|
|
18
|
+
viewBox: "0 0 20 20",
|
|
19
|
+
fill: "none",
|
|
20
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
21
|
+
"aria-hidden": true,
|
|
22
|
+
className: cn("shrink-0", className),
|
|
23
|
+
children: /* @__PURE__ */ jsx(
|
|
24
|
+
"path",
|
|
25
|
+
{
|
|
26
|
+
d: "M8.58333 17.4998C8.72282 17.7535 8.92787 17.9651 9.17708 18.1125C9.42628 18.2599 9.71048 18.3376 10 18.3376C10.2895 18.3376 10.5737 18.2599 10.8229 18.1125C11.0721 17.9651 11.2772 17.7535 11.4167 17.4998M5 6.6665C5 5.34042 5.52678 4.06865 6.46447 3.13097C7.40215 2.19329 8.67392 1.6665 10 1.6665C11.3261 1.6665 12.5979 2.19329 13.5355 3.13097C14.4732 4.06865 15 5.34042 15 6.6665C15 12.4998 17.5 14.1665 17.5 14.1665H2.5C2.5 14.1665 5 12.4998 5 6.6665Z",
|
|
27
|
+
stroke: "currentColor",
|
|
28
|
+
strokeWidth: "2",
|
|
29
|
+
strokeLinecap: "round",
|
|
30
|
+
strokeLinejoin: "round"
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function BuildingIcon({ className }) {
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
"svg",
|
|
39
|
+
{
|
|
40
|
+
width: "20",
|
|
41
|
+
height: "20",
|
|
42
|
+
viewBox: "0 0 24 24",
|
|
43
|
+
fill: "none",
|
|
44
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
45
|
+
"aria-hidden": true,
|
|
46
|
+
className: cn("shrink-0", className),
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx(
|
|
49
|
+
"path",
|
|
50
|
+
{
|
|
51
|
+
d: "M10 12h4",
|
|
52
|
+
stroke: "currentColor",
|
|
53
|
+
strokeWidth: "2",
|
|
54
|
+
strokeLinecap: "round",
|
|
55
|
+
strokeLinejoin: "round"
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
"path",
|
|
60
|
+
{
|
|
61
|
+
d: "M10 8h4",
|
|
62
|
+
stroke: "currentColor",
|
|
63
|
+
strokeWidth: "2",
|
|
64
|
+
strokeLinecap: "round",
|
|
65
|
+
strokeLinejoin: "round"
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"path",
|
|
70
|
+
{
|
|
71
|
+
d: "M14 21v-3a2 2 0 0 0-4 0v3",
|
|
72
|
+
stroke: "currentColor",
|
|
73
|
+
strokeWidth: "2",
|
|
74
|
+
strokeLinecap: "round",
|
|
75
|
+
strokeLinejoin: "round"
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
79
|
+
"path",
|
|
80
|
+
{
|
|
81
|
+
d: "M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2",
|
|
82
|
+
stroke: "currentColor",
|
|
83
|
+
strokeWidth: "2",
|
|
84
|
+
strokeLinecap: "round",
|
|
85
|
+
strokeLinejoin: "round"
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"path",
|
|
90
|
+
{
|
|
91
|
+
d: "M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16",
|
|
92
|
+
stroke: "currentColor",
|
|
93
|
+
strokeWidth: "2",
|
|
94
|
+
strokeLinecap: "round",
|
|
95
|
+
strokeLinejoin: "round"
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function useOnClickOutside(ref, handler, enabled = true) {
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const onPointerDown = (event) => {
|
|
108
|
+
const target = event.target;
|
|
109
|
+
if (!ref.current || !target || ref.current.contains(target)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
handler();
|
|
113
|
+
};
|
|
114
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
115
|
+
document.addEventListener("touchstart", onPointerDown);
|
|
116
|
+
return () => {
|
|
117
|
+
document.removeEventListener("mousedown", onPointerDown);
|
|
118
|
+
document.removeEventListener("touchstart", onPointerDown);
|
|
119
|
+
};
|
|
120
|
+
}, [enabled, handler, ref]);
|
|
121
|
+
}
|
|
122
|
+
function BuildingSelect({
|
|
123
|
+
options,
|
|
124
|
+
value,
|
|
125
|
+
labels,
|
|
126
|
+
onChange,
|
|
127
|
+
disabled = false,
|
|
128
|
+
className,
|
|
129
|
+
menuClassName,
|
|
130
|
+
buildingIcon,
|
|
131
|
+
chevronIcon
|
|
132
|
+
}) {
|
|
133
|
+
const [open, setOpen] = useState(false);
|
|
134
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
135
|
+
const rootRef = useRef(null);
|
|
136
|
+
const listboxId = useId();
|
|
137
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
138
|
+
const displayLabel = selectedOption?.label ?? labels.selectBuilding;
|
|
139
|
+
const selectableOptions = options.filter((option) => !option.disabled);
|
|
140
|
+
const closeMenu = useCallback(() => {
|
|
141
|
+
setOpen(false);
|
|
142
|
+
setHighlightedIndex(-1);
|
|
143
|
+
}, []);
|
|
144
|
+
const openMenu = useCallback(() => {
|
|
145
|
+
if (disabled || selectableOptions.length === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const selectedIndex = selectableOptions.findIndex((option) => option.value === value);
|
|
149
|
+
setHighlightedIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
150
|
+
setOpen(true);
|
|
151
|
+
}, [disabled, selectableOptions, value]);
|
|
152
|
+
const selectOption = useCallback(
|
|
153
|
+
(option) => {
|
|
154
|
+
if (option.disabled) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
onChange?.(option.value);
|
|
158
|
+
closeMenu();
|
|
159
|
+
},
|
|
160
|
+
[closeMenu, onChange]
|
|
161
|
+
);
|
|
162
|
+
useOnClickOutside(rootRef, closeMenu, open);
|
|
163
|
+
const handleTriggerKeyDown = (event) => {
|
|
164
|
+
if (disabled) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
switch (event.key) {
|
|
168
|
+
case "ArrowDown":
|
|
169
|
+
case "Enter":
|
|
170
|
+
case " ":
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
openMenu();
|
|
173
|
+
break;
|
|
174
|
+
case "Escape":
|
|
175
|
+
closeMenu();
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const handleListKeyDown = (event) => {
|
|
180
|
+
if (!open || selectableOptions.length === 0) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
switch (event.key) {
|
|
184
|
+
case "ArrowDown":
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
setHighlightedIndex((current) => (current + 1) % selectableOptions.length);
|
|
187
|
+
break;
|
|
188
|
+
case "ArrowUp":
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
setHighlightedIndex(
|
|
191
|
+
(current) => (current - 1 + selectableOptions.length) % selectableOptions.length
|
|
192
|
+
);
|
|
193
|
+
break;
|
|
194
|
+
case "Enter":
|
|
195
|
+
case " ":
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
if (highlightedIndex >= 0) {
|
|
198
|
+
selectOption(selectableOptions[highlightedIndex]);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case "Escape":
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
closeMenu();
|
|
204
|
+
break;
|
|
205
|
+
case "Tab":
|
|
206
|
+
closeMenu();
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative w-57.5", className), children: [
|
|
211
|
+
/* @__PURE__ */ jsxs(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
type: "button",
|
|
215
|
+
disabled,
|
|
216
|
+
"aria-haspopup": "listbox",
|
|
217
|
+
"aria-expanded": open,
|
|
218
|
+
"aria-controls": listboxId,
|
|
219
|
+
"aria-label": labels.selectBuilding,
|
|
220
|
+
onClick: () => open ? closeMenu() : openMenu(),
|
|
221
|
+
onKeyDown: handleTriggerKeyDown,
|
|
222
|
+
className: cn(
|
|
223
|
+
"flex h-11 w-full items-center gap-3 rounded-xl border border-storm-gray-50 bg-white p-3 text-left transition-colors",
|
|
224
|
+
!disabled && "cursor-pointer hover:bg-storm-gray-50/60",
|
|
225
|
+
open && "bg-storm-gray-50/60",
|
|
226
|
+
disabled && "cursor-not-allowed opacity-60"
|
|
227
|
+
),
|
|
228
|
+
children: [
|
|
229
|
+
buildingIcon ?? /* @__PURE__ */ jsx(BuildingIcon, { className: "shrink-0 text-navy" }),
|
|
230
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-slate-blue", children: displayLabel }),
|
|
231
|
+
chevronIcon ?? /* @__PURE__ */ jsx(
|
|
232
|
+
ChevronsUpDown,
|
|
233
|
+
{
|
|
234
|
+
size: 16,
|
|
235
|
+
strokeWidth: 1.75,
|
|
236
|
+
className: cn(
|
|
237
|
+
"shrink-0 text-storm-gray-100 transition-transform duration-200",
|
|
238
|
+
open && "rotate-180"
|
|
239
|
+
),
|
|
240
|
+
"aria-hidden": true
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
open ? /* @__PURE__ */ jsx(
|
|
247
|
+
"ul",
|
|
248
|
+
{
|
|
249
|
+
id: listboxId,
|
|
250
|
+
role: "listbox",
|
|
251
|
+
"aria-label": labels.selectBuilding,
|
|
252
|
+
tabIndex: -1,
|
|
253
|
+
onKeyDown: handleListKeyDown,
|
|
254
|
+
className: cn(
|
|
255
|
+
"absolute top-[calc(100%+4px)] left-0 z-50 flex w-full flex-col gap-1 overflow-hidden rounded-xl border border-storm-gray-50 bg-white p-2 shadow-[0_8px_24px_rgba(21,26,30,0.08)]",
|
|
256
|
+
menuClassName
|
|
257
|
+
),
|
|
258
|
+
children: options.map((option) => {
|
|
259
|
+
const selectableIndex = selectableOptions.findIndex(
|
|
260
|
+
(selectable) => selectable.value === option.value
|
|
261
|
+
);
|
|
262
|
+
const isSelected = option.value === value;
|
|
263
|
+
const isHighlighted = selectableIndex === highlightedIndex;
|
|
264
|
+
return /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
|
|
265
|
+
"button",
|
|
266
|
+
{
|
|
267
|
+
type: "button",
|
|
268
|
+
role: "option",
|
|
269
|
+
"aria-selected": isSelected,
|
|
270
|
+
disabled: option.disabled,
|
|
271
|
+
onMouseEnter: () => {
|
|
272
|
+
if (!option.disabled && selectableIndex >= 0) {
|
|
273
|
+
setHighlightedIndex(selectableIndex);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
onClick: () => selectOption(option),
|
|
277
|
+
className: cn(
|
|
278
|
+
"flex w-full rounded-lg px-3 py-2.5 text-left text-sm font-medium text-slate-blue transition-colors",
|
|
279
|
+
!option.disabled && "cursor-pointer hover:bg-storm-gray-50",
|
|
280
|
+
(isSelected || isHighlighted) && !option.disabled && "bg-storm-gray-50",
|
|
281
|
+
option.disabled && "cursor-not-allowed opacity-50"
|
|
282
|
+
),
|
|
283
|
+
children: /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label })
|
|
284
|
+
}
|
|
285
|
+
) }, option.value);
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
) : null
|
|
289
|
+
] });
|
|
290
|
+
}
|
|
291
|
+
var defaultSearchFieldStyle = {
|
|
292
|
+
height: 44,
|
|
293
|
+
minHeight: 44,
|
|
294
|
+
maxHeight: 44,
|
|
295
|
+
backgroundColor: "transparent",
|
|
296
|
+
padding: 12,
|
|
297
|
+
width: 260,
|
|
298
|
+
borderRadius: 12
|
|
299
|
+
};
|
|
300
|
+
function AppHeader({
|
|
301
|
+
title,
|
|
302
|
+
labels,
|
|
303
|
+
buildingOptions,
|
|
304
|
+
selectedBuildingId,
|
|
305
|
+
className,
|
|
306
|
+
buildingSelectClassName,
|
|
307
|
+
searchClassName,
|
|
308
|
+
notificationsButtonClassName,
|
|
309
|
+
searchValue,
|
|
310
|
+
searchFieldStyle,
|
|
311
|
+
buildingIcon,
|
|
312
|
+
notificationsIcon,
|
|
313
|
+
searchIcon,
|
|
314
|
+
buildingChevronIcon,
|
|
315
|
+
buildingSelectDisabled = false,
|
|
316
|
+
searchDisabled = false,
|
|
317
|
+
notificationsDisabled = false,
|
|
318
|
+
onBuildingChange,
|
|
319
|
+
onNotificationsClick,
|
|
320
|
+
onSearchChange
|
|
321
|
+
}) {
|
|
322
|
+
return /* @__PURE__ */ jsxs(
|
|
323
|
+
"header",
|
|
324
|
+
{
|
|
325
|
+
className: cn(
|
|
326
|
+
"flex shrink-0 items-center justify-between gap-4 border-b border-storm-gray-50 bg-storm-gray-0 px-6 py-3",
|
|
327
|
+
className
|
|
328
|
+
),
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsx("h1", { className: "truncate text-xl font-bold text-slate-blue", children: title }),
|
|
331
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
|
|
332
|
+
/* @__PURE__ */ jsx(
|
|
333
|
+
BuildingSelect,
|
|
334
|
+
{
|
|
335
|
+
options: buildingOptions,
|
|
336
|
+
value: selectedBuildingId,
|
|
337
|
+
labels,
|
|
338
|
+
onChange: onBuildingChange,
|
|
339
|
+
disabled: buildingSelectDisabled,
|
|
340
|
+
className: buildingSelectClassName,
|
|
341
|
+
buildingIcon,
|
|
342
|
+
chevronIcon: buildingChevronIcon
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsx(
|
|
346
|
+
Input,
|
|
347
|
+
{
|
|
348
|
+
leftIcon: searchIcon ?? /* @__PURE__ */ jsx(Search, { strokeWidth: 1.75 }),
|
|
349
|
+
placeholder: labels.searchPlaceholder,
|
|
350
|
+
value: searchValue,
|
|
351
|
+
onChangeText: (value) => onSearchChange?.(value),
|
|
352
|
+
editable: !searchDisabled,
|
|
353
|
+
className: cn("w-65!", searchClassName),
|
|
354
|
+
containerStyle: { width: 260, maxWidth: 260, flexShrink: 0 },
|
|
355
|
+
fieldStyle: searchFieldStyle ?? defaultSearchFieldStyle,
|
|
356
|
+
accessibilityLabel: labels.searchPlaceholder
|
|
357
|
+
}
|
|
358
|
+
),
|
|
359
|
+
/* @__PURE__ */ jsx(
|
|
360
|
+
"button",
|
|
361
|
+
{
|
|
362
|
+
type: "button",
|
|
363
|
+
onClick: onNotificationsClick,
|
|
364
|
+
disabled: notificationsDisabled,
|
|
365
|
+
className: cn(
|
|
366
|
+
"h-11 cursor-pointer rounded-xl border border-storm-gray-50 p-3 text-slate-blue transition-colors hover:bg-storm-gray-50/60",
|
|
367
|
+
notificationsDisabled && "pointer-events-none opacity-60",
|
|
368
|
+
notificationsButtonClassName
|
|
369
|
+
),
|
|
370
|
+
"aria-label": labels.notifications,
|
|
371
|
+
children: notificationsIcon ?? /* @__PURE__ */ jsx(BellIcon, { className: "text-navy" })
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
] })
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
function useMediaQuery(query) {
|
|
380
|
+
const [matches, setMatches] = useState(() => {
|
|
381
|
+
if (typeof window === "undefined") {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
return window.matchMedia(query).matches;
|
|
385
|
+
});
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
const mediaQuery = window.matchMedia(query);
|
|
388
|
+
const handleChange = () => setMatches(mediaQuery.matches);
|
|
389
|
+
handleChange();
|
|
390
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
391
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
392
|
+
}, [query]);
|
|
393
|
+
return matches;
|
|
394
|
+
}
|
|
395
|
+
function SidebarNavItem({ item, isOpen, onNavigate }) {
|
|
396
|
+
if (item.hidden) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const handleClick = () => {
|
|
400
|
+
item.onClick?.();
|
|
401
|
+
onNavigate?.(item);
|
|
402
|
+
};
|
|
403
|
+
return /* @__PURE__ */ jsx(
|
|
404
|
+
NavLink,
|
|
405
|
+
{
|
|
406
|
+
to: item.to,
|
|
407
|
+
end: item.end,
|
|
408
|
+
onClick: handleClick,
|
|
409
|
+
"aria-disabled": item.disabled,
|
|
410
|
+
tabIndex: item.disabled ? -1 : void 0,
|
|
411
|
+
className: ({ isActive }) => cn(
|
|
412
|
+
"relative flex items-center rounded-xl px-4 py-3 transition-[background-color,color,gap,padding] duration-300 ease-in-out",
|
|
413
|
+
isOpen ? "w-full gap-4" : "justify-center gap-0 px-3",
|
|
414
|
+
item.disabled && "pointer-events-none opacity-50",
|
|
415
|
+
isActive ? "bg-black/12 text-white" : "text-navy-100 hover:bg-white/5"
|
|
416
|
+
),
|
|
417
|
+
children: ({ isActive }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
"span",
|
|
420
|
+
{
|
|
421
|
+
"aria-hidden": true,
|
|
422
|
+
className: cn(
|
|
423
|
+
"absolute top-1.5 -left-1 h-8 w-2 rounded-full bg-white transition-[opacity,transform] duration-300 ease-in-out",
|
|
424
|
+
isActive ? "scale-100 opacity-100" : "scale-75 opacity-0"
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
/* @__PURE__ */ jsx("span", { className: "flex size-5 shrink-0 items-center justify-center", children: item.icon }),
|
|
429
|
+
/* @__PURE__ */ jsx(
|
|
430
|
+
"span",
|
|
431
|
+
{
|
|
432
|
+
className: cn(
|
|
433
|
+
"overflow-hidden text-sm font-semibold whitespace-nowrap transition-[max-width,opacity] duration-300 ease-in-out",
|
|
434
|
+
isOpen ? "max-w-[180px] opacity-100" : "max-w-0 opacity-0"
|
|
435
|
+
),
|
|
436
|
+
children: item.label
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
] })
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
function getInitials(fullName, email) {
|
|
444
|
+
const parts = fullName.trim().split(/\s+/).filter(Boolean);
|
|
445
|
+
if (parts.length > 0) {
|
|
446
|
+
return parts.slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
|
|
447
|
+
}
|
|
448
|
+
return email.slice(0, 2).toUpperCase();
|
|
449
|
+
}
|
|
450
|
+
function SidebarUserCard({
|
|
451
|
+
user,
|
|
452
|
+
isOpen,
|
|
453
|
+
logoutLabel,
|
|
454
|
+
onLogout,
|
|
455
|
+
className
|
|
456
|
+
}) {
|
|
457
|
+
const initials = user.initials ?? getInitials(user.fullName, user.email);
|
|
458
|
+
return /* @__PURE__ */ jsxs(
|
|
459
|
+
"div",
|
|
460
|
+
{
|
|
461
|
+
className: cn(
|
|
462
|
+
"flex w-full items-center rounded-xl transition-[background-color,justify-content,gap,padding] duration-300 ease-in-out",
|
|
463
|
+
isOpen ? "justify-between gap-3 bg-black/12 p-3" : "justify-center px-3 py-3",
|
|
464
|
+
className
|
|
465
|
+
),
|
|
466
|
+
children: [
|
|
467
|
+
/* @__PURE__ */ jsx(
|
|
468
|
+
"div",
|
|
469
|
+
{
|
|
470
|
+
className: "flex size-11 shrink-0 items-center justify-center rounded-full bg-white/5 text-sm font-bold text-white",
|
|
471
|
+
"aria-hidden": true,
|
|
472
|
+
children: initials
|
|
473
|
+
}
|
|
474
|
+
),
|
|
475
|
+
isOpen ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
476
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
|
|
477
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-bold text-white", children: user.fullName }),
|
|
478
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-storm-gray-100", children: user.email })
|
|
479
|
+
] }),
|
|
480
|
+
/* @__PURE__ */ jsx(
|
|
481
|
+
"button",
|
|
482
|
+
{
|
|
483
|
+
type: "button",
|
|
484
|
+
onClick: () => onLogout?.(),
|
|
485
|
+
className: "shrink-0 cursor-pointer text-navy-150 transition-[opacity,transform] duration-300 ease-in-out hover:opacity-80 active:scale-95",
|
|
486
|
+
"aria-label": logoutLabel,
|
|
487
|
+
children: /* @__PURE__ */ jsx(LogOut, { size: 20, strokeWidth: 1.75 })
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
] }) : null
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
function readInitialCollapsedState(storageKey, initialCollapsed) {
|
|
496
|
+
if (initialCollapsed !== void 0) {
|
|
497
|
+
return initialCollapsed;
|
|
498
|
+
}
|
|
499
|
+
if (typeof window === "undefined") {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
const stored = window.localStorage.getItem(storageKey);
|
|
503
|
+
if (stored === null) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
return stored !== "true";
|
|
507
|
+
}
|
|
508
|
+
function Sidebar({
|
|
509
|
+
navItems,
|
|
510
|
+
footerNavItems,
|
|
511
|
+
branding,
|
|
512
|
+
labels,
|
|
513
|
+
sidebarStorageKey,
|
|
514
|
+
user,
|
|
515
|
+
onLogout,
|
|
516
|
+
className,
|
|
517
|
+
mobileOpen,
|
|
518
|
+
onMobileClose,
|
|
519
|
+
onNavItemClick,
|
|
520
|
+
collapseIcon,
|
|
521
|
+
expandIcon,
|
|
522
|
+
closeIcon,
|
|
523
|
+
initialCollapsed,
|
|
524
|
+
collapsed,
|
|
525
|
+
onCollapsedChange
|
|
526
|
+
}) {
|
|
527
|
+
const location = useLocation();
|
|
528
|
+
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
529
|
+
const [internalCollapsed, setInternalCollapsed] = useState(
|
|
530
|
+
() => readInitialCollapsedState(sidebarStorageKey, initialCollapsed)
|
|
531
|
+
);
|
|
532
|
+
const isCollapsed = collapsed ?? internalCollapsed;
|
|
533
|
+
const isExpanded = isDesktop ? !isCollapsed : true;
|
|
534
|
+
const setCollapsed = useCallback(
|
|
535
|
+
(next) => {
|
|
536
|
+
if (collapsed === void 0) {
|
|
537
|
+
setInternalCollapsed(next);
|
|
538
|
+
window.localStorage.setItem(sidebarStorageKey, String(!next));
|
|
539
|
+
}
|
|
540
|
+
onCollapsedChange?.(next);
|
|
541
|
+
},
|
|
542
|
+
[collapsed, onCollapsedChange, sidebarStorageKey]
|
|
543
|
+
);
|
|
544
|
+
const toggleCollapsed = useCallback(() => {
|
|
545
|
+
setCollapsed(!isCollapsed);
|
|
546
|
+
}, [isCollapsed, setCollapsed]);
|
|
547
|
+
const handleHeaderAction = useCallback(() => {
|
|
548
|
+
if (isDesktop) {
|
|
549
|
+
toggleCollapsed();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
onMobileClose?.();
|
|
553
|
+
}, [isDesktop, onMobileClose, toggleCollapsed]);
|
|
554
|
+
const handleNavItemNavigate = useCallback(
|
|
555
|
+
(item) => {
|
|
556
|
+
onNavItemClick?.(item);
|
|
557
|
+
if (!isDesktop) {
|
|
558
|
+
onMobileClose?.();
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
[isDesktop, onMobileClose, onNavItemClick]
|
|
562
|
+
);
|
|
563
|
+
const pathnameRef = useRef(location.pathname);
|
|
564
|
+
useEffect(() => {
|
|
565
|
+
if (pathnameRef.current === location.pathname) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
pathnameRef.current = location.pathname;
|
|
569
|
+
if (!isDesktop) {
|
|
570
|
+
onMobileClose?.();
|
|
571
|
+
}
|
|
572
|
+
}, [location.pathname, isDesktop, onMobileClose]);
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
if (!mobileOpen || isDesktop) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const previousOverflow = document.body.style.overflow;
|
|
578
|
+
document.body.style.overflow = "hidden";
|
|
579
|
+
return () => {
|
|
580
|
+
document.body.style.overflow = previousOverflow;
|
|
581
|
+
};
|
|
582
|
+
}, [mobileOpen, isDesktop]);
|
|
583
|
+
const visibleNavItems = navItems.filter((item) => !item.hidden);
|
|
584
|
+
const visibleFooterNavItems = footerNavItems.filter((item) => !item.hidden);
|
|
585
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
586
|
+
/* @__PURE__ */ jsx(
|
|
587
|
+
"button",
|
|
588
|
+
{
|
|
589
|
+
type: "button",
|
|
590
|
+
"aria-label": labels.closeMenu,
|
|
591
|
+
onClick: onMobileClose,
|
|
592
|
+
className: cn(
|
|
593
|
+
"fixed inset-0 z-40 bg-navy-800/60 backdrop-blur-[1px] transition-opacity duration-300 ease-in-out md:hidden",
|
|
594
|
+
mobileOpen ? "opacity-100" : "pointer-events-none opacity-0"
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
),
|
|
598
|
+
/* @__PURE__ */ jsxs(
|
|
599
|
+
"aside",
|
|
600
|
+
{
|
|
601
|
+
className: cn(
|
|
602
|
+
"fixed inset-y-0 left-0 z-50 flex h-screen w-[min(300px,85vw)] flex-col gap-8 overflow-hidden bg-navy p-4 shadow-xl",
|
|
603
|
+
"transition-[transform,width,padding,gap] duration-300 ease-in-out will-change-[transform,width]",
|
|
604
|
+
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
|
605
|
+
"md:relative md:z-auto md:w-[300px] md:translate-x-0 md:shadow-none",
|
|
606
|
+
isExpanded ? "md:w-[300px]" : "md:w-[76px] md:items-center md:gap-2 md:px-3 md:py-4",
|
|
607
|
+
className
|
|
608
|
+
),
|
|
609
|
+
"aria-hidden": !isDesktop && !mobileOpen,
|
|
610
|
+
children: [
|
|
611
|
+
/* @__PURE__ */ jsxs(
|
|
612
|
+
"div",
|
|
613
|
+
{
|
|
614
|
+
className: cn(
|
|
615
|
+
"flex w-full items-center transition-[justify-content] duration-300 ease-in-out",
|
|
616
|
+
isExpanded ? "justify-between" : "justify-center"
|
|
617
|
+
),
|
|
618
|
+
children: [
|
|
619
|
+
/* @__PURE__ */ jsxs(
|
|
620
|
+
"div",
|
|
621
|
+
{
|
|
622
|
+
className: cn(
|
|
623
|
+
"flex min-w-0 items-center gap-1.5 overflow-hidden transition-[max-width,opacity] duration-300 ease-in-out",
|
|
624
|
+
isExpanded ? "max-w-[240px] opacity-100" : "max-w-0 opacity-0"
|
|
625
|
+
),
|
|
626
|
+
children: [
|
|
627
|
+
/* @__PURE__ */ jsx("img", { src: branding.logoIconSrc, alt: "", className: "h-5 w-[29px] shrink-0" }),
|
|
628
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col leading-none", children: [
|
|
629
|
+
/* @__PURE__ */ jsx("span", { className: "text-[21px] font-bold tracking-tight whitespace-nowrap text-white", children: branding.logoTitle }),
|
|
630
|
+
/* @__PURE__ */ jsx("span", { className: "text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase", children: branding.logoTagline })
|
|
631
|
+
] })
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
),
|
|
635
|
+
/* @__PURE__ */ jsx(
|
|
636
|
+
"button",
|
|
637
|
+
{
|
|
638
|
+
type: "button",
|
|
639
|
+
onClick: handleHeaderAction,
|
|
640
|
+
className: "shrink-0 cursor-pointer text-white transition-[opacity,transform] duration-200 hover:opacity-80 active:scale-95",
|
|
641
|
+
"aria-label": isDesktop ? isExpanded ? labels.collapse : labels.expand : labels.closeMenu,
|
|
642
|
+
children: isDesktop ? isExpanded ? collapseIcon ?? /* @__PURE__ */ jsx(PanelLeftClose, { size: 20, strokeWidth: 1.75 }) : expandIcon ?? /* @__PURE__ */ jsx(PanelLeftOpen, { size: 20, strokeWidth: 1.75 }) : closeIcon ?? /* @__PURE__ */ jsx(X, { size: 20, strokeWidth: 1.75 })
|
|
643
|
+
}
|
|
644
|
+
)
|
|
645
|
+
]
|
|
646
|
+
}
|
|
647
|
+
),
|
|
648
|
+
/* @__PURE__ */ jsx("nav", { className: "flex flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden", children: visibleNavItems.map((item) => /* @__PURE__ */ jsx(
|
|
649
|
+
SidebarNavItem,
|
|
650
|
+
{
|
|
651
|
+
item,
|
|
652
|
+
isOpen: isExpanded,
|
|
653
|
+
onNavigate: handleNavItemNavigate
|
|
654
|
+
},
|
|
655
|
+
item.to
|
|
656
|
+
)) }),
|
|
657
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
658
|
+
visibleFooterNavItems.map((item) => /* @__PURE__ */ jsx(
|
|
659
|
+
SidebarNavItem,
|
|
660
|
+
{
|
|
661
|
+
item,
|
|
662
|
+
isOpen: isExpanded,
|
|
663
|
+
onNavigate: handleNavItemNavigate
|
|
664
|
+
},
|
|
665
|
+
item.to
|
|
666
|
+
)),
|
|
667
|
+
user ? /* @__PURE__ */ jsx(
|
|
668
|
+
SidebarUserCard,
|
|
669
|
+
{
|
|
670
|
+
user,
|
|
671
|
+
isOpen: isExpanded,
|
|
672
|
+
logoutLabel: labels.logout,
|
|
673
|
+
onLogout
|
|
674
|
+
}
|
|
675
|
+
) : null
|
|
676
|
+
] })
|
|
677
|
+
]
|
|
678
|
+
}
|
|
679
|
+
)
|
|
680
|
+
] });
|
|
681
|
+
}
|
|
682
|
+
function SidebarMobileHeader({
|
|
683
|
+
branding,
|
|
684
|
+
openMenuLabel,
|
|
685
|
+
onOpenMenu,
|
|
686
|
+
menuOpen = false,
|
|
687
|
+
className
|
|
688
|
+
}) {
|
|
689
|
+
return /* @__PURE__ */ jsxs(
|
|
690
|
+
"header",
|
|
691
|
+
{
|
|
692
|
+
className: cn(
|
|
693
|
+
"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-storm-gray-50 bg-storm-gray-0 px-4 md:hidden",
|
|
694
|
+
className
|
|
695
|
+
),
|
|
696
|
+
children: [
|
|
697
|
+
/* @__PURE__ */ jsx(
|
|
698
|
+
"button",
|
|
699
|
+
{
|
|
700
|
+
type: "button",
|
|
701
|
+
onClick: onOpenMenu,
|
|
702
|
+
className: "flex size-10 items-center justify-center rounded-xl text-navy transition-colors hover:bg-storm-gray-50",
|
|
703
|
+
"aria-label": openMenuLabel,
|
|
704
|
+
"aria-expanded": menuOpen,
|
|
705
|
+
children: /* @__PURE__ */ jsx(Menu, { size: 22, strokeWidth: 1.75 })
|
|
706
|
+
}
|
|
707
|
+
),
|
|
708
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
709
|
+
/* @__PURE__ */ jsx("img", { src: branding.logoIconSrc, alt: "", className: "h-[30px] w-[42px] shrink-0" }),
|
|
710
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 leading-none", children: [
|
|
711
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-base font-bold tracking-tight text-navy", children: branding.logoTitle }),
|
|
712
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase", children: branding.logoTagline })
|
|
713
|
+
] })
|
|
714
|
+
] })
|
|
715
|
+
]
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
function DashboardLayout({
|
|
720
|
+
sidebar,
|
|
721
|
+
header,
|
|
722
|
+
mobileHeader,
|
|
723
|
+
children,
|
|
724
|
+
className,
|
|
725
|
+
mainClassName
|
|
726
|
+
}) {
|
|
727
|
+
return /* @__PURE__ */ jsxs("div", { className: className ?? "flex min-h-screen bg-storm-gray-0", children: [
|
|
728
|
+
/* @__PURE__ */ jsx(Sidebar, { ...sidebar }),
|
|
729
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
730
|
+
/* @__PURE__ */ jsx(SidebarMobileHeader, { ...mobileHeader }),
|
|
731
|
+
/* @__PURE__ */ jsx(AppHeader, { ...header }),
|
|
732
|
+
/* @__PURE__ */ jsx("main", { className: mainClassName ?? "flex-1 overflow-auto p-4 md:p-6", children })
|
|
733
|
+
] })
|
|
734
|
+
] });
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export { AppHeader, BellIcon, BuildingIcon, BuildingSelect, DashboardLayout, Sidebar, SidebarMobileHeader, SidebarNavItem, SidebarUserCard, cn, useMediaQuery, useOnClickOutside };
|
|
738
|
+
//# sourceMappingURL=index.js.map
|
|
739
|
+
//# sourceMappingURL=index.js.map
|