@noya-app/noya-designsystem 0.1.77 → 0.1.78

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 (81) hide show
  1. package/.turbo/turbo-build.log +20 -12
  2. package/CHANGELOG.md +9 -0
  3. package/dist/chunk-D57E6H3M.mjs +36 -0
  4. package/dist/chunk-D57E6H3M.mjs.map +1 -0
  5. package/dist/emojis.d.mts +1 -0
  6. package/dist/emojis.d.ts +1 -0
  7. package/dist/emojis.js +31 -0
  8. package/dist/emojis.js.map +1 -0
  9. package/dist/emojis.mjs +8 -0
  10. package/dist/emojis.mjs.map +1 -0
  11. package/dist/index.css +1 -1
  12. package/dist/index.d.mts +142 -90
  13. package/dist/index.d.ts +142 -90
  14. package/dist/index.js +3171 -1745
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +3349 -1918
  17. package/dist/index.mjs.map +1 -1
  18. package/package.json +11 -7
  19. package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +1 -1
  20. package/src/components/Avatar.tsx +10 -10
  21. package/src/components/Banner.tsx +1 -1
  22. package/src/components/BaseToolbar.tsx +1 -1
  23. package/src/components/Button.tsx +2 -1
  24. package/src/components/Chip.tsx +1 -1
  25. package/src/components/ColorSwatchControl.tsx +22 -14
  26. package/src/components/CommandPalette.tsx +10 -6
  27. package/src/components/ContextMenu.tsx +249 -38
  28. package/src/components/Dialog.tsx +70 -67
  29. package/src/components/Drawer.tsx +56 -17
  30. package/src/components/DropdownMenu.tsx +305 -46
  31. package/src/components/EditableText.tsx +1 -1
  32. package/src/components/EmojiPicker.tsx +645 -0
  33. package/src/components/GridView.tsx +5 -1
  34. package/src/components/IVirtualizedList.tsx +21 -1
  35. package/src/components/InputField.tsx +2 -10
  36. package/src/components/InspectorContainer.tsx +4 -2
  37. package/src/components/Message.tsx +5 -16
  38. package/src/components/Popover.tsx +73 -107
  39. package/src/components/Progress.tsx +18 -18
  40. package/src/components/ScrollArea.tsx +66 -31
  41. package/src/components/ScrollableSidebar.tsx +1 -1
  42. package/src/components/SegmentedControl.tsx +166 -38
  43. package/src/components/SelectMenu.tsx +193 -101
  44. package/src/components/Slider.tsx +40 -38
  45. package/src/components/Switch.tsx +4 -4
  46. package/src/components/TextArea.tsx +1 -1
  47. package/src/components/Toast.tsx +99 -26
  48. package/src/components/Toolbar.tsx +114 -16
  49. package/src/components/Tooltip.tsx +18 -8
  50. package/src/components/Virtualized.tsx +193 -14
  51. package/src/components/VisuallyHidden.tsx +20 -0
  52. package/src/components/__tests__/Virtualized.math.test.ts +426 -1
  53. package/src/components/__tests__/Virtualized.test.tsx +129 -1
  54. package/src/components/ai-assistant/AIAssistantLayout.tsx +11 -6
  55. package/src/components/internal/Menu.tsx +4 -0
  56. package/src/components/listView/ListViewEditableRowTitle.tsx +1 -1
  57. package/src/components/listView/ListViewRoot.tsx +5 -1
  58. package/src/components/resizablePanels/Panel.tsx +94 -0
  59. package/src/components/resizablePanels/PanelGroup.tsx +498 -0
  60. package/src/components/resizablePanels/PanelGroupContext.tsx +14 -0
  61. package/src/components/resizablePanels/PanelResizeHandle.tsx +61 -0
  62. package/src/components/resizablePanels/index.ts +7 -0
  63. package/src/components/resizablePanels/types.ts +65 -0
  64. package/src/components/workspace/DrawerWorkspaceLayout.tsx +22 -7
  65. package/src/components/workspace/PanelWorkspaceLayout.tsx +30 -84
  66. package/src/components/workspace/WorkspaceLayout.tsx +28 -58
  67. package/src/components/workspace/types.ts +6 -4
  68. package/src/contexts/DialogContext.tsx +15 -24
  69. package/src/emojis.ts +1 -0
  70. package/src/hooks/useTriggerToggle.ts +95 -0
  71. package/src/index.css +2 -0
  72. package/src/index.tsx +1 -2
  73. package/src/theme/proseTheme.ts +2 -0
  74. package/src/utils/mergeProps.ts +57 -0
  75. package/src/utils/skinTone.ts +90 -0
  76. package/tsup.config.ts +1 -1
  77. package/src/__tests__/workspaceLayout.test.ts +0 -281
  78. package/src/components/ScrollArea2.tsx +0 -76
  79. package/src/components/internal/MenuViewport.tsx +0 -178
  80. package/src/components/internal/SelectItem.tsx +0 -138
  81. package/src/components/workspace/panelStorage.ts +0 -216
