torch-glare 2.1.2 → 2.1.3

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 (27) hide show
  1. package/apps/lib/components/Avatar.tsx +1 -1
  2. package/apps/lib/components/Card.tsx +68 -54
  3. package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
  4. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +56 -45
  5. package/apps/lib/components/DataViews/DataViewsHeader.tsx +130 -28
  6. package/apps/lib/components/DataViews/DataViewsLayout.tsx +32 -2
  7. package/apps/lib/components/DataViews/FilterPanel.tsx +148 -3
  8. package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
  9. package/apps/lib/components/DataViews/InboxView.tsx +263 -282
  10. package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
  11. package/apps/lib/components/DataViews/KanbanView.tsx +264 -153
  12. package/apps/lib/components/DataViews/PanelControls.tsx +10 -41
  13. package/apps/lib/components/DataViews/TreeView.tsx +220 -191
  14. package/apps/lib/components/DataViews/index.ts +6 -0
  15. package/apps/lib/components/DataViews/types.ts +30 -1
  16. package/apps/lib/components/Radio.tsx +18 -21
  17. package/apps/lib/components/Switch.tsx +3 -1
  18. package/apps/lib/components/Table.tsx +1 -1
  19. package/apps/lib/components/TreeFolder/TreeFolder.tsx +160 -137
  20. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +221 -93
  21. package/apps/lib/components/TreeFolder/types.ts +9 -0
  22. package/apps/lib/layouts/DataViewCard.tsx +76 -0
  23. package/dist/src/shared/copyComponentsRecursively.js +9 -1
  24. package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
  25. package/docs/components/data-views-config-panel.md +204 -0
  26. package/docs/components/data-views-layout.md +270 -0
  27. package/package.json +1 -1
@@ -9,7 +9,9 @@ export type TreeConfig = {
9
9
  orderField?: string
10
10
  nodeLabel?: string
11
11
  defaultExpanded?: "all" | "roots" | "none"
12
- defaultRightPane?: "table" | "details"
12
+ /** Right-pane mode for the selected tree node. `"details"` is accepted as a
13
+ * deprecated alias of `"card"` for backward compatibility. */
14
+ defaultRightPane?: "table" | "card" | "details"
13
15
  dndEnabled?: boolean
14
16
  }
15
17
 
@@ -51,6 +53,17 @@ export type FieldPreset =
51
53
  | { label: string; min?: number; max?: number }
52
54
  | { label: string; from?: string; to?: string }
53
55
 
56
+ // Palette keys for the Kanban column header pill. Kept in lockstep with
57
+ // `COLUMN_PALETTE` in KanbanView.tsx so consumers can pick a color per status
58
+ // via `FieldConfig.kanbanVariants`.
59
+ export type KanbanColumnColor =
60
+ | "gray"
61
+ | "purple"
62
+ | "orange"
63
+ | "blue"
64
+ | "green"
65
+ | "red"
66
+
54
67
  export type BadgeVariant =
55
68
  | "green"
56
69
  | "greenLight"
