@noya-app/noya-designsystem 0.1.69 → 0.1.71
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 +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +314 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +294 -161
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/Button.tsx +2 -1
- package/src/components/Combobox.tsx +3 -0
- package/src/components/DraggableMenuButton.tsx +5 -0
- package/src/components/InputField.tsx +20 -4
- package/src/components/ListMenu.tsx +1 -1
- package/src/components/TreeView.tsx +1 -3
- package/src/components/UserPicker.tsx +128 -0
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +97 -55
- package/src/components/workspace/PanelWorkspaceLayout.tsx +12 -4
- package/src/components/workspace/WorkspaceLayout.tsx +7 -0
- package/src/components/workspace/types.ts +7 -0
- package/src/index.css +1 -1
- package/src/index.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.71",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@noya-app/noya-colorpicker": "0.1.30",
|
|
27
27
|
"@noya-app/noya-utils": "0.1.9",
|
|
28
28
|
"@noya-app/noya-geometry": "0.1.16",
|
|
29
|
-
"@noya-app/noya-icons": "0.1.
|
|
29
|
+
"@noya-app/noya-icons": "0.1.16",
|
|
30
30
|
"@noya-app/noya-keymap": "0.1.4",
|
|
31
31
|
"@noya-app/noya-tailwind-config": "0.1.8",
|
|
32
32
|
"radix-ui": "1.4.2",
|
|
@@ -14,7 +14,7 @@ import { Spacer } from "./Spacer";
|
|
|
14
14
|
import { Tooltip } from "./Tooltip";
|
|
15
15
|
import type { MenuItemIcon } from "./internal/Menu";
|
|
16
16
|
|
|
17
|
-
type ButtonVariant = "normal" | "thin" | "floating" | "none" | "ghost";
|
|
17
|
+
type ButtonVariant = "normal" | "thin" | "floating" | "none" | "ghost" | "link";
|
|
18
18
|
|
|
19
19
|
type ButtonSize = "small" | "normal" | "large" | "floating";
|
|
20
20
|
|
|
@@ -36,6 +36,7 @@ const variantStyles: Record<ButtonVariant, string> = {
|
|
|
36
36
|
thin: "n-bg-transparent n-text-text hover:n-bg-input-background-light",
|
|
37
37
|
none: "n-bg-transparent n-text-text hover:n-bg-input-background-light",
|
|
38
38
|
ghost: "n-bg-transparent n-text-text hover:n-bg-input-background-light",
|
|
39
|
+
link: "n-bg-transparent n-text-text hover:n-bg-input-background-light",
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
const sizeStyles: Record<ButtonSize | "thin" | "none", string> = {
|
|
@@ -47,6 +47,7 @@ export type BaseComboboxProps<T extends string> = {
|
|
|
47
47
|
readOnly?: boolean;
|
|
48
48
|
tabBehavior?: "autocomplete" | "select";
|
|
49
49
|
openMenuBehavior?: "showAllItems" | "showFilteredItems";
|
|
50
|
+
iconPosition?: "left" | "right";
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
export type StringModeOnlyProps = {
|
|
@@ -103,6 +104,7 @@ export const Combobox = memoGeneric(
|
|
|
103
104
|
openMenuBehavior = "showFilteredItems",
|
|
104
105
|
onFocus,
|
|
105
106
|
onDeleteWhenEmpty,
|
|
107
|
+
iconPosition = "right",
|
|
106
108
|
...rest
|
|
107
109
|
}: ComboboxProps<T>,
|
|
108
110
|
forwardedRef: ForwardedRef<ComboboxRef<T>>
|
|
@@ -500,6 +502,7 @@ export const Combobox = memoGeneric(
|
|
|
500
502
|
onSelectItem={handleUpdateSelection}
|
|
501
503
|
onHoverIndex={handleIndexChange}
|
|
502
504
|
listSize={adjustedListSize}
|
|
505
|
+
iconPosition={iconPosition}
|
|
503
506
|
style={{
|
|
504
507
|
flex: `0 0 ${computedHeight}px`,
|
|
505
508
|
}}
|
|
@@ -70,6 +70,11 @@ export const DraggableMenuButton = memoGeneric(function DraggableMenuButton<
|
|
|
70
70
|
(event: React.PointerEvent) => {
|
|
71
71
|
if (open || !downPosition) {
|
|
72
72
|
setDownPosition(null);
|
|
73
|
+
|
|
74
|
+
// if the button was clicked directly, close the menu
|
|
75
|
+
if (open && event.target === event.currentTarget) {
|
|
76
|
+
setOpen(false);
|
|
77
|
+
}
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -350,6 +350,10 @@ const InputFieldTypeahead = (props: {
|
|
|
350
350
|
* NumberInput
|
|
351
351
|
* ------------------------------------------------------------------------- */
|
|
352
352
|
|
|
353
|
+
type NumberChangeOptions = {
|
|
354
|
+
isEmpty: boolean;
|
|
355
|
+
};
|
|
356
|
+
|
|
353
357
|
type InputFieldNumberInputProps = Omit<
|
|
354
358
|
TextInputProps,
|
|
355
359
|
"value" | "onChange" | "onKeyDown" | "onSubmit" | "readOnly"
|
|
@@ -361,9 +365,10 @@ type InputFieldNumberInputProps = Omit<
|
|
|
361
365
|
onBlur?: FocusEventHandler;
|
|
362
366
|
readOnly?: boolean;
|
|
363
367
|
className?: string;
|
|
368
|
+
triggersOnEmpty?: boolean;
|
|
364
369
|
} & (
|
|
365
370
|
| {
|
|
366
|
-
onChange: (value: number) => void;
|
|
371
|
+
onChange: (value: number, options: NumberChangeOptions) => void;
|
|
367
372
|
}
|
|
368
373
|
| {
|
|
369
374
|
onSubmit: (value: number) => void;
|
|
@@ -375,7 +380,15 @@ function parseNumber(value: string) {
|
|
|
375
380
|
}
|
|
376
381
|
|
|
377
382
|
function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
378
|
-
const {
|
|
383
|
+
const {
|
|
384
|
+
value,
|
|
385
|
+
placeholder,
|
|
386
|
+
onNudge,
|
|
387
|
+
onBlur,
|
|
388
|
+
readOnly,
|
|
389
|
+
triggersOnEmpty,
|
|
390
|
+
...rest
|
|
391
|
+
} = props;
|
|
379
392
|
const onSubmit = "onSubmit" in props ? props.onSubmit : undefined;
|
|
380
393
|
const onChange = "onChange" in props ? props.onChange : undefined;
|
|
381
394
|
|
|
@@ -414,6 +427,9 @@ function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
|
414
427
|
(value: string) => {
|
|
415
428
|
if (value === "" || value === "-" || value === ".") {
|
|
416
429
|
setInternalValue(value);
|
|
430
|
+
if (triggersOnEmpty) {
|
|
431
|
+
onChange?.(0, { isEmpty: true });
|
|
432
|
+
}
|
|
417
433
|
return;
|
|
418
434
|
}
|
|
419
435
|
|
|
@@ -427,10 +443,10 @@ function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
|
427
443
|
const newValue = parseNumber(value);
|
|
428
444
|
|
|
429
445
|
if (!isNaN(newValue)) {
|
|
430
|
-
onChange?.(newValue);
|
|
446
|
+
onChange?.(newValue, { isEmpty: false });
|
|
431
447
|
}
|
|
432
448
|
},
|
|
433
|
-
[onChange]
|
|
449
|
+
[onChange, triggersOnEmpty]
|
|
434
450
|
);
|
|
435
451
|
|
|
436
452
|
const handleBlur = useCallback(
|
|
@@ -85,9 +85,7 @@ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
|
|
|
85
85
|
<Spacer.Horizontal size={6} />
|
|
86
86
|
{expanded === undefined ? (
|
|
87
87
|
<>
|
|
88
|
-
|
|
89
|
-
{/* Right-side padding for end chevron */}
|
|
90
|
-
<Spacer.Horizontal size={8} />
|
|
88
|
+
{/* Right-side padding not needed for end chevron since no text needs to be aligned */}
|
|
91
89
|
</>
|
|
92
90
|
) : (
|
|
93
91
|
<>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
OptionModeProps,
|
|
5
|
+
SelectableMenuItem,
|
|
6
|
+
} from "@noya-app/noya-designsystem";
|
|
7
|
+
import { Avatar, Combobox } from "@noya-app/noya-designsystem";
|
|
8
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
9
|
+
|
|
10
|
+
type ComboboxOptionProps = Omit<
|
|
11
|
+
OptionModeProps<string>,
|
|
12
|
+
"mode" | "items" | "value" | "onSelectItem" | "onChange" | "onHoverItem"
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export type UserPickerUser = {
|
|
16
|
+
id: string;
|
|
17
|
+
name?: string | null;
|
|
18
|
+
email?: string | null;
|
|
19
|
+
image?: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type UserPickerProps<TUser extends UserPickerUser = UserPickerUser> =
|
|
23
|
+
ComboboxOptionProps & {
|
|
24
|
+
users: readonly TUser[];
|
|
25
|
+
value?: string;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
onChangeUserId?: (userId: string | undefined) => void;
|
|
28
|
+
onSelectUser?: (user: TUser | undefined) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function UserPicker<TUser extends UserPickerUser>({
|
|
32
|
+
users,
|
|
33
|
+
value,
|
|
34
|
+
defaultValue,
|
|
35
|
+
onChangeUserId,
|
|
36
|
+
onSelectUser,
|
|
37
|
+
placeholder = "Search users...",
|
|
38
|
+
...rest
|
|
39
|
+
}: UserPickerProps<TUser>) {
|
|
40
|
+
const sortedUsers = useMemo(() => {
|
|
41
|
+
return [...users].sort((a, b) =>
|
|
42
|
+
getUserDisplayName(a).localeCompare(getUserDisplayName(b), undefined, {
|
|
43
|
+
sensitivity: "base",
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}, [users]);
|
|
47
|
+
|
|
48
|
+
const usersById = useMemo(() => {
|
|
49
|
+
return new Map(sortedUsers.map((user) => [user.id, user] as const));
|
|
50
|
+
}, [sortedUsers]);
|
|
51
|
+
|
|
52
|
+
const items = useMemo(() => {
|
|
53
|
+
return sortedUsers.map(createUserMenuItem);
|
|
54
|
+
}, [sortedUsers]);
|
|
55
|
+
|
|
56
|
+
const isControlled = value !== undefined;
|
|
57
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
58
|
+
const selectedUserId = isControlled ? value : uncontrolledValue;
|
|
59
|
+
|
|
60
|
+
const selectedItem = useMemo(() => {
|
|
61
|
+
if (!selectedUserId) return undefined;
|
|
62
|
+
return items.find((item) => item.value === selectedUserId);
|
|
63
|
+
}, [items, selectedUserId]);
|
|
64
|
+
|
|
65
|
+
const updateSelectedUserId = useCallback(
|
|
66
|
+
(userId?: string) => {
|
|
67
|
+
if (!isControlled) {
|
|
68
|
+
setUncontrolledValue(userId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onChangeUserId?.(userId);
|
|
72
|
+
},
|
|
73
|
+
[isControlled, onChangeUserId]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const handleSelectItem = useCallback(
|
|
77
|
+
(item: SelectableMenuItem<string>) => {
|
|
78
|
+
updateSelectedUserId(item.value);
|
|
79
|
+
onSelectUser?.(usersById.get(item.value));
|
|
80
|
+
},
|
|
81
|
+
[updateSelectedUserId, onSelectUser, usersById]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Combobox
|
|
86
|
+
{...rest}
|
|
87
|
+
placeholder={placeholder}
|
|
88
|
+
mode="option"
|
|
89
|
+
items={items}
|
|
90
|
+
value={selectedItem}
|
|
91
|
+
onSelectItem={handleSelectItem}
|
|
92
|
+
iconPosition="left"
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getUserDisplayName(user: UserPickerUser) {
|
|
98
|
+
const name = user.name?.trim();
|
|
99
|
+
const email = user.email?.trim();
|
|
100
|
+
|
|
101
|
+
return name || email || "Anonymous user";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createUserMenuItem(user: UserPickerUser): SelectableMenuItem<string> {
|
|
105
|
+
const title = getUserDisplayName(user);
|
|
106
|
+
const overflow = 3;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
value: user.id,
|
|
110
|
+
title,
|
|
111
|
+
icon: (
|
|
112
|
+
<div className="n-relative n-w-[15px] n-h-[15px]">
|
|
113
|
+
<Avatar
|
|
114
|
+
size={15 + overflow * 2}
|
|
115
|
+
name={title}
|
|
116
|
+
userId={user.id}
|
|
117
|
+
image={user.image ?? undefined}
|
|
118
|
+
className="n-pointer-events-none"
|
|
119
|
+
variant="bare"
|
|
120
|
+
style={{
|
|
121
|
+
position: "absolute",
|
|
122
|
+
inset: -overflow,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -6,8 +6,9 @@ import { DividerVertical } from "../Divider";
|
|
|
6
6
|
import { Drawer } from "../Drawer";
|
|
7
7
|
import { isSelectableMenuItem, MenuItem } from "../internal/Menu";
|
|
8
8
|
import { EDITOR_PANEL_GROUP_ID, RIGHT_SIDEBAR_ID } from "./constants";
|
|
9
|
-
import { LayoutProps, PanelLayoutState } from "./types";
|
|
9
|
+
import { LayoutProps, PanelLayoutState, SidebarRef } from "./types";
|
|
10
10
|
import { VerticalTabMenu } from "./VerticalTabMenu";
|
|
11
|
+
import { Button } from "../Button";
|
|
11
12
|
|
|
12
13
|
export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
13
14
|
LeftTab extends string = string,
|
|
@@ -24,6 +25,7 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
|
24
25
|
rightTabValue,
|
|
25
26
|
onChangeRightTab,
|
|
26
27
|
onChangeLeftTab,
|
|
28
|
+
compactDrawerMenu,
|
|
27
29
|
}: LayoutProps<LeftTab, RightTab>) {
|
|
28
30
|
const portalContainer = useRef<HTMLDivElement>(null);
|
|
29
31
|
|
|
@@ -44,6 +46,14 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
|
44
46
|
|
|
45
47
|
const hasTabs = allTabItems.length > 0;
|
|
46
48
|
|
|
49
|
+
// Determine if we should use the compact top-row menu variant.
|
|
50
|
+
const allSelectableTabItems = useMemo(
|
|
51
|
+
() => allTabItems.filter(isSelectableMenuItem),
|
|
52
|
+
[allTabItems]
|
|
53
|
+
);
|
|
54
|
+
const useCompactInToolbar =
|
|
55
|
+
!!compactDrawerMenu && allSelectableTabItems.length <= 1;
|
|
56
|
+
|
|
47
57
|
const leftSelectableTabItems = useMemo(
|
|
48
58
|
() => (leftTabItems ?? []).filter(isSelectableMenuItem),
|
|
49
59
|
[leftTabItems]
|
|
@@ -54,6 +64,39 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
|
54
64
|
[rightTabItems]
|
|
55
65
|
);
|
|
56
66
|
|
|
67
|
+
// Shared helpers for expanding/collapsing drawers
|
|
68
|
+
const toggleSidebar = (ref: React.RefObject<SidebarRef | null>) => {
|
|
69
|
+
if (ref.current?.isExpanded()) {
|
|
70
|
+
ref.current?.collapse();
|
|
71
|
+
} else {
|
|
72
|
+
ref.current?.expand();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const ensureExpanded = (ref: React.RefObject<SidebarRef | null>) => {
|
|
77
|
+
if (!ref.current?.isExpanded()) {
|
|
78
|
+
ref.current?.expand();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleSelectTab = (value: LeftTab | RightTab) => {
|
|
83
|
+
if (leftSelectableTabItems.some((item) => item.value === value)) {
|
|
84
|
+
if (value === leftTabValue) {
|
|
85
|
+
toggleSidebar(leftSidebarRef);
|
|
86
|
+
} else {
|
|
87
|
+
ensureExpanded(leftSidebarRef);
|
|
88
|
+
onChangeLeftTab?.(value as LeftTab);
|
|
89
|
+
}
|
|
90
|
+
} else if (rightSelectableTabItems.some((item) => item.value === value)) {
|
|
91
|
+
if (value === rightTabValue) {
|
|
92
|
+
toggleSidebar(rightSidebarRef);
|
|
93
|
+
} else {
|
|
94
|
+
ensureExpanded(rightSidebarRef);
|
|
95
|
+
onChangeRightTab?.(value as RightTab);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
57
100
|
const { activeValue, isSidebarCollapsed } =
|
|
58
101
|
leftSelectableTabItems.length > 0 && !leftSidebarCollapsed
|
|
59
102
|
? { activeValue: leftTabValue, isSidebarCollapsed: leftSidebarCollapsed }
|
|
@@ -70,60 +113,60 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
|
70
113
|
id={EDITOR_PANEL_GROUP_ID}
|
|
71
114
|
className="n-flex n-flex-1 n-relative focus:n-outline-none"
|
|
72
115
|
>
|
|
73
|
-
{hasTabs && (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
rightSidebarRef.current?.collapse();
|
|
100
|
-
} else {
|
|
101
|
-
rightSidebarRef.current?.expand();
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
if (!rightSidebarRef.current?.isExpanded()) {
|
|
105
|
-
rightSidebarRef.current?.expand();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
onChangeRightTab?.(value as RightTab);
|
|
116
|
+
{hasTabs && !useCompactInToolbar && (
|
|
117
|
+
<>
|
|
118
|
+
<VerticalTabMenu
|
|
119
|
+
tooltipSide="right"
|
|
120
|
+
items={allTabItems}
|
|
121
|
+
activeValue={activeValue}
|
|
122
|
+
isSidebarCollapsed={isSidebarCollapsed}
|
|
123
|
+
onChange={(value) => handleSelectTab(value as LeftTab | RightTab)}
|
|
124
|
+
/>
|
|
125
|
+
<DividerVertical className="n-h-full" />
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{useCompactInToolbar && (
|
|
130
|
+
<div className="n-absolute n-top-0 n-left-0 n-h-[calc(var(--n-toolbar-height)+1px)] n-w-[calc(var(--n-toolbar-height)+1px)] n-bg-sidebar-background n-border-r n-border-b n-border-divider-strong n-z-10 n-flex n-items-center n-justify-center">
|
|
131
|
+
{allSelectableTabItems[0] && (
|
|
132
|
+
<Button
|
|
133
|
+
variant="none"
|
|
134
|
+
className="n-rounded"
|
|
135
|
+
style={{
|
|
136
|
+
width: "var(--n-input-height)",
|
|
137
|
+
height: "var(--n-input-height)",
|
|
138
|
+
}}
|
|
139
|
+
icon={allSelectableTabItems[0].icon}
|
|
140
|
+
tooltip={
|
|
141
|
+
allSelectableTabItems[0].tooltip ?? allSelectableTabItems[0].title
|
|
109
142
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
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;
|
|
148
|
+
})()}
|
|
149
|
+
onClick={() => {
|
|
150
|
+
const value = allSelectableTabItems[0].value as LeftTab | RightTab;
|
|
151
|
+
handleSelectTab(value);
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
113
156
|
)}
|
|
114
|
-
|
|
157
|
+
|
|
115
158
|
<div className="n-flex-1 n-flex n-relative">{centerPanel}</div>
|
|
159
|
+
|
|
116
160
|
{leftPanel && (
|
|
117
161
|
<Drawer
|
|
118
162
|
ref={leftSidebarRef}
|
|
119
163
|
className="n-flex-1"
|
|
120
|
-
style={
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}}
|
|
164
|
+
style={
|
|
165
|
+
useCompactInToolbar
|
|
166
|
+
? undefined
|
|
167
|
+
: { left: 48, maxWidth: "calc(100% - 48px)" }
|
|
168
|
+
}
|
|
169
|
+
overlayStyle={useCompactInToolbar ? undefined : { left: 48 }}
|
|
127
170
|
open={!leftSidebarCollapsed}
|
|
128
171
|
positioning="absolute"
|
|
129
172
|
side="left"
|
|
@@ -144,13 +187,12 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
|
|
|
144
187
|
id={RIGHT_SIDEBAR_ID}
|
|
145
188
|
ref={rightSidebarRef}
|
|
146
189
|
className="n-flex-1"
|
|
147
|
-
style={
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}}
|
|
190
|
+
style={
|
|
191
|
+
useCompactInToolbar
|
|
192
|
+
? undefined
|
|
193
|
+
: { left: 48, maxWidth: "calc(100% - 48px)" }
|
|
194
|
+
}
|
|
195
|
+
overlayStyle={useCompactInToolbar ? undefined : { left: 48 }}
|
|
154
196
|
open={!rightSidebarCollapsed}
|
|
155
197
|
positioning="absolute"
|
|
156
198
|
side="left"
|
|
@@ -52,6 +52,7 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
52
52
|
rightTabValue,
|
|
53
53
|
onChangeLeftTab,
|
|
54
54
|
onChangeRightTab,
|
|
55
|
+
compactDrawerMenu,
|
|
55
56
|
}: LayoutProps<LeftTab, RightTab>) {
|
|
56
57
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
|
57
58
|
|
|
@@ -159,6 +160,13 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
159
160
|
[setData, propertyKey]
|
|
160
161
|
);
|
|
161
162
|
|
|
163
|
+
const showLeftTabs =
|
|
164
|
+
!!leftSidebarOptions.showTabs &&
|
|
165
|
+
!!leftTabItems &&
|
|
166
|
+
// When compact drawer UI is enabled, only show the rail when the
|
|
167
|
+
// sidebar is collapsed (for large screens/panel layout).
|
|
168
|
+
(!compactDrawerMenu || leftSidebarCollapsed);
|
|
169
|
+
|
|
162
170
|
return (
|
|
163
171
|
<PanelGroup
|
|
164
172
|
ref={panelGroupRef}
|
|
@@ -171,7 +179,7 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
171
179
|
>
|
|
172
180
|
{leftPanel && (
|
|
173
181
|
<>
|
|
174
|
-
{
|
|
182
|
+
{showLeftTabs && (
|
|
175
183
|
<VerticalTabMenu
|
|
176
184
|
tooltipSide="right"
|
|
177
185
|
items={leftTabItems}
|
|
@@ -180,9 +188,9 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
180
188
|
onChange={handleLeftTabChange}
|
|
181
189
|
/>
|
|
182
190
|
)}
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
{showLeftTabs && !leftSidebarCollapsed && (
|
|
192
|
+
<DividerVertical className="n-h-full" />
|
|
193
|
+
)}
|
|
186
194
|
<Panel
|
|
187
195
|
id={LEFT_SIDEBAR_ID}
|
|
188
196
|
className="n-relative"
|
|
@@ -57,6 +57,11 @@ export interface WorkspaceLayoutProps<
|
|
|
57
57
|
sideType?: SideType;
|
|
58
58
|
sideTypeBreakpoint?: number;
|
|
59
59
|
detectSize?: DetectSizeType;
|
|
60
|
+
/**
|
|
61
|
+
* Use a compact top-row menu in Drawer (mobile) layout instead of a
|
|
62
|
+
* full-height left menu. Useful when there is only a single icon.
|
|
63
|
+
*/
|
|
64
|
+
compactDrawerMenu?: boolean;
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
export type WorkspaceLayoutRef = {
|
|
@@ -147,6 +152,7 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
147
152
|
showTabs: rightShowTabs = true,
|
|
148
153
|
} = {},
|
|
149
154
|
theme,
|
|
155
|
+
compactDrawerMenu,
|
|
150
156
|
}: WorkspaceLayoutProps<LeftTab, RightTab>,
|
|
151
157
|
forwardedRef: React.ForwardedRef<WorkspaceLayoutRef>
|
|
152
158
|
) {
|
|
@@ -446,6 +452,7 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
446
452
|
autoSavePrefix={autoSavePrefix}
|
|
447
453
|
leftSidebarRef={leftSidebarRef}
|
|
448
454
|
rightSidebarRef={rightSidebarRef}
|
|
455
|
+
compactDrawerMenu={compactDrawerMenu}
|
|
449
456
|
/>
|
|
450
457
|
)}
|
|
451
458
|
</div>
|
|
@@ -51,6 +51,13 @@ export type LayoutProps<
|
|
|
51
51
|
autoSavePrefix?: string;
|
|
52
52
|
leftSidebarRef: React.RefObject<SidebarRef | null>;
|
|
53
53
|
rightSidebarRef: React.RefObject<SidebarRef | null>;
|
|
54
|
+
/**
|
|
55
|
+
* When using the Drawer (mobile) layout, render the vertical tab menu
|
|
56
|
+
* in a compact top row whose height equals the toolbar height.
|
|
57
|
+
* Intended for scenarios with a single icon to avoid reserving
|
|
58
|
+
* full-height left-side space.
|
|
59
|
+
*/
|
|
60
|
+
compactDrawerMenu?: boolean;
|
|
54
61
|
};
|
|
55
62
|
|
|
56
63
|
export type WorkspaceLayoutGroup = {
|
package/src/index.css
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
--n-active-background: var(--n-indigo-150);
|
|
92
92
|
--n-thumbnail-background: #f0efff;
|
|
93
93
|
--n-thumbnail-shadow: #d3ceed66;
|
|
94
|
-
--n-banner-info-background: var(--n-indigo-
|
|
94
|
+
--n-banner-info-background: var(--n-indigo-25);
|
|
95
95
|
--n-banner-info-border: var(--n-divider-strong);
|
|
96
96
|
--n-banner-info-text: #6579aa;
|
|
97
97
|
--n-banner-error-background: #ffe2e2;
|
package/src/index.tsx
CHANGED
|
@@ -83,6 +83,7 @@ export * from "./components/TextArea";
|
|
|
83
83
|
export * from "./components/Toast";
|
|
84
84
|
export * from "./components/Tooltip";
|
|
85
85
|
export * from "./components/TreeView";
|
|
86
|
+
export * from "./components/UserPicker";
|
|
86
87
|
export * from "./components/UserPointer";
|
|
87
88
|
export * from "./components/workspace/types";
|
|
88
89
|
export * from "./components/workspace/WorkspaceLayout";
|