@@ -0,0 +1,65 @@
1
+ import { ReactNode, RefObject } from "react";
2
+
3
+ export interface PanelGroupProps {
4
+ id?: string;
5
+ className?: string;
6
+ direction: "horizontal";
7
+ autoSaveId?: string;
8
+ onLayout?: (sizes: number[]) => void;
9
+ children: ReactNode;
10
+ ref?: RefObject<ImperativePanelGroupHandle | null>;
11
+ }
12
+
13
+ export interface PanelProps {
14
+ id?: string;
15
+ order: number;
16
+ className?: string;
17
+ defaultSize?: number;
18
+ minSize?: number;
19
+ maxSize?: number;
20
+ collapsible?: boolean;
21
+ defaultCollapsed?: boolean;
22
+ children?: ReactNode;
23
+ }
24
+
25
+ export interface ImperativePanelHandle {
26
+ expand: () => void;
27
+ collapse: () => void;
28
+ isExpanded: () => boolean;
29
+ getSize: () => number;
30
+ }
31
+
32
+ export interface ImperativePanelGroupHandle {
33
+ getLayout: () => number[];
34
+ setLayout: (sizes: number[]) => void;
35
+ }
36
+
37
+ export interface PanelResizeHandleProps {
38
+ className?: string;
39
+ disabled?: boolean;
40
+ /** The index of this handle (0 = between panels 0 and 1, 1 = between panels 1 and 2, etc.) */
41
+ index: number;
42
+ }
43
+
44
+ export interface PanelRegistration {
45
+ id: string;
46
+ order: number;
47
+ defaultSize?: number;
48
+ minSize?: number;
49
+ maxSize?: number;
50
+ collapsible?: boolean;
51
+ defaultCollapsed?: boolean;
52
+ }
53
+
54
+ export interface PanelGroupContextValue {
55
+ registerPanel: (panel: PanelRegistration) => void;
56
+ unregisterPanel: (id: string) => void;
57
+ getPanelSize: (id: string) => number;
58
+ setPanelSize: (id: string, size: number) => void;
59
+ getPanelIndex: (id: string) => number;
60
+ expandPanel: (id: string) => void;
61
+ collapsePanel: (id: string) => void;
62
+ isPanelExpanded: (id: string) => boolean;
63
+ startResize: (handleIndex: number, startX: number) => void;
64
+ containerWidth: number;
65
+ }
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { memoGeneric } from "@noya-app/react-utils";
4
4
  import React, { useMemo, useRef, useState } from "react";
5
+ import { Button } from "../Button";
5
6
  import { DividerVertical } from "../Divider";
6
7
  import { Drawer } from "../Drawer";
7
8
  import { isSelectableMenuItem, MenuItem } from "../internal/Menu";
8
9
  import { EDITOR_PANEL_GROUP_ID, RIGHT_SIDEBAR_ID } from "./constants";
9
10
  import { LayoutProps, PanelLayoutState, SidebarRef } from "./types";
10
11
  import { VerticalTabMenu } from "./VerticalTabMenu";
11
- import { Button } from "../Button";
12
12
 
13
13
  export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
14
14
  LeftTab extends string = string,
@@ -138,16 +138,29 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
138
138
  }}
139
139
  icon={allSelectableTabItems[0].icon}
