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

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/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export type {
81
81
  HeightScaling,
82
82
  FlatPattern,
83
83
  ElevatedScopePanel,
84
+ SelectionStyle,
84
85
  } from './components/FileCity3D';
85
86
 
86
87
  // Re-export HighlightLayer from FileCity3D with distinct name to avoid conflict
@@ -1,3 +1,4 @@
1
+ import { useCallback, useState } from 'react';
1
2
  import type { Meta, StoryObj } from '@storybook/react';
2
3
 
3
4
  import {
@@ -189,7 +190,8 @@ export const RecoloredAndTranslucent: Story = {
189
190
  };
190
191
 
191
192
  // ---------------------------------------------------------------------------
192
- // Selection ring
193
+ // Selection ring (now a first-class `selectedPath` prop on FileCity3D —
194
+ // kept here as a regression demo so the panel/ring composition stays visible).
193
195
  // ---------------------------------------------------------------------------
194
196
 
195
197
  const SELECTED_FOLDER = 'electron-app/src';
@@ -198,37 +200,17 @@ const SELECTION_RING_COLOR = '#fbbf24';
198
200
  export const WithSelectionRing: Story = {
199
201
  args: {
200
202
  ...baseArgs,
201
- elevatedScopePanels: (() => {
202
- const panels = panelsFor(TOP_LEVEL);
203
- const idx = panels.findIndex(p => p.id === `folder::${SELECTED_FOLDER}`);
204
- if (idx < 0) return panels;
205
- const target = panels[idx];
206
- const inflate = 4;
207
- const ring: ElevatedScopePanel = {
208
- id: `folder-border::${SELECTED_FOLDER}`,
209
- color: SELECTION_RING_COLOR,
210
- height: (target.height ?? 4) - 2,
211
- thickness: 1,
212
- bounds: {
213
- minX: target.bounds.minX - inflate,
214
- maxX: target.bounds.maxX + inflate,
215
- minZ: target.bounds.minZ - inflate,
216
- maxZ: target.bounds.maxZ + inflate,
217
- },
218
- };
219
- const next = [...panels];
220
- next.splice(idx, 0, ring);
221
- return next;
222
- })(),
203
+ elevatedScopePanels: panelsFor(TOP_LEVEL),
204
+ selectedPath: SELECTED_FOLDER,
205
+ selectionStyle: { color: SELECTION_RING_COLOR },
223
206
  },
224
207
  parameters: {
225
208
  docs: {
226
209
  description: {
227
210
  story:
228
- 'Insert an inflated, lower-height panel just *before* the target ' +
229
- 'umbrella so only its rim peeks out the selection-ring pattern ' +
230
- 'used by FileCityExplorer. Order matters: the ring must come ' +
231
- 'first so the umbrella draws on top of its centre.',
211
+ '`selectedPath` resolves to a district, so FileCity3D draws the ' +
212
+ 'selection ring above the umbrella covering that district. ' +
213
+ 'Replaces the older inflate-a-panel-underneath-the-umbrella trick.',
232
214
  },
233
215
  },
234
216
  },
@@ -293,3 +275,66 @@ export const HandBuiltPanels: Story = {
293
275
  },
294
276
  },
295
277
  };
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Cmd-click to dismiss (parent-owned dismiss flow)
281
+ // ---------------------------------------------------------------------------
282
+
283
+ function DismissOnCommandClickStory() {
284
+ const [panels, setPanels] = useState<ElevatedScopePanel[]>(() =>
285
+ panelsFor(TOP_LEVEL),
286
+ );
287
+ const [dismissingIds, setDismissingIds] = useState<ReadonlySet<string>>(
288
+ () => new Set(),
289
+ );
290
+
291
+ const wired = panels.map(panel => ({
292
+ ...panel,
293
+ onClick: (event: MouseEvent) => {
294
+ if (!event.metaKey) return;
295
+ setDismissingIds(prev => {
296
+ if (prev.has(panel.id)) return prev;
297
+ const next = new Set(prev);
298
+ next.add(panel.id);
299
+ return next;
300
+ });
301
+ },
302
+ }));
303
+
304
+ const handleDismissed = useCallback((id: string) => {
305
+ setPanels(prev => prev.filter(p => p.id !== id));
306
+ setDismissingIds(prev => {
307
+ if (!prev.has(id)) return prev;
308
+ const next = new Set(prev);
309
+ next.delete(id);
310
+ return next;
311
+ });
312
+ }, []);
313
+
314
+ return (
315
+ <FileCity3D
316
+ {...baseArgs}
317
+ elevatedScopePanels={wired}
318
+ dismissingPanelIds={dismissingIds}
319
+ onPanelDismissed={handleDismissed}
320
+ />
321
+ );
322
+ }
323
+
324
+ export const DismissOnCommandClick: Story = {
325
+ render: () => <DismissOnCommandClickStory />,
326
+ parameters: {
327
+ docs: {
328
+ description: {
329
+ story:
330
+ '⌘-click (or ctrl-click on non-Mac with `event.metaKey`) a panel ' +
331
+ 'to lift it toward the camera and fade it out. The story owns ' +
332
+ 'both the `panels` array and a `dismissingIds` set: clicking adds ' +
333
+ 'the id to that set, `FileCity3D` plays the spring, and once it ' +
334
+ 'settles `onPanelDismissed` fires so the story drops the panel ' +
335
+ 'from both pieces of state. The component never owns the ' +
336
+ 'truth — it just animates and notifies.',
337
+ },
338
+ },
339
+ },
340
+ };
@@ -1570,39 +1570,10 @@ const FileCityExplorerTemplate: React.FC = () => {
1570
1570
  const displayLabel = folderPath ? areaNameByCityPath.get(folderPath) : undefined;
1571
1571
  return displayLabel ? { ...panel, displayLabel } : panel;
1572
1572
  });
