@noya-app/noya-designsystem 0.1.64 → 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 +12 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +501 -228
- package/dist/index.d.ts +501 -228
- package/dist/index.js +6738 -2571
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6697 -2499
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/components/ActivityIndicator.tsx +9 -4
- package/src/components/ActivityLog.tsx +197 -0
- package/src/components/AnimatePresence.tsx +37 -22
- package/src/components/Avatar.tsx +42 -25
- package/src/components/Banner.tsx +57 -11
- package/src/components/BaseToolbar.tsx +29 -7
- package/src/components/Breadcrumbs.tsx +2 -2
- package/src/components/Button.tsx +23 -24
- package/src/components/Checkbox.tsx +6 -35
- package/src/components/Chip.tsx +45 -33
- package/src/components/Collection.tsx +16 -11
- package/src/components/ColorSwatch.tsx +3 -3
- package/src/components/ColorSwatchControl.tsx +2 -2
- package/src/components/Combobox.tsx +6 -6
- package/src/components/ComboboxMenu.tsx +6 -5
- package/src/components/CommandPalette.tsx +1 -1
- package/src/components/Dialog.tsx +6 -12
- package/src/components/Divider.tsx +8 -8
- package/src/components/DraggableMenuButton.tsx +9 -5
- package/src/components/Drawer.tsx +5 -5
- package/src/components/DropdownMenu.tsx +1 -1
- package/src/components/EditableText.tsx +2 -2
- package/src/components/Fade.tsx +13 -13
- package/src/components/FileUploadIndicator.tsx +11 -11
- package/src/components/FloatingWindow.tsx +2 -2
- package/src/components/Grid.tsx +18 -11
- package/src/components/GridView.tsx +32 -25
- package/src/components/IVirtualizedList.tsx +5 -0
- package/src/components/Icons.tsx +24 -4
- package/src/components/InputField.tsx +19 -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 +3 -3
- package/src/components/LabeledField.tsx +6 -6
- package/src/components/List.tsx +103 -45
- package/src/components/ListMenu.tsx +91 -0
- package/src/components/ListNavigator.tsx +106 -0
- package/src/components/ListView.tsx +20 -1158
- package/src/components/MediaThumbnail.tsx +56 -15
- package/src/components/Message.tsx +5 -5
- package/src/components/Navigator.tsx +461 -0
- package/src/components/NoyaLogo.tsx +1 -1
- package/src/components/Popover.tsx +9 -5
- package/src/components/Progress.tsx +19 -9
- package/src/components/ResizableContainer.tsx +187 -0
- package/src/components/RingProgress.tsx +128 -0
- package/src/components/ScrollArea.tsx +4 -4
- package/src/components/ScrollableSidebar.tsx +10 -0
- package/src/components/SearchCompletionMenu.tsx +3 -3
- package/src/components/Section.tsx +107 -57
- package/src/components/SegmentedControl.tsx +29 -29
- package/src/components/SelectMenu.tsx +13 -20
- package/src/components/SelectionToolbar.tsx +4 -3
- package/src/components/Slider.tsx +9 -9
- 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 +4 -4
- package/src/components/Tabs.tsx +3 -3
- package/src/components/Text.tsx +23 -13
- package/src/components/TextArea.tsx +3 -3
- package/src/components/Toast.tsx +4 -4
- package/src/components/Toolbar.tsx +50 -9
- package/src/components/ToolbarDrawer.tsx +2 -2
- package/src/components/Tooltip.tsx +1 -1
- package/src/components/TreeView.tsx +11 -4
- package/src/components/UserPointer.tsx +4 -4
- package/src/components/ai-assistant/AIAssistantLayout.tsx +8 -8
- package/src/components/blocks/ImageBlockComponent.tsx +13 -7
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +4 -4
- package/src/components/file-explorer/FileExplorerLayout.tsx +20 -8
- package/src/components/internal/Checkmark.tsx +1 -1
- package/src/components/internal/Menu.tsx +22 -18
- package/src/components/internal/MenuViewport.tsx +13 -6
- package/src/components/internal/SelectItem.tsx +3 -3
- package/src/components/internal/TextInput.tsx +5 -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 +2 -1
- package/src/components/sorting/SharedDragProvider.tsx +91 -9
- package/src/components/sorting/Sortable.tsx +71 -17
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +5 -5
- package/src/components/workspace/PanelWorkspaceLayout.tsx +65 -49
- package/src/components/workspace/VerticalTabMenu.tsx +25 -15
- package/src/components/workspace/WorkspaceLayout.tsx +30 -21
- package/src/contexts/DialogContext.tsx +17 -5
- package/src/hooks/useIndent.ts +3 -8
- package/src/index.css +24 -10
- package/src/index.tsx +10 -5
- package/src/utils/classNames.ts +52 -6
- package/src/utils/editableBlockStyles.ts +2 -2
- package/src/utils/formatByteSize.ts +1 -0
- package/src/utils/inputs.ts +8 -6
- package/src/utils/sketchColor.ts +0 -34
- package/tailwind.config.ts +1 -0
- package/src/components/FillInputField.tsx +0 -37
- package/src/components/FillPreviewBackground.tsx +0 -134
- package/src/components/GradientPicker.tsx +0 -90
- package/src/utils/getGradientBackground.tsx +0 -31
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Size } from "@noya-app/noya-geometry";
|
|
3
4
|
import {
|
|
4
5
|
FileIcon,
|
|
5
6
|
ImageIcon,
|
|
@@ -48,6 +49,8 @@ export type MediaThumbnailProps = {
|
|
|
48
49
|
|
|
49
50
|
fileName?: string;
|
|
50
51
|
renderThumbnailIcon?: (props: RenderThumbnailIconProps) => React.ReactNode;
|
|
52
|
+
dimensions?: Size;
|
|
53
|
+
aspect?: "contain" | "cover";
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
export const getThumbnailColors = (
|
|
@@ -95,7 +98,9 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
95
98
|
iconName,
|
|
96
99
|
size = "medium",
|
|
97
100
|
fileName,
|
|
101
|
+
dimensions,
|
|
98
102
|
renderThumbnailIcon,
|
|
103
|
+
aspect = "contain",
|
|
99
104
|
}: MediaThumbnailProps) {
|
|
100
105
|
const { icon: iconColor, background: backgroundColor } = getThumbnailColors(
|
|
101
106
|
colorScheme,
|
|
@@ -117,18 +122,41 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
117
122
|
if (rendered) return rendered;
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
if (url) {
|
|
126
|
+
// const renderUrl = new URL(`http://localhost:8080/render`);
|
|
127
|
+
const renderUrl = new URL(`https://image.noya.io/render`);
|
|
128
|
+
|
|
129
|
+
const imageDimensions = dimensions ?? {
|
|
130
|
+
width: imageSizeMap[size],
|
|
131
|
+
height: imageSizeMap[size],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if (imageDimensions.width) {
|
|
135
|
+
renderUrl.searchParams.set("w", imageDimensions.width?.toString());
|
|
136
|
+
}
|
|
137
|
+
if (imageDimensions.height) {
|
|
138
|
+
renderUrl.searchParams.set("h", imageDimensions.height?.toString());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
renderUrl.searchParams.set("url", url);
|
|
142
|
+
renderUrl.searchParams.set("fit", aspect);
|
|
143
|
+
renderUrl.searchParams.set("theme", "light");
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<img
|
|
147
|
+
src={renderUrl.toString()}
|
|
148
|
+
alt=""
|
|
149
|
+
className="n-object-contain n-w-full n-h-full"
|
|
150
|
+
tabIndex={-1}
|
|
151
|
+
width={imageDimensions?.width}
|
|
152
|
+
height={imageDimensions?.height}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
120
157
|
switch (contentType) {
|
|
121
158
|
case "image":
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<img
|
|
126
|
-
src={url}
|
|
127
|
-
alt=""
|
|
128
|
-
className="object-contain w-full h-full"
|
|
129
|
-
tabIndex={-1}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
159
|
+
return <ImageIcon color={iconColor} style={iconStyles} />;
|
|
132
160
|
case "video":
|
|
133
161
|
return <VideoIcon color={iconColor} style={iconStyles} />;
|
|
134
162
|
case "audio":
|
|
@@ -151,7 +179,7 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
151
179
|
<img
|
|
152
180
|
src={`https://api.iconify.design/catppuccin/${fileIcon}.svg?height=none&box=1`}
|
|
153
181
|
alt=""
|
|
154
|
-
className="w-4 h-4"
|
|
182
|
+
className="n-w-4 n-h-4"
|
|
155
183
|
/>
|
|
156
184
|
);
|
|
157
185
|
}
|
|
@@ -160,19 +188,22 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
160
188
|
return <FileIcon color={iconColor} style={iconStyles} />;
|
|
161
189
|
}
|
|
162
190
|
}, [
|
|
163
|
-
|
|
191
|
+
renderThumbnailIcon,
|
|
164
192
|
url,
|
|
193
|
+
contentType,
|
|
194
|
+
fileName,
|
|
195
|
+
dimensions,
|
|
196
|
+
size,
|
|
197
|
+
aspect,
|
|
165
198
|
iconColor,
|
|
166
199
|
iconStyles,
|
|
167
200
|
iconName,
|
|
168
|
-
renderThumbnailIcon,
|
|
169
|
-
fileName,
|
|
170
201
|
]);
|
|
171
202
|
|
|
172
203
|
return (
|
|
173
204
|
<div
|
|
174
205
|
className={cx(
|
|
175
|
-
"w-full h-full justify-center flex items-center aspect-square",
|
|
206
|
+
"n-w-full n-h-full n-justify-center n-flex n-items-center n-aspect-square",
|
|
176
207
|
className
|
|
177
208
|
)}
|
|
178
209
|
style={{ backgroundColor }}
|
|
@@ -192,6 +223,16 @@ const iconSizeMap: Record<CollectionItemSize, number | string> = {
|
|
|
192
223
|
xl: "max(15px, 20%)",
|
|
193
224
|
};
|
|
194
225
|
|
|
226
|
+
const imageSizeMap: Record<CollectionItemSize, number | string> = {
|
|
227
|
+
xxs: 12 * 2,
|
|
228
|
+
xs: 18 * 2,
|
|
229
|
+
small: 24 * 2,
|
|
230
|
+
medium: 36 * 2,
|
|
231
|
+
mediumLarge: 48 * 2,
|
|
232
|
+
large: 64 * 2,
|
|
233
|
+
xl: 96 * 2,
|
|
234
|
+
};
|
|
235
|
+
|
|
195
236
|
function findFileIcon(fileName: string) {
|
|
196
237
|
const fileIcon = fileNames[fileName];
|
|
197
238
|
if (fileIcon) return fileIcon;
|
|
@@ -36,8 +36,8 @@ export const Message = memo(
|
|
|
36
36
|
return (
|
|
37
37
|
<div
|
|
38
38
|
className={cx(
|
|
39
|
-
"flex gap-2",
|
|
40
|
-
role === "user" ? "flex-row-reverse" : "flex-row"
|
|
39
|
+
"n-flex n-gap-2",
|
|
40
|
+
role === "user" ? "n-flex-row-reverse" : "n-flex-row"
|
|
41
41
|
)}
|
|
42
42
|
>
|
|
43
43
|
{role === "assistant" ? (
|
|
@@ -59,9 +59,9 @@ export const Message = memo(
|
|
|
59
59
|
<div
|
|
60
60
|
className={cx(
|
|
61
61
|
"markdown-message",
|
|
62
|
-
"flex flex-row gap-2 rounded-lg py-2 px-3 transition-all duration-200 max-w-[70%] text-sm",
|
|
63
|
-
role === "user" ? "text-white" : "text-text",
|
|
64
|
-
role === "user" ? "ml-auto" : "bg-input-background"
|
|
62
|
+
"n-flex n-flex-row n-gap-2 n-rounded-lg n-py-2 n-px-3 n-transition-all n-duration-200 n-max-w-[70%] n-text-sm",
|
|
63
|
+
role === "user" ? "n-text-white" : "n-text-text",
|
|
64
|
+
role === "user" ? "n-ml-auto" : "n-bg-input-background"
|
|
65
65
|
)}
|
|
66
66
|
style={styles}
|
|
67
67
|
ref={ref}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
Children,
|
|
5
|
+
ReactNode,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
StackItem,
|
|
15
|
+
StackNavigator,
|
|
16
|
+
StackNavigatorProps,
|
|
17
|
+
} from "./StackNavigator";
|
|
18
|
+
|
|
19
|
+
export type NavigatorParams = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export type NavigatorRoute<Params extends NavigatorParams | null> = {
|
|
22
|
+
key: string;
|
|
23
|
+
name: string;
|
|
24
|
+
params: Params;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type NavigatorRoutesConfig<
|
|
28
|
+
RoutesConfig extends Record<string, NavigatorParams | null>,
|
|
29
|
+
> = {
|
|
30
|
+
[K in keyof RoutesConfig]: NavigatorRoute<RoutesConfig[K]>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type NavigatorPushFunction<
|
|
34
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
35
|
+
> = <K extends keyof Routes>(
|
|
36
|
+
...args: Routes[K]["params"] extends null
|
|
37
|
+
? [name: K]
|
|
38
|
+
: [name: K, params: Routes[K]["params"]]
|
|
39
|
+
) => void;
|
|
40
|
+
|
|
41
|
+
export type NavigatorReplaceFunction<
|
|
42
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
43
|
+
> = <K extends keyof Routes>(
|
|
44
|
+
...args: Routes[K]["params"] extends null
|
|
45
|
+
? [name: K]
|
|
46
|
+
: [name: K, params: Routes[K]["params"]]
|
|
47
|
+
) => void;
|
|
48
|
+
|
|
49
|
+
export type NavigatorNavigation<
|
|
50
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
51
|
+
> = {
|
|
52
|
+
push: NavigatorPushFunction<Routes>;
|
|
53
|
+
replace: NavigatorReplaceFunction<Routes>;
|
|
54
|
+
pop: () => void;
|
|
55
|
+
canGoBack: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type NavigatorRenderContext<
|
|
59
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
60
|
+
Name extends keyof Routes,
|
|
61
|
+
> = {
|
|
62
|
+
route: NavigatorRoute<Routes[Name]["params"]>;
|
|
63
|
+
navigation: NavigatorNavigation<Routes>;
|
|
64
|
+
stack: NavigatorStackEntry<Routes>[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type NavigatorHeaderProps<
|
|
68
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
69
|
+
Name extends keyof Routes,
|
|
70
|
+
> = NavigatorRenderContext<Routes, Name> & {
|
|
71
|
+
title: React.ReactNode;
|
|
72
|
+
right: React.ReactNode;
|
|
73
|
+
showBackButton: boolean;
|
|
74
|
+
onBack?: () => void;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type NavigatorRenderHeader<
|
|
78
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
79
|
+
Name extends keyof Routes,
|
|
80
|
+
> =
|
|
81
|
+
| React.ReactNode
|
|
82
|
+
| ((context: NavigatorHeaderProps<Routes, Name>) => React.ReactNode);
|
|
83
|
+
|
|
84
|
+
export type NavigatorScreenProps<
|
|
85
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
86
|
+
Name extends keyof Routes,
|
|
87
|
+
> = {
|
|
88
|
+
name: Name;
|
|
89
|
+
title:
|
|
90
|
+
| React.ReactNode
|
|
91
|
+
| ((context: NavigatorRenderContext<Routes, Name>) => React.ReactNode);
|
|
92
|
+
right?:
|
|
93
|
+
| React.ReactNode
|
|
94
|
+
| ((context: NavigatorRenderContext<Routes, Name>) => React.ReactNode);
|
|
95
|
+
showBackButton?:
|
|
96
|
+
| boolean
|
|
97
|
+
| ((context: NavigatorRenderContext<Routes, Name>) => boolean);
|
|
98
|
+
renderHeader?: NavigatorRenderHeader<Routes, Name>;
|
|
99
|
+
children:
|
|
100
|
+
| React.ReactNode
|
|
101
|
+
| ((context: NavigatorRenderContext<Routes, Name>) => React.ReactNode);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const navigatorScreenIdentifier = Symbol("NavigatorScreen");
|
|
105
|
+
|
|
106
|
+
const NavigatorScreen = Object.assign(
|
|
107
|
+
<
|
|
108
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
109
|
+
Name extends keyof Routes,
|
|
110
|
+
>(
|
|
111
|
+
_props: NavigatorScreenProps<Routes, Name>
|
|
112
|
+
) => null,
|
|
113
|
+
{
|
|
114
|
+
[navigatorScreenIdentifier]: true as const,
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
type NavigatorScreenConfig<
|
|
119
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
120
|
+
Name extends keyof Routes,
|
|
121
|
+
> = NavigatorScreenProps<Routes, Name> & {
|
|
122
|
+
name: Name;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type NavigatorStackEntry<
|
|
126
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
127
|
+
> = {
|
|
128
|
+
key: string;
|
|
129
|
+
name: keyof Routes;
|
|
130
|
+
params: Routes[keyof Routes]["params"];
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type NavigatorProps<
|
|
134
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
135
|
+
Name extends keyof Routes,
|
|
136
|
+
> = {
|
|
137
|
+
initialRoute: Routes[Name]["params"] extends null
|
|
138
|
+
? Name | { name: Name }
|
|
139
|
+
: { name: Name; params: Routes[Name]["params"] };
|
|
140
|
+
children: React.ReactNode;
|
|
141
|
+
showHeader?:
|
|
142
|
+
| boolean
|
|
143
|
+
| ((context: { stack: NavigatorStackEntry<Routes>[] }) => boolean);
|
|
144
|
+
renderHeader?: NavigatorRenderHeader<Routes, any>;
|
|
145
|
+
} & Pick<
|
|
146
|
+
StackNavigatorProps,
|
|
147
|
+
"className" | "style" | "contentClassName" | "cardStyle" | "cardClassName"
|
|
148
|
+
>;
|
|
149
|
+
|
|
150
|
+
type NavigatorScreenContextValue<
|
|
151
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
152
|
+
Name extends keyof Routes,
|
|
153
|
+
> = NavigatorRenderContext<Routes, Name>;
|
|
154
|
+
|
|
155
|
+
const NavigatorScreenContext = React.createContext<NavigatorScreenContextValue<
|
|
156
|
+
any,
|
|
157
|
+
any
|
|
158
|
+
> | null>(null);
|
|
159
|
+
|
|
160
|
+
export function useNavigator<
|
|
161
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
162
|
+
>(): NavigatorNavigation<Routes> {
|
|
163
|
+
const context = React.useContext(NavigatorScreenContext);
|
|
164
|
+
|
|
165
|
+
if (!context) {
|
|
166
|
+
throw new Error("useNavigator must be used within a Navigator screen");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return context.navigation;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function useNavigatorRoute<
|
|
173
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
174
|
+
Name extends keyof Routes,
|
|
175
|
+
>(): NavigatorRoute<Routes[Name]["params"]> {
|
|
176
|
+
const context = React.useContext(NavigatorScreenContext);
|
|
177
|
+
|
|
178
|
+
if (!context) {
|
|
179
|
+
throw new Error("useNavigatorRoute must be used within a Navigator screen");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return context.route;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isNavigatorScreenElement(
|
|
186
|
+
child: ReactNode
|
|
187
|
+
): child is React.ReactElement<NavigatorScreenProps<any, any>> {
|
|
188
|
+
return (
|
|
189
|
+
isValidElement(child) &&
|
|
190
|
+
Boolean(
|
|
191
|
+
(child.type as { [navigatorScreenIdentifier]?: boolean })?.[
|
|
192
|
+
navigatorScreenIdentifier
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function evaluateOption<T, C>(
|
|
199
|
+
option: T | ((context: C) => T) | undefined,
|
|
200
|
+
context: C
|
|
201
|
+
): T | undefined {
|
|
202
|
+
if (option instanceof Function) {
|
|
203
|
+
return option(context);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return option;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function NavigatorImpl<
|
|
210
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
211
|
+
Name extends keyof Routes,
|
|
212
|
+
>({
|
|
213
|
+
initialRoute,
|
|
214
|
+
children,
|
|
215
|
+
className,
|
|
216
|
+
style,
|
|
217
|
+
contentClassName,
|
|
218
|
+
cardStyle,
|
|
219
|
+
cardClassName,
|
|
220
|
+
showHeader,
|
|
221
|
+
renderHeader,
|
|
222
|
+
}: NavigatorProps<Routes, Name>) {
|
|
223
|
+
const screens = useMemo<NavigatorScreenConfig<Routes, any>[]>(() => {
|
|
224
|
+
const configs: NavigatorScreenConfig<Routes, any>[] = [];
|
|
225
|
+
|
|
226
|
+
Children.forEach(children, (child) => {
|
|
227
|
+
if (!isNavigatorScreenElement(child)) return;
|
|
228
|
+
|
|
229
|
+
configs.push(child.props as NavigatorScreenConfig<Routes, any>);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return configs;
|
|
233
|
+
}, [children]);
|
|
234
|
+
|
|
235
|
+
const screenMap = useMemo(() => {
|
|
236
|
+
const map = new Map<keyof Routes, NavigatorScreenConfig<Routes, any>>();
|
|
237
|
+
|
|
238
|
+
for (const screen of screens) {
|
|
239
|
+
if (map.has(screen.name)) {
|
|
240
|
+
console.warn(
|
|
241
|
+
`Navigator: duplicate screen name "${String(screen.name)}" detected. The latter definition will be used.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
map.set(screen.name, screen);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return map;
|
|
249
|
+
}, [screens]);
|
|
250
|
+
|
|
251
|
+
const keyRef = useRef(0);
|
|
252
|
+
const createEntry = useCallback(
|
|
253
|
+
<K extends keyof Routes>(
|
|
254
|
+
name: K,
|
|
255
|
+
params: Routes[K]["params"]
|
|
256
|
+
): NavigatorStackEntry<Routes> => ({
|
|
257
|
+
key: `${String(name)}-${keyRef.current++}`,
|
|
258
|
+
name,
|
|
259
|
+
params,
|
|
260
|
+
}),
|
|
261
|
+
[]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const initialScreenName =
|
|
265
|
+
typeof initialRoute === "string"
|
|
266
|
+
? initialRoute
|
|
267
|
+
: (initialRoute as { name: Name }).name;
|
|
268
|
+
|
|
269
|
+
if (!screenMap.has(initialScreenName)) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Navigator: initial route "${String(initialScreenName)}" is not registered as a screen.`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const [stack, setStack] = useState<NavigatorStackEntry<Routes>[]>(() => [
|
|
276
|
+
createEntry(
|
|
277
|
+
initialScreenName,
|
|
278
|
+
(initialRoute as { params: Routes[Name]["params"] }).params
|
|
279
|
+
),
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
const push = useCallback(
|
|
283
|
+
<K extends keyof Routes>(
|
|
284
|
+
...args: Routes[K]["params"] extends null
|
|
285
|
+
? [name: K]
|
|
286
|
+
: [name: K, params: Routes[K]["params"]]
|
|
287
|
+
) => {
|
|
288
|
+
const [name, params] = args;
|
|
289
|
+
|
|
290
|
+
const screen = screenMap.get(name);
|
|
291
|
+
|
|
292
|
+
if (!screen) {
|
|
293
|
+
console.warn(
|
|
294
|
+
`Navigator: attempted to push unknown screen "${String(name)}".`
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
setStack((prev) => [...prev, createEntry(name, params)]);
|
|
300
|
+
},
|
|
301
|
+
[createEntry, screenMap]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const pop = useCallback(() => {
|
|
305
|
+
setStack((prev) => {
|
|
306
|
+
if (prev.length <= 1) return prev;
|
|
307
|
+
|
|
308
|
+
const nextStack = prev.slice(0, -1);
|
|
309
|
+
|
|
310
|
+
if (nextStack.length === 0) {
|
|
311
|
+
return nextStack;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const updatedStack = [...nextStack];
|
|
315
|
+
const topEntry = updatedStack[updatedStack.length - 1];
|
|
316
|
+
|
|
317
|
+
updatedStack[updatedStack.length - 1] = createEntry(
|
|
318
|
+
topEntry.name as keyof Routes,
|
|
319
|
+
topEntry.params as Routes[keyof Routes]["params"]
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return updatedStack;
|
|
323
|
+
});
|
|
324
|
+
}, [createEntry]);
|
|
325
|
+
|
|
326
|
+
const replace = useCallback(
|
|
327
|
+
<K extends keyof Routes>(
|
|
328
|
+
targetKey: string,
|
|
329
|
+
name: K,
|
|
330
|
+
params: Routes[K]["params"] | null
|
|
331
|
+
) => {
|
|
332
|
+
const screen = screenMap.get(name);
|
|
333
|
+
|
|
334
|
+
if (!screen) {
|
|
335
|
+
console.warn(
|
|
336
|
+
`Navigator: attempted to replace with unknown screen "${String(name)}".`
|
|
337
|
+
);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
setStack((prev) => {
|
|
342
|
+
const index = prev.findIndex((entry) => entry.key === targetKey);
|
|
343
|
+
|
|
344
|
+
if (index === -1) return prev;
|
|
345
|
+
|
|
346
|
+
const next = [...prev];
|
|
347
|
+
next[index] = createEntry(name, params as Routes[K]["params"]);
|
|
348
|
+
|
|
349
|
+
if (next.length > 0) {
|
|
350
|
+
const top = next[next.length - 1];
|
|
351
|
+
next[next.length - 1] = createEntry(
|
|
352
|
+
top.name as keyof Routes,
|
|
353
|
+
top.params as Routes[keyof Routes]["params"]
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return next;
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
[createEntry, screenMap]
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const items = useMemo<StackItem[]>(() => {
|
|
364
|
+
return stack.flatMap((entry) => {
|
|
365
|
+
const screen = screenMap.get(entry.name);
|
|
366
|
+
|
|
367
|
+
if (!screen) return [];
|
|
368
|
+
|
|
369
|
+
const canGoBack = stack.length > 1;
|
|
370
|
+
|
|
371
|
+
const route: NavigatorRoute<Routes[keyof Routes]["params"]> = {
|
|
372
|
+
key: entry.key,
|
|
373
|
+
name: String(entry.name),
|
|
374
|
+
params: (entry.params ?? null) as Routes[keyof Routes]["params"],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const navigation: NavigatorNavigation<Routes> = {
|
|
378
|
+
push,
|
|
379
|
+
replace: (...args) => replace(entry.key, args[0], args[1]),
|
|
380
|
+
pop,
|
|
381
|
+
canGoBack,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const context: NavigatorRenderContext<Routes, any> = {
|
|
385
|
+
route,
|
|
386
|
+
navigation,
|
|
387
|
+
stack,
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const title = evaluateOption(screen.title, context);
|
|
391
|
+
const right = evaluateOption(screen.right, context);
|
|
392
|
+
const showBackButton = evaluateOption(screen.showBackButton, context);
|
|
393
|
+
const headerOption = screen.renderHeader ?? renderHeader;
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
key: entry.key,
|
|
397
|
+
title,
|
|
398
|
+
right,
|
|
399
|
+
showBackButton,
|
|
400
|
+
renderHeader:
|
|
401
|
+
headerOption === undefined
|
|
402
|
+
? undefined
|
|
403
|
+
: (headerProps) =>
|
|
404
|
+
evaluateOption(headerOption, {
|
|
405
|
+
...context,
|
|
406
|
+
title,
|
|
407
|
+
right,
|
|
408
|
+
showBackButton: headerProps.showBackButton,
|
|
409
|
+
onBack: headerProps.onBack,
|
|
410
|
+
}),
|
|
411
|
+
renderContent: () => {
|
|
412
|
+
return (
|
|
413
|
+
<NavigatorScreenContext.Provider value={context}>
|
|
414
|
+
{evaluateOption(screen.children, context)}
|
|
415
|
+
</NavigatorScreenContext.Provider>
|
|
416
|
+
);
|
|
417
|
+
},
|
|
418
|
+
} satisfies StackItem;
|
|
419
|
+
});
|
|
420
|
+
}, [pop, replace, screenMap, stack, push, renderHeader]);
|
|
421
|
+
|
|
422
|
+
const shouldShowHeader = evaluateOption(showHeader, {
|
|
423
|
+
stack,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<StackNavigator
|
|
428
|
+
items={items}
|
|
429
|
+
onBack={pop}
|
|
430
|
+
className={className}
|
|
431
|
+
style={style}
|
|
432
|
+
contentClassName={contentClassName}
|
|
433
|
+
cardStyle={cardStyle}
|
|
434
|
+
cardClassName={cardClassName}
|
|
435
|
+
showHeader={shouldShowHeader}
|
|
436
|
+
/>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export const Navigator = Object.assign(NavigatorImpl, {
|
|
441
|
+
Screen: NavigatorScreen,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
export function createNavigator<
|
|
445
|
+
RoutesConfig extends Record<string, NavigatorParams | null>,
|
|
446
|
+
>() {
|
|
447
|
+
return Object.assign(
|
|
448
|
+
NavigatorImpl as <Name extends keyof RoutesConfig>(
|
|
449
|
+
props: NavigatorProps<NavigatorRoutesConfig<RoutesConfig>, Name>
|
|
450
|
+
) => ReactNode,
|
|
451
|
+
{
|
|
452
|
+
Screen: NavigatorScreen as <Name extends keyof RoutesConfig>(
|
|
453
|
+
props: NavigatorScreenProps<NavigatorRoutesConfig<RoutesConfig>, Name>
|
|
454
|
+
) => ReactNode,
|
|
455
|
+
useNavigator: useNavigator<NavigatorRoutesConfig<RoutesConfig>>,
|
|
456
|
+
useNavigatorRoute: useNavigatorRoute as <Name extends keyof RoutesConfig>(
|
|
457
|
+
name: Name
|
|
458
|
+
) => NavigatorRoute<NavigatorRoutesConfig<RoutesConfig>[Name]["params"]>,
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
}
|
|
@@ -14,7 +14,7 @@ export const Logo = forwardRef(function Logo(
|
|
|
14
14
|
fill="none"
|
|
15
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
16
16
|
{...props}
|
|
17
|
-
className="w-6 aspect-[24/26]"
|
|
17
|
+
className="n-w-6 n-aspect-[24/26]"
|
|
18
18
|
>
|
|
19
19
|
<path
|
|
20
20
|
d="M6.5 3H2V12H5V6H6.5C7.32843 6 8 6.67157 8 7.5V12H11V7.5C11 5.01472 8.98528 3 6.5 3Z"
|
|
@@ -46,13 +46,15 @@ interface Props
|
|
|
46
46
|
onClickClose?: () => void;
|
|
47
47
|
showArrow?: boolean;
|
|
48
48
|
className?: string;
|
|
49
|
+
style?: React.CSSProperties;
|
|
49
50
|
portalContainer?: HTMLElement | null;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export const popoverStyle = {
|
|
53
|
-
base:
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
base:
|
|
55
|
+
"n-rounded n-font-[14px] n-bg-popover-background n-shadow-popover n-max-h-[600px] n-overflow-y-auto n-text-text-muted n-z-[1000]",
|
|
56
|
+
large: "n-w-[680px]",
|
|
57
|
+
normal: "n-w-[240px]",
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
export function Popover({
|
|
@@ -73,6 +75,7 @@ export function Popover({
|
|
|
73
75
|
onClickClose,
|
|
74
76
|
className,
|
|
75
77
|
portalContainer,
|
|
78
|
+
style,
|
|
76
79
|
}: Props) {
|
|
77
80
|
const defaultId = useId();
|
|
78
81
|
const portalScopeId = usePortalScopeId();
|
|
@@ -103,6 +106,7 @@ export function Popover({
|
|
|
103
106
|
<PopoverPrimitive.Content
|
|
104
107
|
{...portalScopeProps(portalScopeId)}
|
|
105
108
|
ref={contentRef}
|
|
109
|
+
style={style}
|
|
106
110
|
className={cx(
|
|
107
111
|
popoverStyle.base,
|
|
108
112
|
variant === "large" && popoverStyle.large,
|
|
@@ -140,11 +144,11 @@ export function Popover({
|
|
|
140
144
|
>
|
|
141
145
|
{typeof children === "function" ? children({ close }) : children}
|
|
142
146
|
{showArrow && (
|
|
143
|
-
<PopoverPrimitive.Arrow className="fill-popover-background" />
|
|
147
|
+
<PopoverPrimitive.Arrow className="n-fill-popover-background" />
|
|
144
148
|
)}
|
|
145
149
|
{closable && (
|
|
146
150
|
<PopoverPrimitive.Close
|
|
147
|
-
className="rounded-full h-[25px] w-[25px] inline-flex items-center justify-center absolute top-[5px] right-[5px]"
|
|
151
|
+
className="n-rounded-full n-h-[25px] n-w-[25px] n-inline-flex n-items-center n-justify-center n-absolute n-top-[5px] n-right-[5px]"
|
|
148
152
|
style={closeStyles}
|
|
149
153
|
>
|
|
150
154
|
<IconButton iconName="Cross2Icon" onClick={onClickClose} />
|