@@ -102,6 +115,16 @@ export type FieldConfig = {
102
115
  variants?: Record<string, BadgeVariant>
103
116
  defaultVariant?: BadgeVariant
104
117
 
118
+ // Per-status overrides for the Kanban board view. Keys must match
119
+ // `variants` keys (or any value present in the data). Lets consumers set a
120
+ // human-friendly column title and pick a column pill color without affecting
121
+ // the badge color used elsewhere.
122
+ kanbanVariants?: Record<
123
+ string,
124
+ { label?: string; color?: KanbanColumnColor }
125
+ >
126
+
127
+
105
128
  variant?: BadgeVariant
106
129
  limit?: number
107
130
 
@@ -131,6 +154,12 @@ export type FieldConfig = {
131
154
  filterable?: boolean
132
155
  filterLabel?: string
133
156
  filterOptions?: string[] | { label: string; value: string }[]
157
+ /**
158
+ * Categorical filter selection mode.
159
+ * - "multi" (default): checkboxes, multi-select. FilterValue is the array of picked options.
160
+ * - "single": radios, single-select. FilterValue is a 1-element array.
161
+ */
162
+ filterMode?: "single" | "multi"
134
163
  presets?: FieldPreset[]
135
164
  rangeMin?: number
136
165
  rangeMax?: number
@@ -1,9 +1,8 @@
1
- 'use client'
1
+ "use client";
2
2
  import { cn } from "../utils/cn";
3
3
  import { cva } from "class-variance-authority";
4
- import * as React from "react"
5
- import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
6
-
4
+ import * as React from "react";
5
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
7
6
 
8
7
  const RadioGroup = React.forwardRef<
9
8
  React.ElementRef<typeof RadioGroupPrimitive.Root>,
@@ -11,37 +10,35 @@ const RadioGroup = React.forwardRef<
11
10
  >(({ className, ...props }, ref) => {
12
11
  return (
13
12
  <RadioGroupPrimitive.Root
14
- className={cn("grid gap-2", className)}
13
+ className={cn("grid gap-0.5", className)}
15
14
  {...props}
16
15
  ref={ref}
17
16
  />
18
- )
19
- })
20
- RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
17
+ );
18
+ });
19
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
21
20
 
22
21
  const Radio = React.forwardRef<
23
22
  React.ElementRef<typeof RadioGroupPrimitive.Item>,
24
- React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> & { size?: "S" | "M" }
23
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> & {
24
+ size?: "S" | "M";
25
+ }
25
26
  >(({ className, size = "S", ...props }, ref) => {
26
27
  return (
27
28
  <RadioGroupPrimitive.Item
28
29
  ref={ref}
29
- className={cn(
30
- glareRadioStyles({ size }),
31
- className
32
- )}
30
+ className={cn(glareRadioStyles({ size }), className)}
33
31
  {...props}
34
32
  >
35
33
  <RadioGroupPrimitive.Indicator className="bg-white rounded-full flex items-center justify-center shrink-0">
36
34
  <i className="ri-record-circle-fill text-background-presentation-state-information-primary leading-none shrink-0"></i>
37
35
  </RadioGroupPrimitive.Indicator>
38
36
  </RadioGroupPrimitive.Item>
39
- )
40
- })
41
- Radio.displayName = "Radio"
42
-
43
- export { RadioGroup, Radio }
37
+ );
38
+ });
39
+ Radio.displayName = "Radio";
44
40
 
41
+ export { RadioGroup, Radio };
45
42
 
46
43
  const glareRadioStyles = cva(
47
44
  [
@@ -61,6 +58,6 @@ const glareRadioStyles = cva(
61
58
  S: ["w-[12px]", "h-[12px] [&_i]:text-[10px] [&_i]:scale-[1.5]"],
62
59
  M: ["w-[24px]", "h-[24px] [&_i]:text-[20px] [&_i]:scale-[1.47]"],
63
60
  },
64
- }
65
- }
66
- );
61
+ },
62
+ },
63
+ );
@@ -17,7 +17,9 @@ const Switch = React.forwardRef<
17
17
  >
18
18
  <SwitchPrimitives.Thumb
19
19
  className={cn(
20
- "pointer-events-none block w-[24px] h-[24px] rounded-full bg-background-presentation-switcher-knob shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-[21px] data-[state=unchecked]:translate-x-0"
20
+ "pointer-events-none block w-[24px] h-[24px] rounded-full bg-background-presentation-switcher-knob shadow-lg ring-0 transition-transform",
21
+ "data-[state=checked]:translate-x-[21px] data-[state=unchecked]:translate-x-0",
22
+ "rtl:data-[state=checked]:-translate-x-[21px] rtl:data-[state=unchecked]:translate-x-0"
21
23
  )}
22
24
  />
23
25
  </SwitchPrimitives.Root>
@@ -128,7 +128,7 @@ const TableHead = React.forwardRef<
128
128
  <th
129
129
  ref={headRef}
