@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.
Files changed (143) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +648 -333
  5. package/dist/index.d.ts +648 -333
  6. package/dist/index.js +13813 -9157
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +13949 -9269
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +10 -22
  11. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  12. package/src/__tests__/workspaceLayout.test.ts +281 -0
  13. package/src/components/ActionMenu.tsx +2 -0
  14. package/src/components/ActivityIndicator.tsx +13 -6
  15. package/src/components/ActivityLog.tsx +197 -0
  16. package/src/components/AnimatePresence.tsx +43 -26
  17. package/src/components/Avatar.tsx +45 -26
  18. package/src/components/Banner.tsx +57 -11
  19. package/src/components/BaseToolbar.tsx +67 -34
  20. package/src/components/Breadcrumbs.tsx +4 -2
  21. package/src/components/Button.tsx +28 -23
  22. package/src/components/Checkbox.tsx +6 -35
  23. package/src/components/Chip.tsx +47 -33
  24. package/src/components/Collection.tsx +20 -12
  25. package/src/components/ColorSwatch.tsx +42 -22
  26. package/src/components/ColorSwatchControl.tsx +7 -5
  27. package/src/components/Combobox.tsx +8 -6
  28. package/src/components/ComboboxMenu.tsx +6 -5
  29. package/src/components/CommandPalette.tsx +3 -1
  30. package/src/components/ContextMenu.tsx +3 -1
  31. package/src/components/Dialog.tsx +9 -13
  32. package/src/components/DimensionInput.tsx +2 -0
  33. package/src/components/Divider.tsx +18 -14
  34. package/src/components/DraggableMenuButton.tsx +11 -5
  35. package/src/components/Drawer.tsx +14 -6
  36. package/src/components/DropdownMenu.tsx +4 -2
  37. package/src/components/EditableText.tsx +4 -2
  38. package/src/components/Fade.tsx +13 -13
  39. package/src/components/FileUploadIndicator.tsx +108 -0
  40. package/src/components/FloatingWindow.tsx +4 -2
  41. package/src/components/Grid.tsx +58 -42
  42. package/src/components/GridView.tsx +65 -36
  43. package/src/components/IVirtualizedList.tsx +5 -0
  44. package/src/components/IconButton.tsx +2 -0
  45. package/src/components/Icons.tsx +24 -4
  46. package/src/components/InputField.tsx +21 -19
  47. package/src/components/InspectorContainer.tsx +2 -2
  48. package/src/components/InspectorPrimitives.tsx +15 -9
  49. package/src/components/Label.tsx +4 -3
  50. package/src/components/LabeledElementView.tsx +9 -5
  51. package/src/components/LabeledField.tsx +8 -6
  52. package/src/components/List.tsx +112 -46
  53. package/src/components/ListMenu.tsx +91 -0
  54. package/src/components/ListNavigator.tsx +106 -0
  55. package/src/components/ListView.tsx +22 -1147
  56. package/src/components/MediaThumbnail.tsx +84 -14
  57. package/src/components/Message.tsx +11 -12
  58. package/src/components/Navigator.tsx +461 -0
  59. package/src/components/NoyaLogo.tsx +1 -1
  60. package/src/components/Popover.tsx +12 -6
  61. package/src/components/Progress.tsx +31 -8
  62. package/src/components/ResizableContainer.tsx +187 -0
  63. package/src/components/RingProgress.tsx +128 -0
  64. package/src/components/ScrollArea.tsx +7 -5
  65. package/src/components/ScrollableSidebar.tsx +10 -0
  66. package/src/components/SearchCompletionMenu.tsx +5 -3
  67. package/src/components/Section.tsx +109 -57
  68. package/src/components/SegmentedControl.tsx +51 -25
  69. package/src/components/SelectMenu.tsx +16 -21
  70. package/src/components/SelectionToolbar.tsx +7 -4
  71. package/src/components/Slider.tsx +12 -10
  72. package/src/components/Spacer.tsx +2 -2
  73. package/src/components/StackNavigator.tsx +378 -0
  74. package/src/components/Stepper.tsx +88 -0
  75. package/src/components/Switch.tsx +7 -5
  76. package/src/components/Tabs.tsx +6 -4
  77. package/src/components/Text.tsx +29 -18
  78. package/src/components/TextArea.tsx +5 -3
  79. package/src/components/Toast.tsx +5 -5
  80. package/src/components/Toolbar.tsx +57 -10
  81. package/src/components/ToolbarDrawer.tsx +29 -0
  82. package/src/components/Tooltip.tsx +2 -2
  83. package/src/components/TreeView.tsx +13 -4
  84. package/src/components/UserPointer.tsx +6 -4
  85. package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
  86. package/src/components/blocks/ImageBlockComponent.tsx +162 -0
  87. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
  88. package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
  89. package/src/components/internal/Checkmark.tsx +24 -0
  90. package/src/components/internal/Menu.tsx +24 -19
  91. package/src/components/internal/MenuViewport.tsx +15 -6
  92. package/src/components/internal/SelectItem.tsx +7 -26
  93. package/src/components/internal/TextInput.tsx +7 -1
  94. package/src/components/listView/ListViewContexts.tsx +68 -0
  95. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  96. package/src/components/listView/ListViewRoot.tsx +521 -0
  97. package/src/components/listView/ListViewRow.tsx +475 -0
  98. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  99. package/src/components/listView/types.ts +11 -0
  100. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  101. package/src/components/sorting/DragRegistration.tsx +10 -5
  102. package/src/components/sorting/SharedDragProvider.tsx +134 -27
  103. package/src/components/sorting/Sortable.tsx +90 -27
  104. package/src/components/sorting/createSharedDrag.tsx +7 -6
  105. package/src/components/sorting/sorting.ts +6 -9
  106. package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
  107. package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
  108. package/src/components/workspace/VerticalTabMenu.tsx +46 -0
  109. package/src/components/workspace/WorkspaceLayout.tsx +76 -63
  110. package/src/components/workspace/constants.ts +4 -0
  111. package/src/components/workspace/panelStorage.ts +216 -0
  112. package/src/components/workspace/types.ts +11 -4
  113. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  114. package/src/contexts/DialogContext.tsx +19 -5
  115. package/src/contexts/FloatingWindowContext.tsx +2 -0
  116. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  117. package/src/contexts/ImageDataContext.tsx +2 -0
  118. package/src/contexts/LinkComponentContext.tsx +17 -0
  119. package/src/contexts/OpenPortalsContext.tsx +2 -0
  120. package/src/contexts/PortalScopeContext.tsx +2 -0
  121. package/src/hooks/mergeEventHandlers.ts +1 -1
  122. package/src/hooks/useHover.ts +30 -17
  123. package/src/hooks/useIndent.ts +5 -8
  124. package/src/hooks/useLabel.ts +2 -0
  125. package/src/hooks/useObjectURL.ts +5 -3
  126. package/src/hooks/usePlatform.ts +5 -3
  127. package/src/hooks/useTheme.ts +3 -1
  128. package/src/index.css +43 -10
  129. package/src/index.tsx +15 -7
  130. package/src/utils/classNames.ts +56 -9
  131. package/src/utils/combobox.ts +2 -0
  132. package/src/utils/editableBlockStyles.ts +2 -2
  133. package/src/utils/formatByteSize.ts +9 -0
  134. package/src/utils/inputs.ts +8 -6
  135. package/src/utils/sketchColor.ts +0 -34
  136. package/src/utils/withSeparatorElements.ts +1 -1
  137. package/tailwind.config.ts +1 -0
  138. package/src/components/FillInputField.tsx +0 -37
  139. package/src/components/FillPreviewBackground.tsx +0 -132
  140. package/src/components/GradientPicker.tsx +0 -88
  141. package/src/hooks/usePreservePanelSize.tsx +0 -183
  142. package/src/mediaQuery.ts +0 -13
  143. 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
- import * as SwitchPrimitive from "@radix-ui/react-switch";
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
  };
@@ -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="tabs"
40
+ variant={variant}
39
41
  items={selectableItems}
40
42
  />
41
43
  </div>
@@ -1,4 +1,6 @@
1
- import React, { ForwardedRef, forwardRef, ReactHTML, ReactNode } from "react";
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, keyof ReactHTML> = {
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-base font-normal",
44
- small: "font-sans text-heading5 font-normal",
45
- button: "font-sans text-button font-medium",
46
- code: "font-mono text-code",
47
- label: "font-sans text-label font-normal uppercase",
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?: keyof ReactHTML;
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
- `${textStyles[variant]} text-${camelToKebabCase(String(color)) ?? "text"} `,
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-all`,
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
  })
@@ -1,4 +1,4 @@
1
- import * as ToastPrimitive from "@radix-ui/react-toast";
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 g-2.5 min-w-[200px] max-w-[100vw] m-0 list-none z-[2147483647] outline-none" />
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
  );