140
140
  tooltip={
141
- allSelectableTabItems[0].tooltip ?? allSelectableTabItems[0].title
141
+ allSelectableTabItems[0].tooltip ??
142
+ allSelectableTabItems[0].title
142
143
  }
143
144
  active={(() => {
144
- const value = allSelectableTabItems[0].value as LeftTab | RightTab;
145
- const isLeftItem = leftSelectableTabItems.some((i) => i.value === value);
146
- const isRightItem = rightSelectableTabItems.some((i) => i.value === value);
147
- return isLeftItem ? !leftSidebarCollapsed : isRightItem ? !rightSidebarCollapsed : false;
145
+ const value = allSelectableTabItems[0].value as
146
+ | LeftTab
147
+ | RightTab;
148
+ const isLeftItem = leftSelectableTabItems.some(
149
+ (i) => i.value === value
150
+ );
151
+ const isRightItem = rightSelectableTabItems.some(
152
+ (i) => i.value === value
153
+ );
154
+ return isLeftItem
155
+ ? !leftSidebarCollapsed
156
+ : isRightItem
157
+ ? !rightSidebarCollapsed
158
+ : false;
148
159
  })()}
149
160
  onClick={() => {
150
- const value = allSelectableTabItems[0].value as LeftTab | RightTab;
161
+ const value = allSelectableTabItems[0].value as
162
+ | LeftTab
163
+ | RightTab;
151
164
  handleSelectTab(value);
152
165
  }}
153
166
  />
@@ -159,6 +172,7 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
159
172
 
