@noya-app/noya-designsystem 0.1.66 → 0.1.68
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 +11 -11
- package/CHANGELOG.md +14 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +81 -5
- package/dist/index.d.ts +81 -5
- package/dist/index.js +1205 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1047 -698
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/components/Anchor.tsx +30 -0
- package/src/components/BaseToolbar.tsx +11 -8
- package/src/components/Chip.tsx +16 -2
- package/src/components/Collection.tsx +10 -0
- package/src/components/Combobox.tsx +30 -21
- package/src/components/ComboboxMenu.tsx +49 -18
- package/src/components/Dialog.tsx +7 -1
- package/src/components/EditableText.tsx +7 -3
- package/src/components/Grid.tsx +2 -0
- package/src/components/GridView.tsx +52 -9
- package/src/components/List.tsx +54 -9
- package/src/components/ListView.tsx +44 -0
- package/src/components/ScrollArea2.tsx +56 -0
- package/src/components/SearchCompletionMenu.tsx +6 -2
- package/src/components/TreeView.tsx +4 -0
- package/src/components/UserPointer.tsx +34 -7
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +1 -3
- package/src/components/listView/ListViewEditableRowTitle.tsx +23 -1
- package/src/components/listView/ListViewRoot.tsx +45 -13
- package/src/components/sorting/Sortable.tsx +22 -10
- package/src/components/workspace/WorkspaceLayout.tsx +62 -4
- package/src/components/workspace/WorkspaceSideContext.tsx +18 -0
- package/src/components/workspace/renderPanelChildren.tsx +2 -2
- package/src/components/workspace/types.ts +8 -1
- package/src/contexts/DialogContext.tsx +22 -10
- package/src/index.css +143 -1
- package/src/index.tsx +4 -0
- package/src/theme/proseTheme.ts +2 -0
- package/src/utils/combobox.ts +17 -2
- package/src/utils/detailLineClamp.ts +33 -0
- package/tailwind.config.ts +3 -1
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.68",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
"lint": "eslint . --max-warnings 0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@base-ui-components/react": "1.0.0-beta.4",
|
|
23
24
|
"@dnd-kit/core": "6.3.1",
|
|
24
25
|
"@dnd-kit/sortable": "10.0.0",
|
|
25
26
|
"@noya-app/noya-colorpicker": "0.1.29",
|
|
26
27
|
"@noya-app/noya-utils": "0.1.8",
|
|
27
28
|
"@noya-app/noya-geometry": "0.1.15",
|
|
28
|
-
"@noya-app/noya-icons": "0.1.
|
|
29
|
+
"@noya-app/noya-icons": "0.1.14",
|
|
29
30
|
"@noya-app/noya-keymap": "0.1.4",
|
|
30
31
|
"@noya-app/noya-tailwind-config": "0.1.7",
|
|
31
32
|
"radix-ui": "1.4.2",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
47
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
46
48
|
"@types/react": "*",
|
|
47
49
|
"@types/react-dom": "*",
|
|
48
50
|
"@types/react-virtualized": "^9.21.30",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { ComponentProps, ForwardedRef, forwardRef } from "react";
|
|
2
|
+
import { cx } from "../utils/classNames";
|
|
3
|
+
|
|
4
|
+
interface AnchorProps extends ComponentProps<"a"> {
|
|
5
|
+
active?: boolean;
|
|
6
|
+
as?: React.ElementType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Anchor = forwardRef(function Anchor(
|
|
10
|
+
{ className, active, children, as, ...props }: AnchorProps,
|
|
11
|
+
ref: ForwardedRef<HTMLAnchorElement>
|
|
12
|
+
) {
|
|
13
|
+
const Component = as ?? "a";
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Component
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cx(
|
|
19
|
+
"n-font-sans n-text-heading5 n-text-breadcrumb-text n-font-normal hover:n-text-breadcrumb-text-hover n-leading-[19px] n-rounded n-no-underline n-inline-flex n-items-center n-justify-center n-gap-2 hover:n-opacity-80 active:n-opacity-90 n-select-none",
|
|
20
|
+
active
|
|
21
|
+
? "n-text-text !n-underline n-decoration-text-decorative-light"
|
|
22
|
+
: "n-text-text-muted",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</Component>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
@@ -82,18 +82,21 @@ export function BaseToolbar({
|
|
|
82
82
|
<Spacer.Horizontal size={10} />
|
|
83
83
|
</>
|
|
84
84
|
)}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
{children && (
|
|
86
|
+
<div
|
|
87
|
+
className={
|
|
88
|
+
"n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none @2xl/toolbar:n-absolute n-inset-0"
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
<div className="n-flex n-items-center n-justify-center n-pointer-events-auto">
|
|
92
|
+
{children}
|
|
93
|
+
</div>
|
|
92
94
|
</div>
|
|
93
|
-
|
|
95
|
+
)}
|
|
94
96
|
<Spacer.Horizontal />
|
|
95
97
|
<Spacer.Horizontal size={10} />
|
|
96
98
|
<div className="n-flex n-gap-toolbar-separator">{right}</div>
|
|
99
|
+
<Spacer.Horizontal size={10} />
|
|
97
100
|
</BaseToolbarContainer>
|
|
98
101
|
);
|
|
99
102
|
}
|
package/src/components/Chip.tsx
CHANGED
|
@@ -29,6 +29,15 @@ const SHADOW_STYLES = {
|
|
|
29
29
|
default: "n-shadow-[0_0_0_1px_var(--n-chip-default-shadow)_inset]",
|
|
30
30
|
} as const;
|
|
31
31
|
|
|
32
|
+
const HOVER_SHADOW_STYLES = {
|
|
33
|
+
primary: "hover:n-shadow-[0_0_0_1px_var(--n-chip-primary-shadow-hover)_inset]",
|
|
34
|
+
secondary:
|
|
35
|
+
"hover:n-shadow-[0_0_0_1px_var(--n-chip-secondary-shadow-hover)_inset]",
|
|
36
|
+
info: "hover:n-shadow-[0_0_0_1px_var(--n-chip-default-shadow-hover)_inset]",
|
|
37
|
+
error: "hover:n-shadow-[0_0_0_1px_var(--n-chip-error-shadow-hover)_inset]",
|
|
38
|
+
default: "hover:n-shadow-[0_0_0_1px_var(--n-chip-default-shadow-hover)_inset]",
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
32
41
|
const COLOR_STYLES = {
|
|
33
42
|
primary: {
|
|
34
43
|
text: "n-text-primary",
|
|
@@ -133,9 +142,14 @@ export const Chip = memo(
|
|
|
133
142
|
const colors = COLOR_STYLES[colorScheme ?? "default"];
|
|
134
143
|
const variantClasses =
|
|
135
144
|
variant === "solid"
|
|
136
|
-
?
|
|
145
|
+
? cx(colors.text, colors.bg)
|
|
137
146
|
: variant === "outlined"
|
|
138
|
-
?
|
|
147
|
+
? cx(
|
|
148
|
+
colors.text,
|
|
149
|
+
"n-bg-transparent",
|
|
150
|
+
SHADOW_STYLES[colorScheme ?? "default"],
|
|
151
|
+
HOVER_SHADOW_STYLES[colorScheme ?? "default"]
|
|
152
|
+
)
|
|
139
153
|
: "";
|
|
140
154
|
|
|
141
155
|
const chipClasses = cx(
|
|
@@ -40,6 +40,11 @@ export interface CollectionProps<T, M extends string = string> {
|
|
|
40
40
|
getHref?: (item: T) => string;
|
|
41
41
|
getExpanded?: (item: T) => boolean | undefined;
|
|
42
42
|
getPlaceholder?: (item: T) => string;
|
|
43
|
+
/**
|
|
44
|
+
* When starting a rename, select only the portion before
|
|
45
|
+
* the last '.' (keeping the extension unselected). Defaults to false.
|
|
46
|
+
*/
|
|
47
|
+
renameSelectsBeforeDot?: boolean;
|
|
43
48
|
/**
|
|
44
49
|
* Whether directories should be expandable.
|
|
45
50
|
* @default true
|
|
@@ -64,6 +69,11 @@ export interface CollectionProps<T, M extends string = string> {
|
|
|
64
69
|
renderDetail?: (item: T, selected: boolean) => React.ReactNode;
|
|
65
70
|
/** Optional right-side content renderer for each item */
|
|
66
71
|
renderRight?: (item: T, selected: boolean) => React.ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* Number of lines to display for detail text when rendered below grid items.
|
|
74
|
+
* @default 1
|
|
75
|
+
*/
|
|
76
|
+
detailLineClamp?: number;
|
|
67
77
|
/** Callback when selection changes. If not provided, selection will be disabled. */
|
|
68
78
|
onSelectionChange?: (
|
|
69
79
|
selectedItemIds: string[],
|
|
@@ -17,12 +17,13 @@ import { cx } from "../utils/classNames";
|
|
|
17
17
|
import { ComboboxState, useComboboxState } from "../utils/combobox";
|
|
18
18
|
import { ActivityIndicator } from "./ActivityIndicator";
|
|
19
19
|
import { ComboboxMenu } from "./ComboboxMenu";
|
|
20
|
+
import { renderIcon } from "./Icons";
|
|
20
21
|
import { InputField, InputFieldSize } from "./InputField";
|
|
21
22
|
import {
|
|
22
23
|
MenuItem,
|
|
24
|
+
MenuItemIcon,
|
|
23
25
|
ScoredMenuItem,
|
|
24
26
|
SelectableMenuItem,
|
|
25
|
-
isMenuItemSectionHeader,
|
|
26
27
|
isNonSelectableMenuItem,
|
|
27
28
|
isSelectableMenuItem,
|
|
28
29
|
} from "./internal/Menu";
|
|
@@ -34,6 +35,7 @@ export type BaseComboboxProps<T extends string> = {
|
|
|
34
35
|
loading?: boolean;
|
|
35
36
|
placeholder?: string;
|
|
36
37
|
items: MenuItem<T>[];
|
|
38
|
+
icon?: MenuItemIcon;
|
|
37
39
|
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
38
40
|
onDeleteWhenEmpty?: () => void;
|
|
39
41
|
size?: InputFieldSize;
|
|
@@ -90,6 +92,7 @@ export const Combobox = memoGeneric(
|
|
|
90
92
|
placeholder,
|
|
91
93
|
items,
|
|
92
94
|
size,
|
|
95
|
+
icon,
|
|
93
96
|
style,
|
|
94
97
|
className,
|
|
95
98
|
label: labelProp,
|
|
@@ -431,15 +434,8 @@ export const Combobox = memoGeneric(
|
|
|
431
434
|
handleBlur();
|
|
432
435
|
}, [shouldBlur, handleBlur]);
|
|
433
436
|
|
|
434
|
-
const { sectionHeaderCount, menuItemsCount } = useMemo(
|
|
435
|
-
() => (
|
|
436
|
-
sectionHeaderCount: filteredItems.filter((item) =>
|
|
437
|
-
isMenuItemSectionHeader(item)
|
|
438
|
-
).length,
|
|
439
|
-
menuItemsCount: filteredItems.filter((item) =>
|
|
440
|
-
isSelectableMenuItem(item)
|
|
441
|
-
).length,
|
|
442
|
-
}),
|
|
437
|
+
const { sectionHeaderCount, menuItemsCount, tallMenuItemsCount } = useMemo(
|
|
438
|
+
() => ListView.getMenuMetrics(filteredItems),
|
|
443
439
|
[filteredItems]
|
|
444
440
|
);
|
|
445
441
|
|
|
@@ -452,6 +448,7 @@ export const Combobox = memoGeneric(
|
|
|
452
448
|
id={id}
|
|
453
449
|
size={size}
|
|
454
450
|
sideOffset={6}
|
|
451
|
+
start={icon ? renderIcon(icon) : undefined}
|
|
455
452
|
end={
|
|
456
453
|
<>
|
|
457
454
|
{loading && (
|
|
@@ -475,15 +472,18 @@ export const Combobox = memoGeneric(
|
|
|
475
472
|
</>
|
|
476
473
|
}
|
|
477
474
|
renderPopoverContent={({ width }) => {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
475
|
+
const hasTooltips = tallMenuItemsCount > 0;
|
|
476
|
+
|
|
477
|
+
const baseRowHeight = ListView.rowHeight; // 31
|
|
478
|
+
|
|
479
|
+
const computedHeight = ListView.calculateHeightWithTallRows({
|
|
480
|
+
menuItemsCount,
|
|
481
|
+
sectionHeaderCount,
|
|
482
|
+
tallMenuItemsCount,
|
|
483
|
+
maxBaseRows: 10.5,
|
|
484
|
+
});
|
|
485
485
|
|
|
486
|
-
const
|
|
486
|
+
const adjustedListSize = { width, height: computedHeight };
|
|
487
487
|
|
|
488
488
|
return (
|
|
489
489
|
<div
|
|
@@ -493,17 +493,26 @@ export const Combobox = memoGeneric(
|
|
|
493
493
|
)}
|
|
494
494
|
>
|
|
495
495
|
{filteredItems.length > 0 && !loading ? (
|
|
496
|
-
<ComboboxMenu
|
|
496
|
+
<ComboboxMenu<T>
|
|
497
497
|
ref={listRef}
|
|
498
498
|
items={filteredItems}
|
|
499
499
|
selectedIndex={selectedIndex}
|
|
500
500
|
onSelectItem={handleUpdateSelection}
|
|
501
501
|
onHoverIndex={handleIndexChange}
|
|
502
|
-
listSize={
|
|
502
|
+
listSize={adjustedListSize}
|
|
503
503
|
style={{
|
|
504
|
-
flex: `0 0 ${
|
|
504
|
+
flex: `0 0 ${computedHeight}px`,
|
|
505
505
|
}}
|
|
506
506
|
indented={hasCheckedItems}
|
|
507
|
+
// Use per-item height if any tooltips exist
|
|
508
|
+
{...(hasTooltips
|
|
509
|
+
? {
|
|
510
|
+
getRowHeightForItem: (item) =>
|
|
511
|
+
isSelectableMenuItem(item) && item.tooltip
|
|
512
|
+
? ListView.tallRowHeight
|
|
513
|
+
: baseRowHeight,
|
|
514
|
+
}
|
|
515
|
+
: { itemHeight: baseRowHeight })}
|
|
507
516
|
/>
|
|
508
517
|
) : (
|
|
509
518
|
<div className="n-flex n-flex-col n-h-[50px] n-p-5 n-items-center n-justify-center">
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Size } from "@noya-app/noya-geometry";
|
|
2
2
|
import { CheckIcon } from "@noya-app/noya-icons";
|
|
3
3
|
import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
|
|
4
|
-
import React, { ForwardedRef } from "react";
|
|
4
|
+
import React, { ForwardedRef, memo } from "react";
|
|
5
5
|
import { cx } from "../utils/classNames";
|
|
6
6
|
|
|
7
7
|
import { fuzzyTokenize } from "../utils/fuzzyScorer";
|
|
8
|
-
import { renderIcon } from "./Icons";
|
|
8
|
+
import { IconProp, renderIcon } from "./Icons";
|
|
9
9
|
import {
|
|
10
10
|
CHECKBOX_INDENT_WIDTH,
|
|
11
11
|
CHECKBOX_RIGHT_INSET,
|
|
12
12
|
CHECKBOX_WIDTH,
|
|
13
|
+
isSelectableMenuItem,
|
|
13
14
|
KeyboardShortcut,
|
|
14
15
|
ScoredMenuItem,
|
|
15
16
|
styles,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
import { IVirtualizedList } from "./IVirtualizedList";
|
|
18
19
|
import { ListView } from "./ListView";
|
|
19
20
|
import { Spacer } from "./Spacer";
|
|
21
|
+
import { Small } from "./Text";
|
|
20
22
|
|
|
21
23
|
interface ComboboxMenuProps<T extends string> {
|
|
22
24
|
items: ScoredMenuItem<T>[];
|
|
@@ -28,6 +30,10 @@ interface ComboboxMenuProps<T extends string> {
|
|
|
28
30
|
indented?: boolean;
|
|
29
31
|
/** @default right */
|
|
30
32
|
iconPosition?: "left" | "right";
|
|
33
|
+
/** Optional fixed row height for items (e.g., to accommodate descriptions) */
|
|
34
|
+
itemHeight?: number;
|
|
35
|
+
/** Optional per-item height resolver */
|
|
36
|
+
getRowHeightForItem?: (item: ScoredMenuItem<T>, index: number) => number;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export const ComboboxMenu = memoGeneric(
|
|
@@ -41,6 +47,8 @@ export const ComboboxMenu = memoGeneric(
|
|
|
41
47
|
style,
|
|
42
48
|
indented,
|
|
43
49
|
iconPosition = "right",
|
|
50
|
+
itemHeight,
|
|
51
|
+
getRowHeightForItem,
|
|
44
52
|
}: ComboboxMenuProps<T>,
|
|
45
53
|
forwardedRef: ForwardedRef<IVirtualizedList>
|
|
46
54
|
) {
|
|
@@ -55,6 +63,10 @@ export const ComboboxMenu = memoGeneric(
|
|
|
55
63
|
}}
|
|
56
64
|
data={items}
|
|
57
65
|
virtualized={listSize}
|
|
66
|
+
{...(itemHeight ? { itemHeight } : {})}
|
|
67
|
+
{...(getRowHeightForItem
|
|
68
|
+
? { getItemHeightForItem: getRowHeightForItem as any }
|
|
69
|
+
: {})}
|
|
58
70
|
pressEventName="onPointerDown"
|
|
59
71
|
sectionHeaderVariant="label"
|
|
60
72
|
style={style}
|
|
@@ -81,6 +93,10 @@ export const ComboboxMenu = memoGeneric(
|
|
|
81
93
|
itemScore: item,
|
|
82
94
|
});
|
|
83
95
|
|
|
96
|
+
const tooltip: React.ReactNode | undefined =
|
|
97
|
+
// Only selectable items can have tooltips in this menu
|
|
98
|
+
isSelectableMenuItem(item) ? item.tooltip : undefined;
|
|
99
|
+
|
|
84
100
|
return (
|
|
85
101
|
<ListView.Row
|
|
86
102
|
key={item.value}
|
|
@@ -93,7 +109,7 @@ export const ComboboxMenu = memoGeneric(
|
|
|
93
109
|
}
|
|
94
110
|
}}
|
|
95
111
|
>
|
|
96
|
-
<div className="n-flex n-flex-1 n-min-w-0 n-items-
|
|
112
|
+
<div className="n-flex n-flex-1 n-min-w-0 n-items-start">
|
|
97
113
|
{indented && <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />}
|
|
98
114
|
{item.checked ? (
|
|
99
115
|
<div {...styles.itemIndicator}>
|
|
@@ -106,30 +122,41 @@ export const ComboboxMenu = memoGeneric(
|
|
|
106
122
|
) : null}
|
|
107
123
|
{iconPosition === "left" && item.icon && (
|
|
108
124
|
<>
|
|
109
|
-
{
|
|
125
|
+
<MenuIcon icon={item.icon} />
|
|
110
126
|
<Spacer.Horizontal size={8} />
|
|
111
127
|
</>
|
|
112
128
|
)}
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
<div className="n-flex n-flex-col n-min-w-0 n-flex-1">
|
|
130
|
+
<div className="n-min-w-0 n-truncate">
|
|
131
|
+
{tokens.map((token, j) => (
|
|
132
|
+
<span
|
|
133
|
+
key={`${item.value}-token-${j}`}
|
|
134
|
+
className={cx(
|
|
135
|
+
token.type === "match"
|
|
136
|
+
? "n-font-bold"
|
|
137
|
+
: "n-font-normal",
|
|
138
|
+
"n-whitespace-pre n-truncate"
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
{token.text}
|
|
142
|
+
</span>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
{tooltip && (
|
|
146
|
+
<Small color="textSubtle" className="n-truncate">
|
|
147
|
+
{tooltip}
|
|
148
|
+
</Small>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
124
151
|
</div>
|
|
125
152
|
{(item.shortcut || (item.icon && iconPosition === "right")) && (
|
|
126
|
-
|
|
153
|
+
<div className="n-flex n-items-center n-min-w-0 n-ml-2">
|
|
127
154
|
{item.shortcut ? (
|
|
128
155
|
<KeyboardShortcut shortcut={item.shortcut} />
|
|
129
156
|
) : (
|
|
130
|
-
item.icon &&
|
|
157
|
+
item.icon && <MenuIcon icon={item.icon} />
|
|
131
158
|
)}
|
|
132
|
-
|
|
159
|
+
</div>
|
|
133
160
|
)}
|
|
134
161
|
</ListView.Row>
|
|
135
162
|
);
|
|
@@ -138,3 +165,7 @@ export const ComboboxMenu = memoGeneric(
|
|
|
138
165
|
);
|
|
139
166
|
})
|
|
140
167
|
);
|
|
168
|
+
|
|
169
|
+
const MenuIcon = memo(function MenuIcon({ icon }: { icon: IconProp }) {
|
|
170
|
+
return <div className="n-relative n-top-[2px]">{renderIcon(icon)}</div>;
|
|
171
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
3
|
+
import { Dialog as DialogPrimitive, VisuallyHidden } from "radix-ui";
|
|
4
4
|
import React, {
|
|
5
5
|
ComponentProps,
|
|
6
6
|
ForwardedRef,
|
|
@@ -121,6 +121,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
121
121
|
onOpenAutoFocus={onOpenAutoFocus}
|
|
122
122
|
style={style}
|
|
123
123
|
className={className}
|
|
124
|
+
aria-describedby={typeof description === "string" ? description : ""}
|
|
124
125
|
{...(closeOnInteractOutside === false && {
|
|
125
126
|
onPointerDownOutside: (event) => event.preventDefault(),
|
|
126
127
|
onInteractOutside: (event) => event.preventDefault(),
|
|
@@ -145,6 +146,11 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
145
146
|
<Spacer.Vertical size={description ? 10 : 20} />
|
|
146
147
|
</>
|
|
147
148
|
)}
|
|
149
|
+
{!title && (
|
|
150
|
+
<VisuallyHidden.Root>
|
|
151
|
+
<StyledTitle>Dialog</StyledTitle>
|
|
152
|
+
</VisuallyHidden.Root>
|
|
153
|
+
)}
|
|
148
154
|
{description && (
|
|
149
155
|
<>
|
|
150
156
|
<StyledDescription>{description}</StyledDescription>
|
|
@@ -138,20 +138,24 @@ export const EditableText = memo(
|
|
|
138
138
|
|
|
139
139
|
const preview = children({
|
|
140
140
|
children: displayValue,
|
|
141
|
-
onClick: () =>
|
|
141
|
+
onClick: () => {
|
|
142
|
+
if (readOnly) return;
|
|
143
|
+
setFocused(true);
|
|
144
|
+
},
|
|
142
145
|
ref: previewRef,
|
|
143
146
|
tabIndex,
|
|
144
147
|
onKeyDown: (e: React.KeyboardEvent) => {
|
|
148
|
+
if (readOnly) return;
|
|
145
149
|
if (e.key === "Enter" || e.key === " ") {
|
|
146
150
|
setFocused(true);
|
|
147
151
|
}
|
|
148
152
|
},
|
|
149
153
|
style: textStyle,
|
|
150
|
-
className: textClassName,
|
|
154
|
+
className: cx(focused && "n-invisible", textClassName),
|
|
151
155
|
});
|
|
152
156
|
|
|
153
157
|
const input = (
|
|
154
|
-
<div className="n-flex n-absolute n-inset-0 -mx-1.5 -n-my-1">
|
|
158
|
+
<div className="n-flex n-absolute n-inset-0 -n-mx-1.5 -n-my-1">
|
|
155
159
|
<InputField.Root onFocusChange={setFocused}>
|
|
156
160
|
<InputField.Input
|
|
157
161
|
ref={inputRef}
|
package/src/components/Grid.tsx
CHANGED
|
@@ -85,6 +85,7 @@ export const Grid = memoGeneric(
|
|
|
85
85
|
renderAction,
|
|
86
86
|
renderDetail,
|
|
87
87
|
renderRight,
|
|
88
|
+
detailLineClamp,
|
|
88
89
|
onSelectionChange,
|
|
89
90
|
selectedIds = emptyArray,
|
|
90
91
|
// itemRoleDescription = "clickable item",
|
|
@@ -162,6 +163,7 @@ export const Grid = memoGeneric(
|
|
|
162
163
|
scrollable={items.length > 0 && scrollable}
|
|
163
164
|
contentClassName={cx(items.length === 0 && "n-flex n-flex-1")}
|
|
164
165
|
gap={gap}
|
|
166
|
+
descriptionLineClamp={detailLineClamp}
|
|
165
167
|
className={cx(
|
|
166
168
|
isDropTargetActive && "n-bg-selected-list-item-background",
|
|
167
169
|
className
|
|
@@ -16,6 +16,10 @@ import React, {
|
|
|
16
16
|
import { useLinkComponent } from "../contexts/LinkComponentContext";
|
|
17
17
|
import { useHover } from "../hooks/useHover";
|
|
18
18
|
import { cx, mergeConflictingClassNames } from "../utils/classNames";
|
|
19
|
+
import {
|
|
20
|
+
DETAIL_LINE_CLAMP_CLASSES,
|
|
21
|
+
normalizeDetailLineClamp,
|
|
22
|
+
} from "../utils/detailLineClamp";
|
|
19
23
|
import withSeparatorElements from "../utils/withSeparatorElements";
|
|
20
24
|
import { ActivityIndicator } from "./ActivityIndicator";
|
|
21
25
|
import { ContextMenu } from "./ContextMenu";
|
|
@@ -47,14 +51,21 @@ const ItemTitle = ({
|
|
|
47
51
|
const ItemDescription = ({
|
|
48
52
|
showBackground,
|
|
49
53
|
children,
|
|
54
|
+
lineClamp,
|
|
50
55
|
}: {
|
|
51
56
|
showBackground?: boolean;
|
|
52
57
|
children?: React.ReactNode;
|
|
58
|
+
lineClamp?: number;
|
|
53
59
|
}) => {
|
|
60
|
+
const normalizedLineClamp = normalizeDetailLineClamp(lineClamp) ?? 1;
|
|
61
|
+
|
|
62
|
+
const clampClassName = DETAIL_LINE_CLAMP_CLASSES[normalizedLineClamp];
|
|
63
|
+
|
|
54
64
|
return (
|
|
55
65
|
<span
|
|
56
66
|
className={cx(
|
|
57
|
-
"n-font-sans n-text-sm n-text-text-muted n-select-none
|
|
67
|
+
"n-font-sans n-text-sm n-text-text-muted n-select-none",
|
|
68
|
+
clampClassName,
|
|
58
69
|
showBackground &&
|
|
59
70
|
"n-bg-sidebar-background n-n-border n-border-divider-subtle n-rounded-[2px] n-backdrop-blur-[4px] n-p-[2px_4px]"
|
|
60
71
|
)}
|
|
@@ -139,8 +150,13 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
139
150
|
onHoverChange,
|
|
140
151
|
});
|
|
141
152
|
|
|
142
|
-
const {
|
|
143
|
-
|
|
153
|
+
const {
|
|
154
|
+
textPosition,
|
|
155
|
+
bordered,
|
|
156
|
+
disabled,
|
|
157
|
+
alwaysShowSubtitles,
|
|
158
|
+
descriptionLineClamp,
|
|
159
|
+
} = useContext(GridViewContext);
|
|
144
160
|
|
|
145
161
|
const handleClick = useCallback(
|
|
146
162
|
(event: React.MouseEvent) => {
|
|
@@ -215,16 +231,25 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
215
231
|
{(alwaysShowSubtitles || subtitle) && (
|
|
216
232
|
<>
|
|
217
233
|
<Spacer.Vertical size={2} />
|
|
218
|
-
<ItemDescription
|
|
234
|
+
<ItemDescription lineClamp={descriptionLineClamp}>
|
|
235
|
+
{subtitle || " "}
|
|
236
|
+
</ItemDescription>
|
|
219
237
|
</>
|
|
220
238
|
)}
|
|
221
239
|
</>
|
|
222
240
|
)}
|
|
223
241
|
{textPosition === "overlay" && hovered && (title || subtitle) && (
|
|
224
|
-
<div
|
|
242
|
+
<div
|
|
243
|
+
className={cx(
|
|
244
|
+
"n-absolute n-inset-0 n-flex n-flex-col n-justify-end n-items-start n-p-1 n-pointer-events-none n-gap-0.5",
|
|
245
|
+
descriptionLineClamp === 1 && "n-truncate"
|
|
246
|
+
)}
|
|
247
|
+
>
|
|
225
248
|
{title && <ItemTitle showBackground>{title}</ItemTitle>}
|
|
226
249
|
{subtitle && (
|
|
227
|
-
<ItemDescription showBackground
|
|
250
|
+
<ItemDescription showBackground lineClamp={descriptionLineClamp}>
|
|
251
|
+
{subtitle}
|
|
252
|
+
</ItemDescription>
|
|
228
253
|
)}
|
|
229
254
|
</div>
|
|
230
255
|
)}
|
|
@@ -259,7 +284,9 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
259
284
|
content={
|
|
260
285
|
<div className="n-flex n-flex-col gap-0.5">
|
|
261
286
|
<ItemTitle>{title}</ItemTitle>
|
|
262
|
-
<ItemDescription
|
|
287
|
+
<ItemDescription lineClamp={descriptionLineClamp}>
|
|
288
|
+
{subtitle}
|
|
289
|
+
</ItemDescription>
|
|
263
290
|
</div>
|
|
264
291
|
}
|
|
265
292
|
>
|
|
@@ -278,6 +305,7 @@ type GridViewContextValue = {
|
|
|
278
305
|
disabled: boolean;
|
|
279
306
|
gap: number;
|
|
280
307
|
alwaysShowSubtitles: boolean;
|
|
308
|
+
descriptionLineClamp: number;
|
|
281
309
|
};
|
|
282
310
|
|
|
283
311
|
const GridViewContext = createContext<GridViewContextValue>({
|
|
@@ -287,6 +315,7 @@ const GridViewContext = createContext<GridViewContextValue>({
|
|
|
287
315
|
disabled: false,
|
|
288
316
|
gap: 0,
|
|
289
317
|
alwaysShowSubtitles: false,
|
|
318
|
+
descriptionLineClamp: 1,
|
|
290
319
|
});
|
|
291
320
|
|
|
292
321
|
interface GridViewRootProps extends Partial<GridViewContextValue> {
|
|
@@ -302,6 +331,7 @@ interface GridViewRootProps extends Partial<GridViewContextValue> {
|
|
|
302
331
|
renderEmptyState?: () => ReactNode;
|
|
303
332
|
contentClassName?: string;
|
|
304
333
|
alwaysShowSubtitles?: boolean;
|
|
334
|
+
descriptionLineClamp?: number;
|
|
305
335
|
}
|
|
306
336
|
|
|
307
337
|
const GridViewRoot = forwardRef(function GridViewRoot(
|
|
@@ -317,6 +347,7 @@ const GridViewRoot = forwardRef(function GridViewRoot(
|
|
|
317
347
|
className,
|
|
318
348
|
contentClassName,
|
|
319
349
|
alwaysShowSubtitles = false,
|
|
350
|
+
descriptionLineClamp = 1,
|
|
320
351
|
...props
|
|
321
352
|
}: GridViewRootProps,
|
|
322
353
|
forwardedRef: ForwardedRef<HTMLDivElement>
|
|
@@ -331,6 +362,9 @@ const GridViewRoot = forwardRef(function GridViewRoot(
|
|
|
331
362
|
[onClick]
|
|
332
363
|
);
|
|
333
364
|
|
|
365
|
+
const normalizedDescriptionLineClamp =
|
|
366
|
+
normalizeDetailLineClamp(descriptionLineClamp) ?? 1;
|
|
367
|
+
|
|
334
368
|
const contextValue = useMemo(
|
|
335
369
|
(): GridViewContextValue => ({
|
|
336
370
|
minColumnWidth,
|
|
@@ -339,13 +373,22 @@ const GridViewRoot = forwardRef(function GridViewRoot(
|
|
|
339
373
|
disabled,
|
|
340
374
|
gap,
|
|
341
375
|
alwaysShowSubtitles,
|
|
376
|
+
descriptionLineClamp: normalizedDescriptionLineClamp,
|
|
342
377
|
}),
|
|
343
|
-
[
|
|
378
|
+
[
|
|
379
|
+
bordered,
|
|
380
|
+
disabled,
|
|
381
|
+
gap,
|
|
382
|
+
minColumnWidth,
|
|
383
|
+
textPosition,
|
|
384
|
+
alwaysShowSubtitles,
|
|
385
|
+
normalizedDescriptionLineClamp,
|
|
386
|
+
]
|
|
344
387
|
);
|
|
345
388
|
|
|
346
389
|
const mergedClassName = useMemo(() => {
|
|
347
390
|
const baseClassName = cx(
|
|
348
|
-
"n-flex n-flex-col n
|
|
391
|
+
"n-flex n-flex-col -n-mx-3",
|
|
349
392
|
scrollable ? "n-basis-0 n-min-h-0" : "n-flex-none",
|
|
350
393
|
className
|
|
351
394
|
);
|