1573
- // Selection indicator: render a thin, slightly-larger panel underneath
1574
- // the selected folder's umbrella so an accent ring peeks out around its
1575
- // edges. Inserted *before* the umbrella in the list so the umbrella
1576
- // draws on top — only the inflated rim shows. If the folder is expanded
1577
- // (no umbrella in the panel list) findIndex returns -1 and no ring is
1578
- // drawn, which is exactly what we want.
1579
- if (selectedPanelFolder) {
1580
- const idx = panels.findIndex(p => p.id === `folder::${selectedPanelFolder}`);
1581
- if (idx >= 0) {
1582
- const target = panels[idx];
1583
- const inflate = 4;
1584
- const border: ElevatedScopePanel = {
1585
- id: `folder-border::${selectedPanelFolder}`,
1586
- color: '#fbbf24',
1587
- height: (target.height ?? 4) - 2,
1588
- thickness: 1,
1589
- bounds: {
1590
- minX: target.bounds.minX - inflate,
1591
- maxX: target.bounds.maxX + inflate,
1592
- minZ: target.bounds.minZ - inflate,
1593
- maxZ: target.bounds.maxZ + inflate,
1594
- },
1595
- };
1596
- const next = [...panels];
1597
- next.splice(idx, 0, border);
1598
- return next;
1599
- }
1600
- }
1601
1573
 
1602
1574
  return panels.length > 0 ? panels : undefined;
1603
1575
  }, [
1604
1576
  activeTab,
1605
- selectedPanelFolder,
1606
1577
  treeModel,
1607
1578
  folderTreeExpansion,
1608
1579
  setFocusDirectoryIfUnpinned,
@@ -1829,6 +1800,8 @@ const FileCityExplorerTemplate: React.FC = () => {
1829
1800
  focusDirectory={focusDirectory}
1830
1801
  highlightLayers={cityHighlightLayers}
1831
1802
  elevatedScopePanels={cityElevatedPanels ?? folderElevatedPanels}
1803
+ selectedPath={activeTab === 'files' ? selectedPanelFolder : null}
1804
+ selectionStyle={{ color: '#fbbf24' }}
1832
1805
  onBuildingClick={handleBuildingClick}
1833
1806
  animation={{
1834
1807
  startFlat: true,