@noya-app/noya-designsystem 0.1.64 → 0.1.65

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 (115) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +501 -228
  5. package/dist/index.d.ts +501 -228
  6. package/dist/index.js +6738 -2571
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +6697 -2499
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -6
  11. package/src/components/ActivityIndicator.tsx +9 -4
  12. package/src/components/ActivityLog.tsx +197 -0
  13. package/src/components/AnimatePresence.tsx +37 -22
  14. package/src/components/Avatar.tsx +42 -25
  15. package/src/components/Banner.tsx +57 -11
  16. package/src/components/BaseToolbar.tsx +29 -7
  17. package/src/components/Breadcrumbs.tsx +2 -2
  18. package/src/components/Button.tsx +23 -24
  19. package/src/components/Checkbox.tsx +6 -35
  20. package/src/components/Chip.tsx +45 -33
  21. package/src/components/Collection.tsx +16 -11
  22. package/src/components/ColorSwatch.tsx +3 -3
  23. package/src/components/ColorSwatchControl.tsx +2 -2
  24. package/src/components/Combobox.tsx +6 -6
  25. package/src/components/ComboboxMenu.tsx +6 -5
  26. package/src/components/CommandPalette.tsx +1 -1
  27. package/src/components/Dialog.tsx +6 -12
  28. package/src/components/Divider.tsx +8 -8
  29. package/src/components/DraggableMenuButton.tsx +9 -5
  30. package/src/components/Drawer.tsx +5 -5
  31. package/src/components/DropdownMenu.tsx +1 -1
  32. package/src/components/EditableText.tsx +2 -2
  33. package/src/components/Fade.tsx +13 -13
  34. package/src/components/FileUploadIndicator.tsx +11 -11
  35. package/src/components/FloatingWindow.tsx +2 -2
  36. package/src/components/Grid.tsx +18 -11
  37. package/src/components/GridView.tsx +32 -25
  38. package/src/components/IVirtualizedList.tsx +5 -0
  39. package/src/components/Icons.tsx +24 -4
  40. package/src/components/InputField.tsx +19 -19
  41. package/src/components/InspectorContainer.tsx +2 -2
  42. package/src/components/InspectorPrimitives.tsx +15 -9
  43. package/src/components/Label.tsx +4 -3
  44. package/src/components/LabeledElementView.tsx +3 -3
  45. package/src/components/LabeledField.tsx +6 -6
  46. package/src/components/List.tsx +103 -45
  47. package/src/components/ListMenu.tsx +91 -0
  48. package/src/components/ListNavigator.tsx +106 -0
  49. package/src/components/ListView.tsx +20 -1158
  50. package/src/components/MediaThumbnail.tsx +56 -15
  51. package/src/components/Message.tsx +5 -5
  52. package/src/components/Navigator.tsx +461 -0
  53. package/src/components/NoyaLogo.tsx +1 -1
  54. package/src/components/Popover.tsx +9 -5
  55. package/src/components/Progress.tsx +19 -9
  56. package/src/components/ResizableContainer.tsx +187 -0
  57. package/src/components/RingProgress.tsx +128 -0
  58. package/src/components/ScrollArea.tsx +4 -4
  59. package/src/components/ScrollableSidebar.tsx +10 -0
  60. package/src/components/SearchCompletionMenu.tsx +3 -3
  61. package/src/components/Section.tsx +107 -57
  62. package/src/components/SegmentedControl.tsx +29 -29
  63. package/src/components/SelectMenu.tsx +13 -20
  64. package/src/components/SelectionToolbar.tsx +4 -3
  65. package/src/components/Slider.tsx +9 -9
  66. package/src/components/Spacer.tsx +2 -2
  67. package/src/components/StackNavigator.tsx +378 -0
  68. package/src/components/Stepper.tsx +88 -0
  69. package/src/components/Switch.tsx +4 -4
  70. package/src/components/Tabs.tsx +3 -3
  71. package/src/components/Text.tsx +23 -13
  72. package/src/components/TextArea.tsx +3 -3
  73. package/src/components/Toast.tsx +4 -4
  74. package/src/components/Toolbar.tsx +50 -9
  75. package/src/components/ToolbarDrawer.tsx +2 -2
  76. package/src/components/Tooltip.tsx +1 -1
  77. package/src/components/TreeView.tsx +11 -4
  78. package/src/components/UserPointer.tsx +4 -4
  79. package/src/components/ai-assistant/AIAssistantLayout.tsx +8 -8
  80. package/src/components/blocks/ImageBlockComponent.tsx +13 -7
  81. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +4 -4
  82. package/src/components/file-explorer/FileExplorerLayout.tsx +20 -8
  83. package/src/components/internal/Checkmark.tsx +1 -1
  84. package/src/components/internal/Menu.tsx +22 -18
  85. package/src/components/internal/MenuViewport.tsx +13 -6
  86. package/src/components/internal/SelectItem.tsx +3 -3
  87. package/src/components/internal/TextInput.tsx +5 -1
  88. package/src/components/listView/ListViewContexts.tsx +68 -0
  89. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  90. package/src/components/listView/ListViewRoot.tsx +521 -0
  91. package/src/components/listView/ListViewRow.tsx +475 -0
  92. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  93. package/src/components/listView/types.ts +11 -0
  94. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  95. package/src/components/sorting/DragRegistration.tsx +2 -1
  96. package/src/components/sorting/SharedDragProvider.tsx +91 -9
  97. package/src/components/sorting/Sortable.tsx +71 -17
  98. package/src/components/workspace/DrawerWorkspaceLayout.tsx +5 -5
  99. package/src/components/workspace/PanelWorkspaceLayout.tsx +65 -49
  100. package/src/components/workspace/VerticalTabMenu.tsx +25 -15
  101. package/src/components/workspace/WorkspaceLayout.tsx +30 -21
  102. package/src/contexts/DialogContext.tsx +17 -5
  103. package/src/hooks/useIndent.ts +3 -8
  104. package/src/index.css +24 -10
  105. package/src/index.tsx +10 -5
  106. package/src/utils/classNames.ts +52 -6
  107. package/src/utils/editableBlockStyles.ts +2 -2
  108. package/src/utils/formatByteSize.ts +1 -0
  109. package/src/utils/inputs.ts +8 -6
  110. package/src/utils/sketchColor.ts +0 -34
  111. package/tailwind.config.ts +1 -0
  112. package/src/components/FillInputField.tsx +0 -37
  113. package/src/components/FillPreviewBackground.tsx +0 -134
  114. package/src/components/GradientPicker.tsx +0 -90
  115. package/src/utils/getGradientBackground.tsx +0 -31
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  Active,
5
+ ClientRect,
5
6
  closestCenter,
