@noya-app/noya-designsystem 0.1.44 → 0.1.46
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 +13 -10
- package/CHANGELOG.md +18 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +1194 -878
- package/dist/index.d.ts +1194 -878
- package/dist/index.js +11432 -10219
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11556 -10360
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/combobox.test.ts +137 -89
- package/src/components/AnimatePresence.tsx +10 -1
- package/src/components/Avatar.tsx +2 -0
- package/src/components/Breadcrumbs.tsx +29 -0
- package/src/components/Checkbox.tsx +6 -1
- package/src/components/Collection.tsx +74 -0
- package/src/components/Combobox.tsx +75 -56
- package/src/components/ComboboxMenu.tsx +61 -28
- package/src/components/CommandPalette.tsx +69 -0
- package/src/components/ContextMenu.tsx +33 -134
- package/src/components/DropdownMenu.tsx +46 -155
- package/src/components/EditableText.tsx +203 -0
- package/src/components/Fade.tsx +62 -0
- package/src/components/Grid.tsx +243 -0
- package/src/components/GridView.tsx +86 -96
- package/src/components/InputField.tsx +109 -133
- package/src/components/Label.tsx +81 -7
- package/src/components/LabeledField.tsx +112 -0
- package/src/components/List.tsx +268 -0
- package/src/components/ListView.tsx +65 -61
- package/src/components/Popover.tsx +12 -1
- package/src/components/SearchCompletionMenu.tsx +210 -0
- package/src/components/SegmentedControl.tsx +12 -6
- package/src/components/SelectMenu.tsx +110 -126
- package/src/components/Slider.tsx +18 -26
- package/src/components/Text.tsx +1 -0
- package/src/components/TextArea.tsx +4 -1
- package/src/components/Toolbar.tsx +9 -4
- package/src/components/TreeView.tsx +4 -0
- package/src/components/WorkspaceLayout.tsx +64 -20
- package/src/components/internal/Menu.tsx +107 -18
- package/src/components/internal/MenuViewport.tsx +152 -0
- package/src/components/internal/SelectItem.tsx +111 -0
- package/src/hooks/useIndent.ts +12 -0
- package/src/hooks/useLabel.ts +51 -0
- package/src/hooks/usePreservePanelSize.tsx +50 -19
- package/src/index.tsx +15 -6
- package/src/utils/combobox.ts +116 -101
- package/src/utils/createSectionedMenu.ts +11 -14
- package/src/utils/fuzzyScorer.ts +11 -8
- package/src/utils/selection.ts +48 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { cx } from "../utils/classNames";
|
|
3
|
+
|
|
4
|
+
export type FadeDirection = "left" | "right" | "top" | "bottom";
|
|
5
|
+
|
|
6
|
+
export type FadeProps = {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
direction?: FadeDirection;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
zIndex?: number;
|
|
13
|
+
/**
|
|
14
|
+
* @default "front"
|
|
15
|
+
* If "back", the fade will be positioned behind the children
|
|
16
|
+
*/
|
|
17
|
+
position?: "front" | "back";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Fade = ({
|
|
21
|
+
children,
|
|
22
|
+
direction = "right",
|
|
23
|
+
width,
|
|
24
|
+
height,
|
|
25
|
+
className,
|
|
26
|
+
zIndex = 0,
|
|
27
|
+
position = "front",
|
|
28
|
+
}: FadeProps) => {
|
|
29
|
+
const gradientDirection = {
|
|
30
|
+
left: "to-l",
|
|
31
|
+
right: "to-r",
|
|
32
|
+
top: "to-t",
|
|
33
|
+
bottom: "to-b",
|
|
34
|
+
}[direction];
|
|
35
|
+
|
|
36
|
+
const positionClasses = {
|
|
37
|
+
left: "left-0 top-0 bottom-0",
|
|
38
|
+
right: "right-0 top-0 bottom-0",
|
|
39
|
+
top: "left-0 right-0 top-0",
|
|
40
|
+
bottom: "left-0 right-0 bottom-0",
|
|
41
|
+
}[direction];
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={cx("relative", className)} style={{ height }}>
|
|
45
|
+
{position === "front" ? children : null}
|
|
46
|
+
<div
|
|
47
|
+
className={cx(
|
|
48
|
+
"absolute pointer-events-none",
|
|
49
|
+
positionClasses,
|
|
50
|
+
`bg-gradient-${gradientDirection}`,
|
|
51
|
+
"from-transparent to-background",
|
|
52
|
+
zIndex && `z-[${zIndex}]`
|
|
53
|
+
)}
|
|
54
|
+
style={{
|
|
55
|
+
width: width ?? "100%",
|
|
56
|
+
height: height ?? "100%",
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
{position === "back" ? children : null}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import React, { useImperativeHandle, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
forwardRefGeneric,
|
|
4
|
+
memoGeneric,
|
|
5
|
+
} from "../../../noya-react-utils/src/utils/reactGenerics";
|
|
6
|
+
import { cx } from "../utils/classNames";
|
|
7
|
+
import { updateSelection } from "../utils/selection";
|
|
8
|
+
import { CollectionProps, CollectionRef } from "./Collection";
|
|
9
|
+
import { EditableText } from "./EditableText";
|
|
10
|
+
import { GridView } from "./GridView";
|
|
11
|
+
import { ListView } from "./ListView";
|
|
12
|
+
|
|
13
|
+
export type GridViewSize = "xxs" | "xs" | "small" | "medium" | "large" | "xl";
|
|
14
|
+
|
|
15
|
+
export const getGridSize = (size: GridViewSize) => {
|
|
16
|
+
// Define size ranges that allow items to grow/shrink within bounds
|
|
17
|
+
const sizes: Record<
|
|
18
|
+
Exclude<GridViewSize, number>,
|
|
19
|
+
{
|
|
20
|
+
minColumnWidth: string;
|
|
21
|
+
gap: number;
|
|
22
|
+
}
|
|
23
|
+
> = {
|
|
24
|
+
xxs: {
|
|
25
|
+
minColumnWidth: "36px",
|
|
26
|
+
gap: 5,
|
|
27
|
+
},
|
|
28
|
+
xs: {
|
|
29
|
+
minColumnWidth: "87px",
|
|
30
|
+
gap: 9,
|
|
31
|
+
},
|
|
32
|
+
small: {
|
|
33
|
+
minColumnWidth: "160px",
|
|
34
|
+
gap: 20,
|
|
35
|
+
},
|
|
36
|
+
medium: {
|
|
37
|
+
minColumnWidth: "220px",
|
|
38
|
+
gap: 20,
|
|
39
|
+
},
|
|
40
|
+
large: {
|
|
41
|
+
minColumnWidth: "280px",
|
|
42
|
+
gap: 24,
|
|
43
|
+
},
|
|
44
|
+
xl: {
|
|
45
|
+
minColumnWidth: "400px",
|
|
46
|
+
gap: 32,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return sizes[size];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const emptyArray: string[] = [];
|
|
54
|
+
|
|
55
|
+
export const Grid = memoGeneric(
|
|
56
|
+
forwardRefGeneric(function Grid<T, M extends string = string>(
|
|
57
|
+
props: CollectionProps<T, M> & { ref?: React.ForwardedRef<CollectionRef> },
|
|
58
|
+
ref: React.ForwardedRef<CollectionRef>
|
|
59
|
+
) {
|
|
60
|
+
const {
|
|
61
|
+
className,
|
|
62
|
+
items,
|
|
63
|
+
getId,
|
|
64
|
+
getName,
|
|
65
|
+
// expandable,
|
|
66
|
+
// getExpanded,
|
|
67
|
+
getRenamable,
|
|
68
|
+
// getDepth,
|
|
69
|
+
// setExpanded,
|
|
70
|
+
menuItems,
|
|
71
|
+
onSelectMenuItem,
|
|
72
|
+
onRename,
|
|
73
|
+
renderThumbnail,
|
|
74
|
+
renderAction,
|
|
75
|
+
renderDetail,
|
|
76
|
+
onSelectionChange,
|
|
77
|
+
selectedIds = emptyArray,
|
|
78
|
+
// itemRoleDescription = "clickable item",
|
|
79
|
+
// detailPosition = "end",
|
|
80
|
+
size = "medium",
|
|
81
|
+
testHoveredId,
|
|
82
|
+
testRenamingId,
|
|
83
|
+
scrollable = false,
|
|
84
|
+
// acceptsDrop,
|
|
85
|
+
// onMoveItem,
|
|
86
|
+
// onFilesDrop,
|
|
87
|
+
renamable,
|
|
88
|
+
onDoubleClickItem,
|
|
89
|
+
} = props;
|
|
90
|
+
|
|
91
|
+
const [hoveredId, setHoveredId] = useState<string>();
|
|
92
|
+
const [internalRenamingId, setRenamingId] = useState<string>();
|
|
93
|
+
|
|
94
|
+
useImperativeHandle(ref, () => ({
|
|
95
|
+
editName: (id: string) => {
|
|
96
|
+
setRenamingId(id);
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const currentHoveredId = testHoveredId ?? hoveredId;
|
|
101
|
+
const renamingId = testRenamingId ?? internalRenamingId;
|
|
102
|
+
|
|
103
|
+
const handleSelect = (itemId: string, event?: ListView.ClickInfo) => {
|
|
104
|
+
if (!onSelectionChange) return;
|
|
105
|
+
|
|
106
|
+
setRenamingId(undefined);
|
|
107
|
+
const newSelectedIds = updateSelection(
|
|
108
|
+
items.map(getId),
|
|
109
|
+
selectedIds,
|
|
110
|
+
itemId,
|
|
111
|
+
event
|
|
112
|
+
);
|
|
113
|
+
onSelectionChange(newSelectedIds, event);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleMenuAction = (action: M) => {
|
|
117
|
+
if (!onSelectMenuItem) return;
|
|
118
|
+
|
|
119
|
+
const selectedItems = items.filter((item) =>
|
|
120
|
+
selectedIds.includes(getId(item))
|
|
121
|
+
);
|
|
122
|
+
onSelectMenuItem(action, selectedItems);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// const fileDropTargetRef = useRef<HTMLDivElement>(null);
|
|
126
|
+
// const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
|
|
127
|
+
// fileDropTargetRef,
|
|
128
|
+
// onFilesDrop
|
|
129
|
+
// );
|
|
130
|
+
|
|
131
|
+
const { minColumnWidth, gap } = getGridSize(size);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<GridView.Root
|
|
135
|
+
minColumnWidth={minColumnWidth}
|
|
136
|
+
scrollable={scrollable}
|
|
137
|
+
className={className}
|
|
138
|
+
gap={gap}
|
|
139
|
+
>
|
|
140
|
+
<GridView.Section>
|
|
141
|
+
{items.map((item) => {
|
|
142
|
+
const id = getId(item);
|
|
143
|
+
const isHovered = currentHoveredId === id;
|
|
144
|
+
const itemIsRenamable = getRenamable?.(item) ?? renamable;
|
|
145
|
+
const isSelected = selectedIds.includes(id);
|
|
146
|
+
const isRenaming = itemIsRenamable && onRename && renamingId === id;
|
|
147
|
+
const isTestingRenaming = testRenamingId === id;
|
|
148
|
+
|
|
149
|
+
const thumbnail = renderThumbnail?.(item, isSelected);
|
|
150
|
+
const action =
|
|
151
|
+
(isHovered || isSelected) &&
|
|
152
|
+
!isRenaming &&
|
|
153
|
+
renderAction?.(item, isSelected);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<GridView.Item<M>
|
|
157
|
+
key={id}
|
|
158
|
+
id={id}
|
|
159
|
+
hovered={isHovered}
|
|
160
|
+
onHoverChange={(isHovering) => {
|
|
161
|
+
setHoveredId(isHovering ? id : undefined);
|
|
162
|
+
}}
|
|
163
|
+
title={
|
|
164
|
+
<EditableText
|
|
165
|
+
value={getName(item)}
|
|
166
|
+
focused={isRenaming}
|
|
167
|
+
testFocused={isTestingRenaming}
|
|
168
|
+
readOnly={!itemIsRenamable || !onRename}
|
|
169
|
+
onSubmit={(value) => {
|
|
170
|
+
if (value !== getName(item)) {
|
|
171
|
+
onRename?.(item, value);
|
|
172
|
+
}
|
|
173
|
+
setRenamingId(undefined);
|
|
174
|
+
}}
|
|
175
|
+
onBlur={() => {
|
|
176
|
+
setRenamingId(undefined);
|
|
177
|
+
}}
|
|
178
|
+
// max height ensures preview text and input are one line
|
|
179
|
+
textClassName="bg-listview-editing-background max-h-[27px]"
|
|
180
|
+
>
|
|
181
|
+
{(props) => (
|
|
182
|
+
<span
|
|
183
|
+
{...props}
|
|
184
|
+
className={cx(
|
|
185
|
+
isSelected && "text-primary",
|
|
186
|
+
"truncate select-none"
|
|
187
|
+
)}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</EditableText>
|
|
191
|
+
}
|
|
192
|
+
subtitle={
|
|
193
|
+
<span className={cx(isSelected && "text-primary")}>
|
|
194
|
+
{renderDetail?.(item, isSelected)}
|
|
195
|
+
</span>
|
|
196
|
+
}
|
|
197
|
+
loading={false}
|
|
198
|
+
selected={isSelected}
|
|
199
|
+
onSelect={(event) => {
|
|
200
|
+
handleSelect(id, event);
|
|
201
|
+
}}
|
|
202
|
+
onKeyDown={(event) => {
|
|
203
|
+
if (event.key === "Enter") {
|
|
204
|
+
setRenamingId(id);
|
|
205
|
+
}
|
|
206
|
+
}}
|
|
207
|
+
onDoubleClick={() => {
|
|
208
|
+
onDoubleClickItem?.(id);
|
|
209
|
+
}}
|
|
210
|
+
menuItems={menuItems}
|
|
211
|
+
onSelectMenuItem={handleMenuAction}
|
|
212
|
+
action={action}
|
|
213
|
+
testHoveredId={testHoveredId}
|
|
214
|
+
onMenuOpenChange={(open: boolean) => {
|
|
215
|
+
if (open && !isSelected) {
|
|
216
|
+
handleSelect(id, {
|
|
217
|
+
shiftKey: false,
|
|
218
|
+
altKey: false,
|
|
219
|
+
metaKey: false,
|
|
220
|
+
ctrlKey: false,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
{thumbnail && (
|
|
226
|
+
<div
|
|
227
|
+
className={cx(
|
|
228
|
+
"rounded overflow-hidden flex-none flex items-center w-full justify-center aspect-square",
|
|
229
|
+
isSelected && "text-primary"
|
|
230
|
+
)}
|
|
231
|
+
tabIndex={-1}
|
|
232
|
+
>
|
|
233
|
+
{thumbnail}
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</GridView.Item>
|
|
237
|
+
);
|
|
238
|
+
})}
|
|
239
|
+
</GridView.Section>
|
|
240
|
+
</GridView.Root>
|
|
241
|
+
);
|
|
242
|
+
})
|
|
243
|
+
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KeyModifiers } from "@noya-app/noya-keymap";
|
|
2
|
-
import { forwardRefGeneric } from "@noya-app/react-utils";
|
|
2
|
+
import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
|
|
3
3
|
import React, {
|
|
4
4
|
createContext,
|
|
5
5
|
CSSProperties,
|
|
@@ -20,48 +20,6 @@ import { ScrollArea } from "./ScrollArea";
|
|
|
20
20
|
import { Spacer } from "./Spacer";
|
|
21
21
|
import { Tooltip } from "./Tooltip";
|
|
22
22
|
|
|
23
|
-
export type GridViewSize = "xxs" | "xs" | "small" | "medium" | "large" | "xl";
|
|
24
|
-
|
|
25
|
-
const sizes: Record<
|
|
26
|
-
GridViewSize,
|
|
27
|
-
{
|
|
28
|
-
itemWidth: number;
|
|
29
|
-
itemHeight: number;
|
|
30
|
-
gap: number;
|
|
31
|
-
}
|
|
32
|
-
> = {
|
|
33
|
-
xxs: {
|
|
34
|
-
itemWidth: 36,
|
|
35
|
-
itemHeight: 36,
|
|
36
|
-
gap: 5,
|
|
37
|
-
},
|
|
38
|
-
xs: {
|
|
39
|
-
itemWidth: 87,
|
|
40
|
-
itemHeight: 87,
|
|
41
|
-
gap: 9,
|
|
42
|
-
},
|
|
43
|
-
small: {
|
|
44
|
-
itemWidth: 160,
|
|
45
|
-
itemHeight: 170,
|
|
46
|
-
gap: 20,
|
|
47
|
-
},
|
|
48
|
-
medium: {
|
|
49
|
-
itemWidth: 220,
|
|
50
|
-
itemHeight: 220,
|
|
51
|
-
gap: 16,
|
|
52
|
-
},
|
|
53
|
-
large: {
|
|
54
|
-
itemWidth: 280,
|
|
55
|
-
itemHeight: 280,
|
|
56
|
-
gap: 24,
|
|
57
|
-
},
|
|
58
|
-
xl: {
|
|
59
|
-
itemWidth: 400,
|
|
60
|
-
itemHeight: 400,
|
|
61
|
-
gap: 32,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
23
|
const ItemTitle = ({
|
|
66
24
|
showBackground,
|
|
67
25
|
children,
|
|
@@ -71,10 +29,11 @@ const ItemTitle = ({
|
|
|
71
29
|
}) => {
|
|
72
30
|
return (
|
|
73
31
|
<span
|
|
74
|
-
className={
|
|
32
|
+
className={cx(
|
|
33
|
+
"font-sans text-heading5 text-text-muted font-medium",
|
|
75
34
|
showBackground &&
|
|
76
|
-
|
|
77
|
-
}
|
|
35
|
+
"bg-sidebar-background border border-divider-subtle rounded-[2px] backdrop-blur-[4px] p-[2px_4px]"
|
|
36
|
+
)}
|
|
78
37
|
>
|
|
79
38
|
{children}
|
|
80
39
|
</span>
|
|
@@ -90,10 +49,11 @@ const ItemDescription = ({
|
|
|
90
49
|
}) => {
|
|
91
50
|
return (
|
|
92
51
|
<span
|
|
93
|
-
className={
|
|
52
|
+
className={cx(
|
|
53
|
+
"font-sans text-sm text-text-muted select-none truncate",
|
|
94
54
|
showBackground &&
|
|
95
|
-
|
|
96
|
-
}
|
|
55
|
+
"bg-sidebar-background border border-divider-subtle rounded-[2px] backdrop-blur-[4px] p-[2px_4px]"
|
|
56
|
+
)}
|
|
97
57
|
>
|
|
98
58
|
{children}
|
|
99
59
|
</span>
|
|
@@ -109,7 +69,7 @@ const SectionTitle = ({
|
|
|
109
69
|
}) => {
|
|
110
70
|
return (
|
|
111
71
|
<span
|
|
112
|
-
className={`font-sans text-heading3 font-medium user-select-none
|
|
72
|
+
className={`font-sans text-heading3 font-medium user-select-none truncate ${last ? "text-text" : "text-text-muted"}`}
|
|
113
73
|
>
|
|
114
74
|
{children}
|
|
115
75
|
</span>
|
|
@@ -123,14 +83,19 @@ interface ItemProps<MenuItemType extends string = string> {
|
|
|
123
83
|
loading?: boolean;
|
|
124
84
|
selected?: boolean;
|
|
125
85
|
onClick?: (event: React.MouseEvent) => void;
|
|
126
|
-
|
|
86
|
+
onSelect?: (options: KeyModifiers) => void;
|
|
127
87
|
onDoubleClick?: () => void;
|
|
128
88
|
onHoverChange?: (isHovering: boolean) => void;
|
|
129
89
|
children?: ReactNode;
|
|
130
90
|
menuItems?: MenuItem<MenuItemType>[];
|
|
131
91
|
onSelectMenuItem?: (value: MenuItemType) => void;
|
|
132
92
|
onContextMenu?: () => void;
|
|
93
|
+
onMenuOpenChange?: (open: boolean) => void;
|
|
133
94
|
style?: CSSProperties;
|
|
95
|
+
action?: ReactNode;
|
|
96
|
+
testHoveredId?: string;
|
|
97
|
+
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
98
|
+
hovered?: boolean;
|
|
134
99
|
}
|
|
135
100
|
|
|
136
101
|
const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
@@ -143,26 +108,28 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
143
108
|
loading,
|
|
144
109
|
selected,
|
|
145
110
|
onClick,
|
|
146
|
-
|
|
111
|
+
onSelect,
|
|
147
112
|
onDoubleClick,
|
|
148
113
|
onHoverChange,
|
|
149
114
|
children,
|
|
150
115
|
menuItems,
|
|
151
116
|
onSelectMenuItem,
|
|
152
117
|
onContextMenu,
|
|
118
|
+
onMenuOpenChange,
|
|
153
119
|
style,
|
|
120
|
+
action,
|
|
121
|
+
testHoveredId,
|
|
122
|
+
onKeyDown,
|
|
123
|
+
hovered = false,
|
|
154
124
|
}: ItemProps<MenuItemType>,
|
|
155
125
|
forwardedRef: ForwardedRef<HTMLDivElement>
|
|
156
126
|
) {
|
|
157
|
-
const [hovered, setHovered] = React.useState(false);
|
|
158
|
-
|
|
159
127
|
const { hoverProps } = useHover({
|
|
160
|
-
onHoverChange
|
|
161
|
-
onHoverChange?.(isHovering);
|
|
162
|
-
setHovered(isHovering);
|
|
163
|
-
},
|
|
128
|
+
onHoverChange,
|
|
164
129
|
});
|
|
165
130
|
|
|
131
|
+
const isHovered = testHoveredId ? testHoveredId === id : hovered;
|
|
132
|
+
|
|
166
133
|
const { textPosition, bordered, disabled } = useContext(GridViewContext);
|
|
167
134
|
|
|
168
135
|
const handleClick = useCallback(
|
|
@@ -171,28 +138,33 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
171
138
|
event.preventDefault();
|
|
172
139
|
|
|
173
140
|
onClick?.(event);
|
|
174
|
-
|
|
141
|
+
onSelect?.(event);
|
|
175
142
|
},
|
|
176
|
-
[onClick,
|
|
143
|
+
[onClick, onSelect]
|
|
177
144
|
);
|
|
178
145
|
|
|
179
146
|
const handleKeyDown = useCallback(
|
|
180
147
|
(event: React.KeyboardEvent) => {
|
|
148
|
+
onKeyDown?.(event);
|
|
181
149
|
if (event.key === "Enter" || event.key === " ") {
|
|
182
150
|
event.stopPropagation();
|
|
183
151
|
event.preventDefault();
|
|
184
152
|
|
|
185
|
-
|
|
153
|
+
onSelect?.(event);
|
|
186
154
|
} else {
|
|
187
155
|
return;
|
|
188
156
|
}
|
|
189
157
|
},
|
|
190
|
-
[
|
|
158
|
+
[onSelect, onKeyDown]
|
|
191
159
|
);
|
|
192
160
|
|
|
193
161
|
let element = (
|
|
194
162
|
<div
|
|
195
|
-
className=
|
|
163
|
+
className={cx(
|
|
164
|
+
"flex flex-col relative focus:shadow-[0px_0px_0px_1px_var(--sidebar-background),_0px_0px_0px_3px_var(--primary)] focus:outline-none rounded p-2 -m-2",
|
|
165
|
+
selected && "bg-primary-pastel",
|
|
166
|
+
isHovered && "ring-1 ring-primary ring-inset"
|
|
167
|
+
)}
|
|
196
168
|
id={id}
|
|
197
169
|
ref={forwardedRef}
|
|
198
170
|
{...hoverProps}
|
|
@@ -200,18 +172,13 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
200
172
|
onKeyDown={handleKeyDown}
|
|
201
173
|
>
|
|
202
174
|
<div
|
|
203
|
-
className={
|
|
204
|
-
|
|
205
|
-
cursor-pointer
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
? "border border-divider"
|
|
211
|
-
: "border border-transparent"
|
|
212
|
-
}
|
|
213
|
-
${disabled ? "opacity-50" : "hover:opacity-85 active:opacity-70"}
|
|
214
|
-
`}
|
|
175
|
+
className={cx(
|
|
176
|
+
"flex items-center justify-center rounded",
|
|
177
|
+
"cursor-pointer",
|
|
178
|
+
bordered ? "border border-divider" : "border border-transparent",
|
|
179
|
+
disabled ? "opacity-50" : "hover:opacity-85 active:opacity-70",
|
|
180
|
+
selected ? "bg-primary-pastel" : "bg-input-background text-text"
|
|
181
|
+
)}
|
|
215
182
|
onClick={handleClick}
|
|
216
183
|
onDoubleClick={onDoubleClick}
|
|
217
184
|
onContextMenu={onContextMenu}
|
|
@@ -226,14 +193,19 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
226
193
|
<ItemDescription>{subtitle || " "}</ItemDescription>
|
|
227
194
|
</>
|
|
228
195
|
)}
|
|
229
|
-
{textPosition === "overlay" &&
|
|
230
|
-
<div className="absolute inset-0 flex flex-col justify-end items-start p-1 pointer-events-none truncate
|
|
196
|
+
{textPosition === "overlay" && isHovered && (title || subtitle) && (
|
|
197
|
+
<div className="absolute inset-0 flex flex-col justify-end items-start p-1 pointer-events-none truncate gap-0.5">
|
|
231
198
|
{title && <ItemTitle showBackground>{title}</ItemTitle>}
|
|
232
199
|
{subtitle && (
|
|
233
200
|
<ItemDescription showBackground>{subtitle}</ItemDescription>
|
|
234
201
|
)}
|
|
235
202
|
</div>
|
|
236
203
|
)}
|
|
204
|
+
{action && (
|
|
205
|
+
<div className="absolute top-[7px] right-[7px] flex flex-col justify-end items-start p-1 truncate gap-0.5">
|
|
206
|
+
{action}
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
237
209
|
{loading && (
|
|
238
210
|
<div className="flex flex-col justify-start items-end p-1 pointer-events-none animate-shimmer absolute inset-0 bg-gradient-to-r from-white/0 via-[rgb(226,232,240)]/50 to-white/0 bg-length:200%_100%]">
|
|
239
211
|
<ActivityIndicator opacity={0.5} size={13} />
|
|
@@ -244,7 +216,11 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
244
216
|
|
|
245
217
|
if (menuItems && onSelectMenuItem) {
|
|
246
218
|
element = (
|
|
247
|
-
<ContextMenu<MenuItemType>
|
|
219
|
+
<ContextMenu<MenuItemType>
|
|
220
|
+
items={menuItems}
|
|
221
|
+
onSelect={onSelectMenuItem}
|
|
222
|
+
onOpenChange={onMenuOpenChange}
|
|
223
|
+
>
|
|
248
224
|
{element}
|
|
249
225
|
</ContextMenu>
|
|
250
226
|
);
|
|
@@ -269,33 +245,38 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
|
|
|
269
245
|
});
|
|
270
246
|
|
|
271
247
|
type GridViewContextValue = {
|
|
272
|
-
|
|
248
|
+
minColumnWidth: string;
|
|
273
249
|
textPosition: "overlay" | "below" | "toolip";
|
|
274
250
|
bordered: boolean;
|
|
275
251
|
disabled: boolean;
|
|
252
|
+
gap: number;
|
|
276
253
|
};
|
|
277
254
|
|
|
278
255
|
const GridViewContext = createContext<GridViewContextValue>({
|
|
279
|
-
|
|
256
|
+
minColumnWidth: "220px",
|
|
280
257
|
textPosition: "below",
|
|
281
258
|
bordered: false,
|
|
282
259
|
disabled: false,
|
|
260
|
+
gap: 0,
|
|
283
261
|
});
|
|
284
262
|
|
|
285
263
|
interface GridViewRootProps extends Partial<GridViewContextValue> {
|
|
286
264
|
children: ReactNode;
|
|
287
265
|
onClick?: () => void;
|
|
288
266
|
scrollable?: boolean;
|
|
267
|
+
className?: string;
|
|
289
268
|
}
|
|
290
269
|
|
|
291
270
|
function GridViewRoot({
|
|
292
|
-
|
|
271
|
+
minColumnWidth = "220px",
|
|
272
|
+
gap = 20,
|
|
293
273
|
children,
|
|
294
|
-
scrollable
|
|
274
|
+
scrollable,
|
|
295
275
|
onClick,
|
|
296
276
|
textPosition = "below",
|
|
297
277
|
bordered = false,
|
|
298
278
|
disabled = false,
|
|
279
|
+
className,
|
|
299
280
|
}: GridViewRootProps) {
|
|
300
281
|
const handleClick = useCallback(
|
|
301
282
|
(event: React.MouseEvent) => {
|
|
@@ -309,21 +290,32 @@ function GridViewRoot({
|
|
|
309
290
|
|
|
310
291
|
const contextValue = useMemo(
|
|
311
292
|
() => ({
|
|
312
|
-
|
|
293
|
+
minColumnWidth,
|
|
313
294
|
textPosition,
|
|
314
295
|
bordered,
|
|
315
296
|
disabled,
|
|
297
|
+
gap,
|
|
316
298
|
}),
|
|
317
|
-
[bordered, disabled,
|
|
299
|
+
[bordered, disabled, gap, minColumnWidth, textPosition]
|
|
318
300
|
);
|
|
319
301
|
|
|
320
302
|
return (
|
|
321
303
|
<GridViewContext.Provider value={contextValue}>
|
|
322
304
|
<div
|
|
323
305
|
onClick={handleClick}
|
|
324
|
-
className={
|
|
306
|
+
className={cx(
|
|
307
|
+
"flex flex-col -mx-3 -mb-1",
|
|
308
|
+
scrollable ? "flex-1" : "flex-[0_0_auto]",
|
|
309
|
+
className
|
|
310
|
+
)}
|
|
325
311
|
>
|
|
326
|
-
{scrollable ?
|
|
312
|
+
{scrollable ? (
|
|
313
|
+
<ScrollArea>
|
|
314
|
+
<div className="p-3">{children}</div>
|
|
315
|
+
</ScrollArea>
|
|
316
|
+
) : (
|
|
317
|
+
<div className="p-3">{children}</div>
|
|
318
|
+
)}
|
|
327
319
|
</div>
|
|
328
320
|
</GridViewContext.Provider>
|
|
329
321
|
);
|
|
@@ -336,17 +328,15 @@ function GridViewSection({
|
|
|
336
328
|
children?: ReactNode;
|
|
337
329
|
className?: string;
|
|
338
330
|
}) {
|
|
339
|
-
const {
|
|
340
|
-
|
|
331
|
+
const { minColumnWidth, gap } = useContext(GridViewContext);
|
|
332
|
+
|
|
333
|
+
const styles: React.CSSProperties = {
|
|
334
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${minColumnWidth}, 1fr))`,
|
|
335
|
+
gap: `${gap}px`,
|
|
336
|
+
};
|
|
341
337
|
|
|
342
338
|
return (
|
|
343
|
-
<div
|
|
344
|
-
className={cx(
|
|
345
|
-
`grid-cols-[auto-fill,_minmax(${sizes[size].itemWidth}px,_1fr)] text-text grid `,
|
|
346
|
-
className
|
|
347
|
-
)}
|
|
348
|
-
style={gapStyle}
|
|
349
|
-
>
|
|
339
|
+
<div className={cx("grid text-text", className)} style={styles}>
|
|
350
340
|
{children}
|
|
351
341
|
</div>
|
|
352
342
|
);
|
|
@@ -372,7 +362,7 @@ function GridViewSectionHeader({ title }: { title: string }) {
|
|
|
372
362
|
|
|
373
363
|
export namespace GridView {
|
|
374
364
|
export const Root = memo(GridViewRoot);
|
|
375
|
-
export const Item =
|
|
365
|
+
export const Item = memoGeneric(GridViewItem);
|
|
376
366
|
export const Section = memo(GridViewSection);
|
|
377
367
|
export const SectionHeader = memo(GridViewSectionHeader);
|
|
378
368
|
}
|