130
130
  className={cn(
131
- "relative py-[2px] px-[2px] border-b-[2px] border-border-presentation-table-header",
131
+ "relative py-[6px] px-[4px] border-b-[2px] border-border-presentation-table-header",
132
132
  )}
133
133
  >
134
134
  <div
@@ -1,4 +1,4 @@
1
- "use client"
1
+ "use client";
2
2
 
3
3
  import {
4
4
  forwardRef,
@@ -8,102 +8,121 @@ import {
8
8
  useRef,
9
9
  useState,
10
10
  type ReactNode,
11
- } from "react"
12
- import { cn } from "../../utils/cn"
13
- import type { Themes } from "../../utils/types"
14
- import { TreeFolderBreadcrumb } from "./TreeFolderBreadcrumb"
15
- import { TreeFolderRow } from "./TreeFolderRow"
16
- import { TreeFolderStyles } from "./TreeFolderStyles"
11
+ } from "react";
12
+ import { cn } from "../../utils/cn";
13
+ import type { Themes } from "../../utils/types";
14
+ import { TreeFolderBreadcrumb } from "./TreeFolderBreadcrumb";
15
+ import { TreeFolderRow } from "./TreeFolderRow";
16
+ import { TreeFolderStyles } from "./TreeFolderStyles";
17
17
  import {
18
18
  applyMove,
19
19
  descendantIds,
20
20
  findNode,
21
21
  findPath,
22
22
  toBreadcrumb,
23
- } from "./treeFolderUtils"
23
+ } from "./treeFolderUtils";
24
24
  import type {
25
25
  TreeFolderIconResolver,
26
26
  TreeFolderMoveArgs,
27
27
  TreeFolderNode,
28
28
  TreeFolderVisibleRow,
29
- } from "./types"
30
- import { useTreeFolderDnD } from "./useTreeFolderDnD"
29
+ } from "./types";
30
+ import { useTreeFolderDnD } from "./useTreeFolderDnD";
31
31
 
32
32
  export type TreeFolderProps = {
33
- data: TreeFolderNode[]
34
- selectedId?: string | null
35
- defaultSelectedId?: string | null
36
- onSelectionChange?: (id: string | null) => void
37
-
38
- expandedIds?: string[]
39
- defaultExpanded?: "all" | "roots" | "none" | string[]
40
- onExpandedChange?: (ids: string[]) => void
41
-
42
- dndEnabled?: boolean
43
- onMove?: (args: TreeFolderMoveArgs) => void
44
- onDataChange?: (next: TreeFolderNode[]) => void
45
-
46
- iconFor?: TreeFolderIconResolver
47
-
48
- title?: string
49
- showBreadcrumb?: boolean
50
- showHeader?: boolean
51
- highlightAncestors?: boolean
52
- highlightSubtree?: boolean
53
- headerAccessory?: ReactNode
54
- emptyState?: ReactNode
55
-
56
- rowHeight?: number
57
- indent?: number
33
+ data: TreeFolderNode[];
34
+ selectedId?: string | null;
35
+ defaultSelectedId?: string | null;
36
+ onSelectionChange?: (id: string | null) => void;
37
+
38
+ expandedIds?: string[];
39
+ defaultExpanded?: "all" | "roots" | "none" | string[];
40
+ onExpandedChange?: (ids: string[]) => void;
41
+
42
+ dndEnabled?: boolean;
43
+ onMove?: (args: TreeFolderMoveArgs) => void;
44
+ onDataChange?: (next: TreeFolderNode[]) => void;
45
+
46
+ iconFor?: TreeFolderIconResolver;
47
+
48
+ title?: string;
49
+ showBreadcrumb?: boolean;
50
+ showHeader?: boolean;
51
+ highlightAncestors?: boolean;
52
+ highlightSubtree?: boolean;
53
+ headerAccessory?: ReactNode;
54
+ emptyState?: ReactNode;
55
+
56
+ rowHeight?: number;
57
+ indent?: number;
58
58
  /** Optional. If set, the row strip will be at least this wide (in px) — useful
59
59
  * when you want the band to extend across a wider canvas regardless of content. */
60
- contentMinWidth?: number
60
+ contentMinWidth?: number;
61
61
 
62
- className?: string
63
- theme?: Themes
64
- }
62
+ className?: string;
63
+ theme?: Themes;
64
+ };
65
65
 
