@noya-app/noya-designsystem 0.1.63 → 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 (143) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +648 -333
  5. package/dist/index.d.ts +648 -333
  6. package/dist/index.js +13813 -9157
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +13949 -9269
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +10 -22
  11. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  12. package/src/__tests__/workspaceLayout.test.ts +281 -0
  13. package/src/components/ActionMenu.tsx +2 -0
  14. package/src/components/ActivityIndicator.tsx +13 -6
  15. package/src/components/ActivityLog.tsx +197 -0
  16. package/src/components/AnimatePresence.tsx +43 -26
  17. package/src/components/Avatar.tsx +45 -26
  18. package/src/components/Banner.tsx +57 -11
  19. package/src/components/BaseToolbar.tsx +67 -34
  20. package/src/components/Breadcrumbs.tsx +4 -2
  21. package/src/components/Button.tsx +28 -23
  22. package/src/components/Checkbox.tsx +6 -35
  23. package/src/components/Chip.tsx +47 -33
  24. package/src/components/Collection.tsx +20 -12
  25. package/src/components/ColorSwatch.tsx +42 -22
  26. package/src/components/ColorSwatchControl.tsx +7 -5
  27. package/src/components/Combobox.tsx +8 -6
  28. package/src/components/ComboboxMenu.tsx +6 -5
  29. package/src/components/CommandPalette.tsx +3 -1
  30. package/src/components/ContextMenu.tsx +3 -1
  31. package/src/components/Dialog.tsx +9 -13
  32. package/src/components/DimensionInput.tsx +2 -0
  33. package/src/components/Divider.tsx +18 -14
  34. package/src/components/DraggableMenuButton.tsx +11 -5
  35. package/src/components/Drawer.tsx +14 -6
  36. package/src/components/DropdownMenu.tsx +4 -2
  37. package/src/components/EditableText.tsx +4 -2
  38. package/src/components/Fade.tsx +13 -13
  39. package/src/components/FileUploadIndicator.tsx +108 -0
  40. package/src/components/FloatingWindow.tsx +4 -2
  41. package/src/components/Grid.tsx +58 -42
  42. package/src/components/GridView.tsx +65 -36
  43. package/src/components/IVirtualizedList.tsx +5 -0
  44. package/src/components/IconButton.tsx +2 -0
  45. package/src/components/Icons.tsx +24 -4
  46. package/src/components/InputField.tsx +21 -19
  47. package/src/components/InspectorContainer.tsx +2 -2
  48. package/src/components/InspectorPrimitives.tsx +15 -9
  49. package/src/components/Label.tsx +4 -3
  50. package/src/components/LabeledElementView.tsx +9 -5
  51. package/src/components/LabeledField.tsx +8 -6
  52. package/src/components/List.tsx +112 -46
  53. package/src/components/ListMenu.tsx +91 -0
  54. package/src/components/ListNavigator.tsx +106 -0
  55. package/src/components/ListView.tsx +22 -1147
  56. package/src/components/MediaThumbnail.tsx +84 -14
  57. package/src/components/Message.tsx +11 -12
  58. package/src/components/Navigator.tsx +461 -0
  59. package/src/components/NoyaLogo.tsx +1 -1
  60. package/src/components/Popover.tsx +12 -6
  61. package/src/components/Progress.tsx +31 -8
  62. package/src/components/ResizableContainer.tsx +187 -0
  63. package/src/components/RingProgress.tsx +128 -0
  64. package/src/components/ScrollArea.tsx +7 -5
  65. package/src/components/ScrollableSidebar.tsx +10 -0
  66. package/src/components/SearchCompletionMenu.tsx +5 -3
  67. package/src/components/Section.tsx +109 -57
  68. package/src/components/SegmentedControl.tsx +51 -25
  69. package/src/components/SelectMenu.tsx +16 -21
  70. package/src/components/SelectionToolbar.tsx +7 -4
  71. package/src/components/Slider.tsx +12 -10
  72. package/src/components/Spacer.tsx +2 -2
  73. package/src/components/StackNavigator.tsx +378 -0
  74. package/src/components/Stepper.tsx +88 -0
  75. package/src/components/Switch.tsx +7 -5
  76. package/src/components/Tabs.tsx +6 -4
  77. package/src/components/Text.tsx +29 -18
  78. package/src/components/TextArea.tsx +5 -3
  79. package/src/components/Toast.tsx +5 -5
  80. package/src/components/Toolbar.tsx +57 -10
  81. package/src/components/ToolbarDrawer.tsx +29 -0
  82. package/src/components/Tooltip.tsx +2 -2
  83. package/src/components/TreeView.tsx +13 -4
  84. package/src/components/UserPointer.tsx +6 -4
  85. package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
  86. package/src/components/blocks/ImageBlockComponent.tsx +162 -0
  87. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
  88. package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
  89. package/src/components/internal/Checkmark.tsx +24 -0
  90. package/src/components/internal/Menu.tsx +24 -19
  91. package/src/components/internal/MenuViewport.tsx +15 -6
  92. package/src/components/internal/SelectItem.tsx +7 -26
  93. package/src/components/internal/TextInput.tsx +7 -1
  94. package/src/components/listView/ListViewContexts.tsx +68 -0
  95. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  96. package/src/components/listView/ListViewRoot.tsx +521 -0
  97. package/src/components/listView/ListViewRow.tsx +475 -0
  98. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  99. package/src/components/listView/types.ts +11 -0
  100. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  101. package/src/components/sorting/DragRegistration.tsx +10 -5
  102. package/src/components/sorting/SharedDragProvider.tsx +134 -27
  103. package/src/components/sorting/Sortable.tsx +90 -27
  104. package/src/components/sorting/createSharedDrag.tsx +7 -6
  105. package/src/components/sorting/sorting.ts +6 -9
  106. package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
  107. package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
  108. package/src/components/workspace/VerticalTabMenu.tsx +46 -0
  109. package/src/components/workspace/WorkspaceLayout.tsx +76 -63
  110. package/src/components/workspace/constants.ts +4 -0
  111. package/src/components/workspace/panelStorage.ts +216 -0
  112. package/src/components/workspace/types.ts +11 -4
  113. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  114. package/src/contexts/DialogContext.tsx +19 -5
  115. package/src/contexts/FloatingWindowContext.tsx +2 -0
  116. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  117. package/src/contexts/ImageDataContext.tsx +2 -0
  118. package/src/contexts/LinkComponentContext.tsx +17 -0
  119. package/src/contexts/OpenPortalsContext.tsx +2 -0
  120. package/src/contexts/PortalScopeContext.tsx +2 -0
  121. package/src/hooks/mergeEventHandlers.ts +1 -1
  122. package/src/hooks/useHover.ts +30 -17
  123. package/src/hooks/useIndent.ts +5 -8
  124. package/src/hooks/useLabel.ts +2 -0
  125. package/src/hooks/useObjectURL.ts +5 -3
  126. package/src/hooks/usePlatform.ts +5 -3
  127. package/src/hooks/useTheme.ts +3 -1
  128. package/src/index.css +43 -10
  129. package/src/index.tsx +15 -7
  130. package/src/utils/classNames.ts +56 -9
  131. package/src/utils/combobox.ts +2 -0
  132. package/src/utils/editableBlockStyles.ts +2 -2
  133. package/src/utils/formatByteSize.ts +9 -0
  134. package/src/utils/inputs.ts +8 -6
  135. package/src/utils/sketchColor.ts +0 -34
  136. package/src/utils/withSeparatorElements.ts +1 -1
  137. package/tailwind.config.ts +1 -0
  138. package/src/components/FillInputField.tsx +0 -37
  139. package/src/components/FillPreviewBackground.tsx +0 -132
  140. package/src/components/GradientPicker.tsx +0 -88
  141. package/src/hooks/usePreservePanelSize.tsx +0 -183
  142. package/src/mediaQuery.ts +0 -13
  143. package/src/utils/getGradientBackground.tsx +0 -31
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
4
  import {
3
5
  forwardRefGeneric,
@@ -5,13 +7,7 @@ import {
5
7
  useSize,
6
8
  useWindowSize,
7
9
  } from "@noya-app/react-utils";
8
- import React, {
9
- memo,
10
- useCallback,
11
- useImperativeHandle,
12
- useRef,
13
- useState,
14
- } from "react";
10
+ import React, { memo, useImperativeHandle, useMemo, useRef } from "react";
15
11
  import { cx } from "../../utils/classNames";
16
12
  import { MenuItem } from "../internal/Menu";
17
13
  import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
@@ -19,7 +15,6 @@ import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
19
15
  import { renderPanelChildren } from "./renderPanelChildren";
20
16
  import {
21
17
  LayoutProps,
22
- PanelLayoutState,
23
18
  PercentageSidebarOptions,
24
19
  RenderPanel,
25
20
  SidebarRef,
@@ -55,7 +50,6 @@ export interface WorkspaceLayoutProps<
55
50
  sideType?: SideType;
56
51
  sideTypeBreakpoint?: number;
57
52
  detectSize?: DetectSizeType;
58
- onChangeLayoutState?: (layoutState: PanelLayoutState) => void;
59
53
  }
60
54
 
61
55
  export type WorkspaceLayoutRef = {
@@ -67,6 +61,8 @@ export type WorkspaceLayoutRef = {
67
61
  };
68
62
 
69
63
  export type SideOptions = {
64
+ // Whether to show the sidebar. Defaults to true.
65
+ visible?: boolean;
70
66
  style?: React.CSSProperties;
71
67
  className?: string;
72
68
  resizable?: boolean;
@@ -78,23 +74,24 @@ export type SideOptions = {
78
74
  minSize?: number | string;
79
75
  maxSize?: number | string;
80
76
  defaultCollapsed?: boolean;
77
+ showTabs?: boolean;
81
78
  };
82
79
 
83
80
  const defaultLeftTabItems: MenuItem<string>[] = [
84
81
  {
85
82
  title: "",
86
- value: "default",
83
+ value: "defaultLeft",
87
84
  icon: "BorderLeftIcon",
88
- tooltip: "Sidebar",
85
+ tooltip: "Left Sidebar",
89
86
  },
90
87
  ];
91
88
 
92
89
  const defaultRightTabItems: MenuItem<string>[] = [
93
90
  {
94
91
  title: "",
95
- value: "default",
92
+ value: "defaultRight",
96
93
  icon: "BorderRightIcon",
97
- tooltip: "Sidebar",
94
+ tooltip: "Right Sidebar",
98
95
  },
99
96
  ];
100
97
 
@@ -117,11 +114,11 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
117
114
  style,
118
115
  toolbar,
119
116
  children,
120
- onChangeLayoutState,
121
117
  sideType = "auto",
122
118
  sideTypeBreakpoint = 800,
123
119
  detectSize = "container",
124
120
  leftOptions: {
121
+ visible: leftVisible = true,
125
122
  minSize: leftMinSize = 200,
126
123
  maxSize: leftMaxSize = 500,
127
124
  initialSize: leftInitialSize = 280,
@@ -129,8 +126,10 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
129
126
  style: leftStyle,
130
127
  className: leftClassName,
131
128
  defaultCollapsed: leftDefaultCollapsed = false,
129
+ showTabs: leftShowTabs = true,
132
130
  } = {},
133
131
  rightOptions: {
132
+ visible: rightVisible = true,
134
133
  minSize: rightMinSize = 200,
135
134
  maxSize: rightMaxSize = 500,
136
135
  initialSize: rightInitialSize = 280,
@@ -138,6 +137,7 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
138
137
  style: rightStyle,
139
138
  className: rightClassName,
140
139
  defaultCollapsed: rightDefaultCollapsed = false,
140
+ showTabs: rightShowTabs = true,
141
141
  } = {},
142
142
  theme,
143
143
  }: WorkspaceLayoutProps<LeftTab, RightTab>,
@@ -213,20 +213,6 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
213
213
  defaultValue: getFirstTabValue(rightTabItems, "" as RightTab),
214
214
  });