160
173
  {leftPanel && (
161
174
  <Drawer
175
+ modal={false}
162
176
  ref={leftSidebarRef}
163
177
  className="n-flex-1"
164
178
  style={
@@ -185,6 +199,7 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
185
199
  {rightPanel && (
186
200
  <Drawer
187
201
  id={RIGHT_SIDEBAR_ID}
202
+ modal={false}
188
203
  ref={rightSidebarRef}
189
204
  className="n-flex-1"
190
205
  style={
@@ -1,18 +1,14 @@
1
1
  "use client";
2
2
 
3
- import {
4
- clientStorage,
5
- memoGeneric,
6
- usePersistentStateObject,
7
- } from "@noya-app/react-utils";
8
- import React, { useCallback, useMemo, useRef } from "react";
3
+ import { memoGeneric } from "@noya-app/react-utils";
4
+ import React, { useCallback, useMemo, useRef, useState } from "react";
9
5
  import {
10
6
  ImperativePanelGroupHandle,
11
7
  ImperativePanelHandle,
12
8
  Panel,
13
9
  PanelGroup,
14
10
  PanelResizeHandle,
15
- } from "react-resizable-panels";
11
+ } from "../resizablePanels";
16
12
  import { DividerVertical } from "../Divider";
17
13
  import {
18
14
  CONTENT_AREA_ID,
@@ -20,17 +16,7 @@ import {
20
16
  LEFT_SIDEBAR_ID,
21
17
  RIGHT_SIDEBAR_ID,
22
18
  } from "./constants";
23
- import {
24
- getInitialLayout,
25
- getLayoutCollapsedState,
26
- getLayoutIds,
27
- getPropertyKey,
28
- getStorageKey,
29
- panelStorage,
30
- setInitialLayout,
31
- toPixelSize,
32
- } from "./panelStorage";
33
- import { LayoutProps, WorkspaceLayoutState } from "./types";
19
+ import { LayoutProps } from "./types";
34
20
  import { VerticalTabMenu } from "./VerticalTabMenu";
35
21
 
36
22
  export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
@@ -42,7 +28,6 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
42
28
  centerPanel,
43
29
  leftSidebarOptions,
44
30
  rightSidebarOptions,
45
- centerPanelPercentage,
46
31
  autoSavePrefix,
47
32
  leftSidebarRef,
48
33
  rightSidebarRef,
@@ -56,56 +41,35 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
56
41
  }: LayoutProps<LeftTab, RightTab>) {
57
42
  const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
58
43
 
59
- const hasLeftPanel = !!leftPanel;
60
- const hasRightPanel = !!rightPanel;
61
- const hasCenterPanel = !!centerPanel;
62
-
63
44
  const autoSaveId = useMemo(() => {
64
45
  return autoSavePrefix
65
- ? `${autoSavePrefix}--v2--${EDITOR_PANEL_GROUP_ID}`
46
+ ? `${autoSavePrefix}--v3--${EDITOR_PANEL_GROUP_ID}`
66
47
  : Math.random().toString(36).substring(2, 15);
67
48
  }, [autoSavePrefix]);
68
49
 
69
- const layoutIds = getLayoutIds({
70
- hasLeftPanel,
71
- hasRightPanel,
72
- hasCenterPanel,
73
- });
74
- const storageKey = getStorageKey(autoSaveId);
75
- const propertyKey = getPropertyKey(layoutIds);
76
-
77
- setInitialLayout({
78
- storageKey,
79
- propertyKey,
80
- calculateLayout: () => {
81
- return getInitialLayout({
82
- layoutIds,
83
- leftSidebarOptions,
84
- rightSidebarOptions,
85
- centerPanelPercentage,
86
- hasLeftPanel,
87
- hasRightPanel,
88
- hasCenterPanel,
89
- });
90
- },
91
- });
92
-
93
- const [data, setData] = usePersistentStateObject<
94
- WorkspaceLayoutState,
95
- string
96
- >(clientStorage, storageKey, {});
50
+ // Track collapsed state based on layout changes
51
+ const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(
52
+ leftSidebarOptions.defaultCollapsed ?? false
53
+ );
54
+ const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(
55
+ rightSidebarOptions.defaultCollapsed ?? false
56
+ );
97
57
 
98
- const layoutGroup = data?.[propertyKey];
58
+ const handleLayoutChange = useCallback(
59
+ (sizes: number[]) => {
60
+ // Update collapsed state based on sizes
61
+ const leftIndex = leftPanel ? 0 : -1;
62
+ const rightIndex = rightPanel ? (leftPanel ? 2 : 1) : -1;
99
63
 
100
- const { leftSidebarCollapsed, rightSidebarCollapsed } = useMemo(() => {
101
- return getLayoutCollapsedState({
102
- layoutGroup,
103
- propertyKey,
104
- layoutIds,
105
- hasLeftPanel,
106
- hasRightPanel,
107
- });
108
- }, [layoutGroup, propertyKey, layoutIds, hasLeftPanel, hasRightPanel]);
64
+ if (leftIndex >= 0) {
65
+ setLeftSidebarCollapsed(sizes[leftIndex] === 0);
66
+ }
67
+ if (rightIndex >= 0) {
68
+ setRightSidebarCollapsed(sizes[rightIndex] === 0);
69
+ }
70
+ },
71
+ [leftPanel, rightPanel]
72
+ );
109
73
 
110
74
  const handleLeftTabChange = useCallback(
111
75
  (value: LeftTab) => {
@@ -143,23 +107,6 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
143
107
  [onChangeRightTab, rightSidebarRef, rightTabValue]
144
108
  );
145
109
 
146
- const handleLayoutChange = useCallback(
147
- (layout: number[]) => {
148
- setData((prev) => {
149
- const newData = {
150
- ...prev,
151
- [propertyKey]: {
152
- layout: layout.map((size) => toPixelSize(size)),
153
- expandToSizes: prev?.[propertyKey]?.expandToSizes ?? {},
154
- },
155
- };
156
-
157
- return newData;
158
- });
159
- },
160
- [setData, propertyKey]
161
- );
162
-
163
110
  const showLeftTabs =
164
111
  !!leftSidebarOptions.showTabs &&
165
112
  !!leftTabItems &&
@@ -173,7 +120,6 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
173
120
  id={EDITOR_PANEL_GROUP_ID}
174
121
  className="n-flex-1"
175
122
  direction="horizontal"
176
- storage={panelStorage}
177
123
  autoSaveId={autoSaveId}
178
124
  onLayout={handleLayoutChange}
179
125
  >
@@ -205,24 +151,23 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
205
151
  : undefined)
206
152
  }
207
153
  collapsible
154
+ defaultCollapsed={leftSidebarOptions.defaultCollapsed}
208
155
  >
209
156
  {leftSidebarCollapsed ? null : leftPanel}
210
157
  </Panel>
211
- <PanelResizeHandle className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
158
+ <PanelResizeHandle index={0} className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
212
159
  </>
213
160
  )}
214
161
  <Panel
215
162
  id={CONTENT_AREA_ID}
216
163
  order={2}
217
- defaultSize={centerPanelPercentage}
218
- minSize={10}
219
164
  className="n-flex n-relative"
220
165
  >
221
166
  {centerPanel}
222
167
  </Panel>
223
168
  {rightPanel && (
224
169
  <>
225
- <PanelResizeHandle className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
170
+ <PanelResizeHandle index={1} className="n-cursor-col-resize n-w-px n-h-full n-bg-divider" />
226
171
  <Panel
227
172
  id={RIGHT_SIDEBAR_ID}
228
173
  className="n-relative"
@@ -237,6 +182,7 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
237
182
  : undefined)
238
183
  }
239
184
  collapsible
185
+ defaultCollapsed={rightSidebarOptions.defaultCollapsed}
240
186
  >
241
187
  {rightSidebarCollapsed ? null : rightPanel}
242
188
  </Panel>
@@ -22,8 +22,8 @@ import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
22
22
  import { renderPanelChildren } from "./renderPanelChildren";
23
23
  import {
24
24
  LayoutProps,
25
- PercentageSidebarOptions,
26
25
  RenderPanel,
26
+ SidebarOptions,
27
27
  SidebarRef,
28
28
  } from "./types";
29
29
  import { WorkspaceSideProvider } from "./WorkspaceSideContext";
@@ -194,46 +194,22 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
194
194
  ? containerSize
195
195
  : windowSize;
196
196
 
197
- function getPercentage(size: number | string) {
198
- return typeof size === "string"
199
- ? parseFloat(size)
200
- : (size / parentSize.width) * 100;
197
+ // Convert size to pixels (handles both number and percentage string)
198
+ function toPixels(size: number | string | undefined): number | undefined {
199
+ if (size === undefined) return undefined;
200
+ if (typeof size === "number") return size;
201
+ // Parse percentage string like "20%"
202
+ const percentage = parseFloat(size);
203
+ return Math.round((percentage / 100) * parentSize.width);
201
204
  }
202
205
 
203
- function clampPercentage(
204
- value: number,
205
- min: number | undefined,
206
- max: number | undefined
207
- ) {
208
- let result = value;
209
- if (min !== undefined) {
210
- result = Math.max(result, min);
211
- }
212
- if (max !== undefined) {
213
- result = Math.min(result, max);
214
- }
215
- return result;
216
- }
206
+ const leftSidebarMinPixels = toPixels(leftMinSize);
207
+ const leftSidebarMaxPixels = toPixels(leftMaxSize);
208
+ const leftSidebarInitialPixels = toPixels(leftInitialSize) ?? 280;
217
209
 
218
- const leftSidebarMinPercentage =
219
- leftMinSize !== undefined ? getPercentage(leftMinSize) : undefined;
220
- const rightSidebarMinPercentage =
221
- rightMinSize !== undefined ? getPercentage(rightMinSize) : undefined;
222
- const leftSidebarMaxPercentage =
223
- leftMaxSize !== undefined ? getPercentage(leftMaxSize) : undefined;
224
- const rightSidebarMaxPercentage =
225
- rightMaxSize !== undefined ? getPercentage(rightMaxSize) : undefined;
226
-
227
- const leftSidebarPercentage = clampPercentage(
228
- getPercentage(leftInitialSize),
229
- leftSidebarMinPercentage,
230
- leftSidebarMaxPercentage
231
- );
232
- const rightSidebarPercentage = clampPercentage(
233
- getPercentage(rightInitialSize),
234
- rightSidebarMinPercentage,
235
- rightSidebarMaxPercentage
236
- );
210
+ const rightSidebarMinPixels = toPixels(rightMinSize);
211
+ const rightSidebarMaxPixels = toPixels(rightMaxSize);
212
+ const rightSidebarInitialPixels = toPixels(rightInitialSize) ?? 280;
237
213
 
238
214
  const leftSidebarRef = useRef<SidebarRef>(null);
239
215
  const rightSidebarRef = useRef<SidebarRef>(null);
@@ -313,11 +289,6 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
313
289
  },
314
290
  });
