@noya-app/noya-designsystem 0.1.47 → 0.1.49

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 (55) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +18 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +389 -241
  5. package/dist/index.d.ts +389 -241
  6. package/dist/index.js +4802 -3990
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4715 -3915
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +7 -7
  11. package/src/__tests__/validateDropIndicator.test.ts +263 -0
  12. package/src/components/ActionMenu.tsx +51 -0
  13. package/src/components/ActivityIndicator.tsx +7 -1
  14. package/src/components/BaseToolbar.tsx +2 -2
  15. package/src/components/Checkbox.tsx +2 -2
  16. package/src/components/Chip.tsx +7 -6
  17. package/src/components/Collection.tsx +30 -2
  18. package/src/components/Combobox.tsx +27 -15
  19. package/src/components/ComboboxMenu.tsx +15 -6
  20. package/src/components/Dialog.tsx +17 -12
  21. package/src/components/DimensionInput.tsx +14 -12
  22. package/src/components/Drawer.tsx +98 -0
  23. package/src/components/EditableText.tsx +3 -1
  24. package/src/components/FillInputField.tsx +2 -2
  25. package/src/components/Grid.tsx +161 -111
  26. package/src/components/GridView.tsx +73 -41
  27. package/src/components/InputField.tsx +148 -208
  28. package/src/components/Label.tsx +4 -68
  29. package/src/components/LabeledField.tsx +7 -1
  30. package/src/components/List.tsx +135 -50
  31. package/src/components/ListView.tsx +63 -43
  32. package/src/components/MediaThumbnail.tsx +117 -0
  33. package/src/components/Message.tsx +8 -8
  34. package/src/components/NoyaLogo.tsx +41 -0
  35. package/src/components/SearchCompletionMenu.tsx +30 -16
  36. package/src/components/SegmentedControl.tsx +1 -1
  37. package/src/components/SelectMenu.tsx +28 -21
  38. package/src/components/Slider.tsx +16 -7
  39. package/src/components/Sortable.tsx +125 -62
  40. package/src/components/internal/Menu.tsx +45 -25
  41. package/src/components/internal/MenuViewport.tsx +7 -1
  42. package/src/components/internal/SelectItem.tsx +53 -28
  43. package/src/components/internal/__tests__/Menu.test.tsx +12 -0
  44. package/src/components/workspace/DrawerWorkspaceLayout.tsx +86 -0
  45. package/src/components/workspace/PanelWorkspaceLayout.tsx +102 -0
  46. package/src/components/{WorkspaceLayout.tsx → workspace/WorkspaceLayout.tsx} +109 -106
  47. package/src/components/workspace/types.ts +31 -0
  48. package/src/contexts/DialogContext.tsx +49 -34
  49. package/src/hooks/usePreservePanelSize.tsx +1 -5
  50. package/src/index.css +8 -1
  51. package/src/index.tsx +6 -1
  52. package/src/theme/index.ts +2 -0
  53. package/src/utils/classNames.ts +51 -3
  54. package/src/utils/inputs.ts +21 -0
  55. package/tailwind.config.ts +8 -0
