@noya-app/noya-designsystem 0.1.63 → 0.1.65
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 +25 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +648 -333
- package/dist/index.d.ts +648 -333
- package/dist/index.js +13813 -9157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13949 -9269
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -22
- package/src/__tests__/validateDropIndicator.test.ts +26 -41
- package/src/__tests__/workspaceLayout.test.ts +281 -0
- package/src/components/ActionMenu.tsx +2 -0
- package/src/components/ActivityIndicator.tsx +13 -6
- package/src/components/ActivityLog.tsx +197 -0
- package/src/components/AnimatePresence.tsx +43 -26
- package/src/components/Avatar.tsx +45 -26
- package/src/components/Banner.tsx +57 -11
- package/src/components/BaseToolbar.tsx +67 -34
- package/src/components/Breadcrumbs.tsx +4 -2
- package/src/components/Button.tsx +28 -23
- package/src/components/Checkbox.tsx +6 -35
- package/src/components/Chip.tsx +47 -33
- package/src/components/Collection.tsx +20 -12
- package/src/components/ColorSwatch.tsx +42 -22
- package/src/components/ColorSwatchControl.tsx +7 -5
- package/src/components/Combobox.tsx +8 -6
- package/src/components/ComboboxMenu.tsx +6 -5
- package/src/components/CommandPalette.tsx +3 -1
- package/src/components/ContextMenu.tsx +3 -1
- package/src/components/Dialog.tsx +9 -13
- package/src/components/DimensionInput.tsx +2 -0
- package/src/components/Divider.tsx +18 -14
- package/src/components/DraggableMenuButton.tsx +11 -5
- package/src/components/Drawer.tsx +14 -6
- package/src/components/DropdownMenu.tsx +4 -2
- package/src/components/EditableText.tsx +4 -2
- package/src/components/Fade.tsx +13 -13
- package/src/components/FileUploadIndicator.tsx +108 -0
- package/src/components/FloatingWindow.tsx +4 -2
- package/src/components/Grid.tsx +58 -42
- package/src/components/GridView.tsx +65 -36
- package/src/components/IVirtualizedList.tsx +5 -0
- package/src/components/IconButton.tsx +2 -0
- package/src/components/Icons.tsx +24 -4
- package/src/components/InputField.tsx +21 -19
- package/src/components/InspectorContainer.tsx +2 -2
- package/src/components/InspectorPrimitives.tsx +15 -9
- package/src/components/Label.tsx +4 -3
- package/src/components/LabeledElementView.tsx +9 -5
- package/src/components/LabeledField.tsx +8 -6
- package/src/components/List.tsx +112 -46
- package/src/components/ListMenu.tsx +91 -0
- package/src/components/ListNavigator.tsx +106 -0
- package/src/components/ListView.tsx +22 -1147
- package/src/components/MediaThumbnail.tsx +84 -14
- package/src/components/Message.tsx +11 -12
- package/src/components/Navigator.tsx +461 -0
- package/src/components/NoyaLogo.tsx +1 -1
- package/src/components/Popover.tsx +12 -6
- package/src/components/Progress.tsx +31 -8
- package/src/components/ResizableContainer.tsx +187 -0
- package/src/components/RingProgress.tsx +128 -0
- package/src/components/ScrollArea.tsx +7 -5
- package/src/components/ScrollableSidebar.tsx +10 -0
- package/src/components/SearchCompletionMenu.tsx +5 -3
- package/src/components/Section.tsx +109 -57
- package/src/components/SegmentedControl.tsx +51 -25
- package/src/components/SelectMenu.tsx +16 -21
- package/src/components/SelectionToolbar.tsx +7 -4
- package/src/components/Slider.tsx +12 -10
- package/src/components/Spacer.tsx +2 -2
- package/src/components/StackNavigator.tsx +378 -0
- package/src/components/Stepper.tsx +88 -0
- package/src/components/Switch.tsx +7 -5
- package/src/components/Tabs.tsx +6 -4
- package/src/components/Text.tsx +29 -18
- package/src/components/TextArea.tsx +5 -3
- package/src/components/Toast.tsx +5 -5
- package/src/components/Toolbar.tsx +57 -10
- package/src/components/ToolbarDrawer.tsx +29 -0
- package/src/components/Tooltip.tsx +2 -2
- package/src/components/TreeView.tsx +13 -4
- package/src/components/UserPointer.tsx +6 -4
- package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
- package/src/components/blocks/ImageBlockComponent.tsx +162 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
- package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
- package/src/components/internal/Checkmark.tsx +24 -0
- package/src/components/internal/Menu.tsx +24 -19
- package/src/components/internal/MenuViewport.tsx +15 -6
- package/src/components/internal/SelectItem.tsx +7 -26
- package/src/components/internal/TextInput.tsx +7 -1
- package/src/components/listView/ListViewContexts.tsx +68 -0
- package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
- package/src/components/listView/ListViewRoot.tsx +521 -0
- package/src/components/listView/ListViewRow.tsx +475 -0
- package/src/components/listView/ListViewRowTitle.tsx +21 -0
- package/src/components/listView/types.ts +11 -0
- package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
- package/src/components/sorting/DragRegistration.tsx +10 -5
- package/src/components/sorting/SharedDragProvider.tsx +134 -27
- package/src/components/sorting/Sortable.tsx +90 -27
- package/src/components/sorting/createSharedDrag.tsx +7 -6
- package/src/components/sorting/sorting.ts +6 -9
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
- package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
- package/src/components/workspace/VerticalTabMenu.tsx +46 -0
- package/src/components/workspace/WorkspaceLayout.tsx +76 -63
- package/src/components/workspace/constants.ts +4 -0
- package/src/components/workspace/panelStorage.ts +216 -0
- package/src/components/workspace/types.ts +11 -4
- package/src/contexts/DesignSystemConfiguration.tsx +12 -5
- package/src/contexts/DialogContext.tsx +19 -5
- package/src/contexts/FloatingWindowContext.tsx +2 -0
- package/src/contexts/GlobalInputBlurContext.tsx +2 -0
- package/src/contexts/ImageDataContext.tsx +2 -0
- package/src/contexts/LinkComponentContext.tsx +17 -0
- package/src/contexts/OpenPortalsContext.tsx +2 -0
- package/src/contexts/PortalScopeContext.tsx +2 -0
- package/src/hooks/mergeEventHandlers.ts +1 -1
- package/src/hooks/useHover.ts +30 -17
- package/src/hooks/useIndent.ts +5 -8
- package/src/hooks/useLabel.ts +2 -0
- package/src/hooks/useObjectURL.ts +5 -3
- package/src/hooks/usePlatform.ts +5 -3
- package/src/hooks/useTheme.ts +3 -1
- package/src/index.css +43 -10
- package/src/index.tsx +15 -7
- package/src/utils/classNames.ts +56 -9
- package/src/utils/combobox.ts +2 -0
- package/src/utils/editableBlockStyles.ts +2 -2
- package/src/utils/formatByteSize.ts +9 -0
- package/src/utils/inputs.ts +8 -6
- package/src/utils/sketchColor.ts +0 -34
- package/src/utils/withSeparatorElements.ts +1 -1
- package/tailwind.config.ts +1 -0
- package/src/components/FillInputField.tsx +0 -37
- package/src/components/FillPreviewBackground.tsx +0 -132
- package/src/components/GradientPicker.tsx +0 -88
- package/src/hooks/usePreservePanelSize.tsx +0 -183
- package/src/mediaQuery.ts +0 -13
- package/src/utils/getGradientBackground.tsx +0 -31
|
@@ -5,9 +5,9 @@ import React, {
|
|
|
5
5
|
memo,
|
|
6
6
|
ReactNode,
|
|
7
7
|
} from "react";
|
|
8
|
+
import { cx } from "../utils/classNames";
|
|
8
9
|
import { Spacer } from "./Spacer";
|
|
9
10
|
import { textStyles } from "./Text";
|
|
10
|
-
import { cx } from "../utils/classNames";
|
|
11
11
|
|
|
12
12
|
export const Section = forwardRef<
|
|
13
13
|
HTMLDivElement,
|
|
@@ -15,7 +15,7 @@ export const Section = forwardRef<
|
|
|
15
15
|
>(({ className, ...props }, ref) => (
|
|
16
16
|
<div
|
|
17
17
|
ref={ref}
|
|
18
|
-
className={cx(`flex-none flex flex-col
|
|
18
|
+
className={cx(`n-flex-none n-flex n-flex-col`, className)}
|
|
19
19
|
{...props}
|
|
20
20
|
/>
|
|
21
21
|
));
|
|
@@ -24,7 +24,7 @@ export const SectionHeader = forwardRef<
|
|
|
24
24
|
HTMLDivElement,
|
|
25
25
|
React.HTMLAttributes<HTMLDivElement>
|
|
26
26
|
>(({ className, ...props }, ref) => (
|
|
27
|
-
<div ref={ref} className={cx(`flex items-center
|
|
27
|
+
<div ref={ref} className={cx(`n-flex n-items-center`, className)} {...props} />
|
|
28
28
|
));
|
|
29
29
|
|
|
30
30
|
type TitleTextStyle = "small" | "heading5" | "heading4" | "heading3";
|
|
@@ -38,7 +38,11 @@ export const Title = forwardRef<
|
|
|
38
38
|
<div
|
|
39
39
|
ref={ref}
|
|
40
40
|
className={cx(
|
|
41
|
-
`flex select-none ${
|
|
41
|
+
`n-flex n-select-none ${
|
|
42
|
+
$textStyle
|
|
43
|
+
? `${textStyles[$textStyle]} n-text-text`
|
|
44
|
+
: "n-font-sans n-text-label n-text-text-muted n-font-semibold"
|
|
45
|
+
} `,
|
|
42
46
|
className
|
|
43
47
|
)}
|
|
44
48
|
{...props}
|
|
@@ -51,7 +55,7 @@ export const Row = forwardRef<
|
|
|
51
55
|
>(({ className, ...props }, ref) => (
|
|
52
56
|
<div
|
|
53
57
|
ref={ref}
|
|
54
|
-
className={cx(`flex-1 flex flex-row items-center
|
|
58
|
+
className={cx(`n-flex-1 n-flex n-flex-row n-items-center`, className)}
|
|
55
59
|
{...props}
|
|
56
60
|
/>
|
|
57
61
|
));
|
|
@@ -62,7 +66,7 @@ export const Column = forwardRef<
|
|
|
62
66
|
>(({ className, ...props }, ref) => (
|
|
63
67
|
<div
|
|
64
68
|
ref={ref}
|
|
65
|
-
className={cx(`flex-1 flex flex-col gap-1
|
|
69
|
+
className={cx(`n-flex-1 n-flex n-flex-col n-gap-1`, className)}
|
|
66
70
|
{...props}
|
|
67
71
|
/>
|
|
68
72
|
));
|
|
@@ -74,7 +78,7 @@ export const Checkbox = forwardRef<
|
|
|
74
78
|
<input
|
|
75
79
|
ref={ref}
|
|
76
80
|
type="checkbox"
|
|
77
|
-
className={cx(`m-0 focus:z-interactable
|
|
81
|
+
className={cx(`n-m-0 focus:n-z-interactable`, className)}
|
|
78
82
|
{...props}
|
|
79
83
|
/>
|
|
80
84
|
));
|
|
@@ -85,7 +89,7 @@ export const Text = forwardRef<
|
|
|
85
89
|
>(({ className, ...props }, ref) => (
|
|
86
90
|
<span
|
|
87
91
|
ref={ref}
|
|
88
|
-
className={cx(`font-sans text-heading5 font-normal
|
|
92
|
+
className={cx(`n-font-sans n-text-heading5 n-font-normal`, className)}
|
|
89
93
|
{...props}
|
|
90
94
|
/>
|
|
91
95
|
));
|
|
@@ -123,7 +127,9 @@ export const RowLabel = forwardRef(function RowLabel(
|
|
|
123
127
|
<label
|
|
124
128
|
ref={ref}
|
|
125
129
|
className={cx(
|
|
126
|
-
`font-sans text-label uppercase flex items-center leading-[19px] font-bold text-text-subtle z-label ${
|
|
130
|
+
`n-font-sans n-text-label n-uppercase n-flex n-items-center n-leading-[19px] n-font-bold n-text-text-subtle n-z-label ${
|
|
131
|
+
$textStyle && textStyles[$textStyle]
|
|
132
|
+
} `,
|
|
127
133
|
className
|
|
128
134
|
)}
|
|
129
135
|
{...props}
|
package/src/components/Label.tsx
CHANGED
|
@@ -3,9 +3,10 @@ import { mergeConflictingClassNames } from "../utils/classNames";
|
|
|
3
3
|
|
|
4
4
|
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
|
|
5
5
|
|
|
6
|
-
const labelStyles =
|
|
6
|
+
const labelStyles =
|
|
7
|
+
"n-flex n-items-center n-select-none n-z-label n-relative";
|
|
7
8
|
const labelTextStyles =
|
|
8
|
-
"font-sans text-label
|
|
9
|
+
"n-font-sans n-text-label n-leading-none n-font-semibold n-text-text-muted";
|
|
9
10
|
|
|
10
11
|
export const Label = memo(
|
|
11
12
|
forwardRef<HTMLLabelElement, LabelProps>(function Label(
|
|
@@ -19,7 +20,7 @@ export const Label = memo(
|
|
|
19
20
|
[
|
|
20
21
|
labelStyles,
|
|
21
22
|
labelTextStyles,
|
|
22
|
-
"text-left min-h-input-height",
|
|
23
|
+
"n-text-left n-min-h-input-height",
|
|
23
24
|
className,
|
|
24
25
|
],
|
|
25
26
|
{
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import * as kiwi from "kiwi.js";
|
|
2
4
|
import * as React from "react";
|
|
3
5
|
|
|
@@ -13,11 +15,13 @@ export const LabeledElementView = React.memo(function LabeledElementView({
|
|
|
13
15
|
const elementIds: string[] = React.Children.toArray(children)
|
|
14
16
|
.flatMap((child) =>
|
|
15
17
|
React.isValidElement(child) && child.type === React.Fragment
|
|
16
|
-
? (child.props.children as React.ReactNode[])
|
|
18
|
+
? ((child as any).props.children as React.ReactNode[])
|
|
17
19
|
: [child]
|
|
18
20
|
)
|
|
19
21
|
.map((child) =>
|
|
20
|
-
React.isValidElement(child) && "id" in child
|
|
22
|
+
React.isValidElement(child) && "id" in (child as any).props
|
|
23
|
+
? (child as any).props.id
|
|
24
|
+
: null
|
|
21
25
|
)
|
|
22
26
|
.filter((id) => !!id);
|
|
23
27
|
const serializedIds = elementIds.join(",");
|
|
@@ -148,9 +152,9 @@ export const LabeledElementView = React.memo(function LabeledElementView({
|
|
|
148
152
|
}, [refs, labelElements]);
|
|
149
153
|
|
|
150
154
|
return (
|
|
151
|
-
<div className="flex flex-1 flex-col relative" ref={containerRef}>
|
|
152
|
-
<div className="flex flex-1 items-center">{children}</div>
|
|
153
|
-
<div className="relative overflow-hidden select-none">
|
|
155
|
+
<div className="n-flex n-flex-1 n-flex-col n-relative" ref={containerRef}>
|
|
156
|
+
<div className="n-flex n-flex-1 n-items-center">{children}</div>
|
|
157
|
+
<div className="n-relative n-overflow-hidden n-select-none">
|
|
154
158
|
{labelElements}
|
|
155
159
|
</div>
|
|
156
160
|
</div>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import React, { memo, useMemo } from "react";
|
|
2
4
|
import { useIndent } from "../hooks/useIndent";
|
|
3
5
|
import {
|
|
@@ -13,9 +15,9 @@ import { Label } from "./Label";
|
|
|
13
15
|
|
|
14
16
|
export const labeledFieldStyles = (labelType?: LabelType) =>
|
|
15
17
|
cx(
|
|
16
|
-
"flex flex-row flex-1 flex-wrap items-center gap-x-1.5",
|
|
17
|
-
labelType === "end" && "flex-row-reverse justify-end",
|
|
18
|
-
labelType === "above" && "flex-col items-stretch justify-start"
|
|
18
|
+
"n-flex n-flex-row n-flex-1 n-flex-wrap n-items-center n-gap-x-1.5",
|
|
19
|
+
labelType === "end" && "n-flex-row-reverse n-justify-end",
|
|
20
|
+
labelType === "above" && "n-flex-col n-items-stretch n-justify-start"
|
|
19
21
|
);
|
|
20
22
|
|
|
21
23
|
const labelTypeInset: LabelTypeContextValue = {
|
|
@@ -94,7 +96,7 @@ export const LabeledField = memo(function LabeledField({
|
|
|
94
96
|
{children}
|
|
95
97
|
</LabelTypeContext.Provider>
|
|
96
98
|
) : (
|
|
97
|
-
<div className={cx("flex relative flex-1", className)} style={style}>
|
|
99
|
+
<div className={cx("n-flex n-relative n-flex-1", className)} style={style}>
|
|
98
100
|
{Boolean(indentLevel) && <div style={indentStyle} />}
|
|
99
101
|
<div className={labeledFieldClasses}>
|
|
100
102
|
{labelType !== "none" && (
|
|
@@ -104,7 +106,7 @@ export const LabeledField = memo(function LabeledField({
|
|
|
104
106
|
// and when start/end position with textarea it aligns label to the top of the field
|
|
105
107
|
className={
|
|
106
108
|
labelType === "start" || labelType === "end"
|
|
107
|
-
? "self-start"
|
|
109
|
+
? "n-self-start"
|
|
108
110
|
: undefined
|
|
109
111
|
}
|
|
110
112
|
style={labelStyle}
|
|
@@ -114,7 +116,7 @@ export const LabeledField = memo(function LabeledField({
|
|
|
114
116
|
</Label>
|
|
115
117
|
)}
|
|
116
118
|
{labelType === "start" ? (
|
|
117
|
-
<div className="flex-auto flex" style={fieldStyle}>
|
|
119
|
+
<div className="n-flex-auto n-flex" style={fieldStyle}>
|
|
118
120
|
{children}
|
|
119
121
|
</div>
|
|
120
122
|
) : (
|
package/src/components/List.tsx
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
forwardRefGeneric,
|
|
3
5
|
memoGeneric,
|
|
4
6
|
useFileDropTarget,
|
|
5
7
|
} from "@noya-app/react-utils";
|
|
6
|
-
import React, {
|
|
8
|
+
import React, {
|
|
9
|
+
memo,
|
|
10
|
+
useImperativeHandle,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
import { useLinkComponent } from "../contexts/LinkComponentContext";
|
|
7
16
|
import { cssVars } from "../theme";
|
|
8
17
|
import { cx } from "../utils/classNames";
|
|
9
18
|
import { updateSelection } from "../utils/selection";
|
|
@@ -13,7 +22,8 @@ import {
|
|
|
13
22
|
CollectionProps,
|
|
14
23
|
CollectionRef,
|
|
15
24
|
} from "./Collection";
|
|
16
|
-
import { ListView
|
|
25
|
+
import { ListView } from "./ListView";
|
|
26
|
+
import { ListViewItemInfo } from "./listView/ListViewRoot";
|
|
17
27
|
import { TreeView } from "./TreeView";
|
|
18
28
|
|
|
19
29
|
const cssVarOverrides = {
|
|
@@ -32,6 +42,7 @@ export const List = memoGeneric(
|
|
|
32
42
|
items,
|
|
33
43
|
getId,
|
|
34
44
|
getName,
|
|
45
|
+
getHref,
|
|
35
46
|
expandable,
|
|
36
47
|
getExpanded,
|
|
37
48
|
getRenamable,
|
|
@@ -43,7 +54,10 @@ export const List = memoGeneric(
|
|
|
43
54
|
renderThumbnail,
|
|
44
55
|
renderAction,
|
|
45
56
|
renderDetail,
|
|
57
|
+
renderRight,
|
|
46
58
|
onSelectionChange,
|
|
59
|
+
itemStyle: itemStyleProp,
|
|
60
|
+
itemClassName: itemClassNameProp,
|
|
47
61
|
itemRoleDescription = "clickable item",
|
|
48
62
|
detailPosition = "end",
|
|
49
63
|
size = "medium",
|
|
@@ -63,9 +77,11 @@ export const List = memoGeneric(
|
|
|
63
77
|
getDropTargetParentIndex,
|
|
64
78
|
getPlaceholder,
|
|
65
79
|
onClickItem,
|
|
80
|
+
onClick,
|
|
66
81
|
dragIndicatorStyle,
|
|
67
82
|
sortableId,
|
|
68
83
|
sharedDragProps,
|
|
84
|
+
virtualized,
|
|
69
85
|
} = props;
|
|
70
86
|
|
|
71
87
|
const [internalHoveredId, setHoveredId] = useState<string>();
|
|
@@ -73,6 +89,16 @@ export const List = memoGeneric(
|
|
|
73
89
|
const [dropdownOpenId, setDropdownOpenId] = useState<string>();
|
|
74
90
|
const hoveredId = internalHoveredId ?? testHoveredId;
|
|
75
91
|
const renamingId = testRenamingId ?? internalRenamingId;
|
|
92
|
+
const itemClassName = cx(
|
|
93
|
+
"n-cursor-pointer n-px-1",
|
|
94
|
+
pyMap[size],
|
|
95
|
+
itemClassNameProp
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const itemStyle = useMemo(
|
|
99
|
+
() => ({ ...cssVarOverrides, ...itemStyleProp }),
|
|
100
|
+
[itemStyleProp]
|
|
101
|
+
);
|
|
76
102
|
|
|
77
103
|
const handleSelect = (itemId: string, event?: ListView.ClickInfo) => {
|
|
78
104
|
if (!onSelectionChange) return;
|
|
@@ -101,8 +127,9 @@ export const List = memoGeneric(
|
|
|
101
127
|
};
|
|
102
128
|
|
|
103
129
|
const ViewComponent = expandable ? TreeView : ListView;
|
|
130
|
+
const LinkComponent = useLinkComponent();
|
|
104
131
|
|
|
105
|
-
const fileDropTargetRef = useRef<HTMLDivElement>(null);
|
|
132
|
+
const fileDropTargetRef = useRef<HTMLDivElement | null>(null);
|
|
106
133
|
const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
|
|
107
134
|
fileDropTargetRef,
|
|
108
135
|
onFilesDrop
|
|
@@ -119,11 +146,12 @@ export const List = memoGeneric(
|
|
|
119
146
|
variant="bare"
|
|
120
147
|
className={cx(
|
|
121
148
|
className,
|
|
122
|
-
isDropTargetActive && "bg-selected-list-item-background",
|
|
123
|
-
scrollable && "basis-0 min-h-0"
|
|
149
|
+
isDropTargetActive && "n-bg-selected-list-item-background",
|
|
150
|
+
scrollable && "n-basis-0 n-min-h-0"
|
|
124
151
|
)}
|
|
125
152
|
containerRef={fileDropTargetRef}
|
|
126
153
|
scrollable={scrollable}
|
|
154
|
+
virtualized={virtualized}
|
|
127
155
|
data={items}
|
|
128
156
|
keyExtractor={getId}
|
|
129
157
|
role="listbox"
|
|
@@ -137,6 +165,8 @@ export const List = memoGeneric(
|
|
|
137
165
|
getDropTargetParentIndex={getDropTargetParentIndex}
|
|
138
166
|
sharedDragProps={sharedDragProps}
|
|
139
167
|
sortableId={sortableId}
|
|
168
|
+
onPress={onClick}
|
|
169
|
+
itemHeight={itemHeightMap[size]}
|
|
140
170
|
{...dropTargetProps}
|
|
141
171
|
renderItem={(
|
|
142
172
|
item: T,
|
|
@@ -144,6 +174,7 @@ export const List = memoGeneric(
|
|
|
144
174
|
{ isDragOverlay }: ListViewItemInfo
|
|
145
175
|
) => {
|
|
146
176
|
const id = getId(item);
|
|
177
|
+
const href = getHref?.(item);
|
|
147
178
|
const isDropdownOpen = dropdownOpenId === id;
|
|
148
179
|
const isHovered =
|
|
149
180
|
(hoveredId === id || isDropdownOpen) && !isDragOverlay;
|
|
@@ -192,11 +223,13 @@ export const List = memoGeneric(
|
|
|
192
223
|
const end =
|
|
193
224
|
action ||
|
|
194
225
|
(detailPosition === "end" && renderDetail?.(item, isSelected));
|
|
226
|
+
const right = renderRight?.(item, isSelected);
|
|
195
227
|
const below =
|
|
196
228
|
detailPosition === "below" && renderDetail?.(item, isSelected);
|
|
197
|
-
|
|
198
229
|
return (
|
|
199
230
|
<ViewComponent.Row
|
|
231
|
+
as={typeof href === "string" ? LinkComponent : undefined}
|
|
232
|
+
href={href}
|
|
200
233
|
id={id}
|
|
201
234
|
key={id}
|
|
202
235
|
role="option"
|
|
@@ -204,12 +237,12 @@ export const List = memoGeneric(
|
|
|
204
237
|
aria-roledescription={itemRoleDescription}
|
|
205
238
|
aria-label={getName(item)}
|
|
206
239
|
aria-selected={isSelected}
|
|
207
|
-
className={
|
|
240
|
+
className={itemClassName}
|
|
208
241
|
testRelativeDropPosition={
|
|
209
242
|
testShowDropIndicatorId === id ? "inside" : undefined
|
|
210
243
|
}
|
|
211
|
-
chevronClassName={cx(expandable && "relative left-2")}
|
|
212
|
-
style={
|
|
244
|
+
chevronClassName={cx(expandable && "n-relative n-left-2")}
|
|
245
|
+
style={itemStyle}
|
|
213
246
|
hovered={isHovered}
|
|
214
247
|
selected={isSelected}
|
|
215
248
|
expanded={expanded}
|
|
@@ -220,6 +253,7 @@ export const List = memoGeneric(
|
|
|
220
253
|
onPress={(e: ListView.ClickInfo) => {
|
|
221
254
|
handleSelect(id, e);
|
|
222
255
|
onClickItem?.(id);
|
|
256
|
+
e.stopPropagation();
|
|
223
257
|
}}
|
|
224
258
|
onDoubleClick={() => {
|
|
225
259
|
onDoubleClickItem?.(id);
|
|
@@ -247,24 +281,24 @@ export const List = memoGeneric(
|
|
|
247
281
|
dragIndicatorStyle={dragIndicatorStyle}
|
|
248
282
|
>
|
|
249
283
|
<div
|
|
250
|
-
className={cx("flex flex-1 items-center px-2", rowGapMap[size])}
|
|
284
|
+
className={cx("n-flex n-flex-1 n-items-center n-px-2", rowGapMap[size])}
|
|
251
285
|
>
|
|
252
286
|
{thumbnail && (
|
|
253
287
|
<div
|
|
254
288
|
className={cx(
|
|
255
|
-
"overflow-hidden flex-none flex items-center justify-center",
|
|
289
|
+
"n-overflow-hidden n-flex-none n-flex n-items-center n-justify-center",
|
|
256
290
|
thumbnailSize === "auto" && thumbnailSizeMap[size],
|
|
257
|
-
isSelected && "text-selected-list-item-text",
|
|
258
|
-
size
|
|
291
|
+
isSelected && "n-text-selected-list-item-text",
|
|
292
|
+
roundedMap[size]
|
|
259
293
|
)}
|
|
260
294
|
tabIndex={-1}
|
|
261
295
|
>
|
|
262
296
|
{thumbnail}
|
|
263
297
|
</div>
|
|
264
298
|
)}
|
|
265
|
-
<div className="flex flex-col flex-1 w-0">
|
|
266
|
-
<div className="flex items-center min-h-input-height flex-1 gap-2">
|
|
267
|
-
<div className="flex-1 w-0 flex items-center" tabIndex={-1}>
|
|
299
|
+
<div className="n-flex n-flex-col n-flex-1 n-w-0">
|
|
300
|
+
<div className="n-flex n-items-center n-min-h-input-height n-flex-1 n-gap-2">
|
|
301
|
+
<div className="n-flex-1 n-w-0 n-flex n-items-center" tabIndex={-1}>
|
|
268
302
|
{isRenaming ? (
|
|
269
303
|
<ViewComponent.EditableRowTitle
|
|
270
304
|
value={getName(item)}
|
|
@@ -283,13 +317,19 @@ export const List = memoGeneric(
|
|
|
283
317
|
<ViewComponent.RowTitle
|
|
284
318
|
className={cx(
|
|
285
319
|
fontStyleMap[size],
|
|
286
|
-
isSelected && "text-selected-list-item-text"
|
|
320
|
+
isSelected && "n-text-selected-list-item-text"
|
|
287
321
|
)}
|
|
288
322
|
>
|
|
289
323
|
{getName(item)}
|
|
290
324
|
</ViewComponent.RowTitle>
|
|
291
325
|
)}
|
|
292
326
|
</div>
|
|
327
|
+
{/* Right content should appear before end/detail when detailPosition is 'end' */}
|
|
328
|
+
{right && detailPosition === "end" && (
|
|
329
|
+
<DetailContainer selected={isSelected}>
|
|
330
|
+
{right}
|
|
331
|
+
</DetailContainer>
|
|
332
|
+
)}
|
|
293
333
|
{end && detailPosition === "end" && (
|
|
294
334
|
<DetailContainer selected={isSelected}>
|
|
295
335
|
{end}
|
|
@@ -302,6 +342,12 @@ export const List = memoGeneric(
|
|
|
302
342
|
</DetailContainer>
|
|
303
343
|
)}
|
|
304
344
|
</div>
|
|
345
|
+
{/* When detail is below, right should still render on the right next to the action */}
|
|
346
|
+
{right && detailPosition === "below" && (
|
|
347
|
+
<DetailContainer selected={isSelected}>
|
|
348
|
+
{right}
|
|
349
|
+
</DetailContainer>
|
|
350
|
+
)}
|
|
305
351
|
{end && detailPosition === "below" && (
|
|
306
352
|
<DetailContainer selected={isSelected}>{end}</DetailContainer>
|
|
307
353
|
)}
|
|
@@ -323,7 +369,7 @@ const DetailContainer = memo(function DetailContainer({
|
|
|
323
369
|
}) {
|
|
324
370
|
return (
|
|
325
371
|
<div
|
|
326
|
-
className={cx("flex items-center gap-2", selected && "text-primary")}
|
|
372
|
+
className={cx("n-flex n-items-center n-gap-2", selected && "n-text-primary")}
|
|
327
373
|
tabIndex={-1}
|
|
328
374
|
>
|
|
329
375
|
{children}
|
|
@@ -332,41 +378,61 @@ const DetailContainer = memo(function DetailContainer({
|
|
|
332
378
|
});
|
|
333
379
|
|
|
334
380
|
const pyMap: Record<CollectionItemSize, string> = {
|
|
335
|
-
xxs: "py-0.5",
|
|
336
|
-
xs: "py-0.5",
|
|
337
|
-
small: "py-
|
|
338
|
-
medium: "py-1",
|
|
339
|
-
mediumLarge: "py-1.5",
|
|
340
|
-
large: "py-2",
|
|
341
|
-
xl: "py-3",
|
|
381
|
+
xxs: "n-py-0.5",
|
|
382
|
+
xs: "n-py-0.5",
|
|
383
|
+
small: "n-py-1",
|
|
384
|
+
medium: "n-py-1",
|
|
385
|
+
mediumLarge: "n-py-1.5",
|
|
386
|
+
large: "n-py-2",
|
|
387
|
+
xl: "n-py-3",
|
|
342
388
|
};
|
|
343
389
|
|
|
344
390
|
const thumbnailSizeMap: Record<CollectionItemSize, string> = {
|
|
345
|
-
xxs: "w-
|
|
346
|
-
xs: "w-5 h-5",
|
|
347
|
-
small: "w-
|
|
348
|
-
medium: "w-8 h-8",
|
|
349
|
-
mediumLarge: "w-12 h-12",
|
|
350
|
-
large: "w-16 h-16",
|
|
351
|
-
xl: "w-24 h-24",
|
|
391
|
+
xxs: "n-w-4 n-h-4",
|
|
392
|
+
xs: "n-w-5 n-h-5",
|
|
393
|
+
small: "n-w-6 n-h-6",
|
|
394
|
+
medium: "n-w-8 n-h-8",
|
|
395
|
+
mediumLarge: "n-w-12 n-h-12",
|
|
396
|
+
large: "n-w-16 n-h-16",
|
|
397
|
+
xl: "n-w-24 n-h-24",
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const roundedMap: Record<CollectionItemSize, string> = {
|
|
401
|
+
xxs: "n-rounded-sm",
|
|
402
|
+
xs: "n-rounded-sm",
|
|
403
|
+
small: "n-rounded",
|
|
404
|
+
medium: "n-rounded",
|
|
405
|
+
mediumLarge: "n-rounded",
|
|
406
|
+
large: "n-rounded",
|
|
407
|
+
xl: "n-rounded",
|
|
352
408
|
};
|
|
353
409
|
|
|
354
410
|
const rowGapMap: Record<CollectionItemSize, string> = {
|
|
355
|
-
xxs: "gap-2",
|
|
356
|
-
xs: "gap-2",
|
|
357
|
-
small: "gap-2",
|
|
358
|
-
medium: "gap-
|
|
359
|
-
mediumLarge: "gap-3",
|
|
360
|
-
large: "gap-3",
|
|
361
|
-
xl: "gap-4",
|
|
411
|
+
xxs: "n-gap-2",
|
|
412
|
+
xs: "n-gap-2",
|
|
413
|
+
small: "n-gap-2",
|
|
414
|
+
medium: "n-gap-2",
|
|
415
|
+
mediumLarge: "n-gap-3",
|
|
416
|
+
large: "n-gap-3",
|
|
417
|
+
xl: "n-gap-4",
|
|
362
418
|
};
|
|
363
419
|
|
|
364
420
|
const fontStyleMap: Record<CollectionItemSize, string> = {
|
|
365
|
-
xxs: "font-medium text-list-small",
|
|
366
|
-
xs: "font-medium text-list-small",
|
|
367
|
-
small: "font-medium text-list-small",
|
|
368
|
-
medium: "font-medium",
|
|
369
|
-
mediumLarge: "font-medium",
|
|
370
|
-
large: "font-medium",
|
|
371
|
-
xl: "font-medium text-heading4 text-text",
|
|
421
|
+
xxs: "n-font-medium n-text-list-small",
|
|
422
|
+
xs: "n-font-medium n-text-list-small",
|
|
423
|
+
small: "n-font-medium n-text-list-small",
|
|
424
|
+
medium: "n-font-medium",
|
|
425
|
+
mediumLarge: "n-font-medium",
|
|
426
|
+
large: "n-font-medium",
|
|
427
|
+
xl: "n-font-medium n-text-heading4 n-text-text",
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const itemHeightMap: Record<CollectionItemSize, number> = {
|
|
431
|
+
xxs: 31,
|
|
432
|
+
xs: 31,
|
|
433
|
+
small: 31,
|
|
434
|
+
medium: 40,
|
|
435
|
+
mediumLarge: 60,
|
|
436
|
+
large: 80,
|
|
437
|
+
xl: 120,
|
|
372
438
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ArrowRightIcon } from "@noya-app/noya-icons";
|
|
4
|
+
import { chunkBy } from "@noya-app/noya-utils";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import withSeparatorElements from "../utils/withSeparatorElements";
|
|
7
|
+
import { CollectionProps } from "./Collection";
|
|
8
|
+
import { Divider } from "./Divider";
|
|
9
|
+
import { renderIcon } from "./Icons";
|
|
10
|
+
import {
|
|
11
|
+
isSelectableMenuItem,
|
|
12
|
+
isSubMenuItem,
|
|
13
|
+
MenuItem,
|
|
14
|
+
SelectableMenuItem,
|
|
15
|
+
SubMenuItem,
|
|
16
|
+
} from "./internal/Menu";
|
|
17
|
+
import { List } from "./List";
|
|
18
|
+
|
|
19
|
+
export type ListMenuProps<T extends string> = {
|
|
20
|
+
items: MenuItem<T>[];
|
|
21
|
+
selectedIds?: T[];
|
|
22
|
+
onSelect: (item: T) => void;
|
|
23
|
+
renderRight?: CollectionProps<MenuItem<T>, T>["renderRight"];
|
|
24
|
+
className?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function getItemId<T extends string>(
|
|
28
|
+
item: SubMenuItem<T> | SelectableMenuItem<T>
|
|
29
|
+
): string {
|
|
30
|
+
if (item.type === "submenu") {
|
|
31
|
+
return item.id;
|
|
32
|
+
}
|
|
33
|
+
return item.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function ListMenu<T extends string>({
|
|
37
|
+
items,
|
|
38
|
+
selectedIds: selectedIdsProp,
|
|
39
|
+
onSelect,
|
|
40
|
+
renderRight,
|
|
41
|
+
className,
|
|
42
|
+
}: ListMenuProps<T>) {
|
|
43
|
+
const selectedIds = items
|
|
44
|
+
.flatMap((item) =>
|
|
45
|
+
isSelectableMenuItem(item) && item.checked ? [item.value] : []
|
|
46
|
+
)
|
|
47
|
+
.concat(selectedIdsProp as T[]);
|
|
48
|
+
|
|
49
|
+
let chunks = chunkBy(
|
|
50
|
+
items,
|
|
51
|
+
(a, b) => a.type !== "separator" && b.type !== "separator"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
chunks = chunks.map((chunk) =>
|
|
55
|
+
chunk.filter((item) => item.type !== "separator")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
chunks = chunks.filter((chunk) => chunk.length > 0);
|
|
59
|
+
|
|
60
|
+
const lists = chunks.map((chunk) => {
|
|
61
|
+
const regularItems = chunk.filter(
|
|
62
|
+
(item) => isSubMenuItem(item) || isSelectableMenuItem(item)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<List
|
|
67
|
+
key={regularItems.map(getItemId).join("-")}
|
|
68
|
+
items={regularItems}
|
|
69
|
+
size="small"
|
|
70
|
+
itemClassName="n-rounded-sm"
|
|
71
|
+
className={className}
|
|
72
|
+
getId={getItemId}
|
|
73
|
+
getName={(item) => item.title.toString()}
|
|
74
|
+
selectedIds={selectedIds}
|
|
75
|
+
renderRight={renderRight}
|
|
76
|
+
renderThumbnail={({ item }) => (
|
|
77
|
+
<div className="n-flex n-flex-1 n-items-center n-justify-center n-bg-divider-subtle n-w-full n-h-full n-rounded-sm">
|
|
78
|
+
{renderIcon(item.icon)}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
renderDetail={(item) => isSubMenuItem(item) && <ArrowRightIcon />}
|
|
82
|
+
onClickItem={(itemId) => onSelect(itemId as T)}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return withSeparatorElements(
|
|
88
|
+
lists,
|
|
89
|
+
<Divider overflow={12} className="n-my-1" />
|
|
90
|
+
);
|
|
91
|
+
}
|