@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
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Size } from "@noya-app/noya-geometry";
|
|
4
|
+
import { isDeepEqual } from "@noya-app/noya-utils";
|
|
5
|
+
import { useSize } from "@noya-app/react-utils";
|
|
6
|
+
import React, {
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { cx } from "../utils/classNames";
|
|
14
|
+
import {
|
|
15
|
+
AnimatePresence,
|
|
16
|
+
AnimatePresenceChildState,
|
|
17
|
+
AnimatePresenceProps,
|
|
18
|
+
} from "./AnimatePresence";
|
|
19
|
+
import { BaseToolbar } from "./BaseToolbar";
|
|
20
|
+
import { Button } from "./Button";
|
|
21
|
+
import { Body } from "./Text";
|
|
22
|
+
|
|
23
|
+
type StackRenderContextValue = Record<string, () => React.ReactNode>;
|
|
24
|
+
|
|
25
|
+
const StackRenderContext = React.createContext<StackRenderContextValue>({});
|
|
26
|
+
|
|
27
|
+
export type StackItem = {
|
|
28
|
+
key: string;
|
|
29
|
+
title?: React.ReactNode;
|
|
30
|
+
right?: React.ReactNode;
|
|
31
|
+
renderContent: () => React.ReactNode;
|
|
32
|
+
/** Force hide/show back button for this item */
|
|
33
|
+
showBackButton?: boolean;
|
|
34
|
+
renderHeader?: StackNavigatorRenderHeader;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type StackNavigatorHeaderProps = {
|
|
38
|
+
onBack?: () => void;
|
|
39
|
+
showBackButton: boolean;
|
|
40
|
+
showDivider: boolean;
|
|
41
|
+
right?: React.ReactNode;
|
|
42
|
+
title?: React.ReactNode;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type StackNavigatorRenderHeader =
|
|
46
|
+
| React.ReactNode
|
|
47
|
+
| ((props: StackNavigatorHeaderProps) => React.ReactNode);
|
|
48
|
+
|
|
49
|
+
export type StackNavigatorProps = {
|
|
50
|
+
/** Items ordered from root to top. The last item is shown. */
|
|
51
|
+
items: StackItem[];
|
|
52
|
+
/** Called when the back button is pressed */
|
|
53
|
+
onBack?: () => void;
|
|
54
|
+
className?: string;
|
|
55
|
+
style?: React.CSSProperties;
|
|
56
|
+
contentClassName?: string;
|
|
57
|
+
/** Whether to show the header. @default true */
|
|
58
|
+
showHeader?: boolean;
|
|
59
|
+
showDivider?: boolean;
|
|
60
|
+
cardStyle?: React.CSSProperties;
|
|
61
|
+
cardClassName?: string;
|
|
62
|
+
renderHeader?: StackNavigatorRenderHeader;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function StackNavigator({
|
|
66
|
+
items,
|
|
67
|
+
onBack,
|
|
68
|
+
className,
|
|
69
|
+
style,
|
|
70
|
+
contentClassName,
|
|
71
|
+
cardStyle,
|
|
72
|
+
cardClassName,
|
|
73
|
+
showDivider = true,
|
|
74
|
+
showHeader = true,
|
|
75
|
+
renderHeader,
|
|
76
|
+
}: StackNavigatorProps) {
|
|
77
|
+
const top = items.at(-1);
|
|
78
|
+
|
|
79
|
+
const previousLengthRef = useRef<number>(items.length);
|
|
80
|
+
const direction = useMemo<"forward" | "back">(() => {
|
|
81
|
+
const previous = previousLengthRef.current;
|
|
82
|
+
previousLengthRef.current = items.length;
|
|
83
|
+
return items.length >= previous ? "forward" : "back";
|
|
84
|
+
}, [items.length]);
|
|
85
|
+
|
|
86
|
+
const [cardSizes, setCardSizes] = useState<Record<string, Size>>({});
|
|
87
|
+
const [containerSize, setContainerSize] = useState<Size | undefined>();
|
|
88
|
+
|
|
89
|
+
const handleSizeChange = useCallback((key: string, size: Size) => {
|
|
90
|
+
setCardSizes((prev) => {
|
|
91
|
+
if (isDeepEqual(prev[key], size)) {
|
|
92
|
+
return prev;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { ...prev, [key]: size };
|
|
96
|
+
});
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!top) {
|
|
101
|
+
setContainerSize(undefined);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const measuredSize = cardSizes[top.key];
|
|
106
|
+
|
|
107
|
+
if (!measuredSize) return;
|
|
108
|
+
|
|
109
|
+
setContainerSize((prev) => {
|
|
110
|
+
if (prev && isDeepEqual(prev, measuredSize)) {
|
|
111
|
+
return prev;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return measuredSize;
|
|
115
|
+
});
|
|
116
|
+
}, [cardSizes, top]);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
setCardSizes((prev) => {
|
|
120
|
+
const next: Record<string, Size> = {};
|
|
121
|
+
let changed = false;
|
|
122
|
+
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
const size = prev[item.key];
|
|
125
|
+
if (size) {
|
|
126
|
+
next[item.key] = size;
|
|
127
|
+
} else {
|
|
128
|
+
changed = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (Object.keys(prev).length !== Object.keys(next).length) {
|
|
133
|
+
changed = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return changed ? next : prev;
|
|
137
|
+
});
|
|
138
|
+
}, [items]);
|
|
139
|
+
|
|
140
|
+
const animationStyles =
|
|
141
|
+
useMemo((): AnimatePresenceProps["animationStyles"] => {
|
|
142
|
+
return {
|
|
143
|
+
entering: {
|
|
144
|
+
opacity: 0,
|
|
145
|
+
transform:
|
|
146
|
+
direction === "forward" ? `translateX(20px)` : `translateX(-20px)`,
|
|
147
|
+
transition: `opacity 200ms ease, transform 200ms ease`,
|
|
148
|
+
},
|
|
149
|
+
entered: {
|
|
150
|
+
opacity: 1,
|
|
151
|
+
transform: "translateX(0px)",
|
|
152
|
+
transition: `opacity 200ms ease, transform 200ms ease`,
|
|
153
|
+
},
|
|
154
|
+
exiting: {
|
|
155
|
+
opacity: 0,
|
|
156
|
+
transform:
|
|
157
|
+
direction === "forward" ? `translateX(-20px)` : `translateX(20px)`,
|
|
158
|
+
transition: `opacity 200ms ease, transform 200ms ease`,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}, [direction]);
|
|
162
|
+
|
|
163
|
+
const shouldAnimateSize = containerSize !== undefined;
|
|
164
|
+
|
|
165
|
+
const contentStyle = useMemo(() => {
|
|
166
|
+
if (!containerSize) return undefined;
|
|
167
|
+
|
|
168
|
+
const style: React.CSSProperties = {
|
|
169
|
+
// width: containerSize.width,
|
|
170
|
+
height: containerSize.height,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (shouldAnimateSize) {
|
|
174
|
+
style.transition = `height 200ms ease, width 200ms ease`;
|
|
175
|
+
} else {
|
|
176
|
+
style.transition = "none";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return style;
|
|
180
|
+
}, [containerSize, shouldAnimateSize]);
|
|
181
|
+
|
|
182
|
+
const [animationStates, setAnimationStates] = useState<
|
|
183
|
+
Record<string, AnimatePresenceChildState>
|
|
184
|
+
>({});
|
|
185
|
+
|
|
186
|
+
const [renderContentCache, setRenderContentCache] =
|
|
187
|
+
useState<StackRenderContextValue>(() =>
|
|
188
|
+
Object.fromEntries(items.map((item) => [item.key, item.renderContent]))
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
setRenderContentCache((prev) => {
|
|
193
|
+
let changed = false;
|
|
194
|
+
const next: StackRenderContextValue = { ...prev };
|
|
195
|
+
|
|
196
|
+
for (const item of items) {
|
|
197
|
+
const state = animationStates[item.key];
|
|
198
|
+
const isFrozen = state && state !== "entered";
|
|
199
|
+
const existing = next[item.key];
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
existing === undefined ||
|
|
203
|
+
(!isFrozen && existing !== item.renderContent)
|
|
204
|
+
) {
|
|
205
|
+
next[item.key] = item.renderContent;
|
|
206
|
+
if (existing !== item.renderContent) {
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return changed ? next : prev;
|
|
213
|
+
});
|
|
214
|
+
}, [animationStates, items]);
|
|
215
|
+
|
|
216
|
+
const handleChildStateChange = useCallback(
|
|
217
|
+
(key: string, state: AnimatePresenceChildState) => {
|
|
218
|
+
if (!key) return;
|
|
219
|
+
|
|
220
|
+
setAnimationStates((prev) => {
|
|
221
|
+
if (state === "exited") {
|
|
222
|
+
if (!(key in prev)) return prev;
|
|
223
|
+
const { [key]: _removed, ...rest } = prev;
|
|
224
|
+
return rest;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (prev[key] === state) return prev;
|
|
228
|
+
return { ...prev, [key]: state };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (state === "exited") {
|
|
232
|
+
setRenderContentCache((prev) => {
|
|
233
|
+
if (!(key in prev)) return prev;
|
|
234
|
+
const next = { ...prev } as StackRenderContextValue;
|
|
235
|
+
delete next[key];
|
|
236
|
+
return next;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
[]
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (!top) return null;
|
|
244
|
+
|
|
245
|
+
const canGoBack = items.length > 1;
|
|
246
|
+
const showBackButton = (top.showBackButton ?? true) && canGoBack;
|
|
247
|
+
|
|
248
|
+
const headerRenderer = top.renderHeader ?? renderHeader;
|
|
249
|
+
|
|
250
|
+
const renderHeaderContent = () => {
|
|
251
|
+
const headerProps: StackNavigatorHeaderProps = {
|
|
252
|
+
onBack,
|
|
253
|
+
showBackButton,
|
|
254
|
+
showDivider,
|
|
255
|
+
right: top.right,
|
|
256
|
+
title: top.title,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (headerRenderer !== undefined) {
|
|
260
|
+
if (typeof headerRenderer === "function") {
|
|
261
|
+
return headerRenderer(headerProps);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return headerRenderer;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return defaultRenderHeader(headerProps);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div
|
|
272
|
+
className={cx("n-flex n-flex-col", className)}
|
|
273
|
+
style={style}
|
|
274
|
+
onClick={(e) => {
|
|
275
|
+
e.stopPropagation();
|
|
276
|
+
// e.preventDefault();
|
|
277
|
+
}}
|
|
278
|
+
onMouseDown={(e) => {
|
|
279
|
+
e.stopPropagation();
|
|
280
|
+
// e.preventDefault();
|
|
281
|
+
}}
|
|
282
|
+
onPointerDown={(e) => {
|
|
283
|
+
e.stopPropagation();
|
|
284
|
+
// e.preventDefault();
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
{showHeader && renderHeaderContent()}
|
|
288
|
+
<div
|
|
289
|
+
className={cx("n-relative n-overflow-hidden", contentClassName)}
|
|
290
|
+
style={contentStyle}
|
|
291
|
+
>
|
|
292
|
+
{/* Cache renderContent so we can keep showing the last frame while cards animate */}
|
|
293
|
+
<StackRenderContext.Provider value={renderContentCache}>
|
|
294
|
+
<AnimatePresence
|
|
295
|
+
duration={200}
|
|
296
|
+
animationStyles={animationStyles}
|
|
297
|
+
skipInitialAnimation
|
|
298
|
+
mode="wait"
|
|
299
|
+
onChildStateChange={handleChildStateChange}
|
|
300
|
+
>
|
|
301
|
+
<StackNavigatorCard
|
|
302
|
+
key={top.key}
|
|
303
|
+
item={top}
|
|
304
|
+
onSizeChange={handleSizeChange}
|
|
305
|
+
style={cardStyle}
|
|
306
|
+
className={cardClassName}
|
|
307
|
+
/>
|
|
308
|
+
</AnimatePresence>
|
|
309
|
+
</StackRenderContext.Provider>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
type StackNavigatorCardProps = {
|
|
316
|
+
item: StackItem;
|
|
317
|
+
onSizeChange: (key: string, size: Size) => void;
|
|
318
|
+
style?: React.CSSProperties;
|
|
319
|
+
className?: string;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const StackNavigatorCard = ({
|
|
323
|
+
item,
|
|
324
|
+
onSizeChange,
|
|
325
|
+
style,
|
|
326
|
+
className,
|
|
327
|
+
}: StackNavigatorCardProps) => {
|
|
328
|
+
const ref = React.useRef<HTMLDivElement | null>(null);
|
|
329
|
+
const size = useSize(ref);
|
|
330
|
+
|
|
331
|
+
const renderContext = React.useContext(StackRenderContext);
|
|
332
|
+
const renderContent = renderContext?.[item.key] ?? item.renderContent;
|
|
333
|
+
|
|
334
|
+
React.useEffect(() => {
|
|
335
|
+
if (!size || size.width === 0 || size.height === 0) return;
|
|
336
|
+
|
|
337
|
+
onSizeChange(item.key, size);
|
|
338
|
+
}, [item.key, onSizeChange, size]);
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div ref={ref} className={cx("n-flex n-flex-col", className)} style={style}>
|
|
342
|
+
{renderContent?.()}
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
function defaultRenderHeader({
|
|
348
|
+
showDivider,
|
|
349
|
+
showBackButton,
|
|
350
|
+
onBack,
|
|
351
|
+
right,
|
|
352
|
+
title,
|
|
353
|
+
}: StackNavigatorHeaderProps) {
|
|
354
|
+
return (
|
|
355
|
+
<BaseToolbar
|
|
356
|
+
innerClassName="n-px-1"
|
|
357
|
+
showDivider={showDivider}
|
|
358
|
+
enableAbsoluteBreakpoint={false}
|
|
359
|
+
left={
|
|
360
|
+
showBackButton && (
|
|
361
|
+
<Button
|
|
362
|
+
className="-n-mx-2"
|
|
363
|
+
icon="ArrowLeftIcon"
|
|
364
|
+
onClick={onBack}
|
|
365
|
+
aria-label="Back"
|
|
366
|
+
/>
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
right={right}
|
|
370
|
+
>
|
|
371
|
+
{typeof title === "string" || typeof title === "number" ? (
|
|
372
|
+
<Body>{title}</Body>
|
|
373
|
+
) : (
|
|
374
|
+
title
|
|
375
|
+
)}
|
|
376
|
+
</BaseToolbar>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { clamp } from "@noya-app/noya-utils";
|
|
4
|
+
import React, { FocusEventHandler, memo, useCallback, useMemo } from "react";
|
|
5
|
+
import { useLabel } from "../hooks/useLabel";
|
|
6
|
+
import { cx } from "../utils/classNames";
|
|
7
|
+
import { Button } from "./Button";
|
|
8
|
+
import { InputField } from "./InputField";
|
|
9
|
+
|
|
10
|
+
type ColorScheme = "primary" | "secondary";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
id?: string;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
className?: string;
|
|
16
|
+
value: number;
|
|
17
|
+
onValueChange: (value: number) => void;
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
step?: number;
|
|
21
|
+
colorScheme?: ColorScheme;
|
|
22
|
+
onFocus?: FocusEventHandler;
|
|
23
|
+
onBlur?: FocusEventHandler;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const Stepper = memo(function Stepper({
|
|
28
|
+
style,
|
|
29
|
+
className,
|
|
30
|
+
value: valueProp,
|
|
31
|
+
onValueChange,
|
|
32
|
+
min,
|
|
33
|
+
max,
|
|
34
|
+
step = 1,
|
|
35
|
+
colorScheme,
|
|
36
|
+
onFocus,
|
|
37
|
+
onBlur,
|
|
38
|
+
readOnly = false,
|
|
39
|
+
...props
|
|
40
|
+
}: Props) {
|
|
41
|
+
const { fieldId: id } = useLabel({
|
|
42
|
+
fieldId: props.id,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const value = useMemo(() => {
|
|
46
|
+
return clamp(valueProp, min, max);
|
|
47
|
+
}, [valueProp, min, max]);
|
|
48
|
+
|
|
49
|
+
const handleValueChange = useCallback(
|
|
50
|
+
(value: number) => {
|
|
51
|
+
const newValue = clamp(value, min, max);
|
|
52
|
+
onValueChange(newValue);
|
|
53
|
+
},
|
|
54
|
+
[onValueChange, min, max]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
id={id}
|
|
60
|
+
className={cx(
|
|
61
|
+
"n-flex n-relative n-items-center n-select-none n-touch-none n-h-input-height n-rounded n-flex-grow n-max-h-input-height n-gap-1.5",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
style={style}
|
|
65
|
+
onFocus={onFocus}
|
|
66
|
+
onBlur={onBlur}
|
|
67
|
+
>
|
|
68
|
+
<Button
|
|
69
|
+
icon="MinusIcon"
|
|
70
|
+
disabled={readOnly}
|
|
71
|
+
onClick={() => handleValueChange(value - step)}
|
|
72
|
+
/>
|
|
73
|
+
<InputField.Root className="n-flex-1">
|
|
74
|
+
<InputField.NumberInput
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={handleValueChange}
|
|
77
|
+
onNudge={(amount) => handleValueChange(value + amount)}
|
|
78
|
+
readOnly={readOnly}
|
|
79
|
+
/>
|
|
80
|
+
</InputField.Root>
|
|
81
|
+
<Button
|
|
82
|
+
icon="PlusIcon"
|
|
83
|
+
disabled={readOnly}
|
|
84
|
+
onClick={() => handleValueChange(value + step)}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Switch as SwitchPrimitive } from "radix-ui";
|
|
2
4
|
import * as React from "react";
|
|
3
5
|
import { cx } from "../utils/classNames";
|
|
4
6
|
|
|
@@ -38,15 +40,15 @@ export const Switch = function Switch({
|
|
|
38
40
|
onChange(newValue);
|
|
39
41
|
}}
|
|
40
42
|
className={cx(
|
|
41
|
-
"all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer ring-offset-background [-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
|
|
42
|
-
colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
|
|
43
|
-
"focus:z-interactable",
|
|
43
|
+
"n-all-unset n-w-8 n-h-[19px] n-bg-active-background n-rounded-full n-relative n-cursor-pointer n-ring-offset-background n-[-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:n-ring-2 focus:n-ring-primary focus:n-ring-offset-[1px] n-outline-none data-[state=checked]:n-bg-primary n-transition-all",
|
|
44
|
+
colorScheme === "secondary" && "data-[state=checked]:n-bg-secondary",
|
|
45
|
+
"focus:n-z-interactable",
|
|
44
46
|
className
|
|
45
47
|
)}
|
|
46
48
|
onFocus={onFocus}
|
|
47
49
|
onBlur={onBlur}
|
|
48
50
|
>
|
|
49
|
-
<SwitchPrimitive.Thumb className="block w-[15px] h-[15px] bg-background rounded-full transition-transform translate-x-[2px] data-[state=checked]:translate-x-[15px]" />
|
|
51
|
+
<SwitchPrimitive.Thumb className="n-block n-w-[15px] n-h-[15px] n-bg-background n-rounded-full n-transition-transform n-translate-x-[2px] data-[state=checked]:n-translate-x-[15px]" />
|
|
50
52
|
</SwitchPrimitive.Root>
|
|
51
53
|
);
|
|
52
54
|
};
|
package/src/components/Tabs.tsx
CHANGED
|
@@ -11,6 +11,7 @@ export type TabsProps<T extends string = string> = {
|
|
|
11
11
|
onValueChange?: (value: T) => void;
|
|
12
12
|
items: MenuItem<T>[];
|
|
13
13
|
renderContent: ({ value }: { value: T }) => React.ReactNode;
|
|
14
|
+
variant?: "default" | "tabs" | "buttons";
|
|
14
15
|
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
15
16
|
|
|
16
17
|
export function Tabs<T extends string = string>({
|
|
@@ -18,6 +19,7 @@ export function Tabs<T extends string = string>({
|
|
|
18
19
|
onValueChange,
|
|
19
20
|
items,
|
|
20
21
|
renderContent,
|
|
22
|
+
variant = "tabs",
|
|
21
23
|
...props
|
|
22
24
|
}: TabsProps<T>) {
|
|
23
25
|
const selectableItems = items.filter(isSelectableMenuItem);
|
|
@@ -29,13 +31,13 @@ export function Tabs<T extends string = string>({
|
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
|
-
<div {...props} className={cx("flex flex-col", props.className)}>
|
|
33
|
-
<div className="flex flex-row px-3">
|
|
34
|
+
<div {...props} className={cx("n-flex n-flex-col", props.className)}>
|
|
35
|
+
<div className="n-flex n-flex-row n-px-3">
|
|
34
36
|
<SegmentedControl
|
|
35
|
-
className="flex-1"
|
|
37
|
+
className="n-flex-1"
|
|
36
38
|
value={activeTab}
|
|
37
39
|
onValueChange={setActiveTab}
|
|
38
|
-
variant=
|
|
40
|
+
variant={variant}
|
|
39
41
|
items={selectableItems}
|
|
40
42
|
/>
|
|
41
43
|
</div>
|
package/src/components/Text.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { ForwardedRef, forwardRef, ReactNode } from "react";
|
|
2
4
|
import { ThemeColor } from "../theme";
|
|
3
5
|
import { camelToKebabCase, KebabToCamelCase } from "../theme/themeUtils";
|
|
4
6
|
import { cx } from "../utils/classNames";
|
|
@@ -13,11 +15,12 @@ type Variant =
|
|
|
13
15
|
| "heading5"
|
|
14
16
|
| "body"
|
|
15
17
|
| "small"
|
|
18
|
+
| "xs"
|
|
16
19
|
| "button"
|
|
17
20
|
| "code"
|
|
18
21
|
| "label";
|
|
19
22
|
|
|
20
|
-
const elements: Record<Variant,
|
|
23
|
+
const elements: Record<Variant, React.HTMLElementType> = {
|
|
21
24
|
title: "h1",
|
|
22
25
|
subtitle: "h1",
|
|
23
26
|
heading1: "h1",
|
|
@@ -27,28 +30,30 @@ const elements: Record<Variant, keyof ReactHTML> = {
|
|
|
27
30
|
heading5: "h5",
|
|
28
31
|
body: "p",
|
|
29
32
|
small: "span",
|
|
33
|
+
xs: "span",
|
|
30
34
|
button: "span",
|
|
31
35
|
code: "code",
|
|
32
36
|
label: "span",
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
export const textStyles: Record<Variant, string> = {
|
|
36
|
-
title: "font-sans text-title font-bold",
|
|
37
|
-
subtitle: "font-sans text-subtitle font-medium",
|
|
38
|
-
heading1: "font-sans text-heading1 font-semibold",
|
|
39
|
-
heading2: "font-sans text-heading2 font-medium",
|
|
40
|
-
heading3: "font-sans text-heading3 font-normal",
|
|
41
|
-
heading4: "font-sans text-heading4 font-medium",
|
|
42
|
-
heading5: "font-sans text-heading5 font-medium",
|
|
43
|
-
body: "font-sans text-
|
|
44
|
-
small: "font-sans text-heading5 font-normal",
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
title: "n-font-sans n-text-title n-font-bold",
|
|
41
|
+
subtitle: "n-font-sans n-text-subtitle n-font-medium",
|
|
42
|
+
heading1: "n-font-sans n-text-heading1 n-font-semibold",
|
|
43
|
+
heading2: "n-font-sans n-text-heading2 n-font-medium",
|
|
44
|
+
heading3: "n-font-sans n-text-heading3 n-font-normal",
|
|
45
|
+
heading4: "n-font-sans n-text-heading4 n-font-medium",
|
|
46
|
+
heading5: "n-font-sans n-text-heading5 n-font-medium",
|
|
47
|
+
body: "n-font-sans n-text-body n-font-normal",
|
|
48
|
+
small: "n-font-sans n-text-heading5 n-font-normal",
|
|
49
|
+
xs: "n-font-sans n-text-xs n-font-normal",
|
|
50
|
+
button: "n-font-sans n-text-button n-font-medium",
|
|
51
|
+
code: "n-font-mono n-text-code",
|
|
52
|
+
label: "n-font-sans n-text-label n-font-normal n-uppercase",
|
|
48
53
|
} as const;
|
|
49
54
|
|
|
50
55
|
export type TextProps = {
|
|
51
|
-
as?:
|
|
56
|
+
as?: React.HTMLElementType;
|
|
52
57
|
href?: string;
|
|
53
58
|
className?: string;
|
|
54
59
|
style?: React.CSSProperties;
|
|
@@ -78,14 +83,14 @@ export const Text = forwardRef(function Text(
|
|
|
78
83
|
}: TextProps,
|
|
79
84
|
forwardedRef: ForwardedRef<HTMLElement>
|
|
80
85
|
) {
|
|
81
|
-
const Component = as ?? elements[variant] ?? "span";
|
|
86
|
+
const Component = (as ?? elements[variant] ?? "span") as "span";
|
|
82
87
|
|
|
83
88
|
return (
|
|
84
89
|
<Component
|
|
85
|
-
// @ts-expect-error expected to be many semantic html elements
|
|
86
90
|
ref={forwardedRef}
|
|
87
91
|
className={cx(
|
|
88
|
-
|
|
92
|
+
textStyles[variant],
|
|
93
|
+
`n-text-${camelToKebabCase(String(color)) ?? "text"}`,
|
|
89
94
|
className
|
|
90
95
|
)}
|
|
91
96
|
style={style}
|
|
@@ -144,6 +149,12 @@ export const Small = forwardRef(
|
|
|
144
149
|
)
|
|
145
150
|
);
|
|
146
151
|
|
|
152
|
+
export const ExtraSmall = forwardRef(
|
|
153
|
+
(props: PresetProps, ref: ForwardedRef<HTMLElement>) => (
|
|
154
|
+
<Text ref={ref} {...props} variant="xs" />
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
|
|
147
158
|
// export const Label = forwardRef(
|
|
148
159
|
// (props: PresetProps, ref: ForwardedRef<HTMLElement>) => (
|
|
149
160
|
// <Text ref={ref} {...props} variant="label" />
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { assignRef } from "@noya-app/react-utils";
|
|
2
4
|
import React, {
|
|
3
5
|
ComponentProps,
|
|
@@ -66,7 +68,7 @@ export const TextArea = memo(
|
|
|
66
68
|
return (
|
|
67
69
|
<textarea
|
|
68
70
|
className={cx(
|
|
69
|
-
`font-sans text-heading5 font-normal text-text bg-input-background flex-1 py-1 px-1.5 border-none outline-none min-h-6 w-full rounded resize-none placeholder:text-text-disabled focus:ring-2 focus:ring-primary read-only:text-text-disabled focus:z-interactable transition-
|
|
71
|
+
`n-font-sans n-text-heading5 n-font-normal n-text-text n-bg-input-background n-flex-1 n-py-1 n-px-1.5 n-border-none n-outline-none n-min-h-6 n-w-full n-rounded n-resize-none placeholder:n-text-text-disabled focus:n-ring-2 focus:n-ring-primary read-only:n-text-text-disabled focus:n-z-interactable n-transition-[box-shadow]`,
|
|
70
72
|
className
|
|
71
73
|
)}
|
|
72
74
|
{...rest}
|
|
@@ -100,10 +102,10 @@ export const TextAreaRow = memo(
|
|
|
100
102
|
return (
|
|
101
103
|
<div
|
|
102
104
|
ref={forwardedRef}
|
|
103
|
-
className={cx("relative w-full h-full", className)}
|
|
105
|
+
className={cx("n-relative n-w-full n-h-full", className)}
|
|
104
106
|
>
|
|
105
107
|
<TextArea className={textAreaClassName} {...rest} ref={textAreaRef} />
|
|
106
|
-
<div className={cx("absolute right-1 top-4", endClassName)}>{end}</div>
|
|
108
|
+
<div className={cx("n-absolute n-right-1 n-top-4", endClassName)}>{end}</div>
|
|
107
109
|
</div>
|
|
108
110
|
);
|
|
109
111
|
})
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Toast as ToastPrimitive } from "radix-ui";
|
|
2
2
|
import React, { ReactNode } from "react";
|
|
3
3
|
import { IconButton } from "./IconButton";
|
|
4
4
|
import { textStyles } from "./Text";
|
|
@@ -15,18 +15,18 @@ export const Toast = ({
|
|
|
15
15
|
}) => {
|
|
16
16
|
return (
|
|
17
17
|
<ToastPrimitive.Root
|
|
18
|
-
className="px-2 py-2.5 grid grid-cols-[auto_max-content] gap-x-2.5 items-center rounded text-sm bg-popover-background shadow-popover text-text-muted"
|
|
18
|
+
className="n-px-2 n-py-2.5 n-grid n-grid-cols-[auto_max-content] n-gap-x-2.5 n-items-center n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted"
|
|
19
19
|
{...props}
|
|
20
20
|
>
|
|
21
21
|
{title && (
|
|
22
22
|
<ToastPrimitive.Title
|
|
23
|
-
className={`mb-[5px] ${textStyles.label} font-bold text-text`}
|
|
23
|
+
className={`n-mb-[5px] ${textStyles.label} n-font-bold n-text-text`}
|
|
24
24
|
>
|
|
25
25
|
{title}
|
|
26
26
|
</ToastPrimitive.Title>
|
|
27
27
|
)}
|
|
28
28
|
<ToastPrimitive.Description
|
|
29
|
-
className={`${textStyles.small} text-text-muted`}
|
|
29
|
+
className={`${textStyles.small} n-text-text-muted`}
|
|
30
30
|
>
|
|
31
31
|
{content}
|
|
32
32
|
</ToastPrimitive.Description>
|
|
@@ -45,6 +45,6 @@ export const Toast = ({
|
|
|
45
45
|
export const ToastProvider = ({ children }: { children: React.ReactNode }) => (
|
|
46
46
|
<ToastPrimitive.Provider>
|
|
47
47
|
{children}
|
|
48
|
-
<ToastPrimitive.Viewport className="fixed bottom-0 right-0 flex flex-col p-5
|
|
48
|
+
<ToastPrimitive.Viewport className="n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none" />
|
|
49
49
|
</ToastPrimitive.Provider>
|
|
50
50
|
);
|