@noya-app/noya-designsystem 0.1.86 → 0.1.88
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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +12 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +180 -117
- package/dist/index.d.ts +180 -117
- package/dist/index.js +2105 -1872
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1930 -1704
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/SharedDragProvider.test.ts +39 -0
- package/src/components/AppToolbar.tsx +464 -0
- package/src/components/Card.tsx +20 -0
- package/src/components/Dialog.tsx +12 -5
- package/src/components/DialogNavigator.tsx +154 -0
- package/src/components/Navigator.tsx +2 -0
- package/src/components/SegmentedControl.tsx +32 -4
- package/src/components/SelectionToolbar.tsx +5 -10
- package/src/components/StatCard.tsx +82 -0
- package/src/components/Toolbar.tsx +32 -459
- package/src/components/__tests__/Card.test.tsx +71 -0
- package/src/components/__tests__/DialogNavigator.test.tsx +88 -0
- package/src/components/__tests__/SegmentedControl.test.ts +46 -0
- package/src/components/__tests__/Toolbar.test.tsx +56 -0
- package/src/components/sorting/SharedDragProvider.tsx +37 -1
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +3 -13
- package/src/components/workspace/VerticalTabMenu.tsx +1 -1
- package/src/components/workspace/__tests__/DrawerWorkspaceLayout.test.tsx +64 -0
- package/src/index.css +6 -0
- package/src/index.tsx +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PointerSensorProps } from "@dnd-kit/core";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import { ContinuousPointerSensor } from "../components/sorting/SharedDragProvider";
|
|
4
|
+
|
|
5
|
+
describe("ContinuousPointerSensor", () => {
|
|
6
|
+
test("forwards the movement that activates a distance-constrained drag", () => {
|
|
7
|
+
const target = document.createElement("div");
|
|
8
|
+
document.body.appendChild(target);
|
|
9
|
+
const pointerDown = new PointerEvent("pointerdown", {
|
|
10
|
+
clientX: 10,
|
|
11
|
+
clientY: 20,
|
|
12
|
+
});
|
|
13
|
+
target.dispatchEvent(pointerDown);
|
|
14
|
+
|
|
15
|
+
const events: string[] = [];
|
|
16
|
+
const props = {
|
|
17
|
+
active: "item",
|
|
18
|
+
activeNode: {},
|
|
19
|
+
context: { current: {} },
|
|
20
|
+
event: pointerDown,
|
|
21
|
+
options: { activationConstraint: { distance: 4 } },
|
|
22
|
+
onAbort: () => {},
|
|
23
|
+
onPending: () => {},
|
|
24
|
+
onStart: () => events.push("start"),
|
|
25
|
+
onMove: ({ x, y }) => events.push(`move:${x},${y}`),
|
|
26
|
+
onEnd: () => events.push("end"),
|
|
27
|
+
onCancel: () => {},
|
|
28
|
+
} as PointerSensorProps;
|
|
29
|
+
|
|
30
|
+
new ContinuousPointerSensor(props);
|
|
31
|
+
document.dispatchEvent(
|
|
32
|
+
new PointerEvent("pointermove", { clientX: 30, clientY: 40 })
|
|
33
|
+
);
|
|
34
|
+
document.dispatchEvent(new PointerEvent("pointerup"));
|
|
35
|
+
|
|
36
|
+
expect(events).toEqual(["start", "move:30,40", "end"]);
|
|
37
|
+
target.remove();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
4
|
+
import React, {
|
|
5
|
+
ComponentProps,
|
|
6
|
+
createContext,
|
|
7
|
+
ReactNode,
|
|
8
|
+
useContext,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { memoGeneric } from "@noya-app/react-utils";
|
|
12
|
+
import { BaseToolbar } from "./BaseToolbar";
|
|
13
|
+
import { Button } from "./Button";
|
|
14
|
+
import { DividerVertical } from "./Divider";
|
|
15
|
+
import { DropdownMenu } from "./DropdownMenu";
|
|
16
|
+
import {
|
|
17
|
+
getKeyboardShortcutsForMenuItems,
|
|
18
|
+
isSelectableMenuItem,
|
|
19
|
+
KeyboardShortcut,
|
|
20
|
+
MenuItem,
|
|
21
|
+
PopoverMenuItem,
|
|
22
|
+
SelectableMenuItem,
|
|
23
|
+
SubMenuItem,
|
|
24
|
+
} from "./internal/Menu";
|
|
25
|
+
import { Popover } from "./Popover";
|
|
26
|
+
import { Spacer } from "./Spacer";
|
|
27
|
+
import { ToolbarDrawer } from "./ToolbarDrawer";
|
|
28
|
+
import { Tooltip } from "./Tooltip";
|
|
29
|
+
|
|
30
|
+
const SIDE_OFFSET = 6;
|
|
31
|
+
|
|
32
|
+
export const ToolbarMenuContext = createContext<{
|
|
33
|
+
dividerOverflow?: number;
|
|
34
|
+
/** When true, PopoverMenuItem items show as dialogs on small screens */
|
|
35
|
+
useDialogOnSmallScreen?: boolean;
|
|
36
|
+
/** Function to detect if screen is small. Required when useDialogOnSmallScreen is true */
|
|
37
|
+
isSmallScreen?: boolean;
|
|
38
|
+
/** Function to open a dialog. Required when useDialogOnSmallScreen is true */
|
|
39
|
+
openDialog?: (options: {
|
|
40
|
+
title: ReactNode;
|
|
41
|
+
className?: string;
|
|
42
|
+
children: ReactNode | ((props: { close: () => void }) => ReactNode);
|
|
43
|
+
}) => Promise<void> & { close: () => void };
|
|
44
|
+
}>({});
|
|
45
|
+
|
|
46
|
+
export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
|
|
47
|
+
T extends string,
|
|
48
|
+
>({
|
|
49
|
+
item,
|
|
50
|
+
onSelectMenuItem,
|
|
51
|
+
onPopoverOpenChange,
|
|
52
|
+
}: {
|
|
53
|
+
item: SubMenuItem<T>;
|
|
54
|
+
onSelectMenuItem?: (value: T) => void;
|
|
55
|
+
onPopoverOpenChange?: (open: boolean) => void;
|
|
56
|
+
}) {
|
|
57
|
+
const [open, setOpen] = React.useState(false);
|
|
58
|
+
|
|
59
|
+
const handleOpenChange = React.useCallback(
|
|
60
|
+
(newOpen: boolean) => {
|
|
61
|
+
setOpen(newOpen);
|
|
62
|
+
onPopoverOpenChange?.(newOpen);
|
|
63
|
+
},
|
|
64
|
+
[onPopoverOpenChange]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<DropdownMenu
|
|
69
|
+
open={open}
|
|
70
|
+
onOpenChange={handleOpenChange}
|
|
71
|
+
items={item.items}
|
|
72
|
+
modal={false}
|
|
73
|
+
onSelect={(value) => {
|
|
74
|
+
if (onSelectMenuItem && value) {
|
|
75
|
+
onSelectMenuItem(value);
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<Button
|
|
80
|
+
disabled={item.disabled}
|
|
81
|
+
active={item.checked || open}
|
|
82
|
+
icon={item.icon}
|
|
83
|
+
iconRight={
|
|
84
|
+
item.icon === "DotsVerticalIcon" || item.icon === "DotsHorizontalIcon"
|
|
85
|
+
? undefined
|
|
86
|
+
: "DropdownChevronIcon"
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
{item.title}
|
|
90
|
+
</Button>
|
|
91
|
+
</DropdownMenu>
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const ToolbarShortcut = ({
|
|
96
|
+
shortcut,
|
|
97
|
+
label,
|
|
98
|
+
}: {
|
|
99
|
+
shortcut: string;
|
|
100
|
+
label?: ReactNode;
|
|
101
|
+
}) => {
|
|
102
|
+
return (
|
|
103
|
+
<div className="n-flex n-items-center">
|
|
104
|
+
{label}
|
|
105
|
+
<Spacer.Horizontal size={6} />
|
|
106
|
+
<KeyboardShortcut shortcut={shortcut} />
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const ToolbarMenuPopover = memoGeneric(function ToolbarMenuPopover({
|
|
112
|
+
item,
|
|
113
|
+
portalContainer,
|
|
114
|
+
onPopoverOpenChange,
|
|
115
|
+
}: {
|
|
116
|
+
item: PopoverMenuItem;
|
|
117
|
+
portalContainer?: HTMLElement | null;
|
|
118
|
+
onPopoverOpenChange?: (open: boolean) => void;
|
|
119
|
+
}) {
|
|
120
|
+
const {
|
|
121
|
+
disabled,
|
|
122
|
+
icon,
|
|
123
|
+
title,
|
|
124
|
+
tooltip,
|
|
125
|
+
content: itemContent,
|
|
126
|
+
checked,
|
|
127
|
+
popoverClassName,
|
|
128
|
+
triggerProps: buttonProps,
|
|
129
|
+
} = item;
|
|
130
|
+
const [open, setOpen] = React.useState(false);
|
|
131
|
+
|
|
132
|
+
const handleOpenChange = React.useCallback(
|
|
133
|
+
(newOpen: boolean) => {
|
|
134
|
+
setOpen(newOpen);
|
|
135
|
+
onPopoverOpenChange?.(newOpen);
|
|
136
|
+
},
|
|
137
|
+
[onPopoverOpenChange]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const { useDialogOnSmallScreen, isSmallScreen, openDialog } =
|
|
141
|
+
useContext(ToolbarMenuContext);
|
|
142
|
+
|
|
143
|
+
const shouldUseDialog = useDialogOnSmallScreen && isSmallScreen && openDialog;
|
|
144
|
+
|
|
145
|
+
const handleOpenDialog = React.useCallback(() => {
|
|
146
|
+
if (!openDialog) return;
|
|
147
|
+
|
|
148
|
+
handleOpenChange(true);
|
|
149
|
+
const dialogContent =
|
|
150
|
+
typeof itemContent === "function" ? (
|
|
151
|
+
(props: { close: () => void }) => (
|
|
152
|
+
<div className="-n-m-3">{itemContent(props)}</div>
|
|
153
|
+
)
|
|
154
|
+
) : (
|
|
155
|
+
<div className="-n-m-3">{itemContent}</div>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const dialog = openDialog({
|
|
159
|
+
title,
|
|
160
|
+
children: dialogContent,
|
|
161
|
+
});
|
|
162
|
+
dialog.then(() => handleOpenChange(false));
|
|
163
|
+
}, [openDialog, itemContent, title, handleOpenChange]);
|
|
164
|
+
|
|
165
|
+
const trigger = (
|
|
166
|
+
<Button
|
|
167
|
+
disabled={disabled}
|
|
168
|
+
active={checked || open}
|
|
169
|
+
icon={icon}
|
|
170
|
+
iconRight="DropdownChevronIcon"
|
|
171
|
+
onClick={shouldUseDialog ? handleOpenDialog : undefined}
|
|
172
|
+
{...buttonProps}
|
|
173
|
+
>
|
|
174
|
+
{title}
|
|
175
|
+
</Button>
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// On small screens with dialog mode enabled, just render the trigger button
|
|
179
|
+
if (shouldUseDialog) {
|
|
180
|
+
return tooltip && !open ? (
|
|
181
|
+
<Tooltip
|
|
182
|
+
sideOffset={SIDE_OFFSET}
|
|
183
|
+
side="top"
|
|
184
|
+
content={tooltip}
|
|
185
|
+
portalContainer={portalContainer}
|
|
186
|
+
>
|
|
187
|
+
{trigger}
|
|
188
|
+
</Tooltip>
|
|
189
|
+
) : (
|
|
190
|
+
trigger
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const content = (
|
|
195
|
+
<span>
|
|
196
|
+
<Popover
|
|
197
|
+
open={open}
|
|
198
|
+
onOpenChange={handleOpenChange}
|
|
199
|
+
side="top"
|
|
200
|
+
showArrow={false}
|
|
201
|
+
sideOffset={SIDE_OFFSET}
|
|
202
|
+
portalContainer={portalContainer}
|
|
203
|
+
trigger={trigger}
|
|
204
|
+
className={popoverClassName}
|
|
205
|
+
>
|
|
206
|
+
{typeof itemContent === "function"
|
|
207
|
+
? itemContent({ close: () => handleOpenChange(false) })
|
|
208
|
+
: itemContent}
|
|
209
|
+
</Popover>
|
|
210
|
+
</span>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
return tooltip && !open ? (
|
|
214
|
+
<Tooltip
|
|
215
|
+
sideOffset={SIDE_OFFSET}
|
|
216
|
+
side="top"
|
|
217
|
+
content={tooltip}
|
|
218
|
+
portalContainer={portalContainer}
|
|
219
|
+
>
|
|
220
|
+
{content}
|
|
221
|
+
</Tooltip>
|
|
222
|
+
) : (
|
|
223
|
+
content
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
|
|
228
|
+
T extends string,
|
|
229
|
+
>({
|
|
230
|
+
item,
|
|
231
|
+
onSelectMenuItem,
|
|
232
|
+
as,
|
|
233
|
+
activeValue,
|
|
234
|
+
tooltipSide,
|
|
235
|
+
displayFilter,
|
|
236
|
+
}: {
|
|
237
|
+
item: SelectableMenuItem<T>;
|
|
238
|
+
onSelectMenuItem?: (value: T) => void;
|
|
239
|
+
as?: ComponentProps<typeof Button>["as"];
|
|
240
|
+
activeValue?: T;
|
|
241
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
242
|
+
displayFilter?: DisplayFilter;
|
|
243
|
+
}) {
|
|
244
|
+
const isActive = item.checked || activeValue === item.value;
|
|
245
|
+
|
|
246
|
+
const content = (
|
|
247
|
+
<Button
|
|
248
|
+
as={as}
|
|
249
|
+
disabled={item.disabled}
|
|
250
|
+
active={isActive}
|
|
251
|
+
icon={displayFilter === "textOnly" ? undefined : item.icon}
|
|
252
|
+
{...(as === "a" && { href: item.value })}
|
|
253
|
+
onClick={() => {
|
|
254
|
+
if (onSelectMenuItem && item.value) {
|
|
255
|
+
onSelectMenuItem(item.value);
|
|
256
|
+
}
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
{displayFilter === "iconOnly" ? undefined : item.title}
|
|
260
|
+
</Button>
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const tooltip =
|
|
264
|
+
item.tooltip ??
|
|
265
|
+
(displayFilter === "iconOnly" && item.title ? item.title : undefined);
|
|
266
|
+
|
|
267
|
+
return item.drawer && isActive ? (
|
|
268
|
+
<ToolbarDrawer trigger={content}>{item.drawer}</ToolbarDrawer>
|
|
269
|
+
) : tooltip ? (
|
|
270
|
+
<Tooltip sideOffset={SIDE_OFFSET} content={tooltip} side={tooltipSide}>
|
|
271
|
+
{content}
|
|
272
|
+
</Tooltip>
|
|
273
|
+
) : (
|
|
274
|
+
content
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
|
|
279
|
+
T extends string,
|
|
280
|
+
>({
|
|
281
|
+
item,
|
|
282
|
+
onSelectMenuItem,
|
|
283
|
+
buttonAs,
|
|
284
|
+
activeValue,
|
|
285
|
+
tooltipSide,
|
|
286
|
+
portalContainer,
|
|
287
|
+
displayFilter,
|
|
288
|
+
dividerOverflow: dividerOverflowProp,
|
|
289
|
+
onPopoverOpenChange,
|
|
290
|
+
}: {
|
|
291
|
+
item: MenuItem<T>;
|
|
292
|
+
onSelectMenuItem?: (value: T) => void;
|
|
293
|
+
buttonAs?: ComponentProps<typeof Button>["as"];
|
|
294
|
+
activeValue?: T;
|
|
295
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
296
|
+
portalContainer?: HTMLElement | null;
|
|
297
|
+
displayFilter?: DisplayFilter;
|
|
298
|
+
dividerOverflow?: number;
|
|
299
|
+
onPopoverOpenChange?: (open: boolean) => void;
|
|
300
|
+
}) {
|
|
301
|
+
const { dividerOverflow: contextDividerOverflow } =
|
|
302
|
+
useContext(ToolbarMenuContext);
|
|
303
|
+
|
|
304
|
+
const dividerOverflow = dividerOverflowProp ?? contextDividerOverflow ?? 4;
|
|
305
|
+
|
|
306
|
+
if (item.type === "sectionHeader") return null;
|
|
307
|
+
|
|
308
|
+
if (item.type === "separator") {
|
|
309
|
+
return <DividerVertical overflow={dividerOverflow} />;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (item.type === "popover") {
|
|
313
|
+
return (
|
|
314
|
+
<ToolbarMenuPopover
|
|
315
|
+
item={item}
|
|
316
|
+
portalContainer={portalContainer}
|
|
317
|
+
onPopoverOpenChange={onPopoverOpenChange}
|
|
318
|
+
/>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (isSelectableMenuItem(item)) {
|
|
323
|
+
return (
|
|
324
|
+
<ToolbarMenuButton
|
|
325
|
+
item={item}
|
|
326
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
327
|
+
as={buttonAs}
|
|
328
|
+
activeValue={activeValue}
|
|
329
|
+
tooltipSide={tooltipSide}
|
|
330
|
+
displayFilter={displayFilter}
|
|
331
|
+
/>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<ToolbarMenuDropdown
|
|
337
|
+
item={item}
|
|
338
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
339
|
+
onPopoverOpenChange={onPopoverOpenChange}
|
|
340
|
+
/>
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
type DisplayFilter = "iconOnly" | "iconAndText" | "textOnly";
|
|
345
|
+
|
|
346
|
+
export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
|
|
347
|
+
items,
|
|
348
|
+
onSelectMenuItem,
|
|
349
|
+
buttonAs,
|
|
350
|
+
activeValue,
|
|
351
|
+
tooltipSide,
|
|
352
|
+
portalContainer,
|
|
353
|
+
displayFilter = "iconAndText",
|
|
354
|
+
dividerOverflow,
|
|
355
|
+
onPopoverOpenChange,
|
|
356
|
+
}: {
|
|
357
|
+
items: MenuItem<T>[];
|
|
358
|
+
onSelectMenuItem?: (value: T) => void;
|
|
359
|
+
buttonAs?: ComponentProps<typeof Button>["as"];
|
|
360
|
+
activeValue?: T;
|
|
361
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
362
|
+
portalContainer?: HTMLElement | null;
|
|
363
|
+
displayFilter?: DisplayFilter;
|
|
364
|
+
dividerOverflow?: number;
|
|
365
|
+
onPopoverOpenChange?: (open: boolean) => void;
|
|
366
|
+
}) {
|
|
367
|
+
return (
|
|
368
|
+
<>
|
|
369
|
+
{items.map((item, i) => {
|
|
370
|
+
return (
|
|
371
|
+
<ToolbarMenuItem
|
|
372
|
+
key={i}
|
|
373
|
+
item={item}
|
|
374
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
375
|
+
buttonAs={buttonAs}
|
|
376
|
+
activeValue={activeValue}
|
|
377
|
+
tooltipSide={tooltipSide}
|
|
378
|
+
portalContainer={portalContainer}
|
|
379
|
+
displayFilter={displayFilter}
|
|
380
|
+
dividerOverflow={dividerOverflow}
|
|
381
|
+
onPopoverOpenChange={onPopoverOpenChange}
|
|
382
|
+
/>
|
|
383
|
+
);
|
|
384
|
+
})}
|
|
385
|
+
</>
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
export interface AppToolbarProps<T extends string = string> {
|
|
390
|
+
children?: React.ReactNode;
|
|
391
|
+
logo?: React.ReactNode;
|
|
392
|
+
leftMenuItems?: MenuItem<T>[];
|
|
393
|
+
rightMenuItems?: MenuItem<T>[];
|
|
394
|
+
onSelectMenuItem?: (value: T) => void;
|
|
395
|
+
/** Whether to bind keyboard shortcuts for menu items. @default true */
|
|
396
|
+
shouldBindKeyboardShortcuts?: boolean;
|
|
397
|
+
dividerOverflow?: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function useBindKeyboardShortcutsForMenuItems<T extends string>(
|
|
401
|
+
items: MenuItem<T>[],
|
|
402
|
+
onSelectMenuItem?: (value: T) => void
|
|
403
|
+
) {
|
|
404
|
+
return useKeyboardShortcuts(
|
|
405
|
+
React.useMemo(
|
|
406
|
+
() =>
|
|
407
|
+
onSelectMenuItem
|
|
408
|
+
? getKeyboardShortcutsForMenuItems(items, onSelectMenuItem)
|
|
409
|
+
: {},
|
|
410
|
+
[items, onSelectMenuItem]
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function AppToolbar<T extends string = string>({
|
|
416
|
+
children,
|
|
417
|
+
logo,
|
|
418
|
+
leftMenuItems = [],
|
|
419
|
+
rightMenuItems = [],
|
|
420
|
+
onSelectMenuItem,
|
|
421
|
+
shouldBindKeyboardShortcuts = true,
|
|
422
|
+
dividerOverflow,
|
|
423
|
+
}: AppToolbarProps<T>) {
|
|
424
|
+
// Register keyboard shortcuts for all menu items
|
|
425
|
+
const allMenuItems = React.useMemo(
|
|
426
|
+
() => [...leftMenuItems, ...rightMenuItems],
|
|
427
|
+
[leftMenuItems, rightMenuItems]
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
useBindKeyboardShortcutsForMenuItems(
|
|
431
|
+
allMenuItems,
|
|
432
|
+
shouldBindKeyboardShortcuts ? onSelectMenuItem : undefined
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<BaseToolbar
|
|
437
|
+
logo={logo}
|
|
438
|
+
left={
|
|
439
|
+
leftMenuItems.length > 0 && (
|
|
440
|
+
<div className="n-flex n-gap-2">
|
|
441
|
+
<ToolbarMenu
|
|
442
|
+
items={leftMenuItems}
|
|
443
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
444
|
+
dividerOverflow={dividerOverflow}
|
|
445
|
+
/>
|
|
446
|
+
</div>
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
right={
|
|
450
|
+
rightMenuItems.length > 0 && (
|
|
451
|
+
<div className="n-flex n-gap-2">
|
|
452
|
+
<ToolbarMenu
|
|
453
|
+
items={rightMenuItems}
|
|
454
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
455
|
+
dividerOverflow={dividerOverflow}
|
|
456
|
+
/>
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
>
|
|
461
|
+
{children}
|
|
462
|
+
</BaseToolbar>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import { cx } from "../utils/classNames";
|
|
3
|
+
|
|
4
|
+
export type CardProps = React.HTMLAttributes<HTMLDivElement>;
|
|
5
|
+
|
|
6
|
+
export const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
|
|
7
|
+
{ className, ...props },
|
|
8
|
+
forwardedRef
|
|
9
|
+
) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
ref={forwardedRef}
|
|
13
|
+
className={cx(
|
|
14
|
+
"n-box-border n-rounded-lg n-border n-border-divider-subtle n-bg-canvas-background n-p-4",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
});
|
|
@@ -32,7 +32,7 @@ const StyledPopup = forwardRef<
|
|
|
32
32
|
<DialogPrimitive.Popup
|
|
33
33
|
ref={ref}
|
|
34
34
|
className={cx(
|
|
35
|
-
`n-fixed n-top-1/2 n-left-1/2 -n-translate-x-1/2 -n-translate-y-1/2 n-w-[90vw] n-max-w-[550px] n-max-h-[85vh] n-p-dialog-padding n-rounded-[2px] n-font-sans n-text-heading5 n-font-normal n-leading-[19px] n-text-text-muted n-bg-popover-background n-pointer-events-all n-z-[1000] focus:n-outline-none n-shadow-dialog-shadow n-flex n-flex-col`,
|
|
35
|
+
`n-fixed n-top-1/2 n-left-1/2 -n-translate-x-1/2 -n-translate-y-1/2 n-w-[calc(100vw-16px)] sm:n-w-[90vw] n-max-w-[550px] n-max-h-[85vh] n-p-dialog-padding n-rounded-[2px] n-font-sans n-text-heading5 n-font-normal n-leading-[19px] n-text-text-muted n-bg-popover-background n-pointer-events-all n-z-[1000] focus:n-outline-none n-shadow-dialog-shadow n-flex n-flex-col`,
|
|
36
36
|
className
|
|
37
37
|
)}
|
|
38
38
|
{...props}
|
|
@@ -74,8 +74,9 @@ export interface IDialog {
|
|
|
74
74
|
containsElement: (element: HTMLElement) => boolean;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
interface
|
|
77
|
+
export interface DialogProps {
|
|
78
78
|
title?: ReactNode;
|
|
79
|
+
titleVisibility?: "visible" | "hidden";
|
|
79
80
|
description?: ReactNode;
|
|
80
81
|
children?: ReactNode;
|
|
81
82
|
style?: React.CSSProperties;
|
|
@@ -90,6 +91,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
90
91
|
{
|
|
91
92
|
children,
|
|
92
93
|
title,
|
|
94
|
+
titleVisibility = "visible",
|
|
93
95
|
description,
|
|
94
96
|
open,
|
|
95
97
|
style,
|
|
@@ -97,7 +99,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
97
99
|
closeOnInteractOutside: _closeOnInteractOutside = true,
|
|
98
100
|
className,
|
|
99
101
|
showCloseButton = true,
|
|
100
|
-
}:
|
|
102
|
+
}: DialogProps,
|
|
101
103
|
forwardedRef: ForwardedRef<IDialog>
|
|
102
104
|
) {
|
|
103
105
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
@@ -142,12 +144,17 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
142
144
|
}
|
|
143
145
|
/>
|
|
144
146
|
)}
|
|
145
|
-
{title && (
|
|
147
|
+
{title && titleVisibility === "visible" && (
|
|
146
148
|
<>
|
|
147
149
|
<StyledTitle>{title}</StyledTitle>
|
|
148
150
|
<Spacer.Vertical size={description ? 10 : 20} />
|
|
149
151
|
</>
|
|
150
152
|
)}
|
|
153
|
+
{title && titleVisibility === "hidden" && (
|
|
154
|
+
<VisuallyHidden>
|
|
155
|
+
<StyledTitle>{title}</StyledTitle>
|
|
156
|
+
</VisuallyHidden>
|
|
157
|
+
)}
|
|
151
158
|
{!title && (
|
|
152
159
|
<VisuallyHidden>
|
|
153
160
|
<StyledTitle>Dialog</StyledTitle>
|
|
@@ -167,7 +174,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
167
174
|
});
|
|
168
175
|
|
|
169
176
|
export const FullscreenDialog = forwardRef(function FullscreenDialog(
|
|
170
|
-
{ style, ...rest }:
|
|
177
|
+
{ style, ...rest }: DialogProps,
|
|
171
178
|
forwardedRef: ForwardedRef<IDialog>
|
|
172
179
|
) {
|
|
173
180
|
return (
|