@@ -0,0 +1,86 @@
1
+ import React, { memo, useLayoutEffect, useRef, useState } from "react";
2
+ import {
3
+ EDITOR_PANEL_GROUP_ID,
4
+ LEFT_SIDEBAR_ID,
5
+ RIGHT_SIDEBAR_ID,
6
+ } from "../../hooks/usePreservePanelSize";
7
+ import { Drawer } from "../Drawer";
8
+ import { LayoutProps } from "./types";
9
+
10
+ export const DrawerWorkspaceLayout = memo(function DrawerWorkspaceLayout({
11
+ leftPanel,
12
+ leftSidebarRef,
13
+ rightPanel,
14
+ rightSidebarRef,
15
+ centerPanel,
16
+ internalLayoutState,
17
+ handleChangeLayoutState,
18
+ }: LayoutProps) {
19
+ const portalContainer = useRef<HTMLDivElement>(null);
20
+ const [mounted, setMounted] = useState(false);
21
+
22
+ useLayoutEffect(() => {
23
+ if (mounted) return;
24
+
25
+ setMounted(true);
26
+ handleChangeLayoutState({
27
+ leftSidebarCollapsed: true,
28
+ rightSidebarCollapsed: true,
29
+ });
30
+ }, [handleChangeLayoutState, mounted]);
31
+
32
+ const leftSidebarCollapsed =
33
+ internalLayoutState?.leftSidebarCollapsed ?? false;
34
+ const rightSidebarCollapsed =
35
+ internalLayoutState?.rightSidebarCollapsed ?? false;
36
+
37
+ return (
38
+ <div
39
+ ref={portalContainer}
40
+ id={EDITOR_PANEL_GROUP_ID}
41
+ className="flex flex-1 relative focus:outline-none"
42
+ >
43
+ {leftPanel && (
44
+ <Drawer
45
+ id={LEFT_SIDEBAR_ID}
46
+ ref={leftSidebarRef}
47
+ className="flex-1"
48
+ open={!leftSidebarCollapsed}
49
+ positioning="absolute"
50
+ side="left"
51
+ trigger={null}
52
+ portalContainer={portalContainer.current}
53
+ onOpenChange={(open) => {
54
+ handleChangeLayoutState({
55
+ rightSidebarCollapsed: rightSidebarCollapsed,
56
+ leftSidebarCollapsed: !open,
57
+ });
58
+ }}
59
+ >
60
+ {leftPanel}
61
+ </Drawer>
62
+ )}
63
+ {centerPanel}
64
+ {rightPanel && (
65
+ <Drawer
66
+ id={RIGHT_SIDEBAR_ID}
67
+ ref={rightSidebarRef}
68
+ className="flex-1"
69
+ open={!rightSidebarCollapsed}
70
+ positioning="absolute"
71
+ side="right"
72
+ trigger={null}
73
+ portalContainer={portalContainer.current}
74
+ onOpenChange={(open) => {
75
+ handleChangeLayoutState({
76
+ leftSidebarCollapsed: leftSidebarCollapsed,
77
+ rightSidebarCollapsed: !open,
78
+ });
79
+ }}
80
+ >
81
+ {rightPanel}
82
+ </Drawer>
83
+ )}
84
+ </div>
85
+ );
86
+ });
@@ -0,0 +1,102 @@
1
+ import React, { memo, useRef } from "react";
2
+ import {
3
+ ImperativePanelGroupHandle,
4
+ ImperativePanelHandle,
5
+ Panel,
6
+ PanelGroup,
7
+ PanelResizeHandle,
8
+ } from "react-resizable-panels";
9
+ import {
10
+ CONTENT_AREA_ID,
11
+ EDITOR_PANEL_GROUP_ID,
12
+ LEFT_SIDEBAR_ID,
13
+ RIGHT_SIDEBAR_ID,
14
+ usePreservePanelSize,
15
+ } from "../../hooks/usePreservePanelSize";
16
+ import { LayoutProps } from "./types";
17
+
18
+ export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
19
+ leftPanel,
20
+ rightPanel,
21
+ centerPanel,
22
+ leftSidebarOptions,
23
+ rightSidebarOptions,
24
+ centerPanelPercentage,
25
+ autoSavePrefix,
26
+ internalLayoutState,
27
+ leftSidebarRef,
28
+ rightSidebarRef,
29
+ handleChangeLayoutState,
30
+ }: LayoutProps) {
31
+ const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
32
+
33
+ usePreservePanelSize(panelGroupRef, handleChangeLayoutState);
34
+
35
+ return (
36
+ <PanelGroup
37
+ ref={panelGroupRef}
38
+ id={EDITOR_PANEL_GROUP_ID}
39
+ className="flex-1"
40
+ direction="horizontal"
41
+ autoSaveId={
42
+ autoSavePrefix
43
+ ? `${autoSavePrefix}--${EDITOR_PANEL_GROUP_ID}`
44
+ : undefined
45
+ }
46
+ >
47
+ {leftPanel && (
48
+ <>
49
+ <Panel
50
+ id={LEFT_SIDEBAR_ID}
51
+ className="relative"
52
+ order={1}
53
+ ref={leftSidebarRef as React.RefObject<ImperativePanelHandle>}
54
+ defaultSize={leftSidebarOptions.initialSize}
55
+ minSize={leftSidebarOptions.minSize}
56
+ maxSize={
57
+ leftSidebarOptions.maxSize ??
58
+ (!leftSidebarOptions.resizable
59
+ ? leftSidebarOptions.initialSize
60
+ : undefined)
61
+ }
62
+ collapsible
63
+ >
64
+ {internalLayoutState?.leftSidebarCollapsed ? null : leftPanel}
65
+ </Panel>
66
+ <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
67
+ </>
68
+ )}
69
+ <Panel
70
+ id={CONTENT_AREA_ID}
71
+ order={2}
72
+ defaultSize={centerPanelPercentage}
73
+ minSize={10}
74
+ className="flex relative"
75
+ >
76
+ {centerPanel}
77
+ </Panel>
78
+ {rightPanel && (
79
+ <>
80
+ <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
81
+ <Panel
82
+ id={RIGHT_SIDEBAR_ID}
83
+ className="relative"
84
+ order={3}
85
+ ref={rightSidebarRef as React.RefObject<ImperativePanelHandle>}
86
+ minSize={rightSidebarOptions.minSize}
87
+ defaultSize={rightSidebarOptions.initialSize}
88
+ maxSize={
89
+ rightSidebarOptions.maxSize ??
90
+ (!rightSidebarOptions.resizable
91
+ ? rightSidebarOptions.initialSize
92
+ : undefined)
93
+ }
94
+ collapsible
95
+ >
96
+ {internalLayoutState?.rightSidebarCollapsed ? null : rightPanel}
97
+ </Panel>
98
+ </>
99
+ )}
100
+ </PanelGroup>
101
+ );
102
+ });
@@ -1,4 +1,5 @@
1
1
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
+ import { useSize } from "@noya-app/react-utils";
2
3
  import React, {
3
4
  forwardRef,
4
5
  memo,
@@ -7,23 +8,19 @@ import React, {
7
8
  useRef,
8
9
  useState,
9
10
  } from "react";
11
+ import { useWindowSize } from "../../hooks/useWindowSize";
12
+ import { cx } from "../../utils/classNames";
13
+ import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
14
+ import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
10
15
  import {
11
- ImperativePanelGroupHandle,
12
- ImperativePanelHandle,
13
- Panel,
14
- PanelGroup,
15
- PanelResizeHandle,
16
- } from "react-resizable-panels";
17
- import {
18
- CONTENT_AREA_ID,
19
- EDITOR_PANEL_GROUP_ID,
20
- LEFT_SIDEBAR_ID,
21
16
  PanelLayoutState,
22
- RIGHT_SIDEBAR_ID,
23
- usePreservePanelSize,
24
- } from "../hooks/usePreservePanelSize";
25
- import { useWindowSize } from "../hooks/useWindowSize";
26
- import { cx } from "../utils/classNames";
17
+ PercentageSidebarOptions,
18
+ SidebarRef,
19
+ } from "./types";
20
+
21
+ export type SideType = "auto" | "panel" | "drawer";
22
+
23
+ export type DetectSizeType = "container" | "window";
27
24
 