66
66
  export type TreeFolderHandle = {
67
- selectId: (id: string | null) => void
68
- expandAll: () => void
69
- collapseAll: () => void
70
- scrollToId: (id: string) => void
71
- }
67
+ selectId: (id: string | null) => void;
68
+ expandAll: () => void;
69
+ collapseAll: () => void;
70
+ scrollToId: (id: string) => void;
71
+ };
72
72
 
73
- const ROW_HEIGHT_DEFAULT = 28
74
- const INDENT_DEFAULT = 14
73
+ const ROW_HEIGHT_DEFAULT = 28;
74
+ const INDENT_DEFAULT = 14;
75
75
 
76
76
  function collectIds(nodes: TreeFolderNode[], out: string[] = []): string[] {
77
77
  for (const n of nodes) {
78
- out.push(n.id)
79
- if (n.children) collectIds(n.children, out)
78
+ out.push(n.id);
79
+ if (n.children) collectIds(n.children, out);
80
80
  }
81
- return out
81
+ return out;
82
82
  }
83
83
 
84
84
  function rootIds(nodes: TreeFolderNode[]): string[] {
85
- return nodes.map((n) => n.id)
85
+ return nodes.map((n) => n.id);
86
86
  }
87
87
 
88
88
  function flattenVisible(
89
89
  data: TreeFolderNode[],
90
90
  expanded: Set<string>,
91
91
  ): TreeFolderVisibleRow[] {
92
- const out: TreeFolderVisibleRow[] = []
92
+ const out: TreeFolderVisibleRow[] = [];
93
+ // `ancestorHasMore` tracks, for each open ancestor depth on the descent
94
+ // path, whether that ancestor still has more siblings to render. We push
95
+ // before descending and pop on the way back up; each row snapshots the
96
+ // current state so its connector gutters know which verticals to draw.
97
+ const ancestorHasMore: boolean[] = [];
93
98
  const walk = (
94
99
  nodes: TreeFolderNode[],
95
100
  level: number,
96
101
  parentId: string | null,
97
102
  ) => {
98
103
  nodes.forEach((node, idx) => {
99
- const isInternal = !!(node.children && node.children.length > 0)
100
- const isOpen = isInternal && expanded.has(node.id)
101
- out.push({ node, level, parentId, childIndex: idx, isOpen, isInternal })
102
- if (isOpen && node.children) walk(node.children, level + 1, node.id)
103
- })
104
- }
105
- walk(data, 0, null)
106
- return out
104
+ const isInternal = !!(node.children && node.children.length > 0);
105
+ const isOpen = isInternal && expanded.has(node.id);
106
+ const isLastChild = idx === nodes.length - 1;
107
+ out.push({
108
+ node,
109
+ level,
110
+ parentId,
111
+ childIndex: idx,
112
+ isOpen,
113
+ isInternal,
114
+ isLastChild,
115
+ ancestorHasMoreSiblings: ancestorHasMore.slice(),
116
+ });
117
+ if (isOpen && node.children) {
118
+ ancestorHasMore.push(!isLastChild);
119
+ walk(node.children, level + 1, node.id);
120
+ ancestorHasMore.pop();
121
+ }
122
+ });
123
+ };
124
+ walk(data, 0, null);
125
+ return out;
107
126
  }
108
127
 
109
128
  export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
@@ -123,7 +142,7 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
123
142
  title = "Layers",
124
143
  showBreadcrumb = true,
125
144
  showHeader = true,
126
- highlightAncestors = true,
145
+ highlightAncestors = false,
127
146
  highlightSubtree = true,
128
147
  headerAccessory,
129
148
  emptyState,
@@ -132,104 +151,104 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
132
151
  contentMinWidth,
133
152
  className,
134
153
  theme,
135
- } = props
154
+ } = props;
136
155
 
