@principal-ai/file-city-react 0.5.39 → 0.5.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/components/FileCity3D/FileCity3D.d.ts +8 -2
  2. package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
  3. package/dist/components/FileCity3D/FileCity3D.js +129 -40
  4. package/dist/components/FileCityExplorer/AddToAreaModal.d.ts +14 -0
  5. package/dist/components/FileCityExplorer/AddToAreaModal.d.ts.map +1 -0
  6. package/dist/components/FileCityExplorer/AddToAreaModal.js +140 -0
  7. package/dist/components/FileCityExplorer/AddToScopeModal.d.ts +14 -0
  8. package/dist/components/FileCityExplorer/AddToScopeModal.d.ts.map +1 -0
  9. package/dist/components/FileCityExplorer/AddToScopeModal.js +176 -0
  10. package/dist/components/FileCityExplorer/FileCityExplorer.d.ts +30 -0
  11. package/dist/components/FileCityExplorer/FileCityExplorer.d.ts.map +1 -0
  12. package/dist/components/FileCityExplorer/FileCityExplorer.js +1045 -0
  13. package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts +10 -0
  14. package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts.map +1 -0
  15. package/dist/components/FileCityExplorer/ScopeInfoOverlay.js +73 -0
  16. package/dist/components/FileCityExplorer/index.d.ts +3 -0
  17. package/dist/components/FileCityExplorer/index.d.ts.map +1 -0
  18. package/dist/components/FileCityExplorer/index.js +1 -0
  19. package/dist/components/FileCityExplorer/layers.d.ts +16 -0
  20. package/dist/components/FileCityExplorer/layers.d.ts.map +1 -0
  21. package/dist/components/FileCityExplorer/layers.js +61 -0
  22. package/dist/components/FileCityExplorer/model.d.ts +32 -0
  23. package/dist/components/FileCityExplorer/model.d.ts.map +1 -0
  24. package/dist/components/FileCityExplorer/model.js +14 -0
  25. package/dist/components/FileCityExplorer/pathConversion.d.ts +19 -0
  26. package/dist/components/FileCityExplorer/pathConversion.d.ts.map +1 -0
  27. package/dist/components/FileCityExplorer/pathConversion.js +26 -0
  28. package/dist/components/FileCityExplorer/scopeTreePaths.d.ts +21 -0
  29. package/dist/components/FileCityExplorer/scopeTreePaths.d.ts.map +1 -0
  30. package/dist/components/FileCityExplorer/scopeTreePaths.js +42 -0
  31. package/dist/components/FileCityExplorer/styles.d.ts +9 -0
  32. package/dist/components/FileCityExplorer/styles.d.ts.map +1 -0
  33. package/dist/components/FileCityExplorer/styles.js +28 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -0
  37. package/dist/utils/folderElevatedPanels.d.ts +62 -0
  38. package/dist/utils/folderElevatedPanels.d.ts.map +1 -0
  39. package/dist/utils/folderElevatedPanels.js +130 -0
  40. package/package.json +2 -1
  41. package/src/components/FileCity3D/FileCity3D.tsx +200 -52
  42. package/src/components/FileCityExplorer/AddToAreaModal.tsx +273 -0
  43. package/src/components/FileCityExplorer/AddToScopeModal.tsx +320 -0
  44. package/src/components/FileCityExplorer/FileCityExplorer.tsx +1457 -0
  45. package/src/components/FileCityExplorer/ScopeInfoOverlay.tsx +229 -0
  46. package/src/components/FileCityExplorer/index.ts +2 -0
  47. package/src/components/FileCityExplorer/layers.ts +72 -0
  48. package/src/components/FileCityExplorer/model.ts +35 -0
  49. package/src/components/FileCityExplorer/pathConversion.ts +32 -0
  50. package/src/components/FileCityExplorer/scopeTreePaths.ts +52 -0
  51. package/src/components/FileCityExplorer/styles.ts +34 -0
  52. package/src/index.ts +8 -0
  53. package/src/stories/2D3DComparison.stories.tsx +13 -2
  54. package/src/stories/ElevatedScopePanels.stories.tsx +295 -0
  55. package/src/stories/FileCity3D.stories.tsx +24 -3
  56. package/src/stories/FileCityExplorer.stories.tsx +2474 -0
  57. package/src/stories/FileCityExplorerComponent.stories.tsx +59 -0
  58. package/src/utils/folderElevatedPanels.ts +176 -0
  59. package/src/stories/ScopeOverlay.stories.tsx +0 -1687