28
25
  export interface WorkspaceLayoutProps {
29
26
  id?: string;
@@ -37,10 +34,13 @@ export interface WorkspaceLayoutProps {
37
34
  right?: React.ReactNode;
38
35
  rightOptions?: SideOptions;
39
36
  toolbar?: React.ReactNode;
37
+ sideType?: SideType;
38
+ sideTypeBreakpoint?: number;
39
+ detectSize?: DetectSizeType;
40
40
  onChangeLayoutState?: (layoutState: PanelLayoutState) => void;
41
41
  }
42
42
 
43
- export type IWorkspaceLayout = {
43
+ export type WorkspaceLayoutRef = {
44
44
  setLeftExpanded: (expanded: boolean) => void;
45
45
  setRightExpanded: (expanded: boolean) => void;
46
46
  toggleSides: () => void;
@@ -72,6 +72,9 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
72
72
  toolbar,
73
73
  children,
74
74
  onChangeLayoutState,
75
+ sideType = "auto",
76
+ sideTypeBreakpoint = 800,
77
+ detectSize = "container",
75
78
  leftOptions: {
76
79
  minSize: leftMinSize = 200,
77
80
  maxSize: leftMaxSize = 500,
@@ -90,13 +93,25 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
90
93
  } = {},
91
94
  theme,
92
95
  }: WorkspaceLayoutProps,
93
- forwardedRef: React.ForwardedRef<IWorkspaceLayout>
96
+ forwardedRef: React.ForwardedRef<WorkspaceLayoutRef>
94
97
  ) {
98
+ const containerRef = React.useRef<HTMLDivElement | null>(null);
99
+ const containerSize = useSize(containerRef);
95
100
  const windowSize = useWindowSize();
101
+
102
+ // If we're in embedded (iframed) mode, use the window size.
103
+ // Otherwise, use the container size (if it's available).
104
+ const parentSize =
105
+ detectSize === "window"
106
+ ? windowSize
107
+ : containerSize && containerSize.width > 0
108
+ ? containerSize
109
+ : windowSize;
110
+
96
111
  function getPercentage(size: number | string) {
97
112
  return typeof size === "string"
98
113
  ? parseFloat(size)
99
- : (size / windowSize.width) * 100;
114
+ : (size / parentSize.width) * 100;
100
115
  }
101
116
 
102
117
  function clampPercentage(
@@ -134,12 +149,12 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
134
149
  rightSidebarMaxPercentage
135
150
  );
136
151
 
137
- const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
138
- const leftSidebarRef = useRef<ImperativePanelHandle>(null);
139
- const rightSidebarRef = useRef<ImperativePanelHandle>(null);
152
+ const leftSidebarRef = useRef<SidebarRef>(null);
153
+ const rightSidebarRef = useRef<SidebarRef>(null);
140
154
 
141
155
  const [internalLayoutState, setInternalLayoutState] =
142
156
  useState<PanelLayoutState | null>(null);
157
+
143
158
  const handleChangeLayoutState = useCallback(
144
159
  (state: PanelLayoutState) => {
145
160
  setInternalLayoutState(state);
@@ -148,8 +163,6 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
148
163
  [onChangeLayoutState]
149
164
  );
150
165
 
151
- usePreservePanelSize(panelGroupRef, handleChangeLayoutState);
152
-
153
166
  useImperativeHandle(forwardedRef, () => ({
154
167
  setLeftExpanded: (expanded: boolean) => {
155
168
  if (expanded) {
@@ -217,96 +230,86 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
217
230
  (left ? leftSidebarPercentage : 0) -
218
231
  (right ? rightSidebarPercentage : 0);
219
232
 
233
+ const leftPanel = left ? (
234
+ <PanelInner
235
+ style={leftStyle}
236
+ className={cx("bg-sidebar-background flex-col", leftClassName)}
237
+ >
238
+ {left}
239
+ </PanelInner>
240
+ ) : null;
241
+
242
+ const rightPanel = right ? (
243
+ <PanelInner
244
+ style={rightStyle}
245
+ className={cx("bg-sidebar-background flex-col", rightClassName)}
246
+ >
247
+ {right}
248
+ </PanelInner>
249
+ ) : null;
250
+
251
+ const centerPanel = (
252
+ <PanelInner className="bg-canvas-background">{children}</PanelInner>
253
+ );
254
+
255
+ const leftSidebarOptions: PercentageSidebarOptions = {
256
+ minSize: leftSidebarMinPercentage,
257
+ maxSize: leftSidebarMaxPercentage,
258
+ initialSize: leftSidebarPercentage,
259
+ resizable: leftResizable,
260
+ };
261
+
262
+ const rightSidebarOptions: PercentageSidebarOptions = {
263
+ minSize: rightSidebarMinPercentage,
264
+ maxSize: rightSidebarMaxPercentage,
265
+ initialSize: rightSidebarPercentage,
266
+ resizable: rightResizable,
267
+ };
268
+
269
+ const LayoutComponent =
270
+ sideType === "panel"
271
+ ? PanelWorkspaceLayout
272
+ : sideType === "drawer"
273
+ ? DrawerWorkspaceLayout
274
+ : parentSize.width > sideTypeBreakpoint
275
+ ? PanelWorkspaceLayout
276
+ : DrawerWorkspaceLayout;
277
+
278
+ // Wait until the container size is available to render the layout.
279
+ // This is to avoid flickering when the layout is initially rendered.
280
+ // If we're in embedded (iframed) mode, we don't need to wait for the container size,
281
+ // because the window size is guaranteed to be available.
282
+ //
283
+ // We could always wait for the container size, but this lets us render
284
+ // sooner in the embedded case.
285
+ const readyToRender =
286
+ detectSize === "window" || (containerSize && containerSize.width > 0);
287
+
220
288
  return (
221
289
  <div
290
+ ref={containerRef}
222
291
  id={id}
223
- className={cx("flex flex-col bg-canvas-background", className)}
292
+ className={cx("flex flex-col bg-canvas-background relative", className)}
224
293
  style={style}
225
294
  data-theme={theme}
226
295
  >
227
296
  {toolbar}
228
- <div className="flex flex-row flex-1">
229
- <PanelGroup
230
- ref={panelGroupRef}
231
- id={EDITOR_PANEL_GROUP_ID}
232
- className="flex-1"
233
- direction="horizontal"
234
- autoSaveId={
235
- autoSavePrefix
236
- ? `${autoSavePrefix}--${EDITOR_PANEL_GROUP_ID}`
237
- : undefined
238
- }
239
- >
240
- {left && (
241
- <>
242
- <Panel
243
- id={LEFT_SIDEBAR_ID}
244
- className="relative"
245
- order={1}
246
- ref={leftSidebarRef}
247
- defaultSize={leftSidebarPercentage}
248
- minSize={leftSidebarMinPercentage ?? leftSidebarPercentage}
249
- maxSize={
250
- leftSidebarMaxPercentage ??
251
- (!leftResizable ? leftSidebarPercentage : undefined)
252
- }
253
- collapsible
254
- >
255
- {internalLayoutState?.leftSidebarCollapsed ? null : (
256
- <PanelInner
257
- style={leftStyle}
258
- className={cx(
259
- "bg-sidebar-background flex-col",
260
- leftClassName
261
- )}
262
- >
263
- {left}
264
- </PanelInner>
265
- )}
266
- </Panel>
267
- <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
268
- </>
269
- )}
270
- <Panel
271
- id={CONTENT_AREA_ID}
272
- order={2}
273
- defaultSize={centerPanelPercentage}
274
- minSize={10}
275
- className="flex relative"
276
- >
277
- <PanelInner className="bg-canvas-background">{children}</PanelInner>
278
- </Panel>
279
- {right && (
280
- <>
281
- <PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
282
- <Panel
283
- id={RIGHT_SIDEBAR_ID}
284
- className="relative"
285
- order={3}
286
- ref={rightSidebarRef}
287
- minSize={rightSidebarMinPercentage ?? rightSidebarPercentage}
288
- defaultSize={rightSidebarPercentage}
289
- maxSize={
290
- rightSidebarMaxPercentage ??
291
- (!rightResizable ? rightSidebarPercentage : undefined)
292
- }
293
- collapsible
294
- >
295
- {internalLayoutState?.rightSidebarCollapsed ? null : (
296
- <PanelInner
297
- style={rightStyle}
298
- className={cx(
299
- "bg-sidebar-background flex-col",
300
- rightClassName
301
- )}
302
- >
303
- {right}
304
- </PanelInner>
305
- )}
306
- </Panel>
307
- </>
308
- )}
309
- </PanelGroup>
297
+ <div className="flex flex-row flex-1 relative">
298
+ {readyToRender && (
299
+ <LayoutComponent
300
+ leftPanel={leftPanel}
301
+ rightPanel={rightPanel}
302
+ centerPanel={centerPanel}
303
+ leftSidebarOptions={leftSidebarOptions}
304
+ rightSidebarOptions={rightSidebarOptions}
305
+ centerPanelPercentage={centerPanelPercentage}
306
+ autoSavePrefix={autoSavePrefix}
307
+ internalLayoutState={internalLayoutState}
308
+ leftSidebarRef={leftSidebarRef}
309
+ rightSidebarRef={rightSidebarRef}
310
+ handleChangeLayoutState={handleChangeLayoutState}
311
+ />
312
+ )}
310
313
  </div>
311
314
  </div>
312
315
  );
@@ -0,0 +1,31 @@
1
+ export type SidebarRef = {
2
+ expand: () => void;
3
+ collapse: () => void;
4
+ isExpanded: () => boolean;
5
+ };
6
+
7
+ export type PercentageSidebarOptions = {
8
+ minSize?: number;
9
+ maxSize?: number;
10
+ initialSize?: number;
11
+ resizable?: boolean;
12
+ };
13
+
14
+ export type PanelLayoutState = {
15
+ leftSidebarCollapsed: boolean;
16
+ rightSidebarCollapsed: boolean;
17
+ };
18
+
19
+ export type LayoutProps = {
20
+ leftPanel: React.ReactNode;
21
+ rightPanel: React.ReactNode;
22
+ centerPanel: React.ReactNode;
23
+ leftSidebarOptions: PercentageSidebarOptions;
24
+ rightSidebarOptions: PercentageSidebarOptions;
25
+ centerPanelPercentage: number;
26
+ autoSavePrefix?: string;
27
+ internalLayoutState: PanelLayoutState | null;
28
+ leftSidebarRef: React.RefObject<SidebarRef>;
29
+ rightSidebarRef: React.RefObject<SidebarRef>;
30
+ handleChangeLayoutState: (state: PanelLayoutState) => void;
31
+ };
@@ -24,31 +24,26 @@ function createDeferredPromise<T>() {
24
24
  return { promise, resolve, reject };
25
25
  }
26
26
 
27
+ type RenderableProps = { close: () => void };
28
+
27
29
  export type DialogContextValue = {
28
- openDialog(options: {
29
- title: string;
30
- description?: ReactNode;
31
- children: ReactNode;
32
- }): Promise<void>;
30
+ openDialog(
31
+ options: BaseDialogContents & {
32
+ children: ReactNode | ((props: RenderableProps) => ReactNode);
33
+ }
34
+ ): Promise<void>;
33
35
 
34
36
  openInputDialog(
35
37
  options:
36
38
  | string
37
- | {
38
- title: string;
39
- description?: ReactNode;
39
+ | (BaseDialogContents & {
40
40
  placeholder?: string;
41
41
  initialValue?: string;
42
- }
42
+ })
43
43
  ): Promise<string | undefined>;
44
44
 
45
45
  openConfirmationDialog(
46
- options:
47
- | string
48
- | {
49
- title: string;
50
- description?: ReactNode;
51
- }
46
+ options: string | BaseDialogContents
52
47
  ): Promise<boolean>;
53
48
 
54
49
  containsElement(element: HTMLElement): boolean;
@@ -56,26 +51,27 @@ export type DialogContextValue = {
56
51
 
57
52
  const DialogContext = createContext<DialogContextValue | undefined>(undefined);
58
53
 
59
- type InputDialogContents = {
60
- type: "input";
54
+ type BaseDialogContents = {
55
+ style?: React.CSSProperties;
56
+ className?: string;
61
57
  title: string;
62
58
  description?: ReactNode;
59
+ };
60
+
61
+ type InputDialogContents = BaseDialogContents & {
62
+ type: "input";
63
63
  placeholder?: string;
64
64
  inputValue: string;
65
65
  resolve: (value: string | undefined) => void;
66
66
  };
67
67
 
68
- type ConfirmationDialogContents = {
68
+ type ConfirmationDialogContents = BaseDialogContents & {
69
69
  type: "confirmation";
70
- title: string;
71
- description?: ReactNode;
72
70
  resolve: (value: boolean) => void;
73
71
  };
74
72
 
75
- type CustomDialogContents = {
73
+ type CustomDialogContents = BaseDialogContents & {
76
74
  type: "custom";
77
- title: string;
78
- description?: ReactNode;
79
75
  children: ReactNode;
80
76
  resolve: () => void;
81
77
  };
@@ -95,16 +91,20 @@ export const DialogProvider = function DialogProvider({
95
91
  const isOpen = !!contents;
96
92
 
97
93
  const close = useCallback(() => {
98
- if (!contents) return;
99
-
100
- if (contents.type === "input") {
101
- contents.resolve(undefined);
102
- } else {
103
- contents.resolve(false);
94
+ if (contents) {
95
+ if (contents.type === "input") {
96
+ contents.resolve(undefined);
97
+ } else {
98
+ contents.resolve(false);
99
+ }
104
100
  }
101
+
105
102
  setContents(undefined);
106
103
  }, [contents]);
107
104
 
105
+ const closeRef = useRef(close);
106
+ closeRef.current = close;
107
+
108
108
  const submit = useCallback(() => {
109
109
  if (!contents || contents.type !== "input") return;
110
110
 
@@ -115,7 +115,7 @@ export const DialogProvider = function DialogProvider({
115
115
  const open: DialogContextValue["openInputDialog"] = useCallback((options) => {
116
116
  const { promise, resolve } = createDeferredPromise<string | undefined>();
117
117
 
118
- const { title, description, initialValue, placeholder } =
118
+ const { title, description, initialValue, placeholder, style, className } =
119
119
  typeof options === "string" ? { title: options } : options;
120
120
 
121
121
  setContents({
@@ -124,6 +124,8 @@ export const DialogProvider = function DialogProvider({
124
124
  description,
125
125
  inputValue: initialValue ?? "",
126
126
  placeholder,
127
+ style,
128
+ className,
127
129
  resolve: (value: string | undefined) => {
128
130
  resolve(value);
129
131
  setContents(undefined);
@@ -137,13 +139,15 @@ export const DialogProvider = function DialogProvider({
137
139
  useCallback((options) => {
138
140
  const { promise, resolve } = createDeferredPromise<boolean>();
139
141
 
140
- const { title, description } =
142
+ const { title, description, style, className } =
141
143
  typeof options === "string" ? { title: options } : options;
142
144
 
143
145
  setContents({
144
146
  type: "confirmation",
145
147
  title,
146
148
  description,
149
+ style,
150
+ className,
147
151
  resolve: (value: boolean) => {
148
152
  resolve(value);
149
153
  setContents(undefined);
@@ -157,11 +161,20 @@ export const DialogProvider = function DialogProvider({
157
161
  (options) => {
158
162
  const { promise, resolve } = createDeferredPromise<void>();
159
163
 
164
+ const { title, description, style, className } = options;
165
+
166
+ const children =
167
+ typeof options.children === "function"
168
+ ? options.children({ close: closeRef.current })
169
+ : options.children;
170
+
160
171
  setContents({
161
172
  type: "custom",
162
- title: options.title,
163
- description: options.description,
164
- children: options.children,
173
+ title,
174
+ description,
175
+ style,
176
+ className,
177
+ children,
165
178
  resolve: () => {
166
179
  resolve();
167
180
  setContents(undefined);
@@ -214,6 +227,8 @@ export const DialogProvider = function DialogProvider({
214
227
  title={contents?.title}
215
228
  description={contents?.description}
216
229
  open={isOpen}
230
+ style={contents?.style}
231
+ className={contents?.className}
217
232
  onOpenChange={useCallback(
218
233
  (isOpen: boolean) => {
219
234
  if (!isOpen) {
@@ -1,5 +1,6 @@
1
1
  import React, { useLayoutEffect, useRef } from "react";
2
2
  import { ImperativePanelGroupHandle } from "react-resizable-panels";
3
+ import { PanelLayoutState } from "../components/workspace/types";
3
4
  import { useWindowSize } from "./useWindowSize";
4
5
 
5
6
  export const EDITOR_PANEL_GROUP_ID = "editor-panel-group";
@@ -7,11 +8,6 @@ export const LEFT_SIDEBAR_ID = "editor-left-sidebar";
7
8
  export const RIGHT_SIDEBAR_ID = "editor-right-sidebar";
8
9
  export const CONTENT_AREA_ID = "editor-content-area";
9
10
 
10
- export type PanelLayoutState = {
11
- leftSidebarCollapsed: boolean;
12
- rightSidebarCollapsed: boolean;
13
- };
14
-
15
11
  export function usePreservePanelSize(
16
12
  panelGroupRef: React.RefObject<ImperativePanelGroupHandle | null>,
17
13
  setPanelLayoutState?: (state: PanelLayoutState) => void