137
- const scrollRef = useRef<HTMLDivElement | null>(null)
156
+ const scrollRef = useRef<HTMLDivElement | null>(null);
138
157
 
139
158
  // ---- Selection state ----
140
159
  const [internalSelected, setInternalSelected] = useState<string | null>(
141
160
  defaultSelectedId,
142
- )
161
+ );
143
162
  const selectedId =
144
- controlledSelected !== undefined ? controlledSelected : internalSelected
145
- const isSelectionControlled = controlledSelected !== undefined
163
+ controlledSelected !== undefined ? controlledSelected : internalSelected;
164
+ const isSelectionControlled = controlledSelected !== undefined;
146
165
 
147
166
  // ---- Expansion state ----
148
167
  const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {
149
- if (Array.isArray(defaultExpanded)) return defaultExpanded
150
- if (defaultExpanded === "all") return collectIds(data)
151
- if (defaultExpanded === "none") return []
152
- return rootIds(data)
153
- })
154
- const expandedIds = controlledExpanded ?? internalExpanded
155
- const isExpansionControlled = controlledExpanded !== undefined
156
- const expandedSet = useMemo(() => new Set(expandedIds), [expandedIds])
168
+ if (Array.isArray(defaultExpanded)) return defaultExpanded;
169
+ if (defaultExpanded === "all") return collectIds(data);
170
+ if (defaultExpanded === "none") return [];
171
+ return rootIds(data);
172
+ });
173
+ const expandedIds = controlledExpanded ?? internalExpanded;
174
+ const isExpansionControlled = controlledExpanded !== undefined;
175
+ const expandedSet = useMemo(() => new Set(expandedIds), [expandedIds]);
157
176
 
158
177
  // ---- Visible rows ----
159
178
  const visibleRows = useMemo(
160
179
  () => flattenVisible(data, expandedSet),
161
180
  [data, expandedSet],
162
- )
181
+ );
163
182
  const rowsById = useMemo(() => {
164
- const m = new Map<string, TreeFolderVisibleRow>()
165
- for (const r of visibleRows) m.set(r.node.id, r)
166
- return m
167
- }, [visibleRows])
183
+ const m = new Map<string, TreeFolderVisibleRow>();
184
+ for (const r of visibleRows) m.set(r.node.id, r);
185
+ return m;
186
+ }, [visibleRows]);
168
187
 
169
188
  // ---- Ancestor + subtree highlight ----
170
189
  const ancestorPath = useMemo(
171
190
  () => (selectedId ? findPath(data, selectedId) : null),
172
191
  [data, selectedId],
173
- )
192
+ );
174
193
  const ancestorIdSet = useMemo(
175
194
  () => new Set((ancestorPath ?? []).slice(0, -1).map((n) => n.id)),
176
195
  [ancestorPath],
177
- )
196
+ );
178
197
  const isAncestorFn = useCallback(
179
198
  (id: string) => highlightAncestors && ancestorIdSet.has(id),
180
199
  [highlightAncestors, ancestorIdSet],
181
- )
200
+ );
182
201
 
183
202
  const descendantIdSet = useMemo(() => {
184
- if (!highlightSubtree || !selectedId) return new Set<string>()
185
- const node = findNode(data, selectedId)
186
- if (!node) return new Set<string>()
187
- return descendantIds(node, false)
188
- }, [data, selectedId, highlightSubtree])
203
+ if (!highlightSubtree || !selectedId) return new Set<string>();
204
+ const node = findNode(data, selectedId);
205
+ if (!node) return new Set<string>();
206
+ return descendantIds(node, false);
207
+ }, [data, selectedId, highlightSubtree]);
189
208
  const isDescendantOfSelected = useCallback(
190
209
  (id: string) => descendantIdSet.has(id),
191
210
  [descendantIdSet],
192
- )
211
+ );
193
212
 
194
213
  const breadcrumbItems = useMemo(
195
214
  () => (ancestorPath ? toBreadcrumb(ancestorPath) : []),
196
215
  [ancestorPath],
197
- )
216
+ );
198
217
 