@@ -0,0 +1,229 @@
1
+ import React from 'react';
2
+ import { useTheme } from '@principal-ade/industry-theme';
3
+ import type { Event, Namespace, Scope } from './model';
4
+ import { makeOverlayStyle, makeSectionLabelStyle } from './styles';
5
+
6
+ export const ScopeInfoOverlay: React.FC<{
7
+ info: { scope: Scope; ns: Namespace | null; ev: Event | null };
8
+ }> = ({ info }) => {
9
+ const { theme } = useTheme();
10
+ const overlayStyle = makeOverlayStyle(theme);
11
+ const sectionLabelStyle = makeSectionLabelStyle(theme);
12
+
13
+ const severityBg: Record<NonNullable<Event['severity']>, string> = {
14
+ ERROR: theme.colors.error,
15
+ WARN: theme.colors.warning,
16
+ INFO: theme.colors.info,
17
+ };
18
+ const defaultSeverityBg = theme.colors.backgroundSecondary;
19
+
20
+ const sectionDivider = `1px solid ${theme.colors.backgroundSecondary}`;
21
+ const codeChipStyle: React.CSSProperties = {
22
+ fontSize: theme.fontSizes[0],
23
+ color: theme.colors.textSecondary,
24
+ background: theme.colors.backgroundDark ?? theme.colors.background,
25
+ padding: '4px 6px',
26
+ borderRadius: theme.radii[2],
27
+ wordBreak: 'break-all',
28
+ };
29
+
30
+ const { scope, ns, ev } = info;
31
+
32
+ // Event leaf selected — show event detail.
33
+ if (ns && ev) {
34
+ return (
35
+ <div style={overlayStyle}>
36
+ <div style={{ padding: '14px 16px', borderBottom: sectionDivider }}>
37
+ <div style={sectionLabelStyle}>Event</div>
38
+ <div style={{ fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1], marginTop: 6 }}>
39
+ {ev.name}
40
+ </div>
41
+ {ev.severity && (
42
+ <div
43
+ style={{
44
+ display: 'inline-block',
45
+ fontSize: theme.fontSizes[0],
46
+ marginTop: theme.space[2],
47
+ padding: '2px 6px',
48
+ borderRadius: theme.radii[1],
49
+ background: severityBg[ev.severity] ?? defaultSeverityBg,
50
+ color: theme.colors.highlight,
51
+ }}
52
+ >
53
+ {ev.severity}
54
+ </div>
55
+ )}
56
+ {ev.description && (
57
+ <div style={{ fontSize: theme.fontSizes[0], color: theme.colors.textSecondary, marginTop: 10, lineHeight: 1.5 }}>
58
+ {ev.description}
59
+ </div>
60
+ )}
61
+ </div>
62
+ <div style={{ padding: '14px 16px' }}>
63
+ <div style={sectionLabelStyle}>Owning namespace</div>
64
+ <div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: theme.space[2] }}>
65
+ <span
66
+ style={{ width: 12, height: 12, borderRadius: theme.radii[1], background: ns.color, flexShrink: 0 }}
67
+ />
68
+ <span style={{ fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1] }}>{ns.name}</span>
69
+ </div>
70
+ <div style={{ fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginTop: 14, fontStyle: 'italic' }}>
71
+ Files-per-event mapping not wired yet — for now the event highlights its parent
72
+ namespace&apos;s paths.
73
+ </div>
74
+ </div>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ // Namespace selected — show namespace detail.
80
+ if (ns) {
81
+ return (
82
+ <div style={overlayStyle}>
83
+ <div style={{ padding: '14px 16px', borderBottom: sectionDivider }}>
84
+ <div style={sectionLabelStyle}>Namespace</div>
85
+ <div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: theme.space[2] }}>
86
+ <span
87
+ style={{ width: 12, height: 12, borderRadius: theme.radii[1], background: ns.color, flexShrink: 0 }}
88
+ />
89
+ <span style={{ fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1] }}>{ns.name}</span>
90
+ </div>
91
+ <div style={{ fontSize: theme.fontSizes[0], color: theme.colors.textMuted, marginTop: theme.space[2], lineHeight: 1.5 }}>
92
+ {ns.description}
93
+ </div>
94
+ <div style={{ fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginTop: theme.space[2] }}>
95
+ in <span style={{ fontFamily: theme.fonts.monospace }}>{scope.id}</span>
96
+ </div>
97
+ </div>
98
+ <div style={{ padding: '14px 16px', borderBottom: sectionDivider }}>
99
+ <div style={sectionLabelStyle}>Claimed paths ({ns.paths.length})</div>
100
+ <div style={{ display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }}>
101
+ {ns.paths.map(p => (
102
+ <code key={p} style={codeChipStyle}>
103
+ {p}
104
+ </code>
105
+ ))}
106
+ </div>
107
+ </div>
108
+ <div style={{ padding: '14px 16px' }}>
109
+ <div style={sectionLabelStyle}>Events ({ns.events.length})</div>
110
+ <div style={{ display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }}>
111
+ {ns.events.map(e => (
112
+ <div
113
+ key={e.name}
114
+ style={{
115
+ display: 'flex',
116
+ alignItems: 'center',
117
+ gap: 6,
118
+ padding: '4px 6px',
119
+ background: theme.colors.backgroundDark ?? theme.colors.background,
120
+ borderRadius: theme.radii[2],
121
+ }}
122
+ >
123
+ {e.severity && (
124
+ <span
125
+ style={{
126
+ fontSize: theme.fontSizes[0],
127
+ padding: '1px 4px',
128
+ borderRadius: theme.radii[1],
129
+ background: severityBg[e.severity] ?? defaultSeverityBg,
130
+ color: theme.colors.highlight,
131
+ flexShrink: 0,
132
+ }}
133
+ >
134
+ {e.severity}
135
+ </span>
136
+ )}
137
+ <code style={{ fontSize: theme.fontSizes[0], color: theme.colors.textSecondary }}>
138
+ {e.name}
139
+ </code>
140
+ </div>
141
+ ))}
142
+ </div>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ // Scope selected — show scope summary.
149
+ const totalEvents = scope.namespaces.reduce((n, x) => n + x.events.length, 0);
150
+ return (
151
+ <div style={overlayStyle}>
152
+ <div style={{ padding: '14px 16px', borderBottom: sectionDivider }}>
153
+ <div style={sectionLabelStyle}>Scope</div>
154
+ <div style={{ fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[1], marginTop: 6 }}>{scope.id}</div>
155
+ <div style={{ fontSize: theme.fontSizes[0], color: theme.colors.textMuted, marginTop: theme.space[2], lineHeight: 1.5 }}>
156
+ {scope.description}
157
+ </div>
158
+ <div style={{ display: 'flex', gap: theme.space[3], marginTop: 12, fontSize: theme.fontSizes[0], color: theme.colors.textTertiary }}>
159
+ <div>
160
+ <div>{scope.paths.length}</div>
161
+ <div style={sectionLabelStyle}>scope paths</div>
162
+ </div>
163
+ <div>
164
+ <div>{scope.namespaces.length}</div>
165
+ <div style={sectionLabelStyle}>namespaces</div>
166
+ </div>
167
+ <div>
168
+ <div>{totalEvents}</div>
169
+ <div style={sectionLabelStyle}>events</div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ {scope.paths.length > 0 && (
174
+ <div style={{ padding: '14px 16px', borderBottom: sectionDivider }}>
175
+ <div style={sectionLabelStyle}>Scope-level paths ({scope.paths.length})</div>
176
+ <div style={{ display: 'flex', flexDirection: 'column', gap: theme.space[1], marginTop: 6 }}>
177
+ {scope.paths.map(p => (
178
+ <code key={p} style={codeChipStyle}>
179
+ {p}
180
+ </code>
181
+ ))}
182
+ </div>
183
+ </div>
184
+ )}
185
+ <div style={{ padding: '14px 16px' }}>
186
+ <div style={sectionLabelStyle}>Namespaces</div>
187
+ <div style={{ display: 'flex', flexDirection: 'column', gap: theme.space[2], marginTop: theme.space[2] }}>
188
+ {scope.namespaces.map(n => (
189
+ <div
190
+ key={n.name}
191
+ style={{
192
+ padding: theme.space[2],
193
+ background: theme.colors.backgroundDark ?? theme.colors.background,
194
+ borderRadius: theme.radii[3],
195
+ }}
196
+ >
197
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.space[2] }}>
198
+ <span
199
+ style={{
200
+ width: 10,
201
+ height: 10,
202
+ borderRadius: theme.radii[1],
203
+ background: n.color,
204
+ flexShrink: 0,
205
+ }}
206
+ />
207
+ <span style={{ fontFamily: theme.fonts.monospace, fontSize: theme.fontSizes[0] }}>{n.name}</span>
208
+ <span style={{ fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, marginLeft: 'auto' }}>
209
+ {n.events.length} event{n.events.length === 1 ? '' : 's'}
210
+ </span>
211
+ </div>
212
+ <div
213
+ style={{
214
+ fontSize: theme.fontSizes[0],
215
+ color: theme.colors.textTertiary,
216
+ fontFamily: theme.fonts.monospace,
217
+ marginTop: theme.space[1],
218
+ wordBreak: 'break-all',
219
+ }}
220
+ >
221
+ {n.paths.join(' · ')}
222
+ </div>
223
+ </div>
224
+ ))}
225
+ </div>
226
+ </div>
227
+ </div>
228
+ );
229
+ };
@@ -0,0 +1,2 @@
1
+ export { FileCityExplorer, type FileCityExplorerProps } from './FileCityExplorer';
2
+ export type { Event, Namespace, ProjectArea, Scope } from './model';
@@ -0,0 +1,72 @@
1
+ import type { HighlightLayer } from '../FileCity3D';
2
+ import type { Scope } from './model';
3
+
4
+ export const NAMESPACE_PALETTE = [
5
+ '#22c55e',
6
+ '#3b82f6',
7
+ '#f59e0b',
8
+ '#ec4899',
9
+ '#8b5cf6',
10
+ '#06b6d4',
11
+ '#ef4444',
12
+ '#14b8a6',
13
+ '#a855f7',
14
+ '#eab308',
15
+ ];
16
+
17
+ /** Colour used for area umbrella tiles and their picker swatches. */
18
+ export const AREA_PANEL_COLOR = '#64748b';
19
+
20
+ export function pickNamespaceColor(scopes: readonly Scope[]): string {
21
+ const used = new Set(scopes.flatMap(s => s.namespaces.map(n => n.color)));
22
+ return (
23
+ NAMESPACE_PALETTE.find(c => !used.has(c)) ??
24
+ NAMESPACE_PALETTE[scopes.length % NAMESPACE_PALETTE.length]
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Build highlight layers for a scope: one fill layer per namespace plus a
30
+ * border-only layer for scope-level paths. Priority is path depth (longest-
31
+ * prefix wins) per the partition convention in docs/scope-namespace-overlay.md.
32
+ *
33
+ * `toCityPath` translates scope-relative paths back to city paths so
34
+ * districts can be matched.
35
+ */
36
+ export function buildLayersForScope(
37
+ scope: Scope,
38
+ toCityPath: (scopePath: string) => string,
39
+ ): HighlightLayer[] {
40
+ const layers: HighlightLayer[] = scope.namespaces.map(ns => {
41
+ const maxDepth = Math.max(1, ...ns.paths.map(p => p.split('/').length));
42
+ return {
43
+ id: `${scope.id}::${ns.name}`,
44
+ name: ns.name,
45
+ enabled: true,
46
+ color: ns.color,
47
+ opacity: 0.55,
48
+ priority: maxDepth,
49
+ items: ns.paths.map(p => ({
50
+ path: toCityPath(p),
51
+ type: 'directory' as const,
52
+ renderStrategy: 'fill' as const,
53
+ })),
54
+ };
55
+ });
56
+ if (scope.paths.length > 0) {
57
+ layers.push({
58
+ id: `${scope.id}::__scope__`,
59
+ name: `${scope.id} (scope-level)`,
60
+ enabled: true,
61
+ color: '#64748b',
62
+ opacity: 0.4,
63
+ priority: 0,
64
+ items: scope.paths.map(p => ({
65
+ path: toCityPath(p),
66
+ type: 'directory' as const,
67
+ renderStrategy: 'fill' as const,
68
+ })),
69
+ });
70
+ }
71
+ return layers;
72
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Scope / namespace / area model for the explorer.
3
+ *
4
+ * `Event` and `Namespace` are derived from the upstream
5
+ * `EventNamespaceNode['namespace']` shape in `@principal-ai/principal-view-core`.
6
+ * `Namespace` adds a UI-required `color` (palette pick).
7
+ *
8
+ * `Scope` stays local: it flattens namespaces inline (`scope.namespaces[]`),
9
+ * which the upstream canvas-node split (`OtelScopeNode` + per-scope
10
+ * `*.events.canvas`) doesn't model. Treat as an explorer view-model.
11
+ *
12
+ * `ProjectArea` is re-exported directly from upstream.
13
+ */
14
+
15
+ import type { EventNamespaceNode } from '@principal-ai/principal-view-core';
16
+ export type { ProjectArea } from '@principal-ai/principal-view-core';
17
+
18
+ export type Event = EventNamespaceNode['namespace']['events'][number];
19
+
20
+ export type Namespace = EventNamespaceNode['namespace'] & {
21
+ /** UI palette pick — not part of the upstream canvas-node shape. */
22
+ color: string;
23
+ /** Required here even though upstream allows `paths?` — explorer always sets it. */
24
+ paths: string[];
25
+ };
26
+
27
+ export interface Scope {
28
+ /** Maps to `OtelScopeNode.otel.scope` upstream. */
29
+ id: string;
30
+ name: string;
31
+ description: string;
32
+ /** Scope-level paths — corresponds to optional `OtelScopeNode.paths` upstream. */
33
+ paths: string[];
34
+ namespaces: Namespace[];
35
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Path converters for the explorer.
3
+ *
4
+ * City data is rooted at `packageRoot` (e.g. `electron-app/`). Scope/namespace
5
+ * paths are authored relative to the package root (matching how principal-view
6
+ * canvases are stored). These converters round-trip between the two
7
+ * representations.
8
+ *
9
+ * `packageRoot` should include a trailing slash. Pass `''` if the city data
10
+ * is already rooted at the project root.
11
+ */
12
+
13
+ export interface PathConverters {
14
+ /** City path → scope/namespace path (strip the package-root prefix). */
15
+ toScopePath: (cityPath: string) => string;
16
+ /** Scope/namespace path → city path (re-add the package-root prefix). */
17
+ toCityPath: (scopePath: string) => string;
18
+ }
19
+
20
+ export function createPathConverters(packageRoot: string): PathConverters {
21
+ return {
22
+ toScopePath(cityPath: string): string {
23
+ let p = cityPath.endsWith('/') ? cityPath.slice(0, -1) : cityPath;
24
+ if (packageRoot && p.startsWith(packageRoot)) p = p.slice(packageRoot.length);
25
+ return p;
26
+ },
27
+ toCityPath(scopePath: string): string {
28
+ if (!packageRoot) return scopePath;
29
+ return scopePath.startsWith(packageRoot) ? scopePath : packageRoot + scopePath;
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,52 @@
1
+ import type { Scope } from './model';
2
+
3
+ export interface ScopeTreeSelection {
4
+ scopeId: string;
5
+ namespaceName?: string;
6
+ eventName?: string;
7
+ }
8
+
9
+ /**
10
+ * Sentinel leaves used when a scope has no namespaces or a namespace has no
11
+ * events — the trees library infers directories from paths, so empty branches
12
+ * need a placeholder leaf to render.
13
+ */
14
+ export const EMPTY_NS_SENTINEL = '(no namespaces)';
15
+ export const EMPTY_EVENTS_SENTINEL = '(no events)';
16
+
17
+ /**
18
+ * Build canonical paths for the scope tree: `<scope.id>/<namespace.name>/<event.name>`.
19
+ * Scopes are top-level directories, namespaces children, events leaves.
20
+ * Empty scopes/namespaces emit a sentinel leaf so they still appear.
21
+ */
22
+ export function buildScopeTreePaths(scopes: readonly Scope[]): string[] {
23
+ const out: string[] = [];
24
+ for (const scope of scopes) {
25
+ if (scope.namespaces.length === 0) {
26
+ out.push(`${scope.id}/${EMPTY_NS_SENTINEL}`);
27
+ continue;
28
+ }
29
+ for (const ns of scope.namespaces) {
30
+ if (ns.events.length === 0) {
31
+ out.push(`${scope.id}/${ns.name}/${EMPTY_EVENTS_SENTINEL}`);
32
+ continue;
33
+ }
34
+ for (const ev of ns.events) {
35
+ out.push(`${scope.id}/${ns.name}/${ev.name}`);
36
+ }
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+
42
+ export function parseScopeTreePath(path: string): ScopeTreeSelection {
43
+ const [scopeId, namespaceName, eventName] = path.split('/');
44
+ const result: ScopeTreeSelection = { scopeId };
45
+ if (namespaceName && namespaceName !== EMPTY_NS_SENTINEL) {
46
+ result.namespaceName = namespaceName;
47
+ }
48
+ if (eventName && eventName !== EMPTY_EVENTS_SENTINEL) {
49
+ result.eventName = eventName;
50
+ }
51
+ return result;
52
+ }
@@ -0,0 +1,34 @@
1
+ import type React from 'react';
2
+ import type { Theme } from '@principal-ade/industry-theme';
3
+
4
+ /** Translucent overlay using the theme's background colour. */
5
+ export const withAlpha = (color: string, percent: number): string =>
6
+ `color-mix(in oklab, ${color} ${percent}%, transparent)`;
7
+
8
+ /** Reused across overlays, modals, and the selected-folder card. */
9
+ export const makeSectionLabelStyle = (theme: Theme): React.CSSProperties => ({
10
+ fontSize: theme.fontSizes[0],
11
+ color: theme.colors.textTertiary,
12
+ textTransform: 'uppercase',
13
+ letterSpacing: 0.5,
14
+ });
15
+
16
+ /** Frame style for the right-side info overlay. */
17
+ export const makeOverlayStyle = (theme: Theme): React.CSSProperties => ({
18
+ position: 'absolute',
19
+ top: 16,
20
+ left: 16,
21
+ width: 360,
22
+ maxHeight: 'calc(100vh - 32px)',
23
+ overflowY: 'auto',
24
+ background: withAlpha(theme.colors.background, 72),
25
+ backdropFilter: 'blur(8px)',
26
+ WebkitBackdropFilter: 'blur(8px)',
27
+ border: `1px solid ${theme.colors.border}`,
28
+ borderRadius: theme.radii[4],
29
+ color: theme.colors.text,
30
+ fontFamily: theme.fonts.body,
31
+ fontSize: theme.fontSizes[1],
32
+ zIndex: 100,
33
+ boxShadow: theme.shadows[3],
34
+ });
package/src/index.ts CHANGED
@@ -87,6 +87,14 @@ export type {
87
87
  // with the 2D HighlightLayer from drawLayeredBuildings
88
88
  export type { HighlightLayer as FileCity3DHL } from './components/FileCity3D';
89
89
 
90
+ // Folder-driven elevated panels (file-tree expansion → 3D umbrella tiles)
91
+ export {
92
+ buildFolderElevatedPanels,
93
+ buildFolderIndex,
94
+ hashFolderColor,
95
+ } from './utils/folderElevatedPanels';
96
+ export type { BuildFolderElevatedPanelsOptions } from './utils/folderElevatedPanels';
97
+
90
98
  // Visualization resolution utilities
91
99
  // See docs/VISUALIZATION_STATE_RESOLUTION.md for documentation
92
100
  export { resolveVisualizationIntent } from './utils/visualizationResolution';
@@ -1,8 +1,8 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
 
4
4
  import { ArchitectureMapHighlightLayers } from '../components/ArchitectureMapHighlightLayers';
5
- import { FileCity3D, type HighlightLayer, type IsolationMode, type CityData } from '../components/FileCity3D';
5
+ import { FileCity3D, type HighlightLayer, type CityData } from '../components/FileCity3D';
6
6
  import { createFileColorHighlightLayers } from '../utils/fileColorHighlightLayers';
7
7
  import authServerCityData from '../../../../assets/auth-server-city-data.json';
8
8
 
@@ -53,6 +53,7 @@ export const ViewModeSwitch: StoryObj = {
53
53
  setHideOverlay(false);
54
54
  setOverlayOpacity(1);
55
55
  }
56
+ return undefined;
56
57
  }, [viewMode]);
57
58
 
58
59
  return (
@@ -266,6 +267,7 @@ const testScenarios: TestScenario[] = [
266
267
  id: 'api-layer',
267
268
  name: 'API Routes',
268
269
  enabled: true,
270
+ priority: 0,
269
271
  color: '#22c55e',
270
272
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
271
273
  },
@@ -282,6 +284,7 @@ const testScenarios: TestScenario[] = [
282
284
  id: 'api-layer',
283
285
  name: 'API Routes',
284
286
  enabled: true,
287
+ priority: 0,
285
288
  color: '#3b82f6',
286
289
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
287
290
  },
@@ -298,6 +301,7 @@ const testScenarios: TestScenario[] = [
298
301
  id: 'lib-layer',
299
302
  name: 'Libraries',
300
303
  enabled: true,
304
+ priority: 0,
301
305
  color: '#8b5cf6',
302
306
  items: [{ path: 'auth-server/src/lib', type: 'directory' as const }],
303
307
  },
@@ -314,6 +318,7 @@ const testScenarios: TestScenario[] = [
314
318
  id: 'api-layer',
315
319
  name: 'API Routes',
316
320
  enabled: true,
321
+ priority: 0,
317
322
  color: '#22c55e',
318
323
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
319
324
  },
@@ -321,6 +326,7 @@ const testScenarios: TestScenario[] = [
321
326
  id: 'lib-layer',
322
327
  name: 'Libraries',
323
328
  enabled: true,
329
+ priority: 0,
324
330
  color: '#f59e0b',
325
331
  items: [{ path: 'auth-server/src/lib', type: 'directory' as const }],
326
332
  },
@@ -336,6 +342,7 @@ const testScenarios: TestScenario[] = [
336
342
  id: 'api-layer',
337
343
  name: 'API Routes',
338
344
  enabled: true,
345
+ priority: 0,
339
346
  color: '#3b82f6',
340
347
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
341
348
  },
@@ -343,6 +350,7 @@ const testScenarios: TestScenario[] = [
343
350
  id: 'bruno-layer',
344
351
  name: 'Bruno Tests',
345
352
  enabled: true,
353
+ priority: 0,
346
354
  color: '#ef4444',
347
355
  items: [{ path: 'auth-server/bruno', type: 'directory' as const }],
348
356
  },
@@ -358,6 +366,7 @@ const testScenarios: TestScenario[] = [
358
366
  id: 'single-file-layer',
359
367
  name: 'Single File',
360
368
  enabled: true,
369
+ priority: 0,
361
370
  color: '#ec4899',
362
371
  items: [{ path: 'auth-server/src/app/api/auth/workos/callback/route.ts', type: 'file' as const }],
363
372
  },
@@ -373,6 +382,7 @@ const testScenarios: TestScenario[] = [
373
382
  id: 'files-layer',
374
383
  name: 'Selected Files',
375
384
  enabled: true,
385
+ priority: 0,
376
386
  color: '#14b8a6',
377
387
  items: [
378
388
  { path: 'auth-server/src/app/api/auth/workos/callback/route.ts', type: 'file' as const },
@@ -658,6 +668,7 @@ export const PanelTransitionTest: StoryObj = {
658
668
  setOverlayOpacity(1);
659
669
  setHideOverlay(true);
660
670
  }
671
+ return undefined;
661
672
  }, [viewMode]);
662
673
 
663
674
  return (