6
7
  CollisionDetection,
7
8
  DndContext,
@@ -15,7 +16,12 @@ import {
15
16
  useSensor,
16
17
  useSensors,
17
18
  } from "@dnd-kit/core";
18
- import { Point, rectContainsPoint, unionRects } from "@noya-app/noya-geometry";
19
+ import {
20
+ Insets,
21
+ Point,
22
+ rectContainsPoint,
23
+ unionRects,
24
+ } from "@noya-app/noya-geometry";
19
25
  import { windowsOf } from "@noya-app/noya-utils";
20
26
  import { useIsomorphicLayoutEffect } from "@noya-app/react-utils";
21
27
  import * as React from "react";
@@ -45,6 +51,7 @@ export type SharedDragProviderProps = {
45
51
  acceptsDrop?: AcceptsDrop;
46
52
  onMoveItem?: MoveDragItemHandler;
47
53
  sharedDragProps?: SharedDragProps;
54
+ itemHitSlop?: Insets;
48
55
  };
49
56
 
50
57
  export function SharedDragProvider({
@@ -55,6 +62,7 @@ export function SharedDragProvider({
55
62
  dragRegistrationContext: DragRegistrationContext,
56
63
  activeDragContext: ActiveDragContext,
57
64
  },
65
+ itemHitSlop,
58
66
  }: SharedDragProviderProps) {
59
67
  const sensors = useSensors(
60
68
  useSensor(PointerSensor, {
@@ -216,8 +224,8 @@ export function SharedDragProvider({
216
224
  );
217
225
 
218
226
  const collisionDetection = useMemo(() => {
219
- return getItemFirstCollisionDetection(registeredLists);
220
- }, [registeredLists]);
227
+ return getItemFirstCollisionDetection(registeredLists, itemHitSlop);
228
+ }, [registeredLists, itemHitSlop]);
221
229
 
222
230
  return (
223
231
  <AnyDragContext.Provider value={true}>
@@ -279,9 +287,18 @@ export function withDragProvider<T extends object>(
279
287
 
280
288
  // Custom collision detection that prioritizes items over containers
281
289
  export const getItemFirstCollisionDetection =
282
- (registeredLists: Map<string, RegisteredList>): CollisionDetection =>
290
+ (
291
+ registeredLists: Map<string, RegisteredList>,
292
+ itemHitSlop: Insets | undefined
293
+ ): CollisionDetection =>
283
294
  (parameters) => {
284
- const pointerCollisions = pointerWithin(parameters);
295
+ const parametersWithHitSlop = getCollisionParametersWithHitSlop(
296
+ registeredLists,
297
+ parameters,
298
+ itemHitSlop
299
+ );
300
+
301
+ const pointerCollisions = pointerWithin(parametersWithHitSlop);
285
302
 
286
303
  if (pointerCollisions.length > 0) {
287
304
  const itemCollisions = pointerCollisions.filter((collision) => {
@@ -300,11 +317,11 @@ export const getItemFirstCollisionDetection =
300
317
  }
301
318
 
302
319
  const listCollision = pointerCollisions.at(0);
303
- const nearestCenters = closestCenter(parameters);
320
+ const nearestCenters = closestCenter(parametersWithHitSlop);
304
321
 
305
322
  // Handle a pointer between items by checking if the pointer is within
306
323
  // a window of two items.
307
- if (parameters.pointerCoordinates && listCollision) {
324
+ if (parametersWithHitSlop.pointerCoordinates && listCollision) {
308
325
  const list = registeredLists.get(listCollision.id as string);
309
326
 
310
327
  if (list) {
@@ -312,7 +329,7 @@ export const getItemFirstCollisionDetection =
312
329
 
313
330
  for (const pair of pairs) {
314
331
  const clientRects = pair.map(
315
- (key) => parameters.droppableRects.get(key)!
332
+ (key) => parametersWithHitSlop.droppableRects.get(key)!
316
333
  );
317
334
 
318
335
  const merged = unionRects(
@@ -324,7 +341,9 @@ export const getItemFirstCollisionDetection =
324
341
  }))
325
342
  );
326
343
 
327
- if (rectContainsPoint(merged, parameters.pointerCoordinates)) {
344
+ if (
345
+ rectContainsPoint(merged, parametersWithHitSlop.pointerCoordinates)
346
+ ) {
328
347
  return nearestCenters.filter((center) =>
329
348
  pair.some((id) => id === center.id)
330
349
  );
@@ -337,6 +356,69 @@ export const getItemFirstCollisionDetection =
337
356
  return pointerCollisions;
338
357
  };
339
358
 
359
+ type CollisionDetectionParameters = Parameters<CollisionDetection>[0];
360
+
361
+ function getCollisionParametersWithHitSlop(
362
+ registeredLists: Map<string, RegisteredList>,
363
+ parameters: CollisionDetectionParameters,
364
+ defaultHitSlop: Insets | undefined
365
+ ): CollisionDetectionParameters {
366
+ const adjustedDroppableRects = createDroppableRectsWithHitSlop(
367
+ registeredLists,
368
+ parameters.droppableRects,
369
+ defaultHitSlop
370
+ );
371
+
372
+ if (!adjustedDroppableRects) {
373
+ return parameters;
374
+ }
375
+
376
+ return {
377
+ ...parameters,
378
+ droppableRects: adjustedDroppableRects,
379
+ };
380
+ }
381
+
382
+ function createDroppableRectsWithHitSlop(
383
+ registeredLists: Map<string, RegisteredList>,
384
+ droppableRects: CollisionDetectionParameters["droppableRects"],
385
+ defaultHitSlop: Insets | undefined
386
+ ): Map<string | number, ClientRect> | undefined {
387
+ let adjustedRects: Map<string | number, ClientRect> | undefined;
388
+
389
+ for (const [, list] of registeredLists) {
390
+ const hitSlop = list.itemHitSlop ?? defaultHitSlop;
391
+
392
+ if (!hitSlop) continue;
393
+
394
+ for (const key of list.keys) {
395
+ const rect = droppableRects.get(key);
396
+
397
+ if (!rect) continue;
398
+
399
+ if (!adjustedRects) {
400
+ adjustedRects = new Map(droppableRects);
401
+ }
402
+
403
+ adjustedRects.set(key, expandRectByInsets(rect, hitSlop));
404
+ }
405
+ }
406
+
407
+ return adjustedRects;
408
+ }
409
+
410
+ function expandRectByInsets(rect: ClientRect, hitSlop: Insets): ClientRect {
411
+ return {
412
+ ...rect,
413
+ left: rect.left - hitSlop.left,
414
+ right: rect.right + hitSlop.right,
415
+ top: rect.top - hitSlop.top,
416
+ bottom: rect.bottom + hitSlop.bottom,
417
+ width: rect.width + hitSlop.left + hitSlop.right,
418
+ height: rect.height + hitSlop.top + hitSlop.bottom,
419
+ };
420
+ }
421
+
340
422
  function findDropTarget({
341
423
  activationPoint,
342
424
  delta,
@@ -7,6 +7,7 @@ import {
7
7
  useSortable,
8
8
  verticalListSortingStrategy,
9
9
  } from "@dnd-kit/sortable";
10
+ import { Insets } from "@noya-app/noya-geometry";
10
11
  import * as React from "react";
11
12
  import {
12
13
  ActiveDragContext,
@@ -30,12 +31,21 @@ import {
30
31
 
31
32
  type UseSortableReturnType = ReturnType<typeof useSortable>;
32
33
 
33
- type ItemChildrenProps<T> = {
34
- ref: React.Ref<T>;
35
- relativeDropPosition?: RelativeDropPosition;
34
+ export type ItemChildrenProps<T> = {
35
+ ref: React.RefCallback<T>;
36
36
  style?: React.CSSProperties;
37
+ onPointerDown?: React.PointerEventHandler;
37
38
  } & UseSortableReturnType["attributes"];
38
39
 
40
+ export type ItemChildrenExtraProps = {
41
+ isDragging: boolean;
42
+ isOverlay: boolean;
43
+ relativeDropPosition: RelativeDropPosition | undefined;
44
+ setActivatorNodeRef: React.RefCallback<HTMLElement>;
45
+ setDraggableNodeRef: React.RefCallback<HTMLElement>;
46
+ setDroppableNodeRef: React.RefCallback<HTMLElement>;
47
+ };
48
+
39
49
  // Context for drop indicators within a sortable list
40
50
  interface SortableItemContextProps {
41
51
  acceptsDrop: DropValidator;
@@ -117,11 +127,13 @@ interface RootProps {
117
127
  children?: React.ReactNode;
118
128
  renderOverlay?: (index: number) => React.ReactNode;
119
129
  onMoveItem?: MoveDragItemHandler;
130
+ onIsDraggingChange?: (isDragging: boolean) => void;
120
131
  acceptsFromList?: AcceptsFromList;
121
132
  acceptsDrop?: DropValidator;
122
133
  getDropTargetParentIndex?: (index: number) => number | undefined;
123
134
  sharedDragProps?: SharedDragProps;
124
135
  containerRef?: React.RefObject<HTMLElement | null>;
136
+ itemHitSlop?: Insets;
125
137
  }
126
138
 
127
139
  function SortableItem<TElement extends HTMLElement>({
@@ -131,14 +143,26 @@ function SortableItem<TElement extends HTMLElement>({
131
143
  }: {
132
144
  id: UniqueIdentifier;
133
145
  disabled?: boolean;
134
- children: (props: ItemChildrenProps<TElement>) => React.ReactNode;
146
+ children: (
147
+ props: ItemChildrenProps<TElement>,
148
+ extraProps: Omit<ItemChildrenExtraProps, "isOverlay">
149
+ ) => React.ReactNode;
135
150
  }) {
136
151
  const { acceptsDrop, listId, getDropTargetParentIndex } =
137
152
  React.useContext(SortableItemContext);
138
153
 
139
154
  const dragItemKey = createDragItemKey(listId, id);
140
155
 
141
- const { attributes, listeners, setNodeRef, isDragging, index } = useSortable({
156
+ const {
157
+ attributes,
158
+ listeners,
159
+ setNodeRef,
160
+ isDragging,
161
+ index,
162
+ setActivatorNodeRef,
163
+ setDraggableNodeRef,
164
+ setDroppableNodeRef,
165
+ } = useSortable({
142
166
  id: dragItemKey,
143
167
  disabled,
144
168
  });
@@ -156,21 +180,28 @@ function SortableItem<TElement extends HTMLElement>({
156
180
  getDropTargetParentIndex,
157
181
  });
158
182
 
159
- return children({
160
- ref,
161
- ...attributes,
162
- ...listeners,
163
- relativeDropPosition,
164
- style: {
165
- opacity: isDragging ? 0.5 : 1,
183
+ return children(
184
+ {
185
+ ref,
186
+ ...attributes,
187
+ ...listeners,
188
+ style: { opacity: isDragging ? 0.5 : 1 },
166
189
  },
167
- });
190
+ {
191
+ isDragging,
192
+ setActivatorNodeRef,
193
+ setDraggableNodeRef,
194
+ setDroppableNodeRef,
195
+ relativeDropPosition,
196
+ }
197
+ );
168
198
  }
169
199
 
170
200
  function SortableRoot_({
171
201
  id: idProp,
172
202
  keys: keysProp,
173
203
  onMoveItem,
204
+ onIsDraggingChange,
174
205
  renderOverlay,
175
206
  acceptsFromList,
176
207
  acceptsDrop = defaultAcceptsDrop,
@@ -179,6 +210,7 @@ function SortableRoot_({
179
210
  getDropTargetParentIndex,
180
211
  sharedDragProps,
181
212
  containerRef,
213
+ itemHitSlop,
182
214
  }: RootProps) {
183
215
  const defaultId = React.useId();
184
216
  const id = idProp ?? defaultId;
@@ -234,6 +266,7 @@ function SortableRoot_({
234
266
 
235
267
  return relativeDropPosition;
236
268
  },
269
+ itemHitSlop,
237
270
  };
238
271
 
239
272
  registerList(listConfig);
@@ -251,6 +284,7 @@ function SortableRoot_({
251
284
  registerList,
252
285
  unregisterList,
253
286
  keys,
287
+ itemHitSlop,
254
288
  ]);
255
289
 
256
290
  const contextValue: SortableItemContextProps = React.useMemo(
@@ -265,6 +299,16 @@ function SortableRoot_({
265
299
  [keys, acceptsDrop, id, getDropTargetParentIndex, sharedDragProps]
266
300
  );
267
301
 
302
+ // Notify drag state changes to consumers if requested
303
+ const activeDragContext =
304
+ sharedDragProps?.activeDragContext ?? ActiveDragContext;
305
+ const dragState = React.useContext(activeDragContext);
306
+ const isDragging = !!dragState?.source;
307
+
308
+ React.useEffect(() => {
309
+ onIsDraggingChange?.(isDragging);
310
+ }, [isDragging, onIsDraggingChange]);
311
+
268
312
  // Set the droppable ref after every render.
269
313
  // Since we don't render the dom node internally, we aggressively set it
270
314
  // after every render to make sure we are using the latest.
@@ -300,7 +344,7 @@ export interface SortableProps<TItem, TElement extends HTMLElement>
300
344
  renderItem: (
301
345
  item: TItem,
302
346
  props: ItemChildrenProps<TElement>,
303
- { isOverlay }: { isOverlay: boolean }
347
+ extraProps: { isOverlay: boolean } & ItemChildrenExtraProps
304
348
  ) => React.ReactNode;
305
349
  }
306
350
 
@@ -330,7 +374,14 @@ const Sortable_ = function Sortable_<TItem, TElement extends HTMLElement>({
330
374
  "aria-describedby": "Item being dragged",
331
375
  role: "button",
332
376
  },
333
- { isOverlay: true }
377
+ {
378
+ isOverlay: true,
379
+ setActivatorNodeRef: () => {},
380
+ setDraggableNodeRef: () => {},
381
+ setDroppableNodeRef: () => {},
382
+ relativeDropPosition: undefined,
383
+ isDragging: true,
384
+ }
334
385
  );
335
386
  },
336
387
  [renderItem, items]
@@ -344,8 +395,11 @@ const Sortable_ = function Sortable_<TItem, TElement extends HTMLElement>({
344
395
  >
345
396
  {keys.map((key, index) => (
346
397
  <SortableItem<TElement> key={key} id={key}>
347
- {(props) => {
348
- return renderItem(items[index], props, { isOverlay: false });
398
+ {(props, extraProps) => {
399
+ return renderItem(items[index], props, {
400
+ isOverlay: false,
401
+ ...extraProps,
402
+ });
349
403
  }}
350
404
  </SortableItem>
351
405
  ))}
@@ -68,7 +68,7 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
68
68
  <div
69
69
  ref={portalContainer}
70
70
  id={EDITOR_PANEL_GROUP_ID}
71
- className="flex flex-1 relative focus:outline-none"
71
+ className="n-flex n-flex-1 n-relative focus:n-outline-none"
72
72
  >
73
73
  {hasTabs && (
74
74
  <VerticalTabMenu
@@ -111,12 +111,12 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
111
111
  }}
112
112
  />
113
113
  )}
114
- {hasTabs && <DividerVertical className="h-full" />}
115
- <div className="flex-1 flex relative">{centerPanel}</div>
114
+ {hasTabs && <DividerVertical className="n-h-full" />}
115
+ <div className="n-flex-1 n-flex n-relative">{centerPanel}</div>
116
116
  {leftPanel && (
117
117
  <Drawer
118
118
  ref={leftSidebarRef}
119
- className="flex-1"
119
+ className="n-flex-1"
120
120
  style={{
121
121
  left: 48,
122
122
  maxWidth: "calc(100% - 48px)",
@@ -143,7 +143,7 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
143
143
  <Drawer
144
144
  id={RIGHT_SIDEBAR_ID}
145
145
  ref={rightSidebarRef}
146
- className="flex-1"
146
+ className="n-flex-1"
147
147
  style={{
148
148
  left: 48,
149
149
  maxWidth: "calc(100% - 48px)",
@@ -5,7 +5,7 @@ import {
5
5
  memoGeneric,
6
6
  usePersistentStateObject,
7
7
  } from "@noya-app/react-utils";
8
- import React, { useMemo, useRef } from "react";
8
+ import React, { useCallback, useMemo, useRef } from "react";
9
9
  import {
10
10
  ImperativePanelGroupHandle,
11
11
  ImperativePanelHandle,
@@ -106,26 +106,68 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
106
106
  });
107
107
  }, [layoutGroup, propertyKey, layoutIds, hasLeftPanel, hasRightPanel]);
108
108
 
109
+ const handleLeftTabChange = useCallback(
110
+ (value: LeftTab) => {
111
+ if (value === leftTabValue) {
112
+ if (leftSidebarRef.current?.isExpanded()) {
113
+ leftSidebarRef.current?.collapse();
114
+ } else {
115
+ leftSidebarRef.current?.expand();
116
+ }
117
+ } else {
118
+ if (!leftSidebarRef.current?.isExpanded()) {
119
+ leftSidebarRef.current?.expand();
120
+ }
121
+ onChangeLeftTab?.(value);
122
+ }
123
+ },
124
+ [leftSidebarRef, leftTabValue, onChangeLeftTab]
125
+ );
126
+
127
+ const handleRightTabChange = useCallback(
128
+ (value: RightTab) => {
129
+ if (value === rightTabValue) {
130
+ if (rightSidebarRef.current?.isExpanded()) {
131
+ rightSidebarRef.current?.collapse();
132
+ } else {
133
+ rightSidebarRef.current?.expand();
134
+ }
135
+ } else {
136
+ if (!rightSidebarRef.current?.isExpanded()) {
137
+ rightSidebarRef.current?.expand();
138
+ }
139
+ onChangeRightTab?.(value);
140
+ }
141
+ },
142
+ [onChangeRightTab, rightSidebarRef, rightTabValue]
143
+ );
144
+
145
+ const handleLayoutChange = useCallback(
146
+ (layout: number[]) => {
147
+ setData((prev) => {
148
+ const newData = {
149
+ ...prev,
150
+ [propertyKey]: {
151
+ layout: layout.map((size) => toPixelSize(size)),
152
+ expandToSizes: prev?.[propertyKey]?.expandToSizes ?? {},
153
+ },
154
+ };
155
+
156
+ return newData;
157
+ });
158
+ },
159
+ [setData, propertyKey]
160
+ );
161
+
109
162
  return (
110
163
  <PanelGroup
111
164
  ref={panelGroupRef}
112
165
  id={EDITOR_PANEL_GROUP_ID}
113
- className="flex-1"
166
+ className="n-flex-1"
114
167
  direction="horizontal"
115
168
  storage={panelStorage}
116
169
  autoSaveId={autoSaveId}
117
- onLayout={(layout) => {
118
- setData((prev) => {
119
- const newData = {
120
- ...prev,
121
- [propertyKey]: {
122
- layout: layout.map((size) => toPixelSize(size)),
123
- expandToSizes: prev?.[propertyKey]?.expandToSizes ?? {},
124
- },
125
- };
126
- return newData;
127
- });
128
- }}
170
+ onLayout={handleLayoutChange}
129
171
  >
130
172
  {leftPanel && (
131
173
  <>
@@ -135,28 +177,15 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
135
177
  items={leftTabItems}
136
178
  activeValue={leftTabValue}
137
179
  isSidebarCollapsed={leftSidebarCollapsed}
138
- onChange={(value) => {
139
- if (value === leftTabValue) {
140
- if (leftSidebarRef.current?.isExpanded()) {
141
- leftSidebarRef.current?.collapse();
142
- } else {
143
- leftSidebarRef.current?.expand();
144
- }
145
- } else {
146
- if (!leftSidebarRef.current?.isExpanded()) {
147
- leftSidebarRef.current?.expand();
148
- }
149
- onChangeLeftTab?.(value);
150
- }
151
- }}
180
+ onChange={handleLeftTabChange}
152
181
  />
153
182
  )}
154
183
  {leftSidebarOptions.showTabs &&
155
184
  leftTabItems &&
156
- !leftSidebarCollapsed && <DividerVertical className="h-full" />}
185
+ !leftSidebarCollapsed && <DividerVertical className="n-h-full" />}
157
186
  <Panel
158
187
  id={LEFT_SIDEBAR_ID}
159
- className="relative"
188
+ className="n-relative"
160
189
  order={1}
161
190
  ref={leftSidebarRef as React.RefObject<ImperativePanelHandle>}
162
191
  defaultSize={leftSidebarOptions.initialSize}
@@ -171,7 +200,7 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
171
200
  >
172
201
  {leftSidebarCollapsed ? null : leftPanel}
173
202
  </Panel>
174
- <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
203
+ <PanelResizeHandle className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
175
204
  </>
176
205
  )}
177
206
  <Panel
@@ -179,16 +208,16 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
179
208
  order={2}
180
209
  defaultSize={centerPanelPercentage}
181
210
  minSize={10}
182
- className="flex relative"
211
+ className="n-flex n-relative"
183
212
  >
184
213
  {centerPanel}
185
214
  </Panel>
186
215
  {rightPanel && (
187
216
  <>
188
- <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
217
+ <PanelResizeHandle className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
189
218
  <Panel
190
219
  id={RIGHT_SIDEBAR_ID}
191
- className="relative"
220
+ className="n-relative"
192
221
  order={3}
193
222
  ref={rightSidebarRef as React.RefObject<ImperativePanelHandle>}
194
223
  minSize={rightSidebarOptions.minSize}
@@ -205,27 +234,14 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
205
234
  </Panel>
206
235
  {rightSidebarOptions.showTabs &&
207
236
  rightTabItems &&
208
- !rightSidebarCollapsed && <DividerVertical className="h-full" />}
237
+ !rightSidebarCollapsed && <DividerVertical className="n-h-full" />}
209
238
  {rightSidebarOptions.showTabs && rightTabItems && (
210
239
  <VerticalTabMenu
211
240
  tooltipSide="left"
212
241
  items={rightTabItems}
213
242
  activeValue={rightTabValue}
214
243
  isSidebarCollapsed={rightSidebarCollapsed}
215
- onChange={(value) => {
216
- if (value === rightTabValue) {
217
- if (rightSidebarRef.current?.isExpanded()) {
218
- rightSidebarRef.current?.collapse();
219
- } else {
220
- rightSidebarRef.current?.expand();
221
- }
222
- } else {
223
- if (!rightSidebarRef.current?.isExpanded()) {
224
- rightSidebarRef.current?.expand();
225
- }
226
- onChangeRightTab?.(value);
227
- }
228
- }}
244
+ onChange={handleRightTabChange}
229
245
  />
230
246
  )}
231
247
  </>
@@ -1,36 +1,46 @@
1
- import React, { ComponentProps } from "react";
1
+ import { memoGeneric } from "@noya-app/react-utils";
2
+ import React, { ComponentProps, useMemo } from "react";
2
3
  import { cssVarNames } from "../../theme";
3
4
  import { MenuItem } from "../internal/Menu";
4
5
  import { ToolbarMenu } from "../Toolbar";
5
6
  import { Tooltip } from "../Tooltip";
6
7
 
7
- export function VerticalTabMenu<TabValue extends string>({
8
- items,
9
- activeValue,
10
- isSidebarCollapsed,
11
- onChange,
12
- tooltipSide,
13
- }: {
8
+ type VerticalTabMenuProps<TabValue extends string> = {
14
9
  items: MenuItem<TabValue>[];
15
10
  activeValue: TabValue | undefined;
16
11
  isSidebarCollapsed: boolean;
17
12
  onChange: (value: TabValue) => void;
18
13
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
19
- }) {
14
+ };
15
+
16
+ export const VerticalTabMenu = memoGeneric(function VerticalTabMenu<
17
+ TabValue extends string,
18
+ >({
19
+ items,
20
+ activeValue,
21
+ isSidebarCollapsed,
22
+ onChange,
23
+ tooltipSide,
24
+ }: VerticalTabMenuProps<TabValue>) {
25
+ const style = useMemo(() => {
26
+ return {
27
+ [cssVarNames.colors.inputBackground]: "transparent",
28
+ [cssVarNames.colors.buttonBackground]: "transparent",
29
+ };
30
+ }, []);
31
+
20
32
  return (
21
33
  <div
22
- className="w-[47px] h-full bg-sidebar-background flex flex-col items-center py-3 gap-3"
23
- style={{
24
- [cssVarNames.colors.inputBackground]: "transparent",
25
- [cssVarNames.colors.buttonBackground]: "transparent",
26
- }}
34
+ className="n-w-[47px] n-h-full n-bg-sidebar-background n-flex n-flex-col n-items-center n-py-3 n-gap-3"
35
+ style={style}
27
36
  >
28
37
  <ToolbarMenu
29
38
  items={items}
30
39
  activeValue={isSidebarCollapsed ? undefined : activeValue}
31
40
  onSelectMenuItem={onChange}
32
41
  tooltipSide={tooltipSide}
42
+ displayFilter="iconOnly"
33
43
  />
34
44
  </div>
35
45
  );
36
- }
46
+ });