199
218
  // ---- Handlers ----
200
219
  const handleSelect = useCallback(
201
220
  (id: string | null) => {
202
- if (!isSelectionControlled) setInternalSelected(id)
203
- onSelectionChange?.(id)
221
+ if (!isSelectionControlled) setInternalSelected(id);
222
+ onSelectionChange?.(id);
204
223
  },
205
224
  [isSelectionControlled, onSelectionChange],
206
- )
225
+ );
207
226
 
208
227
  const handleToggle = useCallback(
209
228
  (id: string) => {
210
229
  const next = expandedIds.includes(id)
211
230
  ? expandedIds.filter((x) => x !== id)
212
- : [...expandedIds, id]
213
- if (!isExpansionControlled) setInternalExpanded(next)
214
- onExpandedChange?.(next)
231
+ : [...expandedIds, id];
232
+ if (!isExpansionControlled) setInternalExpanded(next);
233
+ onExpandedChange?.(next);
215
234
  },
216
235
  [expandedIds, isExpansionControlled, onExpandedChange],
217
- )
236
+ );
218
237
 
219
238
  const handleMoveInternal = useCallback(
220
239
  (args: TreeFolderMoveArgs) => {
221
- onMove?.(args)
222
- if (onDataChange) onDataChange(applyMove(data, args))
240
+ onMove?.(args);
241
+ if (onDataChange) onDataChange(applyMove(data, args));
223
242
  },
224
243
  [data, onMove, onDataChange],
225
- )
244
+ );
226
245
 
227
246
  const scrollToId = useCallback((id: string) => {
228
247
  const el = scrollRef.current?.querySelector<HTMLElement>(
229
248
  `[data-row-id="${CSS.escape(id)}"]`,
230
- )
231
- el?.scrollIntoView({ block: "nearest", behavior: "smooth" })
232
- }, [])
249
+ );
250
+ el?.scrollIntoView({ block: "nearest", behavior: "smooth" });
251
+ }, []);
233
252
 
234
253
  // ---- DnD ----
235
254
  const { dragIds, dropTarget, getRowDragHandlers } = useTreeFolderDnD({
@@ -240,32 +259,32 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
240
259
  onMove: handleMoveInternal,
241
260
  canDrop: ({ parentId }) => {
242
261
  // Honor per-node `droppable: false` on the resolved parent.
243
- if (parentId == null) return true
244
- const target = findNode(data, parentId)
245
- if (!target) return false
246
- if (target.disabled || target.droppable === false) return false
247
- return true
262
+ if (parentId == null) return true;
263
+ const target = findNode(data, parentId);
264
+ if (!target) return false;
265
+ if (target.disabled || target.droppable === false) return false;
266
+ return true;
248
267
  },
249
- })
250
- const dragIdSet = useMemo(() => new Set(dragIds), [dragIds])
268
+ });
269
+ const dragIdSet = useMemo(() => new Set(dragIds), [dragIds]);
251
270
 
252
271
  // ---- Imperative handle ----
253
272
  useImperativeHandle(
254
273
  ref,
255
274
  () => ({
256
275
  selectId: (id: string | null) => {
257
- if (!isSelectionControlled) setInternalSelected(id)
258
- onSelectionChange?.(id)
259
- if (id) scrollToId(id)
276
+ if (!isSelectionControlled) setInternalSelected(id);
277
+ onSelectionChange?.(id);
278
+ if (id) scrollToId(id);
260
279
  },
261
280
  expandAll: () => {
262
- const all = collectIds(data)
263
- if (!isExpansionControlled) setInternalExpanded(all)
264
- onExpandedChange?.(all)
281
+ const all = collectIds(data);
282
+ if (!isExpansionControlled) setInternalExpanded(all);
283
+ onExpandedChange?.(all);
265
284
  },
266
285
  collapseAll: () => {
267
- if (!isExpansionControlled) setInternalExpanded([])
268
- onExpandedChange?.([])
286
+ if (!isExpansionControlled) setInternalExpanded([]);
287
+ onExpandedChange?.([]);
269
288
  },
270
289
  scrollToId,
271
290
  }),
@@ -277,12 +296,12 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
277
296
  onExpandedChange,
278
297
  scrollToId,
279
298
  ],
