@scripso-homepad/ui 0.3.9 → 0.4.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.
@@ -0,0 +1,1211 @@
1
+ 'use strict';
2
+
3
+ var lucideReact = require('lucide-react');
4
+ var react = require('react');
5
+ var reactNative = require('react-native');
6
+ var Svg = require('react-native-svg');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+ var clsx = require('clsx');
9
+ var tailwindMerge = require('tailwind-merge');
10
+ var reactRouterDom = require('react-router-dom');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var Svg__default = /*#__PURE__*/_interopDefault(Svg);
15
+
16
+ // src/web/layout/AppHeader.tsx
17
+
18
+ // src/icons/eyeIconPaths.ts
19
+ var EYE_OPEN_OUTLINE_PATH = "M1.71835 10.2898C1.6489 10.1027 1.6489 9.89691 1.71835 9.70981C2.39476 8.06969 3.54294 6.66735 5.01732 5.68056C6.4917 4.69378 8.22588 4.16699 10 4.16699C11.7741 4.16699 13.5083 4.69378 14.9827 5.68056C16.4571 6.66735 17.6053 8.06969 18.2817 9.70981C18.3511 9.89691 18.3511 10.1027 18.2817 10.2898C17.6053 11.9299 16.4571 13.3323 14.9827 14.3191C13.5083 15.3058 11.7741 15.8326 10 15.8326C8.22588 15.8326 6.4917 15.3058 5.01732 14.3191C3.54294 13.3323 2.39476 11.9299 1.71835 10.2898Z";
20
+ var EYE_OPEN_PUPIL_PATH = "M10 12.4998C11.3807 12.4998 12.5 11.3805 12.5 9.99981C12.5 8.6191 11.3807 7.49981 10 7.49981C8.6193 7.49981 7.50001 8.6191 7.50001 9.99981C7.50001 11.3805 8.6193 12.4998 10 12.4998Z";
21
+ var EYE_OFF_PATH = "M10.733 5.076C13.0624 4.7984 15.4186 5.29082 17.4419 6.47805C19.4651 7.66528 21.0442 9.48208 21.938 11.651C22.0213 11.8755 22.0213 12.1225 21.938 12.347C21.5705 13.238 21.0848 14.0755 20.494 14.837M14.084 14.158C13.5182 14.7045 12.7604 15.0069 11.9738 15C11.1872 14.9932 10.4348 14.6777 9.87854 14.1215C9.32232 13.5652 9.00681 12.8128 8.99998 12.0262C8.99314 11.2396 9.29553 10.4818 9.842 9.916M17.479 17.499C16.1525 18.2848 14.6725 18.776 13.1394 18.9394C11.6063 19.1028 10.056 18.9345 8.59363 18.4459C7.13131 17.9573 5.79119 17.1599 4.66421 16.1077C3.53723 15.0556 2.64975 13.7734 2.062 12.348C1.97866 12.1235 1.97866 11.8765 2.062 11.652C2.94863 9.50186 4.50867 7.69725 6.508 6.509M2 2L22 22";
22
+ function EyeIcon({
23
+ size = 20,
24
+ color = "#c7cdd1",
25
+ strokeWidth = 2
26
+ }) {
27
+ return /* @__PURE__ */ jsxRuntime.jsxs(Svg__default.default, { width: size, height: size, viewBox: "0 0 20 20", fill: "none", children: [
28
+ /* @__PURE__ */ jsxRuntime.jsx(
29
+ Svg.Path,
30
+ {
31
+ d: EYE_OPEN_OUTLINE_PATH,
32
+ stroke: color,
33
+ strokeWidth,
34
+ strokeLinecap: "round",
35
+ strokeLinejoin: "round"
36
+ }
37
+ ),
38
+ /* @__PURE__ */ jsxRuntime.jsx(
39
+ Svg.Path,
40
+ {
41
+ d: EYE_OPEN_PUPIL_PATH,
42
+ stroke: color,
43
+ strokeWidth,
44
+ strokeLinecap: "round",
45
+ strokeLinejoin: "round"
46
+ }
47
+ )
48
+ ] });
49
+ }
50
+ function EyeOffIcon({
51
+ size = 20,
52
+ color = "#c7cdd1",
53
+ strokeWidth = 2
54
+ }) {
55
+ return /* @__PURE__ */ jsxRuntime.jsx(Svg__default.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
56
+ Svg.Path,
57
+ {
58
+ d: EYE_OFF_PATH,
59
+ stroke: color,
60
+ strokeWidth,
61
+ strokeLinecap: "round",
62
+ strokeLinejoin: "round"
63
+ }
64
+ ) });
65
+ }
66
+
67
+ // src/theme/tokens.ts
68
+ var stormGray = {
69
+ "0.5": "#eceef0",
70
+ "1.5": "#c7cdd1",
71
+ "2": "#b5bcc1",
72
+ "3": "#8f9aa3",
73
+ "4": "#6a7984"};
74
+ var navy = {
75
+ "0.5": "#e5e9ef",
76
+ "5": "#08275d"};
77
+ var rubyRed = {
78
+ "0.5": "#f7dddf",
79
+ "5": "#ae0011"};
80
+ var brand = {
81
+ slateBlue: "#182e3c",
82
+ navy: navy["5"],
83
+ rubyRed: rubyRed["5"]};
84
+ var colors = {
85
+ // Brand colors
86
+ slateBlue: brand.slateBlue,
87
+ /** Primary brand navy — Figma Navy 5 */
88
+ navy: brand.navy,
89
+ stormGray50: stormGray["0.5"],
90
+ stormGray150: stormGray["1.5"],
91
+ stormGray200: stormGray["2"],
92
+ stormGray300: stormGray["3"],
93
+ stormGray400: stormGray["4"],
94
+ white: "#ffffff",
95
+ /** @deprecated Use `rubyRed` */
96
+ inputError: brand.rubyRed,
97
+ inputOutlineFocus: navy["0.5"],
98
+ inputOutlineError: rubyRed["0.5"],
99
+ transparent: "transparent"
100
+ };
101
+ var radii = {
102
+ lg: 16};
103
+ var spacing = {
104
+ sm: 8};
105
+ var fontSize = {
106
+ sm: 12,
107
+ md: 14};
108
+ var fontWeight = {
109
+ medium: "500"};
110
+ var fonts = {
111
+ sans: "Noto Sans Armenian"
112
+ };
113
+ var labelTypography = {
114
+ fontFamily: fonts.sans,
115
+ fontSize: fontSize.sm,
116
+ fontWeight: fontWeight.medium,
117
+ lineHeight: fontSize.sm,
118
+ letterSpacing: 0
119
+ };
120
+
121
+ // src/theme/input.ts
122
+ var INPUT_HEIGHT = 52;
123
+ var INPUT_ICON_SIZE = 20;
124
+ var INPUT_ICON_GAP = 10;
125
+ var INPUT_OUTLINE_WIDTH = 2;
126
+ function resolveInputVisualState({
127
+ focused = false,
128
+ error = false,
129
+ disabled = false
130
+ }) {
131
+ if (disabled) return "disabled";
132
+ if (error) return "error";
133
+ if (focused) return "focused";
134
+ return "default";
135
+ }
136
+ function createOutlineStyle(ringColor) {
137
+ return {
138
+ borderRadius: radii.lg + INPUT_OUTLINE_WIDTH,
139
+ borderWidth: INPUT_OUTLINE_WIDTH,
140
+ borderColor: ringColor,
141
+ padding: 0,
142
+ backgroundColor: colors.transparent,
143
+ width: "100%",
144
+ alignSelf: "stretch",
145
+ ...reactNative.Platform.OS !== "web" ? { overflow: "hidden" } : null
146
+ };
147
+ }
148
+ var defaultOutline = createOutlineStyle(colors.transparent);
149
+ function getInputFieldStyles(state) {
150
+ const containerBase = {
151
+ borderRadius: radii.lg,
152
+ ...reactNative.Platform.OS !== "web" ? { overflow: "hidden" } : null
153
+ };
154
+ switch (state) {
155
+ case "disabled":
156
+ return {
157
+ outline: defaultOutline,
158
+ container: {
159
+ ...containerBase,
160
+ borderWidth: 1,
161
+ borderColor: colors.stormGray50,
162
+ backgroundColor: colors.stormGray50
163
+ },
164
+ text: { color: colors.stormGray300 },
165
+ placeholder: colors.stormGray300,
166
+ icon: colors.stormGray150
167
+ };
168
+ case "error":
169
+ return {
170
+ outline: createOutlineStyle(colors.inputOutlineError),
171
+ container: {
172
+ ...containerBase,
173
+ borderWidth: 1,
174
+ borderColor: colors.inputError,
175
+ backgroundColor: colors.white
176
+ },
177
+ text: { color: colors.inputError },
178
+ placeholder: colors.stormGray200,
179
+ icon: colors.inputError
180
+ };
181
+ case "focused":
182
+ return {
183
+ outline: createOutlineStyle(colors.inputOutlineFocus),
184
+ container: {
185
+ ...containerBase,
186
+ borderWidth: 1,
187
+ borderColor: colors.navy,
188
+ backgroundColor: colors.white
189
+ },
190
+ text: { color: colors.slateBlue },
191
+ placeholder: colors.stormGray200,
192
+ icon: colors.navy
193
+ };
194
+ default:
195
+ return {
196
+ outline: defaultOutline,
197
+ container: {
198
+ ...containerBase,
199
+ borderWidth: 1,
200
+ borderColor: colors.stormGray50,
201
+ backgroundColor: colors.white
202
+ },
203
+ text: { color: colors.slateBlue },
204
+ placeholder: colors.stormGray200,
205
+ icon: colors.stormGray150
206
+ };
207
+ }
208
+ }
209
+ var inputFieldMetrics = reactNative.StyleSheet.create({
210
+ container: {
211
+ flexDirection: "row",
212
+ alignItems: "center",
213
+ height: INPUT_HEIGHT,
214
+ minHeight: INPUT_HEIGHT,
215
+ maxHeight: INPUT_HEIGHT,
216
+ borderRadius: radii.lg,
217
+ padding: 16,
218
+ gap: INPUT_ICON_GAP,
219
+ ...reactNative.Platform.OS === "web" ? { boxSizing: "border-box" } : null
220
+ },
221
+ input: {
222
+ flex: 1,
223
+ alignSelf: "stretch",
224
+ fontFamily: fonts.sans,
225
+ fontSize: fontSize.md,
226
+ fontWeight: fontWeight.medium,
227
+ lineHeight: reactNative.Platform.OS === "web" ? fontSize.md : 20,
228
+ paddingVertical: 0,
229
+ paddingHorizontal: 0,
230
+ margin: 0,
231
+ borderWidth: 0,
232
+ backgroundColor: colors.transparent,
233
+ ...reactNative.Platform.OS === "android" ? { includeFontPadding: false } : null,
234
+ ...reactNative.Platform.OS === "web" ? {
235
+ height: "100%",
236
+ minHeight: 0,
237
+ outlineStyle: "none"
238
+ } : null
239
+ }
240
+ });
241
+ function hasClassList(node) {
242
+ return typeof node === "object" && node !== null && "classList" in node && typeof node.classList?.add === "function";
243
+ }
244
+ function resolveWebElement(ref) {
245
+ const node = ref.current;
246
+ if (!node) return null;
247
+ if (hasClassList(node)) return node;
248
+ const host = node;
249
+ if (hasClassList(host._touchableNode)) return host._touchableNode;
250
+ if (typeof host.getScrollableNode === "function") {
251
+ const scrollNode = host.getScrollableNode();
252
+ if (hasClassList(scrollNode)) return scrollNode;
253
+ }
254
+ return null;
255
+ }
256
+ function useApplyWebClassName(ref, className, enabled = true) {
257
+ react.useLayoutEffect(() => {
258
+ if (!enabled || reactNative.Platform.OS !== "web" || !className?.trim()) return;
259
+ const element = resolveWebElement(ref);
260
+ if (!element) return;
261
+ const classes = className.trim().split(/\s+/);
262
+ element.classList.add(...classes);
263
+ return () => {
264
+ element.classList.remove(...classes);
265
+ };
266
+ }, [ref, className, enabled]);
267
+ }
268
+ function Label({
269
+ children,
270
+ required = false,
271
+ disabled = false,
272
+ style,
273
+ className,
274
+ ...props
275
+ }) {
276
+ const ref = react.useRef(null);
277
+ useApplyWebClassName(ref, className);
278
+ return /* @__PURE__ */ jsxRuntime.jsxs(
279
+ reactNative.Text,
280
+ {
281
+ ref,
282
+ style: [styles.label, disabled && styles.labelDisabled, style],
283
+ accessibilityRole: "text",
284
+ ...props,
285
+ children: [
286
+ children,
287
+ required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.required, children: " *" }) : null
288
+ ]
289
+ }
290
+ );
291
+ }
292
+ var styles = reactNative.StyleSheet.create({
293
+ label: {
294
+ ...labelTypography,
295
+ color: colors.slateBlue
296
+ },
297
+ labelDisabled: {
298
+ color: colors.stormGray400
299
+ },
300
+ required: {
301
+ color: colors.inputError
302
+ }
303
+ });
304
+ function renderInputIcon(icon, color) {
305
+ if (!icon) return null;
306
+ if (react.isValidElement(icon)) {
307
+ return react.cloneElement(icon, {
308
+ size: icon.props.size ?? INPUT_ICON_SIZE,
309
+ color: icon.props.color ?? color
310
+ });
311
+ }
312
+ return icon;
313
+ }
314
+ function Input({
315
+ label,
316
+ leftIcon,
317
+ rightIcon,
318
+ error,
319
+ hint,
320
+ containerStyle,
321
+ style,
322
+ className,
323
+ fieldClassName,
324
+ fieldStyle,
325
+ labelClassName,
326
+ inputClassName,
327
+ errorClassName,
328
+ hintClassName,
329
+ editable = true,
330
+ secureTextEntry,
331
+ showPasswordToggle,
332
+ onFocus,
333
+ onBlur,
334
+ ...props
335
+ }) {
336
+ const wrapperRef = react.useRef(null);
337
+ const fieldRef = react.useRef(null);
338
+ const inputRef = react.useRef(null);
339
+ const helperRef = react.useRef(null);
340
+ const [focused, setFocused] = react.useState(false);
341
+ const [passwordVisible, setPasswordVisible] = react.useState(false);
342
+ useApplyWebClassName(wrapperRef, className);
343
+ useApplyWebClassName(fieldRef, fieldClassName);
344
+ useApplyWebClassName(inputRef, inputClassName);
345
+ useApplyWebClassName(helperRef, error ? errorClassName : hintClassName);
346
+ const isDisabled = editable === false;
347
+ const passwordToggleEnabled = secureTextEntry === true && showPasswordToggle !== false && rightIcon == null;
348
+ const effectiveSecureTextEntry = passwordToggleEnabled ? !passwordVisible : secureTextEntry;
349
+ const visualState = resolveInputVisualState({
350
+ focused,
351
+ error: Boolean(error),
352
+ disabled: isDisabled
353
+ });
354
+ const fieldStyles = getInputFieldStyles(visualState);
355
+ const iconColor = fieldStyles.icon;
356
+ function handleFocus(event) {
357
+ setFocused(true);
358
+ onFocus?.(event);
359
+ }
360
+ function handleBlur(event) {
361
+ setFocused(false);
362
+ onBlur?.(event);
363
+ }
364
+ const helperMessage = error ?? hint;
365
+ function togglePasswordVisibility() {
366
+ if (!isDisabled) {
367
+ setPasswordVisible((visible) => !visible);
368
+ }
369
+ }
370
+ const trailingIcon = passwordToggleEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
371
+ reactNative.Pressable,
372
+ {
373
+ onPress: togglePasswordVisibility,
374
+ disabled: isDisabled,
375
+ accessibilityRole: "button",
376
+ accessibilityLabel: passwordVisible ? "Hide password" : "Show password",
377
+ hitSlop: 8,
378
+ style: styles2.iconPressable,
379
+ children: renderInputIcon(
380
+ passwordVisible ? /* @__PURE__ */ jsxRuntime.jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(EyeIcon, {}),
381
+ iconColor
382
+ )
383
+ }
384
+ ) : rightIcon ? renderInputIcon(rightIcon, iconColor) : null;
385
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ref: wrapperRef, style: [styles2.wrapper, containerStyle], children: [
386
+ label ? /* @__PURE__ */ jsxRuntime.jsx(Label, { disabled: isDisabled, className: labelClassName, children: label }) : null,
387
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: fieldStyles.outline, children: /* @__PURE__ */ jsxRuntime.jsxs(
388
+ reactNative.View,
389
+ {
390
+ ref: fieldRef,
391
+ style: [inputFieldMetrics.container, fieldStyles.container, fieldStyle],
392
+ children: [
393
+ leftIcon ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconSlot, children: renderInputIcon(leftIcon, iconColor) }) : null,
394
+ /* @__PURE__ */ jsxRuntime.jsx(
395
+ reactNative.TextInput,
396
+ {
397
+ ref: inputRef,
398
+ style: [
399
+ inputFieldMetrics.input,
400
+ fieldStyles.text,
401
+ style
402
+ ],
403
+ placeholderTextColor: fieldStyles.placeholder,
404
+ editable,
405
+ secureTextEntry: effectiveSecureTextEntry,
406
+ onFocus: handleFocus,
407
+ onBlur: handleBlur,
408
+ accessibilityState: { disabled: isDisabled },
409
+ ...props
410
+ }
411
+ ),
412
+ trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconSlot, children: trailingIcon }) : null
413
+ ]
414
+ }
415
+ ) }),
416
+ helperMessage ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { ref: helperRef, style: error ? styles2.error : styles2.hint, children: helperMessage }) : null
417
+ ] });
418
+ }
419
+ var styles2 = reactNative.StyleSheet.create({
420
+ wrapper: {
421
+ width: "100%",
422
+ gap: spacing.sm
423
+ },
424
+ iconSlot: {
425
+ width: INPUT_ICON_SIZE,
426
+ height: INPUT_ICON_SIZE,
427
+ alignItems: "center",
428
+ justifyContent: "center",
429
+ flexShrink: 0
430
+ },
431
+ iconPressable: {
432
+ width: INPUT_ICON_SIZE,
433
+ height: INPUT_ICON_SIZE,
434
+ alignItems: "center",
435
+ justifyContent: "center"
436
+ },
437
+ error: {
438
+ fontSize: 12,
439
+ lineHeight: 16,
440
+ color: colors.inputError
441
+ },
442
+ hint: {
443
+ fontSize: 12,
444
+ lineHeight: 16,
445
+ color: colors.stormGray300
446
+ }
447
+ });
448
+ function cn(...inputs) {
449
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
450
+ }
451
+ function BellIcon({ className }) {
452
+ return /* @__PURE__ */ jsxRuntime.jsx(
453
+ "svg",
454
+ {
455
+ width: "20",
456
+ height: "20",
457
+ viewBox: "0 0 20 20",
458
+ fill: "none",
459
+ xmlns: "http://www.w3.org/2000/svg",
460
+ "aria-hidden": true,
461
+ className: cn("shrink-0", className),
462
+ children: /* @__PURE__ */ jsxRuntime.jsx(
463
+ "path",
464
+ {
465
+ 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",
466
+ stroke: "currentColor",
467
+ strokeWidth: "2",
468
+ strokeLinecap: "round",
469
+ strokeLinejoin: "round"
470
+ }
471
+ )
472
+ }
473
+ );
474
+ }
475
+ function BuildingIcon({ className }) {
476
+ return /* @__PURE__ */ jsxRuntime.jsxs(
477
+ "svg",
478
+ {
479
+ width: "20",
480
+ height: "20",
481
+ viewBox: "0 0 24 24",
482
+ fill: "none",
483
+ xmlns: "http://www.w3.org/2000/svg",
484
+ "aria-hidden": true,
485
+ className: cn("shrink-0", className),
486
+ children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx(
488
+ "path",
489
+ {
490
+ d: "M10 12h4",
491
+ stroke: "currentColor",
492
+ strokeWidth: "2",
493
+ strokeLinecap: "round",
494
+ strokeLinejoin: "round"
495
+ }
496
+ ),
497
+ /* @__PURE__ */ jsxRuntime.jsx(
498
+ "path",
499
+ {
500
+ d: "M10 8h4",
501
+ stroke: "currentColor",
502
+ strokeWidth: "2",
503
+ strokeLinecap: "round",
504
+ strokeLinejoin: "round"
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsxRuntime.jsx(
508
+ "path",
509
+ {
510
+ d: "M14 21v-3a2 2 0 0 0-4 0v3",
511
+ stroke: "currentColor",
512
+ strokeWidth: "2",
513
+ strokeLinecap: "round",
514
+ strokeLinejoin: "round"
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsxRuntime.jsx(
518
+ "path",
519
+ {
520
+ 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",
521
+ stroke: "currentColor",
522
+ strokeWidth: "2",
523
+ strokeLinecap: "round",
524
+ strokeLinejoin: "round"
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "path",
529
+ {
530
+ d: "M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16",
531
+ stroke: "currentColor",
532
+ strokeWidth: "2",
533
+ strokeLinecap: "round",
534
+ strokeLinejoin: "round"
535
+ }
536
+ )
537
+ ]
538
+ }
539
+ );
540
+ }
541
+ function useOnClickOutside(ref, handler, enabled = true) {
542
+ react.useEffect(() => {
543
+ if (!enabled) {
544
+ return;
545
+ }
546
+ const onPointerDown = (event) => {
547
+ const target = event.target;
548
+ if (!ref.current || !target || ref.current.contains(target)) {
549
+ return;
550
+ }
551
+ handler();
552
+ };
553
+ document.addEventListener("mousedown", onPointerDown);
554
+ document.addEventListener("touchstart", onPointerDown);
555
+ return () => {
556
+ document.removeEventListener("mousedown", onPointerDown);
557
+ document.removeEventListener("touchstart", onPointerDown);
558
+ };
559
+ }, [enabled, handler, ref]);
560
+ }
561
+ function BuildingSelect({
562
+ options,
563
+ value,
564
+ labels,
565
+ onChange,
566
+ disabled = false,
567
+ className,
568
+ menuClassName,
569
+ buildingIcon,
570
+ chevronIcon
571
+ }) {
572
+ const [open, setOpen] = react.useState(false);
573
+ const [highlightedIndex, setHighlightedIndex] = react.useState(-1);
574
+ const rootRef = react.useRef(null);
575
+ const listboxId = react.useId();
576
+ const selectedOption = options.find((option) => option.value === value);
577
+ const displayLabel = selectedOption?.label ?? labels.selectBuilding;
578
+ const selectableOptions = options.filter((option) => !option.disabled);
579
+ const closeMenu = react.useCallback(() => {
580
+ setOpen(false);
581
+ setHighlightedIndex(-1);
582
+ }, []);
583
+ const openMenu = react.useCallback(() => {
584
+ if (disabled || selectableOptions.length === 0) {
585
+ return;
586
+ }
587
+ const selectedIndex = selectableOptions.findIndex((option) => option.value === value);
588
+ setHighlightedIndex(selectedIndex >= 0 ? selectedIndex : 0);
589
+ setOpen(true);
590
+ }, [disabled, selectableOptions, value]);
591
+ const selectOption = react.useCallback(
592
+ (option) => {
593
+ if (option.disabled) {
594
+ return;
595
+ }
596
+ onChange?.(option.value);
597
+ closeMenu();
598
+ },
599
+ [closeMenu, onChange]
600
+ );
601
+ useOnClickOutside(rootRef, closeMenu, open);
602
+ const handleTriggerKeyDown = (event) => {
603
+ if (disabled) {
604
+ return;
605
+ }
606
+ switch (event.key) {
607
+ case "ArrowDown":
608
+ case "Enter":
609
+ case " ":
610
+ event.preventDefault();
611
+ openMenu();
612
+ break;
613
+ case "Escape":
614
+ closeMenu();
615
+ break;
616
+ }
617
+ };
618
+ const handleListKeyDown = (event) => {
619
+ if (!open || selectableOptions.length === 0) {
620
+ return;
621
+ }
622
+ switch (event.key) {
623
+ case "ArrowDown":
624
+ event.preventDefault();
625
+ setHighlightedIndex((current) => (current + 1) % selectableOptions.length);
626
+ break;
627
+ case "ArrowUp":
628
+ event.preventDefault();
629
+ setHighlightedIndex(
630
+ (current) => (current - 1 + selectableOptions.length) % selectableOptions.length
631
+ );
632
+ break;
633
+ case "Enter":
634
+ case " ":
635
+ event.preventDefault();
636
+ if (highlightedIndex >= 0) {
637
+ selectOption(selectableOptions[highlightedIndex]);
638
+ }
639
+ break;
640
+ case "Escape":
641
+ event.preventDefault();
642
+ closeMenu();
643
+ break;
644
+ case "Tab":
645
+ closeMenu();
646
+ break;
647
+ }
648
+ };
649
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn("relative w-57.5", className), children: [
650
+ /* @__PURE__ */ jsxRuntime.jsxs(
651
+ "button",
652
+ {
653
+ type: "button",
654
+ disabled,
655
+ "aria-haspopup": "listbox",
656
+ "aria-expanded": open,
657
+ "aria-controls": listboxId,
658
+ "aria-label": labels.selectBuilding,
659
+ onClick: () => open ? closeMenu() : openMenu(),
660
+ onKeyDown: handleTriggerKeyDown,
661
+ className: cn(
662
+ "flex h-11 w-full items-center gap-3 rounded-xl border border-storm-gray-50 bg-white p-3 text-left transition-colors",
663
+ !disabled && "cursor-pointer hover:bg-storm-gray-50/60",
664
+ open && "bg-storm-gray-50/60",
665
+ disabled && "cursor-not-allowed opacity-60"
666
+ ),
667
+ children: [
668
+ buildingIcon ?? /* @__PURE__ */ jsxRuntime.jsx(BuildingIcon, { className: "shrink-0 text-navy" }),
669
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-slate-blue", children: displayLabel }),
670
+ chevronIcon ?? /* @__PURE__ */ jsxRuntime.jsx(
671
+ lucideReact.ChevronsUpDown,
672
+ {
673
+ size: 16,
674
+ strokeWidth: 1.75,
675
+ className: cn(
676
+ "shrink-0 text-storm-gray-100 transition-transform duration-200",
677
+ open && "rotate-180"
678
+ ),
679
+ "aria-hidden": true
680
+ }
681
+ )
682
+ ]
683
+ }
684
+ ),
685
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
686
+ "ul",
687
+ {
688
+ id: listboxId,
689
+ role: "listbox",
690
+ "aria-label": labels.selectBuilding,
691
+ tabIndex: -1,
692
+ onKeyDown: handleListKeyDown,
693
+ className: cn(
694
+ "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)]",
695
+ menuClassName
696
+ ),
697
+ children: options.map((option) => {
698
+ const selectableIndex = selectableOptions.findIndex(
699
+ (selectable) => selectable.value === option.value
700
+ );
701
+ const isSelected = option.value === value;
702
+ const isHighlighted = selectableIndex === highlightedIndex;
703
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ role: "option",
708
+ "aria-selected": isSelected,
709
+ disabled: option.disabled,
710
+ onMouseEnter: () => {
711
+ if (!option.disabled && selectableIndex >= 0) {
712
+ setHighlightedIndex(selectableIndex);
713
+ }
714
+ },
715
+ onClick: () => selectOption(option),
716
+ className: cn(
717
+ "flex w-full rounded-lg px-3 py-2.5 text-left text-sm font-medium text-slate-blue transition-colors",
718
+ !option.disabled && "cursor-pointer hover:bg-storm-gray-50",
719
+ (isSelected || isHighlighted) && !option.disabled && "bg-storm-gray-50",
720
+ option.disabled && "cursor-not-allowed opacity-50"
721
+ ),
722
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
723
+ }
724
+ ) }, option.value);
725
+ })
726
+ }
727
+ ) : null
728
+ ] });
729
+ }
730
+ var defaultSearchFieldStyle = {
731
+ height: 44,
732
+ minHeight: 44,
733
+ maxHeight: 44,
734
+ backgroundColor: "transparent",
735
+ padding: 12,
736
+ width: 260,
737
+ borderRadius: 12
738
+ };
739
+ function AppHeader({
740
+ title,
741
+ labels,
742
+ buildingOptions,
743
+ selectedBuildingId,
744
+ className,
745
+ buildingSelectClassName,
746
+ searchClassName,
747
+ notificationsButtonClassName,
748
+ searchValue,
749
+ searchFieldStyle,
750
+ buildingIcon,
751
+ notificationsIcon,
752
+ searchIcon,
753
+ buildingChevronIcon,
754
+ buildingSelectDisabled = false,
755
+ searchDisabled = false,
756
+ notificationsDisabled = false,
757
+ onBuildingChange,
758
+ onNotificationsClick,
759
+ onSearchChange
760
+ }) {
761
+ return /* @__PURE__ */ jsxRuntime.jsxs(
762
+ "header",
763
+ {
764
+ className: cn(
765
+ "flex shrink-0 items-center justify-between gap-4 border-b border-storm-gray-50 bg-storm-gray-0 px-6 py-3",
766
+ className
767
+ ),
768
+ children: [
769
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "truncate text-xl font-bold text-slate-blue", children: title }),
770
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
771
+ /* @__PURE__ */ jsxRuntime.jsx(
772
+ BuildingSelect,
773
+ {
774
+ options: buildingOptions,
775
+ value: selectedBuildingId,
776
+ labels,
777
+ onChange: onBuildingChange,
778
+ disabled: buildingSelectDisabled,
779
+ className: buildingSelectClassName,
780
+ buildingIcon,
781
+ chevronIcon: buildingChevronIcon
782
+ }
783
+ ),
784
+ /* @__PURE__ */ jsxRuntime.jsx(
785
+ Input,
786
+ {
787
+ leftIcon: searchIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { strokeWidth: 1.75 }),
788
+ placeholder: labels.searchPlaceholder,
789
+ value: searchValue,
790
+ onChangeText: (value) => onSearchChange?.(value),
791
+ editable: !searchDisabled,
792
+ className: cn("w-65!", searchClassName),
793
+ containerStyle: { width: 260, maxWidth: 260, flexShrink: 0 },
794
+ fieldStyle: searchFieldStyle ?? defaultSearchFieldStyle,
795
+ accessibilityLabel: labels.searchPlaceholder
796
+ }
797
+ ),
798
+ /* @__PURE__ */ jsxRuntime.jsx(
799
+ "button",
800
+ {
801
+ type: "button",
802
+ onClick: onNotificationsClick,
803
+ disabled: notificationsDisabled,
804
+ className: cn(
805
+ "h-11 cursor-pointer rounded-xl border border-storm-gray-50 p-3 text-slate-blue transition-colors hover:bg-storm-gray-50/60",
806
+ notificationsDisabled && "pointer-events-none opacity-60",
807
+ notificationsButtonClassName
808
+ ),
809
+ "aria-label": labels.notifications,
810
+ children: notificationsIcon ?? /* @__PURE__ */ jsxRuntime.jsx(BellIcon, { className: "text-navy" })
811
+ }
812
+ )
813
+ ] })
814
+ ]
815
+ }
816
+ );
817
+ }
818
+ function useMediaQuery(query) {
819
+ const [matches, setMatches] = react.useState(() => {
820
+ if (typeof window === "undefined") {
821
+ return false;
822
+ }
823
+ return window.matchMedia(query).matches;
824
+ });
825
+ react.useEffect(() => {
826
+ const mediaQuery = window.matchMedia(query);
827
+ const handleChange = () => setMatches(mediaQuery.matches);
828
+ handleChange();
829
+ mediaQuery.addEventListener("change", handleChange);
830
+ return () => mediaQuery.removeEventListener("change", handleChange);
831
+ }, [query]);
832
+ return matches;
833
+ }
834
+ function SidebarNavItem({ item, isOpen, onNavigate }) {
835
+ if (item.hidden) {
836
+ return null;
837
+ }
838
+ const handleClick = () => {
839
+ item.onClick?.();
840
+ onNavigate?.(item);
841
+ };
842
+ return /* @__PURE__ */ jsxRuntime.jsx(
843
+ reactRouterDom.NavLink,
844
+ {
845
+ to: item.to,
846
+ end: item.end,
847
+ onClick: handleClick,
848
+ "aria-disabled": item.disabled,
849
+ tabIndex: item.disabled ? -1 : void 0,
850
+ className: ({ isActive }) => cn(
851
+ "relative flex items-center rounded-xl px-4 py-3 transition-[background-color,color,gap,padding] duration-300 ease-in-out",
852
+ isOpen ? "w-full gap-4" : "justify-center gap-0 px-3",
853
+ item.disabled && "pointer-events-none opacity-50",
854
+ isActive ? "bg-black/12 text-white" : "text-navy-100 hover:bg-white/5"
855
+ ),
856
+ children: ({ isActive }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
857
+ /* @__PURE__ */ jsxRuntime.jsx(
858
+ "span",
859
+ {
860
+ "aria-hidden": true,
861
+ className: cn(
862
+ "absolute top-1.5 -left-1 h-8 w-2 rounded-full bg-white transition-[opacity,transform] duration-300 ease-in-out",
863
+ isActive ? "scale-100 opacity-100" : "scale-75 opacity-0"
864
+ )
865
+ }
866
+ ),
867
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-5 shrink-0 items-center justify-center", children: item.icon }),
868
+ /* @__PURE__ */ jsxRuntime.jsx(
869
+ "span",
870
+ {
871
+ className: cn(
872
+ "overflow-hidden text-sm font-semibold whitespace-nowrap transition-[max-width,opacity] duration-300 ease-in-out",
873
+ isOpen ? "max-w-[180px] opacity-100" : "max-w-0 opacity-0"
874
+ ),
875
+ children: item.label
876
+ }
877
+ )
878
+ ] })
879
+ }
880
+ );
881
+ }
882
+ function getInitials(fullName, email) {
883
+ const parts = fullName.trim().split(/\s+/).filter(Boolean);
884
+ if (parts.length > 0) {
885
+ return parts.slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
886
+ }
887
+ return email.slice(0, 2).toUpperCase();
888
+ }
889
+ function SidebarUserCard({
890
+ user,
891
+ isOpen,
892
+ logoutLabel,
893
+ onLogout,
894
+ className
895
+ }) {
896
+ const initials = user.initials ?? getInitials(user.fullName, user.email);
897
+ return /* @__PURE__ */ jsxRuntime.jsxs(
898
+ "div",
899
+ {
900
+ className: cn(
901
+ "flex w-full items-center rounded-xl p-3 transition-[background-color,justify-content,gap] duration-300 ease-in-out",
902
+ isOpen ? "justify-between gap-3 bg-black/12" : "justify-center",
903
+ className
904
+ ),
905
+ children: [
906
+ /* @__PURE__ */ jsxRuntime.jsxs(
907
+ "div",
908
+ {
909
+ className: cn(
910
+ "flex min-w-0 items-center transition-[gap,flex] duration-300 ease-in-out",
911
+ isOpen ? "flex-1 gap-3" : "gap-0"
912
+ ),
913
+ children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx(
915
+ "div",
916
+ {
917
+ className: "flex size-11 shrink-0 items-center justify-center rounded-full bg-white/5 text-sm font-bold text-white",
918
+ "aria-hidden": true,
919
+ children: initials
920
+ }
921
+ ),
922
+ /* @__PURE__ */ jsxRuntime.jsxs(
923
+ "div",
924
+ {
925
+ className: cn(
926
+ "min-w-0 overflow-hidden transition-[max-width,opacity] duration-300 ease-in-out",
927
+ isOpen ? "max-w-[160px] opacity-100" : "max-w-0 opacity-0"
928
+ ),
929
+ children: [
930
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-bold text-white", children: user.fullName }),
931
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-storm-gray-100", children: user.email })
932
+ ]
933
+ }
934
+ )
935
+ ]
936
+ }
937
+ ),
938
+ /* @__PURE__ */ jsxRuntime.jsx(
939
+ "button",
940
+ {
941
+ type: "button",
942
+ onClick: () => onLogout?.(),
943
+ className: cn(
944
+ "shrink-0 cursor-pointer text-navy-150 transition-[opacity,transform,max-width] duration-300 ease-in-out hover:opacity-80 active:scale-95",
945
+ isOpen ? "max-w-8 scale-100 opacity-100" : "pointer-events-none max-w-0 scale-90 opacity-0"
946
+ ),
947
+ "aria-label": logoutLabel,
948
+ tabIndex: isOpen ? 0 : -1,
949
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LogOut, { size: 20, strokeWidth: 1.75 })
950
+ }
951
+ )
952
+ ]
953
+ }
954
+ );
955
+ }
956
+ function readInitialCollapsedState(storageKey, initialCollapsed) {
957
+ if (initialCollapsed !== void 0) {
958
+ return initialCollapsed;
959
+ }
960
+ if (typeof window === "undefined") {
961
+ return false;
962
+ }
963
+ const stored = window.localStorage.getItem(storageKey);
964
+ if (stored === null) {
965
+ return false;
966
+ }
967
+ return stored !== "true";
968
+ }
969
+ function Sidebar({
970
+ navItems,
971
+ footerNavItems,
972
+ branding,
973
+ labels,
974
+ sidebarStorageKey,
975
+ user,
976
+ onLogout,
977
+ className,
978
+ mobileOpen,
979
+ onMobileClose,
980
+ onNavItemClick,
981
+ collapseIcon,
982
+ expandIcon,
983
+ closeIcon,
984
+ initialCollapsed,
985
+ collapsed,
986
+ onCollapsedChange
987
+ }) {
988
+ const location = reactRouterDom.useLocation();
989
+ const isDesktop = useMediaQuery("(min-width: 768px)");
990
+ const [internalCollapsed, setInternalCollapsed] = react.useState(
991
+ () => readInitialCollapsedState(sidebarStorageKey, initialCollapsed)
992
+ );
993
+ const isCollapsed = collapsed ?? internalCollapsed;
994
+ const isExpanded = isDesktop ? !isCollapsed : true;
995
+ const setCollapsed = react.useCallback(
996
+ (next) => {
997
+ if (collapsed === void 0) {
998
+ setInternalCollapsed(next);
999
+ window.localStorage.setItem(sidebarStorageKey, String(!next));
1000
+ }
1001
+ onCollapsedChange?.(next);
1002
+ },
1003
+ [collapsed, onCollapsedChange, sidebarStorageKey]
1004
+ );
1005
+ const toggleCollapsed = react.useCallback(() => {
1006
+ setCollapsed(!isCollapsed);
1007
+ }, [isCollapsed, setCollapsed]);
1008
+ const handleHeaderAction = react.useCallback(() => {
1009
+ if (isDesktop) {
1010
+ toggleCollapsed();
1011
+ return;
1012
+ }
1013
+ onMobileClose?.();
1014
+ }, [isDesktop, onMobileClose, toggleCollapsed]);
1015
+ const handleNavItemNavigate = react.useCallback(
1016
+ (item) => {
1017
+ onNavItemClick?.(item);
1018
+ if (!isDesktop) {
1019
+ onMobileClose?.();
1020
+ }
1021
+ },
1022
+ [isDesktop, onMobileClose, onNavItemClick]
1023
+ );
1024
+ const pathnameRef = react.useRef(location.pathname);
1025
+ react.useEffect(() => {
1026
+ if (pathnameRef.current === location.pathname) {
1027
+ return;
1028
+ }
1029
+ pathnameRef.current = location.pathname;
1030
+ if (!isDesktop) {
1031
+ onMobileClose?.();
1032
+ }
1033
+ }, [location.pathname, isDesktop, onMobileClose]);
1034
+ react.useEffect(() => {
1035
+ if (!mobileOpen || isDesktop) {
1036
+ return;
1037
+ }
1038
+ const previousOverflow = document.body.style.overflow;
1039
+ document.body.style.overflow = "hidden";
1040
+ return () => {
1041
+ document.body.style.overflow = previousOverflow;
1042
+ };
1043
+ }, [mobileOpen, isDesktop]);
1044
+ const visibleNavItems = navItems.filter((item) => !item.hidden);
1045
+ const visibleFooterNavItems = footerNavItems.filter((item) => !item.hidden);
1046
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1047
+ /* @__PURE__ */ jsxRuntime.jsx(
1048
+ "button",
1049
+ {
1050
+ type: "button",
1051
+ "aria-label": labels.closeMenu,
1052
+ onClick: onMobileClose,
1053
+ className: cn(
1054
+ "fixed inset-0 z-40 bg-navy-800/60 backdrop-blur-[1px] transition-opacity duration-300 ease-in-out md:hidden",
1055
+ mobileOpen ? "opacity-100" : "pointer-events-none opacity-0"
1056
+ )
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsxRuntime.jsxs(
1060
+ "aside",
1061
+ {
1062
+ className: cn(
1063
+ "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",
1064
+ "transition-[transform,width,padding,gap] duration-300 ease-in-out will-change-[transform,width]",
1065
+ mobileOpen ? "translate-x-0" : "-translate-x-full",
1066
+ "md:relative md:z-auto md:w-[300px] md:translate-x-0 md:shadow-none",
1067
+ isExpanded ? "md:w-[300px]" : "md:w-[52px] md:items-center md:gap-4 md:px-3 md:py-4",
1068
+ className
1069
+ ),
1070
+ "aria-hidden": !isDesktop && !mobileOpen,
1071
+ children: [
1072
+ /* @__PURE__ */ jsxRuntime.jsxs(
1073
+ "div",
1074
+ {
1075
+ className: cn(
1076
+ "flex w-full items-center transition-[justify-content] duration-300 ease-in-out",
1077
+ isExpanded ? "justify-between" : "justify-center"
1078
+ ),
1079
+ children: [
1080
+ /* @__PURE__ */ jsxRuntime.jsxs(
1081
+ "div",
1082
+ {
1083
+ className: cn(
1084
+ "flex min-w-0 items-center gap-1.5 overflow-hidden transition-[max-width,opacity] duration-300 ease-in-out",
1085
+ isExpanded ? "max-w-[240px] opacity-100" : "max-w-0 opacity-0"
1086
+ ),
1087
+ children: [
1088
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: branding.logoIconSrc, alt: "", className: "h-5 w-[29px] shrink-0" }),
1089
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col leading-none", children: [
1090
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[21px] font-bold tracking-tight whitespace-nowrap text-white", children: branding.logoTitle }),
1091
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase", children: branding.logoTagline })
1092
+ ] })
1093
+ ]
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsxRuntime.jsx(
1097
+ "button",
1098
+ {
1099
+ type: "button",
1100
+ onClick: handleHeaderAction,
1101
+ className: "shrink-0 cursor-pointer text-white transition-[opacity,transform] duration-200 hover:opacity-80 active:scale-95",
1102
+ "aria-label": isDesktop ? isExpanded ? labels.collapse : labels.expand : labels.closeMenu,
1103
+ children: isDesktop ? isExpanded ? collapseIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftClose, { size: 20, strokeWidth: 1.75 }) : expandIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 20, strokeWidth: 1.75 }) : closeIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 20, strokeWidth: 1.75 })
1104
+ }
1105
+ )
1106
+ ]
1107
+ }
1108
+ ),
1109
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden", children: visibleNavItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1110
+ SidebarNavItem,
1111
+ {
1112
+ item,
1113
+ isOpen: isExpanded,
1114
+ onNavigate: handleNavItemNavigate
1115
+ },
1116
+ item.to
1117
+ )) }),
1118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-4", children: [
1119
+ visibleFooterNavItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1120
+ SidebarNavItem,
1121
+ {
1122
+ item,
1123
+ isOpen: isExpanded,
1124
+ onNavigate: handleNavItemNavigate
1125
+ },
1126
+ item.to
1127
+ )),
1128
+ user ? /* @__PURE__ */ jsxRuntime.jsx(
1129
+ SidebarUserCard,
1130
+ {
1131
+ user,
1132
+ isOpen: isExpanded,
1133
+ logoutLabel: labels.logout,
1134
+ onLogout
1135
+ }
1136
+ ) : null
1137
+ ] })
1138
+ ]
1139
+ }
1140
+ )
1141
+ ] });
1142
+ }
1143
+ function SidebarMobileHeader({
1144
+ branding,
1145
+ openMenuLabel,
1146
+ onOpenMenu,
1147
+ menuOpen = false,
1148
+ className
1149
+ }) {
1150
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1151
+ "header",
1152
+ {
1153
+ className: cn(
1154
+ "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",
1155
+ className
1156
+ ),
1157
+ children: [
1158
+ /* @__PURE__ */ jsxRuntime.jsx(
1159
+ "button",
1160
+ {
1161
+ type: "button",
1162
+ onClick: onOpenMenu,
1163
+ className: "flex size-10 items-center justify-center rounded-xl text-navy transition-colors hover:bg-storm-gray-50",
1164
+ "aria-label": openMenuLabel,
1165
+ "aria-expanded": menuOpen,
1166
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Menu, { size: 22, strokeWidth: 1.75 })
1167
+ }
1168
+ ),
1169
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
1170
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: branding.logoIconSrc, alt: "", className: "h-[30px] w-[42px] shrink-0" }),
1171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 leading-none", children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-base font-bold tracking-tight text-navy", children: branding.logoTitle }),
1173
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase", children: branding.logoTagline })
1174
+ ] })
1175
+ ] })
1176
+ ]
1177
+ }
1178
+ );
1179
+ }
1180
+ function DashboardLayout({
1181
+ sidebar,
1182
+ header,
1183
+ mobileHeader,
1184
+ children,
1185
+ className,
1186
+ mainClassName
1187
+ }) {
1188
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: className ?? "flex min-h-screen bg-storm-gray-0", children: [
1189
+ /* @__PURE__ */ jsxRuntime.jsx(Sidebar, { ...sidebar }),
1190
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
1191
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarMobileHeader, { ...mobileHeader }),
1192
+ /* @__PURE__ */ jsxRuntime.jsx(AppHeader, { ...header }),
1193
+ /* @__PURE__ */ jsxRuntime.jsx("main", { className: mainClassName ?? "flex-1 overflow-auto p-4 md:p-6", children })
1194
+ ] })
1195
+ ] });
1196
+ }
1197
+
1198
+ exports.AppHeader = AppHeader;
1199
+ exports.BellIcon = BellIcon;
1200
+ exports.BuildingIcon = BuildingIcon;
1201
+ exports.BuildingSelect = BuildingSelect;
1202
+ exports.DashboardLayout = DashboardLayout;
1203
+ exports.Sidebar = Sidebar;
1204
+ exports.SidebarMobileHeader = SidebarMobileHeader;
1205
+ exports.SidebarNavItem = SidebarNavItem;
1206
+ exports.SidebarUserCard = SidebarUserCard;
1207
+ exports.cn = cn;
1208
+ exports.useMediaQuery = useMediaQuery;
1209
+ exports.useOnClickOutside = useOnClickOutside;
1210
+ //# sourceMappingURL=index.cjs.map
1211
+ //# sourceMappingURL=index.cjs.map