@the-portland-company/shell 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1331 @@
1
+ import { extendTheme, Tooltip, ChakraProvider, Flex, Heading, Spacer, Box, Menu, MenuButton, Avatar, MenuList, MenuItem, VStack, Link as Link$1, Text } from '@chakra-ui/react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import clsx2 from 'clsx';
4
+ import { Link, useLocation } from 'react-router-dom';
5
+ import { createContext, useEffect, useState, useMemo, useContext } from 'react';
6
+
7
+ // src/theme/index.ts
8
+
9
+ // src/theme/colors.ts
10
+ var colors = {
11
+ // Primary brand color - Politogy Red/Orange (Energy & Action)
12
+ brand: {
13
+ 50: "#fff5f0",
14
+ 100: "#ffe8dc",
15
+ 200: "#ffd0b8",
16
+ 300: "#ffb088",
17
+ 400: "#ff8c5a",
18
+ 500: "#e86a3a",
19
+ // Primary brand color - burnt orange/red
20
+ 600: "#cc5429",
21
+ 700: "#a6421f",
22
+ 800: "#803317",
23
+ 900: "#5c2410"
24
+ },
25
+ // Politogy Brand Colors
26
+ politogy: {
27
+ // Legacy Blue - kept for reference
28
+ blue: {
29
+ 50: "#e8f4fd",
30
+ 100: "#bee3f8",
31
+ 200: "#90cdf4",
32
+ 300: "#63b3ed",
33
+ 400: "#4299e1",
34
+ 500: "#3182ce",
35
+ 600: "#2b6cb0",
36
+ 700: "#2c5282",
37
+ 800: "#2a4365",
38
+ 900: "#1a365d"
39
+ },
40
+ // Primary Red/Orange - Energy & Action (main brand color)
41
+ red: {
42
+ 50: "#fff5f0",
43
+ 100: "#ffe8dc",
44
+ 200: "#ffd0b8",
45
+ 300: "#ffb088",
46
+ 400: "#ff8c5a",
47
+ 500: "#e86a3a",
48
+ 600: "#cc5429",
49
+ 700: "#a6421f",
50
+ 800: "#803317",
51
+ 900: "#5c2410"
52
+ },
53
+ // Navy - Professional & Authoritative
54
+ navy: {
55
+ 50: "#e8eef6",
56
+ 100: "#c5d4e8",
57
+ 200: "#9fb8d9",
58
+ 300: "#789cca",
59
+ 400: "#5b86be",
60
+ 500: "#3e70b2",
61
+ 600: "#325a8e",
62
+ 700: "#26446a",
63
+ 800: "#1a2e47",
64
+ 900: "#0e1823"
65
+ },
66
+ // White & Off-white
67
+ white: "#ffffff",
68
+ offWhite: "#f8fafc",
69
+ // Text colors
70
+ text: {
71
+ primary: "#1a202c",
72
+ secondary: "#4a5568",
73
+ muted: "#718096",
74
+ light: "#a0aec0"
75
+ }
76
+ },
77
+ // Semantic color tokens that will switch based on color mode
78
+ gray: {
79
+ 50: "#f7fafc",
80
+ 100: "#edf2f7",
81
+ 200: "#e2e8f0",
82
+ 300: "#cbd5e0",
83
+ 400: "#a0aec0",
84
+ 500: "#718096",
85
+ 600: "#4a5568",
86
+ 700: "#2d3748",
87
+ 800: "#1a202c",
88
+ 900: "#171923"
89
+ }
90
+ };
91
+ var semanticTokens = {
92
+ colors: {
93
+ // Text colors
94
+ "text.primary": {
95
+ default: "gray.900",
96
+ _dark: "gray.50"
97
+ },
98
+ "text.secondary": {
99
+ default: "gray.600",
100
+ _dark: "gray.400"
101
+ },
102
+ "text.muted": {
103
+ default: "gray.500",
104
+ _dark: "gray.500"
105
+ },
106
+ // Background colors
107
+ "bg.primary": {
108
+ default: "white",
109
+ _dark: "gray.800"
110
+ },
111
+ "bg.secondary": {
112
+ default: "gray.50",
113
+ _dark: "gray.900"
114
+ },
115
+ "bg.surface": {
116
+ default: "white",
117
+ _dark: "gray.700"
118
+ },
119
+ "bg.hover": {
120
+ default: "gray.50",
121
+ _dark: "gray.600"
122
+ },
123
+ // Border colors
124
+ "border.default": {
125
+ default: "gray.200",
126
+ _dark: "gray.600"
127
+ },
128
+ "border.muted": {
129
+ default: "gray.100",
130
+ _dark: "gray.700"
131
+ },
132
+ // Component specific
133
+ "card.bg": {
134
+ default: "white",
135
+ _dark: "gray.700"
136
+ },
137
+ "card.border": {
138
+ default: "gray.200",
139
+ _dark: "gray.600"
140
+ },
141
+ "input.bg": {
142
+ default: "white",
143
+ _dark: "gray.700"
144
+ },
145
+ "input.border": {
146
+ default: "gray.200",
147
+ _dark: "gray.600"
148
+ },
149
+ "input.hover": {
150
+ default: "gray.400",
151
+ _dark: "gray.500"
152
+ },
153
+ // Navigation
154
+ "nav.bg": {
155
+ default: "white",
156
+ _dark: "gray.800"
157
+ },
158
+ "nav.border": {
159
+ default: "gray.200",
160
+ _dark: "gray.700"
161
+ },
162
+ // Sidebar
163
+ "sidebar.bg": {
164
+ default: "gray.50",
165
+ _dark: "gray.900"
166
+ },
167
+ "sidebar.hover": {
168
+ default: "gray.100",
169
+ _dark: "gray.800"
170
+ },
171
+ "sidebar.active": {
172
+ default: "brand.50",
173
+ _dark: "brand.900"
174
+ },
175
+ // Status colors
176
+ "success.bg": {
177
+ default: "green.50",
178
+ _dark: "green.900"
179
+ },
180
+ "error.bg": {
181
+ default: "red.50",
182
+ _dark: "red.900"
183
+ },
184
+ "warning.bg": {
185
+ default: "yellow.50",
186
+ _dark: "yellow.900"
187
+ },
188
+ "info.bg": {
189
+ default: "blue.50",
190
+ _dark: "blue.900"
191
+ }
192
+ }
193
+ };
194
+
195
+ // src/theme/components.ts
196
+ var components = {
197
+ Button: {
198
+ baseStyle: {
199
+ fontWeight: "medium"
200
+ },
201
+ defaultProps: {
202
+ colorScheme: "brand"
203
+ },
204
+ variants: {
205
+ primary: {
206
+ bg: "brand.500",
207
+ color: "white",
208
+ _hover: {
209
+ bg: "brand.600",
210
+ _disabled: {
211
+ bg: "brand.500"
212
+ }
213
+ },
214
+ _active: {
215
+ bg: "brand.700"
216
+ }
217
+ },
218
+ secondary: {
219
+ borderWidth: "1px",
220
+ borderColor: "gray.300",
221
+ bg: "white",
222
+ color: "gray.700",
223
+ _hover: {
224
+ bg: "gray.50"
225
+ }
226
+ },
227
+ solid: (props) => {
228
+ const { colorScheme: c } = props;
229
+ if (c === "brand") {
230
+ return {
231
+ bg: "brand.500",
232
+ color: "white",
233
+ _hover: {
234
+ bg: "brand.600",
235
+ _disabled: {
236
+ bg: "brand.500"
237
+ }
238
+ },
239
+ _active: {
240
+ bg: "brand.700"
241
+ }
242
+ };
243
+ }
244
+ return {
245
+ bg: `${c}.500`,
246
+ color: "white",
247
+ _hover: {
248
+ bg: `${c}.600`,
249
+ _disabled: {
250
+ bg: `${c}.500`
251
+ }
252
+ },
253
+ _active: {
254
+ bg: `${c}.700`
255
+ }
256
+ };
257
+ },
258
+ ghost: (props) => {
259
+ const { colorScheme: c } = props;
260
+ return {
261
+ color: `${c}.600`,
262
+ _hover: {
263
+ bg: `${c}.50`
264
+ }
265
+ };
266
+ },
267
+ outline: (props) => {
268
+ const { colorScheme: c } = props;
269
+ return {
270
+ borderColor: `${c}.500`,
271
+ color: `${c}.600`,
272
+ _hover: {
273
+ bg: `${c}.50`
274
+ }
275
+ };
276
+ },
277
+ link: {
278
+ color: "brand.500",
279
+ _hover: {
280
+ color: "brand.600",
281
+ textDecoration: "underline"
282
+ }
283
+ }
284
+ }
285
+ },
286
+ Heading: {
287
+ baseStyle: {
288
+ color: "politogy.navy.800",
289
+ fontWeight: "semibold"
290
+ }
291
+ },
292
+ Card: {
293
+ baseStyle: {
294
+ container: {
295
+ bg: "card.bg",
296
+ borderWidth: "1px",
297
+ borderColor: "card.border",
298
+ borderRadius: "lg"
299
+ }
300
+ }
301
+ },
302
+ Input: {
303
+ baseStyle: {
304
+ field: {
305
+ bg: "input.bg",
306
+ borderColor: "var(--mode-input-border-color)",
307
+ _hover: {
308
+ borderColor: "var(--mode-input-hover-border-color)"
309
+ },
310
+ _focus: {
311
+ borderColor: "var(--mode-primary-color)",
312
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
313
+ }
314
+ }
315
+ },
316
+ defaultProps: {
317
+ variant: "outline"
318
+ },
319
+ variants: {
320
+ outline: {
321
+ field: {
322
+ borderColor: "var(--mode-input-border-color)",
323
+ _hover: {
324
+ borderColor: "var(--mode-input-hover-border-color)"
325
+ },
326
+ _focus: {
327
+ borderColor: "var(--mode-primary-color)",
328
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
329
+ }
330
+ }
331
+ }
332
+ }
333
+ },
334
+ NumberInput: {
335
+ defaultProps: {
336
+ variant: "outline"
337
+ },
338
+ variants: {
339
+ outline: {
340
+ field: {
341
+ borderColor: "var(--mode-input-border-color)",
342
+ _hover: {
343
+ borderColor: "var(--mode-input-hover-border-color)"
344
+ },
345
+ _focus: {
346
+ borderColor: "var(--mode-primary-color)",
347
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
348
+ }
349
+ }
350
+ }
351
+ }
352
+ },
353
+ Select: {
354
+ baseStyle: {
355
+ field: {
356
+ bg: "input.bg",
357
+ borderColor: "var(--mode-input-border-color)",
358
+ _hover: {
359
+ borderColor: "var(--mode-input-hover-border-color)"
360
+ },
361
+ _focus: {
362
+ borderColor: "var(--mode-primary-color)",
363
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
364
+ }
365
+ }
366
+ },
367
+ defaultProps: {
368
+ variant: "outline"
369
+ },
370
+ variants: {
371
+ outline: {
372
+ field: {
373
+ borderColor: "var(--mode-input-border-color)",
374
+ _hover: {
375
+ borderColor: "var(--mode-input-hover-border-color)"
376
+ },
377
+ _focus: {
378
+ borderColor: "var(--mode-primary-color)",
379
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
380
+ }
381
+ }
382
+ }
383
+ }
384
+ },
385
+ Textarea: {
386
+ baseStyle: {
387
+ bg: "input.bg",
388
+ borderColor: "var(--mode-input-border-color)",
389
+ _hover: {
390
+ borderColor: "var(--mode-input-hover-border-color)"
391
+ },
392
+ _focus: {
393
+ borderColor: "var(--mode-primary-color)",
394
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
395
+ }
396
+ },
397
+ defaultProps: {
398
+ variant: "outline"
399
+ },
400
+ variants: {
401
+ outline: {
402
+ borderColor: "var(--mode-input-border-color)",
403
+ _hover: {
404
+ borderColor: "var(--mode-input-hover-border-color)"
405
+ },
406
+ _focus: {
407
+ borderColor: "var(--mode-primary-color)",
408
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
409
+ }
410
+ }
411
+ }
412
+ },
413
+ Menu: {
414
+ baseStyle: {
415
+ list: {
416
+ bg: "bg.surface",
417
+ borderColor: "var(--mode-input-border-color)",
418
+ boxShadow: "lg"
419
+ },
420
+ item: {
421
+ bg: "transparent",
422
+ _hover: {
423
+ bg: "bg.hover"
424
+ },
425
+ _focus: {
426
+ bg: "bg.hover"
427
+ }
428
+ }
429
+ }
430
+ },
431
+ Modal: {
432
+ baseStyle: {
433
+ dialog: {
434
+ bg: "bg.surface"
435
+ },
436
+ overlay: {
437
+ bg: "blackAlpha.600"
438
+ }
439
+ }
440
+ },
441
+ Popover: {
442
+ baseStyle: {
443
+ content: {
444
+ bg: "bg.surface",
445
+ borderColor: "border.default",
446
+ boxShadow: "lg"
447
+ }
448
+ }
449
+ },
450
+ Drawer: {
451
+ baseStyle: {
452
+ dialog: {
453
+ bg: "bg.surface"
454
+ },
455
+ overlay: {
456
+ bg: "blackAlpha.600"
457
+ }
458
+ }
459
+ },
460
+ Tabs: {
461
+ baseStyle: {
462
+ root: {
463
+ width: "100%"
464
+ },
465
+ tablist: {
466
+ width: "100%",
467
+ alignItems: "flex-end",
468
+ gap: "var(--app-tab-gap)"
469
+ },
470
+ tabpanels: {
471
+ width: "100%"
472
+ },
473
+ tabpanel: {
474
+ width: "100%"
475
+ },
476
+ tab: {
477
+ minHeight: "var(--app-tab-height)",
478
+ height: "var(--app-tab-height)",
479
+ lineHeight: 1,
480
+ borderTopLeftRadius: "var(--app-tab-radius)",
481
+ borderTopRightRadius: "var(--app-tab-radius)",
482
+ borderBottomLeftRadius: 0,
483
+ borderBottomRightRadius: 0,
484
+ color: "text.secondary",
485
+ _selected: {
486
+ color: "white",
487
+ bg: "brand.500",
488
+ borderColor: "brand.500",
489
+ fontWeight: "semibold"
490
+ },
491
+ _hover: {
492
+ bg: "gray.100"
493
+ }
494
+ }
495
+ },
496
+ variants: {
497
+ enclosed: {
498
+ tab: {
499
+ color: "gray.600",
500
+ bg: "transparent",
501
+ borderColor: "gray.200",
502
+ _selected: {
503
+ color: "white",
504
+ bg: "brand.500",
505
+ borderColor: "brand.500",
506
+ borderBottomColor: "brand.500"
507
+ },
508
+ _hover: {
509
+ bg: "gray.100"
510
+ }
511
+ }
512
+ },
513
+ "enclosed-colored": {
514
+ tab: {
515
+ color: "gray.600",
516
+ _selected: {
517
+ color: "white",
518
+ bg: "brand.500"
519
+ }
520
+ }
521
+ },
522
+ "soft-rounded": {
523
+ tab: {
524
+ color: "gray.600",
525
+ _selected: {
526
+ color: "white",
527
+ bg: "brand.500"
528
+ }
529
+ }
530
+ },
531
+ "solid-rounded": {
532
+ tab: {
533
+ color: "gray.600",
534
+ _selected: {
535
+ color: "white",
536
+ bg: "brand.500"
537
+ }
538
+ }
539
+ },
540
+ line: {
541
+ tab: {
542
+ color: "gray.600",
543
+ _selected: {
544
+ color: "white",
545
+ bg: "brand.500",
546
+ borderColor: "brand.500"
547
+ },
548
+ _hover: {
549
+ bg: "gray.100"
550
+ }
551
+ }
552
+ },
553
+ unstyled: {
554
+ tab: {
555
+ // Allow inline styles to take precedence for unstyled variant
556
+ }
557
+ }
558
+ },
559
+ defaultProps: {
560
+ colorScheme: "brand"
561
+ }
562
+ },
563
+ Badge: {
564
+ baseStyle: {
565
+ borderRadius: "md",
566
+ px: 2,
567
+ py: 1,
568
+ fontWeight: "medium"
569
+ },
570
+ defaultProps: {
571
+ colorScheme: "brand"
572
+ }
573
+ },
574
+ Link: {
575
+ baseStyle: {
576
+ color: "brand.500",
577
+ _hover: {
578
+ color: "brand.600",
579
+ textDecoration: "underline"
580
+ }
581
+ }
582
+ },
583
+ Alert: {
584
+ baseStyle: {
585
+ container: {
586
+ borderRadius: "md"
587
+ }
588
+ },
589
+ variants: {
590
+ subtle: (props) => {
591
+ const { colorScheme: c } = props;
592
+ if (c === "blue" || c === "info") {
593
+ return {
594
+ container: {
595
+ bg: "blue.50"
596
+ },
597
+ icon: {
598
+ color: "blue.500"
599
+ },
600
+ title: {
601
+ color: "blue.800"
602
+ },
603
+ description: {
604
+ color: "blue.700"
605
+ }
606
+ };
607
+ }
608
+ return {};
609
+ },
610
+ "left-accent": (props) => {
611
+ const { colorScheme: c } = props;
612
+ if (c === "blue" || c === "info") {
613
+ return {
614
+ container: {
615
+ bg: "blue.50",
616
+ borderLeftColor: "blue.500"
617
+ },
618
+ icon: {
619
+ color: "blue.500"
620
+ }
621
+ };
622
+ }
623
+ return {};
624
+ },
625
+ solid: (props) => {
626
+ const { colorScheme: c } = props;
627
+ if (c === "blue" || c === "info") {
628
+ return {
629
+ container: {
630
+ bg: "blue.500",
631
+ color: "white"
632
+ }
633
+ };
634
+ }
635
+ return {};
636
+ }
637
+ }
638
+ },
639
+ Table: {
640
+ baseStyle: {
641
+ table: {
642
+ width: "100%",
643
+ borderCollapse: "collapse",
644
+ borderSpacing: "0",
645
+ borderColor: "gray.200"
646
+ },
647
+ th: {
648
+ borderColor: "gray.200",
649
+ borderBottom: "none",
650
+ bg: "gray.200",
651
+ color: "text.secondary",
652
+ fontWeight: "semibold",
653
+ px: 3,
654
+ py: 2.5,
655
+ minHeight: "44px",
656
+ height: "44px",
657
+ verticalAlign: "middle"
658
+ },
659
+ td: {
660
+ borderColor: "gray.200",
661
+ borderBottom: "none",
662
+ px: 3,
663
+ py: 2.5,
664
+ minHeight: "44px",
665
+ height: "44px",
666
+ verticalAlign: "middle"
667
+ },
668
+ tr: {
669
+ borderBottom: "none",
670
+ borderColor: "transparent",
671
+ _odd: {
672
+ bg: "white"
673
+ },
674
+ _even: {
675
+ bg: "gray.50"
676
+ },
677
+ _hover: {
678
+ bg: "gray.100"
679
+ }
680
+ },
681
+ tbody: {
682
+ tr: {
683
+ _odd: {
684
+ bg: "white"
685
+ },
686
+ _even: {
687
+ bg: "gray.50"
688
+ },
689
+ _hover: {
690
+ bg: "blue.100"
691
+ }
692
+ }
693
+ }
694
+ },
695
+ variants: {
696
+ simple: {},
697
+ striped: {}
698
+ },
699
+ defaultProps: {
700
+ variant: "striped"
701
+ }
702
+ },
703
+ Divider: {
704
+ baseStyle: {
705
+ borderColor: "border.default"
706
+ }
707
+ }
708
+ };
709
+
710
+ // src/theme/index.ts
711
+ var config = {
712
+ initialColorMode: "light",
713
+ // Always use light theme
714
+ useSystemColorMode: false
715
+ // Disable system color mode detection
716
+ };
717
+ var styles = {
718
+ global: () => ({
719
+ body: {
720
+ bg: "white",
721
+ color: "gray.900"
722
+ },
723
+ ":root": {
724
+ "--mode-primary-color": "var(--app-color-brand-500)",
725
+ "--mode-input-border-color": "var(--app-color-brand-500)",
726
+ "--mode-input-hover-border-color": "var(--app-color-brand-600)",
727
+ "--mode-input-focus-shadow-color": "var(--app-color-brand-500)"
728
+ },
729
+ // Ensure non-Chakra/native text-like inputs follow mode border colors as well.
730
+ 'input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]), textarea, select': {
731
+ borderColor: "var(--mode-input-border-color)"
732
+ },
733
+ 'input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):hover, textarea:hover, select:hover': {
734
+ borderColor: "var(--mode-input-hover-border-color)"
735
+ },
736
+ 'input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):focus, textarea:focus, select:focus': {
737
+ borderColor: "var(--mode-primary-color)",
738
+ boxShadow: "0 0 0 1px var(--mode-input-focus-shadow-color)"
739
+ }
740
+ })
741
+ };
742
+ var fonts = {
743
+ heading: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif',
744
+ body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif',
745
+ mono: 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
746
+ };
747
+ var politogyTheme = extendTheme({
748
+ config,
749
+ colors,
750
+ semanticTokens,
751
+ components,
752
+ styles,
753
+ fonts,
754
+ // Additional theme configurations
755
+ shadows: {
756
+ outline: "0 0 0 3px var(--app-color-brand-500)"
757
+ },
758
+ radii: {
759
+ none: "0",
760
+ sm: "0.125rem",
761
+ base: "0.25rem",
762
+ md: "0.375rem",
763
+ lg: "0.5rem",
764
+ xl: "0.75rem",
765
+ "2xl": "1rem",
766
+ "3xl": "1.5rem",
767
+ full: "9999px"
768
+ }
769
+ });
770
+ function Card({
771
+ title,
772
+ children,
773
+ minH,
774
+ className,
775
+ style,
776
+ ...props
777
+ }) {
778
+ return /* @__PURE__ */ jsxs(
779
+ "div",
780
+ {
781
+ className: `rounded-lg border border-slate-200 bg-white p-6 shadow-sm ${className || ""}`,
782
+ style: { minHeight: minH, ...style },
783
+ ...props,
784
+ children: [
785
+ title && /* @__PURE__ */ jsx("h3", { className: "mb-4 text-lg font-semibold", children: title }),
786
+ children
787
+ ]
788
+ }
789
+ );
790
+ }
791
+ function PageContainer({
792
+ children,
793
+ maxW = "container.xl",
794
+ px,
795
+ py,
796
+ lockHeight = true
797
+ }) {
798
+ const isFullWidth = maxW === "full";
799
+ const effectivePx = isFullWidth ? 0 : px === void 0 ? 4 : Number(px);
800
+ const effectivePy = py === void 0 ? isFullWidth ? 0 : 4 : py;
801
+ const pxValue = String(effectivePx);
802
+ const pyValue = String(effectivePy);
803
+ const maxWClass = isFullWidth ? "max-w-none" : maxW === "container.xl" ? "max-w-7xl" : maxW === "container.lg" ? "max-w-6xl" : maxW === "container.md" ? "max-w-5xl" : typeof maxW === "string" && /^(\d+)px$/.test(maxW) ? "" : "max-w-7xl";
804
+ const pxClass = pxValue === "0" ? "px-0" : pxValue === "1" ? "px-1" : pxValue === "2" ? "px-2" : pxValue === "3" ? "px-3" : pxValue === "4" ? "px-4" : "px-4";
805
+ const pyClass = pyValue === "0" ? "py-0" : pyValue === "4" ? "py-4" : pyValue === "5" ? "py-5" : pyValue === "6" ? "py-6" : "py-6";
806
+ const inlineMaxWStyle = typeof maxW === "string" && /^(\d+)px$/.test(maxW) ? { maxWidth: maxW } : void 0;
807
+ return /* @__PURE__ */ jsx(
808
+ "main",
809
+ {
810
+ className: lockHeight ? "h-[calc(100vh-80px)] min-h-[calc(100vh-80px)]" : "",
811
+ style: { background: "var(--app-tab-content-bg)" },
812
+ children: /* @__PURE__ */ jsx(
813
+ "div",
814
+ {
815
+ className: clsx2(
816
+ "mx-auto w-full",
817
+ maxWClass,
818
+ pxClass,
819
+ pyClass,
820
+ lockHeight ? "h-full" : "h-auto"
821
+ ),
822
+ style: inlineMaxWStyle,
823
+ children
824
+ }
825
+ )
826
+ }
827
+ );
828
+ }
829
+ var resolveMaxWidthClass = (maxW) => {
830
+ if (maxW === "full") return "max-w-none";
831
+ if (maxW === "container.lg") return "max-w-6xl";
832
+ if (maxW === "container.md") return "max-w-5xl";
833
+ return "max-w-7xl";
834
+ };
835
+ function PageHeader({
836
+ title,
837
+ breadcrumbs = [],
838
+ actions = null,
839
+ tabBar = null,
840
+ maxW = "full",
841
+ hideSidebar = false
842
+ }) {
843
+ const maxWClass = resolveMaxWidthClass(maxW);
844
+ const titleText = typeof title === "string" ? title.trim() : "";
845
+ const normalizedTitle = titleText.toLowerCase();
846
+ const breadcrumbsWithoutCurrent = [...breadcrumbs];
847
+ const last = breadcrumbsWithoutCurrent[breadcrumbsWithoutCurrent.length - 1];
848
+ if (breadcrumbsWithoutCurrent.length > 1 && normalizedTitle && last && last.label.trim().toLowerCase() === normalizedTitle) {
849
+ breadcrumbsWithoutCurrent.pop();
850
+ }
851
+ if (!title && breadcrumbsWithoutCurrent.length === 0 && !actions && !tabBar) {
852
+ return null;
853
+ }
854
+ return /* @__PURE__ */ jsxs(
855
+ "div",
856
+ {
857
+ style: { backgroundColor: "var(--app-tab-header-bg, #888888)" },
858
+ className: clsx2("text-slate-100", tabBar ? "pt-3 pb-0" : "py-3 border-b border-gray-200", {
859
+ "md:ml-60": !hideSidebar
860
+ }),
861
+ children: [
862
+ /* @__PURE__ */ jsx("div", { className: clsx2("w-full px-4", maxWClass), children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-nowrap items-start justify-between gap-4", children: [
863
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
864
+ title ? /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx("div", { className: "truncate text-lg font-semibold text-white", children: title }) }) : null,
865
+ breadcrumbsWithoutCurrent.length > 0 ? /* @__PURE__ */ jsx(
866
+ "nav",
867
+ {
868
+ "aria-label": "Breadcrumb",
869
+ className: clsx2("min-w-0 text-slate-100", title ? "mt-1" : ""),
870
+ children: /* @__PURE__ */ jsx("ol", { className: "flex flex-nowrap items-center gap-2 overflow-hidden text-sm text-gray-600", children: breadcrumbsWithoutCurrent.map((item, index) => /* @__PURE__ */ jsxs(
871
+ "li",
872
+ {
873
+ className: "flex min-w-0 flex-shrink-0 items-center gap-2",
874
+ children: [
875
+ index > 0 ? /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: ">" }) : null,
876
+ item.href ? /* @__PURE__ */ jsx(Link, { to: item.href, className: "truncate text-slate-100 hover:underline", children: item.label }) : /* @__PURE__ */ jsx("span", { className: "truncate text-slate-100", children: item.label })
877
+ ]
878
+ },
879
+ `${item.label}-${index}`
880
+ )) })
881
+ }
882
+ ) : null
883
+ ] }),
884
+ actions ? /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: actions }) : null
885
+ ] }) }),
886
+ tabBar ? /* @__PURE__ */ jsx("div", { className: "m-0 w-full px-0", style: { backgroundColor: "transparent" }, children: tabBar }) : null
887
+ ]
888
+ }
889
+ );
890
+ }
891
+ function BreadcrumbBar({
892
+ breadcrumbs = [],
893
+ actions = null,
894
+ maxW = "full"
895
+ }) {
896
+ if ((!breadcrumbs || breadcrumbs.length === 0) && !actions) {
897
+ return null;
898
+ }
899
+ const maxWClass = maxW === "full" ? "max-w-none" : maxW === "container.xl" ? "max-w-7xl" : maxW === "container.lg" ? "max-w-6xl" : maxW === "container.md" ? "max-w-5xl" : "max-w-7xl";
900
+ return /* @__PURE__ */ jsx(
901
+ "div",
902
+ {
903
+ className: "ml-0 border-b border-gray-200 px-4 py-3 md:ml-60",
904
+ style: { backgroundColor: "var(--app-tab-header-bg, #888888)" },
905
+ children: /* @__PURE__ */ jsx("div", { className: clsx2("mx-auto w-full px-0", maxWClass), children: /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between", children: [
906
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-2 text-sm text-slate-100", children: breadcrumbs.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
907
+ index > 0 ? /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: ">" }) : null,
908
+ item.href ? /* @__PURE__ */ jsx(Link, { to: item.href, className: "text-slate-100 hover:underline", children: item.label }) : /* @__PURE__ */ jsx("span", { className: "text-slate-100", children: item.label })
909
+ ] }, index)) }) }),
910
+ actions && /* @__PURE__ */ jsx("div", { children: actions })
911
+ ] }) })
912
+ }
913
+ );
914
+ }
915
+ function HeaderTabBar({
916
+ items,
917
+ activeId,
918
+ onChange,
919
+ className,
920
+ ariaLabel = "Tabs"
921
+ }) {
922
+ return /* @__PURE__ */ jsx(
923
+ "div",
924
+ {
925
+ role: "tablist",
926
+ "aria-label": ariaLabel,
927
+ className: clsx2(
928
+ // Header-wide tab strip: background + bottom border only (no outer box).
929
+ "app-header-tab-bar m-0 flex w-full bg-slate-100 p-0",
930
+ "border-b border-slate-200",
931
+ className
932
+ ),
933
+ children: items.map((item, idx) => {
934
+ const isActive = item.id === activeId;
935
+ const isDisabled = Boolean(item.disabled);
936
+ return /* @__PURE__ */ jsx(
937
+ Tooltip,
938
+ {
939
+ label: item.label,
940
+ placement: "top",
941
+ hasArrow: true,
942
+ bg: "black",
943
+ color: "white",
944
+ isDisabled,
945
+ children: /* @__PURE__ */ jsx(
946
+ "button",
947
+ {
948
+ type: "button",
949
+ role: "tab",
950
+ "aria-selected": isActive,
951
+ "aria-label": item.label,
952
+ disabled: isDisabled,
953
+ onClick: () => onChange(item.id),
954
+ className: clsx2(
955
+ "app-header-tab-button relative inline-flex flex-1 items-center justify-center",
956
+ "transition-colors",
957
+ "rounded-none",
958
+ idx === 0 ? "rounded-tl-lg" : null,
959
+ idx === items.length - 1 ? "rounded-tr-lg" : null,
960
+ isActive ? "text-white" : "bg-transparent text-slate-600 hover:bg-white/60 hover:text-slate-900",
961
+ isDisabled ? "cursor-not-allowed opacity-50" : null
962
+ ),
963
+ style: {
964
+ background: isActive ? "var(--mode-primary-color, var(--app-btn-primary-bg))" : void 0
965
+ },
966
+ children: /* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center text-[14px] leading-none", children: item.icon })
967
+ }
968
+ )
969
+ },
970
+ item.id
971
+ );
972
+ })
973
+ }
974
+ );
975
+ }
976
+ var FLAG_PREFIX = ["__", "politogy", "enable"].join("");
977
+ var GLOBAL_UI_IDS_FLAG = [FLAG_PREFIX, "global", "ui", "ids"].join("_");
978
+ function isFlagEnabled(name) {
979
+ if (typeof window === "undefined") return false;
980
+ try {
981
+ const params = new URLSearchParams(window.location.search);
982
+ if (params.get(name) === "1") return true;
983
+ } catch {
984
+ }
985
+ try {
986
+ return window.localStorage.getItem(name) === "1";
987
+ } catch {
988
+ return false;
989
+ }
990
+ }
991
+ function isGlobalUiIdsEnabled() {
992
+ return isFlagEnabled(GLOBAL_UI_IDS_FLAG);
993
+ }
994
+ var TARGET_SELECTOR = [
995
+ "main",
996
+ "header",
997
+ "footer",
998
+ "aside",
999
+ "nav",
1000
+ "section",
1001
+ '[role="region"]',
1002
+ '[role="banner"]',
1003
+ '[role="main"]',
1004
+ '[role="navigation"]',
1005
+ '[role="complementary"]',
1006
+ "button",
1007
+ '[role="button"]',
1008
+ 'a[role="button"]',
1009
+ "input",
1010
+ "textarea",
1011
+ "select",
1012
+ '[role="combobox"]',
1013
+ '[role="listbox"]',
1014
+ '[role="option"]',
1015
+ '[role="textbox"]',
1016
+ '[role="tablist"]',
1017
+ '[role="tab"]',
1018
+ '[role="tabpanel"]',
1019
+ '[role="menu"]',
1020
+ '[role="menuitem"]',
1021
+ '[role="menuitemcheckbox"]',
1022
+ '[role="menuitemradio"]',
1023
+ "svg",
1024
+ "[data-icon]"
1025
+ ].join(",");
1026
+ function sanitizeToken(raw, fallback) {
1027
+ if (!raw) return fallback;
1028
+ const cleaned = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
1029
+ return cleaned || fallback;
1030
+ }
1031
+ function pageToken(pathname) {
1032
+ if (!pathname || pathname === "/") return "home";
1033
+ return sanitizeToken(pathname.replace(/\//g, "-"), "page");
1034
+ }
1035
+ function inferCategory(el) {
1036
+ const role = (el.getAttribute("role") || "").toLowerCase();
1037
+ const tag = el.tagName.toLowerCase();
1038
+ if (role.includes("tab") || tag === "tabs") return "tabs";
1039
+ if (role.startsWith("menu") || role === "menu") return "menu";
1040
+ if (tag === "button" || role === "button") return "button";
1041
+ if (tag === "input" || tag === "textarea" || tag === "select" || role === "combobox")
1042
+ return "field";
1043
+ if (tag === "svg" || el.hasAttribute("data-icon")) return "icon";
1044
+ if (["main", "header", "footer", "aside", "nav", "section"].includes(tag) || ["region", "banner", "main", "navigation", "complementary"].includes(role)) {
1045
+ return "layout";
1046
+ }
1047
+ return "ui";
1048
+ }
1049
+ function inferName(el) {
1050
+ const htmlEl = el;
1051
+ return sanitizeToken(
1052
+ el.getAttribute("data-testid") || el.getAttribute("aria-label") || el.getAttribute("name") || el.getAttribute("title") || htmlEl.placeholder || htmlEl.innerText || el.getAttribute("class"),
1053
+ "item"
1054
+ );
1055
+ }
1056
+ function assignUiIds(pathname) {
1057
+ const used = /* @__PURE__ */ new Set();
1058
+ document.querySelectorAll("[id]").forEach((el) => {
1059
+ const id = el.id?.trim();
1060
+ if (id) used.add(id);
1061
+ });
1062
+ const counters = {};
1063
+ const page = pageToken(pathname);
1064
+ const elements = document.querySelectorAll(TARGET_SELECTOR);
1065
+ elements.forEach((el) => {
1066
+ const category = inferCategory(el);
1067
+ const name = inferName(el);
1068
+ const stableKey = `ui-${category}-${name}`;
1069
+ el.setAttribute("data-ui-category", category);
1070
+ el.setAttribute("data-ui-key", stableKey);
1071
+ el.setAttribute("data-ui-page", page);
1072
+ if (el.id) {
1073
+ return;
1074
+ }
1075
+ const counterKey = `${page}-${category}-${name}`;
1076
+ counters[counterKey] = (counters[counterKey] || 0) + 1;
1077
+ let nextId = `ui-${page}-${category}-${name}-${counters[counterKey]}`;
1078
+ while (used.has(nextId)) {
1079
+ counters[counterKey] += 1;
1080
+ nextId = `ui-${page}-${category}-${name}-${counters[counterKey]}`;
1081
+ }
1082
+ el.id = nextId;
1083
+ used.add(nextId);
1084
+ });
1085
+ }
1086
+ function GlobalUiIdProvider() {
1087
+ const location = useLocation();
1088
+ const enabled = isGlobalUiIdsEnabled();
1089
+ useEffect(() => {
1090
+ if (!enabled) return;
1091
+ let rafId = 0;
1092
+ const schedule = () => {
1093
+ if (rafId) return;
1094
+ rafId = window.requestAnimationFrame(() => {
1095
+ rafId = 0;
1096
+ assignUiIds(location.pathname);
1097
+ });
1098
+ };
1099
+ schedule();
1100
+ const observer = new MutationObserver(() => {
1101
+ schedule();
1102
+ });
1103
+ observer.observe(document.body, {
1104
+ subtree: true,
1105
+ childList: true,
1106
+ attributes: true,
1107
+ attributeFilter: ["role", "aria-label", "name", "title", "placeholder", "class", "data-icon"]
1108
+ });
1109
+ return () => {
1110
+ if (rafId) window.cancelAnimationFrame(rafId);
1111
+ observer.disconnect();
1112
+ };
1113
+ }, [enabled, location.pathname]);
1114
+ return null;
1115
+ }
1116
+ var ShellAuthContext = createContext(null);
1117
+ var ShellMetaContext = createContext(null);
1118
+ function toShellUser(session) {
1119
+ if (!session?.user) return null;
1120
+ const su = session.user;
1121
+ const fullName = su.user_metadata?.full_name ?? null;
1122
+ return {
1123
+ id: su.id,
1124
+ email: su.email ?? null,
1125
+ displayName: fullName ?? su.email ?? null,
1126
+ raw: su
1127
+ };
1128
+ }
1129
+ function ShellProvider({
1130
+ children,
1131
+ supabaseClient,
1132
+ currentApp,
1133
+ appRegistry,
1134
+ basename
1135
+ }) {
1136
+ const [status, setStatus] = useState("loading");
1137
+ const [session, setSession] = useState(null);
1138
+ useEffect(() => {
1139
+ let cancelled = false;
1140
+ supabaseClient.auth.getSession().then(({ data }) => {
1141
+ if (cancelled) return;
1142
+ setSession(data.session);
1143
+ setStatus(data.session ? "authenticated" : "unauthenticated");
1144
+ });
1145
+ const sub = supabaseClient.auth.onAuthStateChange((_event, newSession) => {
1146
+ setSession(newSession);
1147
+ setStatus(newSession ? "authenticated" : "unauthenticated");
1148
+ });
1149
+ return () => {
1150
+ cancelled = true;
1151
+ sub.data.subscription.unsubscribe();
1152
+ };
1153
+ }, [supabaseClient]);
1154
+ const value = useMemo(
1155
+ () => ({
1156
+ status,
1157
+ session,
1158
+ user: toShellUser(session),
1159
+ signOut: async () => {
1160
+ await supabaseClient.auth.signOut();
1161
+ }
1162
+ }),
1163
+ [status, session, supabaseClient]
1164
+ );
1165
+ const meta = useMemo(
1166
+ () => ({
1167
+ currentApp,
1168
+ appRegistry: appRegistry ?? [],
1169
+ basename: basename ?? ""
1170
+ }),
1171
+ [currentApp, appRegistry, basename]
1172
+ );
1173
+ return /* @__PURE__ */ jsx(ShellAuthContext.Provider, { value, children: /* @__PURE__ */ jsx(ShellMetaContext.Provider, { value: meta, children: /* @__PURE__ */ jsx(ChakraProvider, { theme: politogyTheme, children }) }) });
1174
+ }
1175
+ function useShellAuth() {
1176
+ const ctx = useContext(ShellAuthContext);
1177
+ if (!ctx) {
1178
+ throw new Error("useShellAuth must be used inside <ShellProvider>");
1179
+ }
1180
+ return ctx;
1181
+ }
1182
+ function useAppRegistry() {
1183
+ const meta = useContext(ShellMetaContext);
1184
+ if (!meta) {
1185
+ throw new Error("useAppRegistry must be used inside <ShellProvider>");
1186
+ }
1187
+ return meta.appRegistry;
1188
+ }
1189
+ function useCurrentApp() {
1190
+ const meta = useContext(ShellMetaContext);
1191
+ if (!meta) {
1192
+ throw new Error("useCurrentApp must be used inside <ShellProvider>");
1193
+ }
1194
+ return meta.currentApp;
1195
+ }
1196
+ function Header({ extrasSlot }) {
1197
+ const { user, signOut, status } = useShellAuth();
1198
+ return /* @__PURE__ */ jsxs(
1199
+ Flex,
1200
+ {
1201
+ as: "header",
1202
+ align: "center",
1203
+ px: 4,
1204
+ h: "56px",
1205
+ bg: "white",
1206
+ borderBottom: "1px solid",
1207
+ borderColor: "gray.200",
1208
+ children: [
1209
+ /* @__PURE__ */ jsx(Heading, { size: "md", color: "brand.500", children: "politogy" }),
1210
+ /* @__PURE__ */ jsx(Spacer, {}),
1211
+ extrasSlot && /* @__PURE__ */ jsx(Box, { mr: 3, children: extrasSlot }),
1212
+ status === "authenticated" && user && /* @__PURE__ */ jsxs(Menu, { children: [
1213
+ /* @__PURE__ */ jsx(MenuButton, { children: /* @__PURE__ */ jsx(Avatar, { size: "sm", name: user.displayName ?? user.email ?? "" }) }),
1214
+ /* @__PURE__ */ jsx(MenuList, { children: /* @__PURE__ */ jsx(MenuItem, { onClick: () => signOut(), children: "Sign out" }) })
1215
+ ] })
1216
+ ]
1217
+ }
1218
+ );
1219
+ }
1220
+ function Sidebar({ entries, currentApp, footerSlot }) {
1221
+ return /* @__PURE__ */ jsxs(
1222
+ Box,
1223
+ {
1224
+ as: "nav",
1225
+ w: "200px",
1226
+ minH: "100%",
1227
+ bg: "gray.50",
1228
+ borderRight: "1px solid",
1229
+ borderColor: "gray.200",
1230
+ p: 3,
1231
+ display: "flex",
1232
+ flexDirection: "column",
1233
+ children: [
1234
+ /* @__PURE__ */ jsx(VStack, { align: "stretch", spacing: 1, children: entries.filter((e) => e.visible !== false).map((entry) => /* @__PURE__ */ jsx(
1235
+ Link$1,
1236
+ {
1237
+ href: entry.path,
1238
+ "data-current": entry.id === currentApp,
1239
+ px: 3,
1240
+ py: 2,
1241
+ borderRadius: "md",
1242
+ fontWeight: entry.id === currentApp ? "bold" : "normal",
1243
+ bg: entry.id === currentApp ? "brand.50" : "transparent",
1244
+ _hover: { bg: "gray.100" },
1245
+ children: /* @__PURE__ */ jsx(Text, { children: entry.label })
1246
+ },
1247
+ entry.id
1248
+ )) }),
1249
+ /* @__PURE__ */ jsx(Box, { flex: "1" }),
1250
+ footerSlot && /* @__PURE__ */ jsx(Box, { pt: 3, children: footerSlot })
1251
+ ]
1252
+ }
1253
+ );
1254
+ }
1255
+ function Footer({ buildInfo }) {
1256
+ return /* @__PURE__ */ jsx(
1257
+ Box,
1258
+ {
1259
+ as: "footer",
1260
+ px: 4,
1261
+ py: 2,
1262
+ borderTop: "1px solid",
1263
+ borderColor: "gray.200",
1264
+ fontSize: "xs",
1265
+ color: "gray.500",
1266
+ children: /* @__PURE__ */ jsxs(Text, { children: [
1267
+ "politogy ",
1268
+ buildInfo ? `\xB7 ${buildInfo}` : ""
1269
+ ] })
1270
+ }
1271
+ );
1272
+ }
1273
+ function AppLayout({
1274
+ children,
1275
+ buildInfo,
1276
+ headerExtrasSlot,
1277
+ modalsSlot,
1278
+ notificationsSlot,
1279
+ sidebarFooterSlot,
1280
+ overlaySlot
1281
+ }) {
1282
+ const currentApp = useCurrentApp();
1283
+ const entries = useAppRegistry();
1284
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", minH: "100vh", children: [
1285
+ /* @__PURE__ */ jsx(Header, { extrasSlot: headerExtrasSlot }),
1286
+ /* @__PURE__ */ jsxs(Flex, { flex: "1", minH: 0, children: [
1287
+ /* @__PURE__ */ jsx(Sidebar, { entries, currentApp, footerSlot: sidebarFooterSlot }),
1288
+ /* @__PURE__ */ jsxs(Box, { as: "main", flex: "1", overflow: "auto", position: "relative", children: [
1289
+ children,
1290
+ notificationsSlot
1291
+ ] })
1292
+ ] }),
1293
+ /* @__PURE__ */ jsx(Footer, { buildInfo }),
1294
+ modalsSlot,
1295
+ overlaySlot
1296
+ ] });
1297
+ }
1298
+
1299
+ // src/sw/registerShellPrecache.ts
1300
+ async function registerShellPrecache(opts = {}) {
1301
+ if (typeof window === "undefined") return;
1302
+ if (!("serviceWorker" in navigator)) return;
1303
+ const scriptUrl = opts.scriptUrl ?? "/sw-shell.js";
1304
+ const scope = opts.scope ?? "/";
1305
+ try {
1306
+ await navigator.serviceWorker.register(scriptUrl, { scope });
1307
+ } catch (err) {
1308
+ console.warn("[shell] precache SW registration failed:", err);
1309
+ }
1310
+ }
1311
+
1312
+ // src/speculation/emitRules.ts
1313
+ function buildSpeculationRules(currentApp, registry) {
1314
+ const others = registry.filter(
1315
+ (entry) => entry.id !== currentApp && entry.visible !== false
1316
+ );
1317
+ if (others.length === 0) return null;
1318
+ const payload = {
1319
+ prerender: [
1320
+ {
1321
+ urls: others.map((entry) => entry.path),
1322
+ eagerness: "moderate"
1323
+ }
1324
+ ]
1325
+ };
1326
+ return JSON.stringify(payload);
1327
+ }
1328
+
1329
+ export { AppLayout, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, PageContainer, PageHeader, ShellProvider, Sidebar, buildSpeculationRules, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth };
1330
+ //# sourceMappingURL=index.js.map
1331
+ //# sourceMappingURL=index.js.map