280
- )
299
+ );
281
300
 
282
- const isEmpty = data.length === 0
301
+ const isEmpty = data.length === 0;
283
302
  const stripStyle = contentMinWidth
284
303
  ? { minWidth: contentMinWidth }
285
- : undefined
304
+ : undefined;
286
305
 
287
306
  return (
288
307
  <div
@@ -306,8 +325,8 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
306
325
  <TreeFolderBreadcrumb
307
326
  items={breadcrumbItems}
308
327
  onSelect={(id) => {
309
- handleSelect(id)
310
- scrollToId(id)
328
+ handleSelect(id);
329
+ scrollToId(id);
311
330
  }}
312
331
  />
313
332
  </div>
@@ -320,19 +339,20 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
320
339
  className="tf-scroll flex-1 min-h-0 overflow-auto"
321
340
  >
322
341
  {isEmpty ? (
323
- emptyState ?? (
342
+ (emptyState ?? (
324
343
  <div className="text-xs text-content-presentation-global-tertiary p-3">
325
344
  Nothing here yet.
326
345
  </div>
327
- )
346
+ ))
328
347
  ) : (
329
348
  <div className="min-w-max" style={stripStyle}>
330
349
  {visibleRows.map((row, idx) => {
331
- const prevRow = visibleRows[idx - 1]
332
- const nextRow = visibleRows[idx + 1]
333
- const isSelected = selectedId === row.node.id
334
- const isDescendant = isDescendantOfSelected(row.node.id) && !isSelected
335
- const inBand = isSelected || isDescendant
350
+ const prevRow = visibleRows[idx - 1];
351
+ const nextRow = visibleRows[idx + 1];
352
+ const isSelected = selectedId === row.node.id;
353
+ const isDescendant =
354
+ isDescendantOfSelected(row.node.id) && !isSelected;
355
+ const inBand = isSelected || isDescendant;
336
356
 
337
357
  // Neighbour-aware band rounding: a row is "in-band" if it's the selected
338
358
  // node itself or one of its descendants. The Set lookup handles deep nesting.
@@ -340,20 +360,23 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
340
360
  inBand &&
341
361
  !!prevRow &&
342
362
  (prevRow.node.id === selectedId ||
343
- descendantIdSet.has(prevRow.node.id))
363
+ descendantIdSet.has(prevRow.node.id));
344
364
  const nextInBand =
345
365
  inBand &&
346
366
  !!nextRow &&
347
367
  (nextRow.node.id === selectedId ||
348
- descendantIdSet.has(nextRow.node.id))
368
+ descendantIdSet.has(nextRow.node.id));
349
369
 
350
- const isDragging = dragIdSet.has(row.node.id)
370
+ const isDragging = dragIdSet.has(row.node.id);
351
371
  const isDropTargetInside =
352
- dropTarget?.rowId === row.node.id && dropTarget.position === "inside"
372
+ dropTarget?.rowId === row.node.id &&
373
+ dropTarget.position === "inside";
353
374
  const isDropBefore =
354
- dropTarget?.rowId === row.node.id && dropTarget.position === "before"
375
+ dropTarget?.rowId === row.node.id &&
376
+ dropTarget.position === "before";
355
377
  const isDropAfter =
356
- dropTarget?.rowId === row.node.id && dropTarget.position === "after"
378
+ dropTarget?.rowId === row.node.id &&
379
+ dropTarget.position === "after";
357
380
 
358
381
  return (
359
382
  <TreeFolderRow
@@ -376,12 +399,12 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
376
399
  onToggle={handleToggle}
377
400
  dragHandlers={getRowDragHandlers(row.node.id)}
378
401
  />
379
- )
402
+ );
380
403
  })}
381
404
  </div>
382
405
  )}
383
406
  </div>
384
407
  </div>
385
- )
408
+ );
386
409
  },
387
- )
410
+ );