315
291
 
316
- const centerPanelPercentage =
317
- 100 -
318
- (left ? leftSidebarPercentage : 0) -
319
- (right ? rightSidebarPercentage : 0);
320
-
321
292
  const isDrawerActive =
322
293
  sideType === "drawer" ||
323
294
  (sideType === "auto" && parentSize.width <= sideTypeBreakpoint);
@@ -390,38 +361,38 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
390
361
  <PanelInner className="n-bg-canvas-background">{children}</PanelInner>
391
362
  );
392
363
 
393
- const leftSidebarOptions: PercentageSidebarOptions = useMemo(
364
+ const leftSidebarOptions: SidebarOptions = useMemo(
394
365
  () => ({
395
- minSize: leftSidebarMinPercentage,
396
- maxSize: leftSidebarMaxPercentage,
397
- initialSize: leftSidebarPercentage,
366
+ minSize: leftSidebarMinPixels,
367
+ maxSize: leftSidebarMaxPixels,
368
+ initialSize: leftSidebarInitialPixels,
398
369
  resizable: leftResizable,
399
370
  defaultCollapsed: leftDefaultCollapsed,
400
371
  showTabs: leftShowTabs,
401
372
  }),
402
373
  [
403
- leftSidebarMinPercentage,
404
- leftSidebarMaxPercentage,
405
- leftSidebarPercentage,
374
+ leftSidebarMinPixels,
375
+ leftSidebarMaxPixels,
376
+ leftSidebarInitialPixels,
406
377
  leftResizable,
407
378
  leftDefaultCollapsed,
408
379
  leftShowTabs,
409
380
  ]
410
381
  );
411
382
 
412
- const rightSidebarOptions: PercentageSidebarOptions = useMemo(
383
+ const rightSidebarOptions: SidebarOptions = useMemo(
413
384
  () => ({
414
- minSize: rightSidebarMinPercentage,
415
- maxSize: rightSidebarMaxPercentage,
416
- initialSize: rightSidebarPercentage,
385
+ minSize: rightSidebarMinPixels,
386
+ maxSize: rightSidebarMaxPixels,
387
+ initialSize: rightSidebarInitialPixels,
417
388
  resizable: rightResizable,
418
389
  defaultCollapsed: rightDefaultCollapsed,
419
390
  showTabs: rightShowTabs,
420
391
  }),
421
392
  [
422
- rightSidebarMinPercentage,
423
- rightSidebarMaxPercentage,
424
- rightSidebarPercentage,
393
+ rightSidebarMinPixels,
394
+ rightSidebarMaxPixels,
395
+ rightSidebarInitialPixels,
425
396
  rightResizable,
426
397
  rightDefaultCollapsed,
427
398
  rightShowTabs,
@@ -476,7 +447,6 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
476
447
  onChangeRightTab={setRightTabValue}
477
448
  leftSidebarOptions={leftSidebarOptions}
478
449
  rightSidebarOptions={rightSidebarOptions}
479
- centerPanelPercentage={centerPanelPercentage}
480
450
  autoSavePrefix={autoSavePrefix}
481
451
  leftSidebarRef={leftSidebarRef}
482
452
  rightSidebarRef={rightSidebarRef}
@@ -7,7 +7,10 @@ export type SidebarRef = {
7
7
  isExpanded: () => boolean;
8
8
  };
9
9
 
10
- export type PercentageSidebarOptions = {
10
+ /**
11
+ * Sidebar options with pixel-based sizing.
12
+ */
13
+ export type SidebarOptions = {
11
14
  minSize?: number;
12
15
  maxSize?: number;
13
16
  initialSize?: number;
@@ -45,9 +48,8 @@ export type LayoutProps<
45
48
  rightTabValue?: RightTab;
46
49
  onChangeRightTab?: (value: RightTab) => void;
47
50
  centerPanel: React.ReactNode;
48
- leftSidebarOptions: PercentageSidebarOptions;
49
- rightSidebarOptions: PercentageSidebarOptions;
50
- centerPanelPercentage: number;
51
+ leftSidebarOptions: SidebarOptions;
52
+ rightSidebarOptions: SidebarOptions;
51
53
  autoSavePrefix?: string;
52
54
  leftSidebarRef: React.RefObject<SidebarRef | null>;
53
55
  rightSidebarRef: React.RefObject<SidebarRef | null>;
@@ -231,6 +231,21 @@ export const DialogProvider = function DialogProvider({
231
231
  const confirmButtonRef = useRef<HTMLButtonElement>(null);
232
232
  const dialogRef = useRef<IDialog>(null);
233
233
 
234
+ // Handle auto-focus when dialog opens
235
+ React.useEffect(() => {
236
+ if (!contents) return;
237
+
238
+ // Use requestAnimationFrame to ensure the dialog is rendered
239
+ requestAnimationFrame(() => {
240
+ if (contents.type === "input") {
241
+ inputRef.current?.focus();
242
+ inputRef.current?.setSelectionRange(0, inputRef.current.value.length);
243
+ } else if (contents.type === "confirmation") {
244
+ confirmButtonRef.current?.focus();
245
+ }
246
+ });
247
+ }, [contents]);
248
+
234
249
  const containsElement = useCallback((element: HTMLElement) => {
235
250
  if (!dialogRef.current) return false;
236
251
 
@@ -267,30 +282,6 @@ export const DialogProvider = function DialogProvider({
267
282
  },
268
283
  [close]
269
284
  )}
270
- onOpenAutoFocus={useCallback(
271
- (event: Event) => {
272
- if (!contents) return;
273
-
274
- if (contents.type === "input") {
275
- event.stopPropagation();
276
- event.preventDefault();
277
- inputRef.current?.focus();
278
- inputRef.current?.setSelectionRange(
279
- 0,
280
- inputRef.current.value.length
281
- );
282
- return;
283
- }
284
-
285
- if (contents.type === "confirmation") {
286
- event.stopPropagation();
287
- event.preventDefault();
288
- confirmButtonRef.current?.focus();
289
- return;
290
- }
291
- },
292
- [contents]
293
- )}
294
285
  >
295
286
  {contents?.type === "confirmation" ? (
296
287
  <div className="n-flex n-flex-1 n-flex-row n-items-center">
package/src/emojis.ts ADDED
@@ -0,0 +1 @@
1
+ export { gemoji as emojis } from "gemoji";
@@ -0,0 +1,95 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+
3
+ interface UseTriggerToggleOptions {
4
+ /**
5
+ * Called when an interaction outside the popup occurs.
6
+ * Return false to prevent the component from closing.
7
+ */
8
+ shouldCloseOnInteractOutside?: (event: PointerEvent) => boolean;
9
+ }
10
+
11
+ /**
12
+ * Hook that provides toggle behavior for trigger-based components (Popover, Drawer, etc.)
13
+ *
14
+ * When a component is open and the user clicks the trigger, this hook ensures:
15
+ * 1. The component closes (normal behavior)
16
+ * 2. The immediate reopen attempt from the same click is blocked
17
+ * 3. Subsequent clicks work normally
18
+ *
19
+ * @param isOpen - Whether the component is currently open
20
+ * @param setIsOpen - Function to update the open state
21
+ * @param options - Optional configuration
22
+ * @returns Object with anchorElement state, popupRef, and handleOpenChange
23
+ */
24
+ export function useTriggerToggle(
25
+ isOpen: boolean,
26
+ setIsOpen: (open: boolean) => void,
27
+ options: UseTriggerToggleOptions = {}
28
+ ) {
29
+ const { shouldCloseOnInteractOutside } = options;
30
+
31
+ const [anchorElement, setAnchorElement] = useState<Element | null>(null);
32
+ const popupRef = useRef<HTMLDivElement>(null);
33
+ // Track whether a close was just initiated (to prevent immediate reopen from same click)
34
+ const closeInitiatedRef = useRef(false);
35
+ // Track whether the next close should be prevented based on shouldCloseOnInteractOutside
36
+ const shouldPreventCloseRef = useRef(false);
37
+
38
+ useEffect(() => {
39
+ if (!isOpen) return;
40
+
41
+ const handlePointerDown = (event: PointerEvent) => {
42
+ // Check if click is outside the popup content for shouldCloseOnInteractOutside
43
+ if (
44
+ popupRef.current &&
45
+ !popupRef.current.contains(event.target as Node)
46
+ ) {
47
+ if (shouldCloseOnInteractOutside) {
48
+ const shouldClose = shouldCloseOnInteractOutside(event);
49
+ shouldPreventCloseRef.current = !shouldClose;
50
+ }
51
+ }
52
+ };
53
+
54
+ // Use capture phase to intercept before Base UI's handler
55
+ document.addEventListener("pointerdown", handlePointerDown, true);
56
+
57
+ return () => {
58
+ document.removeEventListener("pointerdown", handlePointerDown, true);
59
+ };
60
+ }, [isOpen, anchorElement, shouldCloseOnInteractOutside]);
61
+
62
+ const handleOpenChange = useCallback(
63
+ (newOpen: boolean) => {
64
+ // If closing and we should prevent it, skip
65
+ if (!newOpen && shouldPreventCloseRef.current) {
66
+ shouldPreventCloseRef.current = false;
67
+ return;
68
+ }
69
+
70
+ // When closing, set flag and clear it on next macrotask
71
+ // This detects if open is called within the same event loop cycle (same click)
72
+ if (!newOpen) {
73
+ closeInitiatedRef.current = true;
74
+ setTimeout(() => {
75
+ closeInitiatedRef.current = false;
76
+ }, 0);
77
+ }
78
+
79
+ // Block immediate reopen from the same click that closed the component
80
+ if (newOpen && closeInitiatedRef.current) {
81
+ return;
82
+ }
83
+
84
+ setIsOpen(newOpen);
85
+ },
86
+ [setIsOpen]
87
+ );
88
+
89
+ return {
90
+ anchorElement,
91
+ setAnchorElement,
92
+ popupRef,
93
+ handleOpenChange,
94
+ };
95
+ }
package/src/index.css CHANGED
@@ -109,6 +109,8 @@
109
109
  --n-inspector-v-separator: 10px;
110
110
  --n-dialog-padding: 32px;
111
111
  --n-input-height: 27px;
112
+ --n-input-padding-x: 8px;
113
+ --n-input-padding-y: 4px;
112
114
  --n-icon: var(--n-indigo-300);
113
115
  --n-icon-selected: rgb(220, 220, 220);
114
116
  --n-warning: rgb(251, 211, 0);
package/src/index.tsx CHANGED
@@ -26,6 +26,7 @@ export * from "./components/DraggableMenuButton";
26
26
  export * from "./components/Drawer";
27
27
  export * from "./components/DropdownMenu";
28
28
  export * from "./components/EditableText";
29
+ export * from "./components/EmojiPicker";
29
30
  export * from "./components/Fade";
30
31
  export * from "./components/file-explorer/FileExplorerLayout";
31
32
  export * from "./components/FileUploadIndicator";
@@ -43,7 +44,6 @@ export type {
43
44
  MenuItem,
44
45
  SelectableMenuItem,
45
46
  } from "./components/internal/Menu";
46
- export * from "./components/internal/MenuViewport";
47
47
  export * from "./components/IVirtualizedList";
48
48
  export * from "./components/Label";
49
49
  export * from "./components/LabeledElementView";
@@ -63,7 +63,6 @@ export * from "./components/ResizableContainer";
63
63
  export * from "./components/RingProgress";
64
64
  export * from "./components/ScrollableSidebar";
65
65
  export * from "./components/ScrollArea";
66
- export * from "./components/ScrollArea2";
67
66
  export * from "./components/SearchCompletionMenu";
68
67
  export * from "./components/Section";
69
68
  export * from "./components/SegmentedControl";
@@ -13,6 +13,8 @@ export const whiteboardProseTheme = (isDark: boolean) =>
13
13
  "prose-li:n-m-0",
14
14
  "prose-li:n-p-0",
15
15
  "prose-a:n-text-inherit",
16
+ "prose-pre:n-my-1",
17
+ "prose-pre:n-text-inherit",
16
18
  "prose-strong:n-text-inherit",
17
19
  "prose-em:n-italic",
18
20
  "prose-ol:n-list-inside",