215
215
 
216
- const [internalLayoutState, setInternalLayoutState] =
217
- useState<PanelLayoutState | null>({
218
- leftSidebarCollapsed: leftDefaultCollapsed,
219
- rightSidebarCollapsed: rightDefaultCollapsed,
220
- });
221
-
222
- const handleChangeLayoutState = useCallback(
223
- (state: PanelLayoutState) => {
224
- setInternalLayoutState(state);
225
- onChangeLayoutState?.(state);
226
- },
227
- [onChangeLayoutState]
228
- );
229
-
230
216
  useImperativeHandle(forwardedRef, () => ({
231
217
  setLeftExpanded: (expanded: boolean) => {
232
218
  if (expanded) {
@@ -294,41 +280,67 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
294
280
  (left ? leftSidebarPercentage : 0) -
295
281
  (right ? rightSidebarPercentage : 0);
296
282
 
297
- const leftPanel = left ? (
298
- <PanelInner
299
- style={leftStyle}
300
- className={cx("bg-sidebar-background flex-col", leftClassName)}
301
- >
302
- {leftTabValue ? renderPanelChildren(left, leftTabValue) : null}
303
- </PanelInner>
304
- ) : null;
305
-
306
- const rightPanel = right ? (
307
- <PanelInner
308
- style={rightStyle}
309
- className={cx("bg-sidebar-background flex-col", rightClassName)}
310
- >
311
- {rightTabValue ? renderPanelChildren(right, rightTabValue) : null}
312
- </PanelInner>
313
- ) : null;
283
+ const leftPanel =
284
+ left && leftVisible !== false ? (
285
+ <PanelInner
286
+ style={leftStyle}
287
+ className={cx("n-bg-sidebar-background n-flex-col", leftClassName)}
288
+ >
289
+ {leftTabValue ? renderPanelChildren(left, leftTabValue) : null}
290
+ </PanelInner>
291
+ ) : null;
292
+
293
+ const rightPanel =
294
+ right && rightVisible !== false ? (
295
+ <PanelInner
296
+ style={rightStyle}
297
+ className={cx("n-bg-sidebar-background n-flex-col", rightClassName)}
298
+ >
299
+ {rightTabValue ? renderPanelChildren(right, rightTabValue) : null}
300
+ </PanelInner>
301
+ ) : null;
314
302
 
315
303
  const centerPanel = (
316
- <PanelInner className="bg-canvas-background">{children}</PanelInner>
304
+ <PanelInner className="n-bg-canvas-background">{children}</PanelInner>
317
305
  );
318
306
 
319
- const leftSidebarOptions: PercentageSidebarOptions = {
320
- minSize: leftSidebarMinPercentage,
321
- maxSize: leftSidebarMaxPercentage,
322
- initialSize: leftSidebarPercentage,
323
- resizable: leftResizable,
324
- };
307
+ const leftSidebarOptions: PercentageSidebarOptions = useMemo(
308
+ () => ({
309
+ minSize: leftSidebarMinPercentage,
310
+ maxSize: leftSidebarMaxPercentage,
311
+ initialSize: leftSidebarPercentage,
312
+ resizable: leftResizable,
313
+ defaultCollapsed: leftDefaultCollapsed,
314
+ showTabs: leftShowTabs,
315
+ }),
316
+ [
317
+ leftSidebarMinPercentage,
318
+ leftSidebarMaxPercentage,
319
+ leftSidebarPercentage,
320
+ leftResizable,
321
+ leftDefaultCollapsed,
322
+ leftShowTabs,
323
+ ]
324
+ );
325
325
 
326
- const rightSidebarOptions: PercentageSidebarOptions = {
327
- minSize: rightSidebarMinPercentage,
328
- maxSize: rightSidebarMaxPercentage,
329
- initialSize: rightSidebarPercentage,
330
- resizable: rightResizable,
331
- };
326
+ const rightSidebarOptions: PercentageSidebarOptions = useMemo(
327
+ () => ({
328
+ minSize: rightSidebarMinPercentage,
329
+ maxSize: rightSidebarMaxPercentage,
330
+ initialSize: rightSidebarPercentage,
331
+ resizable: rightResizable,
332
+ defaultCollapsed: rightDefaultCollapsed,
333
+ showTabs: rightShowTabs,
334
+ }),
335
+ [
336
+ rightSidebarMinPercentage,
337
+ rightSidebarMaxPercentage,
338
+ rightSidebarPercentage,
339
+ rightResizable,
340
+ rightDefaultCollapsed,
341
+ rightShowTabs,
342
+ ]
343
+ );
332
344
 
333
345
  const LayoutComponent =
334
346
  sideType === "panel"
@@ -353,12 +365,12 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
353
365
  <div
354
366
  ref={containerRef}
355
367
  id={id}
356
- className={cx("flex flex-col bg-canvas-background relative", className)}
368
+ className={cx("n-flex n-flex-col n-bg-canvas-background n-relative", className)}
357
369
  style={style}
358
370
  data-theme={theme}
359
371
  >
360
372
  {toolbar}
361
- <div className="flex flex-row flex-1 relative">
373
+ <div className="n-flex n-flex-row n-flex-1 n-relative">
362
374
  {readyToRender && (
363
375
  <LayoutComponent
364
376
  leftPanel={leftPanel}
@@ -374,10 +386,8 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
374
386
  rightSidebarOptions={rightSidebarOptions}
375
387
  centerPanelPercentage={centerPanelPercentage}
376
388
  autoSavePrefix={autoSavePrefix}
377
- internalLayoutState={internalLayoutState}
378
389
  leftSidebarRef={leftSidebarRef}
379
390
  rightSidebarRef={rightSidebarRef}
380
- handleChangeLayoutState={handleChangeLayoutState}
381
391
  />
382
392
  )}
383
393
  </div>
@@ -395,7 +405,10 @@ const PanelInner = memo(function PanelInner({
395
405
  className?: string;
396
406
  }) {
397
407
  return (
398
- <div style={style} className={cx("absolute inset-0 flex", className)}>
408
+ <div
409
+ style={style}
410
+ className={cx("n-absolute n-inset-0 n-min-w-0 n-flex", className)}
411
+ >
399
412
  {children}
400
413
  </div>
401
414
  );
@@ -0,0 +1,4 @@
1
+ export const EDITOR_PANEL_GROUP_ID = "editor-panel-group";
2
+ export const LEFT_SIDEBAR_ID = "editor-1-left-sidebar";
3
+ export const RIGHT_SIDEBAR_ID = "editor-3-right-sidebar";
4
+ export const CONTENT_AREA_ID = "editor-2-content-area";
@@ -0,0 +1,216 @@
1
+ import { clientStorage, IClientStorage } from "@noya-app/react-utils";
2
+ import { PanelGroupStorage } from "react-resizable-panels";
3
+ import {
4
+ CONTENT_AREA_ID,
5
+ LEFT_SIDEBAR_ID,
6
+ RIGHT_SIDEBAR_ID,
7
+ } from "./constants";
8
+ import {
9
+ PercentageSidebarOptions,
10
+ WorkspaceLayoutGroup,
11
+ WorkspaceLayoutState,
12
+ } from "./types";
13
+
14
+ // Workspace sizes are stored as percentages (e.g. layout: [78.7224911972, 21.2775088028])
15
+ // we need to convert them to pixels relative to the window width before storing
16
+ // and convert them back to percentages when getting the item
17
+ export const panelStorage: PanelGroupStorage = {
18
+ getItem: (name) => {
19
+ const item = clientStorage.getItem(name);
20
+
21
+ if (item) {
22
+ const parsed = JSON.parse(item);
23
+ const percentageSize = mapWorkspaceLayoutState(parsed, (size) =>
24
+ toPercentageSize(size)
25
+ );
26
+ const stringified = JSON.stringify(percentageSize);
27
+ // console.log("getItem", name, stringified);
28
+ return stringified;
29
+ }
30
+
31
+ return item;
32
+ },
33
+
34
+ setItem: (name, value) => {
35
+ const parsed = JSON.parse(value);
36
+ const pixelSize = mapWorkspaceLayoutState(parsed, (size) =>
37
+ toPixelSize(size)
38
+ );
39
+ // console.log("setItem", name, pixelSize);
40
+ clientStorage.setItem(name, JSON.stringify(pixelSize));
41
+ },
42
+ };
43
+
44
+ export const toPixelSize = (size: number, windowWidth = window.innerWidth) =>
45
+ Math.round((size * windowWidth) / 100);
46
+ export const toPercentageSize = (
47
+ size: number,
48
+ windowWidth = window.innerWidth
49
+ ) => (size * 100) / windowWidth;
50
+
51
+ export function mapWorkspaceLayoutState(
52
+ layout: WorkspaceLayoutState,
53
+ f: (size: number) => number
54
+ ): WorkspaceLayoutState {
55
+ return Object.fromEntries(
56
+ Object.entries(layout).map(
57
+ ([key, value]): [string, WorkspaceLayoutGroup] => {
58
+ const expandToSizes = Object.fromEntries(
59
+ Object.entries(value.expandToSizes).map(([key, value]) => [
60
+ key,
61
+ f(value),
62
+ ])
63
+ );
64
+
65
+ return [
66
+ key,
67
+ {
68
+ expandToSizes,
69
+ layout: value.layout.map(f),
70
+ },
71
+ ];
72
+ }
73
+ )
74
+ );
75
+ }
76
+
77
+ export function getLayoutIds({
78
+ hasLeftPanel,
79
+ hasRightPanel,
80
+ hasCenterPanel,
81
+ }: {
82
+ hasLeftPanel: boolean;
83
+ hasRightPanel: boolean;
84
+ hasCenterPanel: boolean;
85
+ }) {
86
+ return [
87
+ hasLeftPanel && LEFT_SIDEBAR_ID,
88
+ hasCenterPanel && CONTENT_AREA_ID,
89
+ hasRightPanel && RIGHT_SIDEBAR_ID,
90
+ ].flatMap((id) => (id ? [id] : []));
91
+ }
92
+
93
+ export function getPropertyKey(layoutIds: string[]) {
94
+ return layoutIds.sort().join(",");
95
+ }
96
+
97
+ export function getStorageKey(autoSaveId: string) {
98
+ return `react-resizable-panels:${autoSaveId}`;
99
+ }
100
+
101
+ export function getInitialLayout({
102
+ layoutIds,
103
+ leftSidebarOptions,
104
+ rightSidebarOptions,
105
+ centerPanelPercentage,
106
+ hasLeftPanel,
107
+ hasRightPanel,
108
+ hasCenterPanel,
109
+ windowWidth = window.innerWidth,
110
+ }: {
111
+ layoutIds: string[];
112
+ leftSidebarOptions: PercentageSidebarOptions;
113
+ rightSidebarOptions: PercentageSidebarOptions;
114
+ centerPanelPercentage: number;
115
+ hasLeftPanel: boolean;
116
+ hasRightPanel: boolean;
117
+ hasCenterPanel: boolean;
118
+ windowWidth?: number;
119
+ }) {
120
+ return layoutIds.map((id) => {
121
+ if (id === LEFT_SIDEBAR_ID && hasLeftPanel) {
122
+ return leftSidebarOptions.defaultCollapsed
123
+ ? 0
124
+ : toPixelSize(leftSidebarOptions.initialSize ?? 0, windowWidth);
125
+ } else if (id === RIGHT_SIDEBAR_ID && hasRightPanel) {
126
+ return rightSidebarOptions.defaultCollapsed
127
+ ? 0
128
+ : toPixelSize(rightSidebarOptions.initialSize ?? 0, windowWidth);
129
+ } else if (id === CONTENT_AREA_ID && hasCenterPanel) {
130
+ const shouldShowLeftSidebar =
131
+ hasLeftPanel && !leftSidebarOptions.defaultCollapsed;
132
+ const shouldShowRightSidebar =
133
+ hasRightPanel && !rightSidebarOptions.defaultCollapsed;
134
+
135
+ if (!shouldShowLeftSidebar && !shouldShowRightSidebar) {
136
+ return toPixelSize(100, windowWidth);
137
+ }
138
+
139
+ return toPixelSize(centerPanelPercentage, windowWidth);
140
+ }
141
+ return 0;
142
+ });
143
+ }
144
+
145
+ export function setInitialLayout({
146
+ storageKey,
147
+ propertyKey,
148
+ calculateLayout,
149
+ storage = clientStorage,
150
+ }: {
151
+ storageKey: string;
152
+ propertyKey: string;
153
+ calculateLayout: () => number[];
154
+ storage?: IClientStorage;
155
+ }) {
156
+ const savedLayout = storage.getItem(storageKey);
157
+
158
+ if (!savedLayout) {
159
+ const layoutObject: WorkspaceLayoutState = {
160
+ [propertyKey]: {
161
+ layout: calculateLayout(),
162
+ expandToSizes: {},
163
+ },
164
+ };
165
+
166
+ storage.setItem(storageKey, JSON.stringify(layoutObject));
167
+ } else {
168
+ let parsedLayout: WorkspaceLayoutState;
169
+
170
+ try {
171
+ parsedLayout = JSON.parse(savedLayout);
172
+ } catch (error) {
173
+ console.error(error);
174
+ return;
175
+ }
176
+
177
+ if (!(propertyKey in parsedLayout)) {
178
+ const layoutObject: WorkspaceLayoutState = {
179
+ ...parsedLayout,
180
+ [propertyKey]: {
181
+ layout: calculateLayout(),
182
+ expandToSizes: {},
183
+ },
184
+ };
185
+
186
+ storage.setItem(storageKey, JSON.stringify(layoutObject));
187
+ }
188
+ }
189
+ }
190
+
191
+ export function getLayoutCollapsedState({
192
+ layoutGroup,
193
+ layoutIds,
194
+ hasLeftPanel,
195
+ hasRightPanel,
196
+ }: {
197
+ layoutGroup: WorkspaceLayoutGroup;
198
+ propertyKey: string;
199
+ layoutIds: string[];
200
+ hasLeftPanel: boolean;
201
+ hasRightPanel: boolean;
202
+ }) {
203
+ const propertySize = Object.fromEntries(
204
+ layoutIds.map((id, index) => [id, layoutGroup.layout?.[index]])
205
+ );
206
+
207
+ const leftSize = propertySize?.[LEFT_SIDEBAR_ID];
208
+ const leftSidebarCollapsed = hasLeftPanel && leftSize === 0 ? true : false;
209
+ const rightSize = propertySize?.[RIGHT_SIDEBAR_ID];
210
+ const rightSidebarCollapsed = hasRightPanel && rightSize === 0 ? true : false;
211
+
212
+ return {
213
+ leftSidebarCollapsed,
214
+ rightSidebarCollapsed,
215
+ };
216
+ }
@@ -12,6 +12,8 @@ export type PercentageSidebarOptions = {
12
12
  maxSize?: number;
13
13
  initialSize?: number;
14
14
  resizable?: boolean;
15
+ defaultCollapsed?: boolean;
16
+ showTabs?: boolean;
15
17
  };
16
18
 
17
19
  export type PanelLayoutState = {
@@ -40,8 +42,13 @@ export type LayoutProps<
40
42
  rightSidebarOptions: PercentageSidebarOptions;
41
43
  centerPanelPercentage: number;
42
44
  autoSavePrefix?: string;
43
- internalLayoutState: PanelLayoutState | null;
44
- leftSidebarRef: React.RefObject<SidebarRef>;
45
- rightSidebarRef: React.RefObject<SidebarRef>;
46
- handleChangeLayoutState: (state: PanelLayoutState) => void;
45
+ leftSidebarRef: React.RefObject<SidebarRef | null>;
46
+ rightSidebarRef: React.RefObject<SidebarRef | null>;
47
47
  };
48
+
49
+ export type WorkspaceLayoutGroup = {
50
+ expandToSizes: Record<string, number>;
51
+ layout: number[];
52
+ };
53
+
54
+ export type WorkspaceLayoutState = Record<string, WorkspaceLayoutGroup>;
@@ -1,8 +1,11 @@
1
+ "use client";
2
+
1
3
  import { getCurrentPlatform, PlatformName } from "@noya-app/noya-keymap";
2
4
  import * as React from "react";
3
5
  import { ToastProvider } from "../components/Toast";
4
6
  import { DialogProvider } from "./DialogContext";
5
7
  import { FloatingWindowProvider } from "./FloatingWindowContext";
8
+ import { LinkComponent, LinkComponentProvider } from "./LinkComponentContext";
6
9
  import { OpenPortalsProvider } from "./OpenPortalsContext";
7
10
 
8
11
  export type DesignSystemConfigurationContextValue = {
@@ -18,9 +21,11 @@ export const DesignSystemConfigurationProvider = React.memo(
18
21
  function DesignSystemConfigurationProvider({
19
22
  children,
20
23
  platform,
24
+ LinkComponent,
21
25
  }: {
22
26
  children: React.ReactNode;
23
27
  platform?: PlatformName;
28
+ LinkComponent?: LinkComponent;
24
29
  }) {
25
30
  const [internalPlatform, setInternalPlatform] =
26
31
  React.useState<PlatformName>(platform ?? "key");
@@ -41,11 +46,13 @@ export const DesignSystemConfigurationProvider = React.memo(
41
46
  return (
42
47
  <DesignSystemConfigurationContext.Provider value={contextValue}>
43
48
  <OpenPortalsProvider>
44
- <DialogProvider>
45
- <ToastProvider>
46
- <FloatingWindowProvider>{children}</FloatingWindowProvider>
47
- </ToastProvider>
48
- </DialogProvider>
49
+ <LinkComponentProvider value={LinkComponent}>
50
+ <DialogProvider>
51
+ <ToastProvider>
52
+ <FloatingWindowProvider>{children}</FloatingWindowProvider>
53
+ </ToastProvider>
54
+ </DialogProvider>
55
+ </LinkComponentProvider>
49
56
  </OpenPortalsProvider>
50
57
  </DesignSystemConfigurationContext.Provider>
51
58
  );
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, {
2
4
  createContext,
3
5
  ReactNode,
@@ -26,12 +28,16 @@ function createDeferredPromise<T>() {
26
28
 
27
29
  type RenderableProps = { close: () => void };
28
30
 
31
+ type ClosablePromise<T> = Promise<T> & {
32
+ close: () => void;
33
+ };
34
+
29
35
  export type DialogContextValue = {
30
36
  openDialog(
31
37
  options: BaseDialogContents & {
32
38
  children: ReactNode | ((props: RenderableProps) => ReactNode);
33
39
  }
34
- ): Promise<void>;
40
+ ): ClosablePromise<void>;
35
41
 
36
42
  openInputDialog(
37
43
  options:
@@ -139,7 +145,11 @@ export const DialogProvider = function DialogProvider({
139
145
  },
140
146
  });
141
147
 
142
- return promise;
148
+ const closablePromise = Object.assign(promise, {
149
+ close: closeRef.current,
150
+ });
151
+
152
+ return closablePromise;
143
153
  }, []);
144
154
 
145
155
  const openConfirmation: DialogContextValue["openConfirmationDialog"] =
@@ -196,7 +206,11 @@ export const DialogProvider = function DialogProvider({
196
206
  },
197
207
  });
198
208
 
199
- return promise;
209
+ const closablePromise = Object.assign(promise, {
210
+ close: closeRef.current,
211
+ });
212
+
213
+ return closablePromise;
200
214
  },
201
215
  []
202
216
  );
@@ -269,7 +283,7 @@ export const DialogProvider = function DialogProvider({
269
283
  )}
270
284
  >
271
285
  {contents?.type === "confirmation" ? (
272
- <div className="flex flex-1 flex-row items-center">
286
+ <div className="n-flex n-flex-1 n-flex-row n-items-center">
273
287
  <Spacer.Horizontal />
274
288
  <Button
275
289
  onClick={() => {
@@ -314,7 +328,7 @@ export const DialogProvider = function DialogProvider({
314
328
  />
315
329
  </InputField.Root>
316
330
  <Spacer.Vertical size={20} />
317
- <div className="flex flex-1 flex-row items-center">
331
+ <div className="n-flex n-flex-1 n-flex-row n-items-center">
318
332
  <Spacer.Horizontal />
319
333
  <Button onClick={close}>Cancel</Button>
320
334
  <Spacer.Horizontal size={16} />
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { uuid } from "@noya-app/noya-utils";
2
4
  import React, { createContext, useContext, useMemo, useState } from "react";
3
5
  import { createPortal } from "react-dom";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { createContext, useContext, useEffect } from 'react';
2
4
 
3
5
  export type GlobalInputBlurContextValue = {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import * as React from "react";
2
4
 
3
5
  export type ImageDataContextValue = {
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import React, { createContext, useContext } from "react";
4
+
5
+ export type LinkComponent = React.ComponentType<React.ComponentProps<"a">>;
6
+
7
+ const LinkComponentContext = createContext<LinkComponent | undefined>(
8
+ undefined
9
+ );
10
+
11
+ export const LinkComponentProvider = LinkComponentContext.Provider;
12
+
13
+ export const useLinkComponent = () => {
14
+ const Link = useContext(LinkComponentContext);
15
+
16
+ return Link ?? "a";
17
+ };
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import * as React from "react";
2
4
 
3
5
  export type OpenPortalsContextValue = {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import * as React from "react";
2
4
 
3
5
  export type PortalScopeContextValue = string;
@@ -1,7 +1,7 @@
1
1
  import { unique } from "@noya-app/noya-utils";
2
2
  import { composeEventHandlers } from "@radix-ui/primitive";
3
3
 
4
- function composeAllEventHandlers<E>(...handlers: ((e: E) => void)[]) {
4
+ function composeAllEventHandlers<E extends { defaultPrevented: boolean }>(...handlers: ((e: E) => void)[]) {
5
5
  const [first, ...rest] = handlers;
6
6
 
7
7
  return rest.reduce(