@noya-app/noya-designsystem 0.1.41 → 0.1.43
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 +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +268 -147
- package/dist/index.d.ts +268 -147
- package/dist/index.js +7618 -6908
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6161 -5487
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/__tests__/combobox.test.ts +578 -0
- package/src/components/ActivityIndicator.tsx +4 -4
- package/src/components/AnimatePresence.tsx +5 -5
- package/src/components/Avatar.tsx +5 -2
- package/src/components/BaseToolbar.tsx +61 -0
- package/src/components/Button.tsx +1 -1
- package/src/components/Checkbox.tsx +6 -5
- package/src/components/Combobox.tsx +327 -337
- package/src/components/ComboboxMenu.tsx +88 -0
- package/src/components/Dialog.tsx +10 -6
- package/src/components/DimensionInput.tsx +4 -4
- package/src/components/FillPreviewBackground.tsx +51 -44
- package/src/components/FloatingWindow.tsx +5 -2
- package/src/components/GradientPicker.tsx +14 -14
- package/src/components/IconButton.tsx +6 -12
- package/src/components/Icons.tsx +3 -3
- package/src/components/InputField.tsx +145 -160
- package/src/components/Label.tsx +4 -4
- package/src/components/LabeledElementView.tsx +35 -41
- package/src/components/ListView.tsx +49 -39
- package/src/components/Message.tsx +75 -0
- package/src/components/Progress.tsx +2 -2
- package/src/components/ScrollArea.tsx +7 -5
- package/src/components/SegmentedControl.tsx +171 -0
- package/src/components/SelectMenu.tsx +61 -10
- package/src/components/Slider.tsx +5 -2
- package/src/components/Sortable.tsx +17 -27
- package/src/components/Spacer.tsx +28 -9
- package/src/components/Switch.tsx +8 -8
- package/src/components/TextArea.tsx +59 -12
- package/src/components/Toolbar.tsx +129 -0
- package/src/components/Tooltip.tsx +10 -7
- package/src/components/WorkspaceLayout.tsx +145 -152
- package/src/components/internal/Menu.tsx +5 -4
- package/src/contexts/DesignSystemConfiguration.tsx +15 -24
- package/src/contexts/DialogContext.tsx +137 -49
- package/src/contexts/ImageDataContext.tsx +6 -12
- package/src/index.css +1 -3
- package/src/index.tsx +12 -3
- package/src/utils/combobox.ts +369 -0
- package/tailwind.config.ts +1 -2
- package/src/components/RadioGroup.tsx +0 -100
- package/src/utils/completions.ts +0 -21
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
7
7
|
import * as Select from "@radix-ui/react-select";
|
|
8
8
|
import { type SelectProps } from "@radix-ui/react-select";
|
|
9
|
-
import React, {
|
|
9
|
+
import React, {
|
|
10
|
+
CSSProperties,
|
|
11
|
+
FocusEventHandler,
|
|
12
|
+
useMemo,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
10
15
|
import { cx } from "../utils/classNames";
|
|
11
16
|
import { Button } from "./Button";
|
|
12
17
|
import { renderIcon } from "./Icons";
|
|
@@ -26,14 +31,15 @@ type Props<T extends string> = {
|
|
|
26
31
|
label?: React.ReactNode;
|
|
27
32
|
onFocus?: FocusEventHandler;
|
|
28
33
|
onBlur?: FocusEventHandler;
|
|
29
|
-
} & Pick<SelectProps, "open">;
|
|
34
|
+
} & Pick<SelectProps, "open" | "onOpenChange">;
|
|
30
35
|
|
|
31
36
|
const readOnlyStyle: CSSProperties = {
|
|
32
37
|
justifyContent: "flex-start",
|
|
33
38
|
textAlign: "left",
|
|
34
39
|
};
|
|
35
40
|
|
|
36
|
-
const
|
|
41
|
+
export const labelTextStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%]`;
|
|
42
|
+
const labelStyles = `pointer-events-none flex items-center absolute top-0 bottom-0 right-[1.3rem] z-label`;
|
|
37
43
|
|
|
38
44
|
const scrollButtonStyles = `
|
|
39
45
|
flex
|
|
@@ -61,12 +67,21 @@ export const SelectMenu = memoGeneric(function SelectMenu<
|
|
|
61
67
|
open,
|
|
62
68
|
onFocus,
|
|
63
69
|
onBlur,
|
|
70
|
+
onOpenChange,
|
|
64
71
|
}: Props<T>) {
|
|
65
72
|
const selectedItem = menuItems.find(
|
|
66
73
|
(item): item is RegularMenuItem<T> =>
|
|
67
74
|
typeof item !== "string" && item.value === value
|
|
68
75
|
);
|
|
69
76
|
const icon = selectedItem?.icon;
|
|
77
|
+
const [internalOpen, setInternalOpen] = useState(open);
|
|
78
|
+
|
|
79
|
+
const handleOpenChange = (open: boolean) => {
|
|
80
|
+
if (onOpenChange) {
|
|
81
|
+
onOpenChange(open);
|
|
82
|
+
}
|
|
83
|
+
setInternalOpen(open);
|
|
84
|
+
};
|
|
70
85
|
|
|
71
86
|
const readOnlyButton = useMemo(
|
|
72
87
|
() => (
|
|
@@ -95,7 +110,11 @@ export const SelectMenu = memoGeneric(function SelectMenu<
|
|
|
95
110
|
<Button
|
|
96
111
|
id={id}
|
|
97
112
|
style={style}
|
|
98
|
-
className={
|
|
113
|
+
className={cx(
|
|
114
|
+
className ?? "w-full",
|
|
115
|
+
"flex-1 focus:z-interactable relative",
|
|
116
|
+
internalOpen && "ring-2 ring-primary"
|
|
117
|
+
)}
|
|
99
118
|
disabled={disabled}
|
|
100
119
|
>
|
|
101
120
|
{icon && (
|
|
@@ -108,15 +127,31 @@ export const SelectMenu = memoGeneric(function SelectMenu<
|
|
|
108
127
|
<Select.Value placeholder={placeholder} />
|
|
109
128
|
</span>
|
|
110
129
|
{label && (
|
|
111
|
-
<label htmlFor={id} className={labelStyles}>
|
|
130
|
+
<label htmlFor={id} className={cx(labelStyles, labelTextStyles)}>
|
|
112
131
|
{label}
|
|
113
132
|
</label>
|
|
114
133
|
)}
|
|
115
|
-
<DropdownChevronIcon
|
|
134
|
+
<DropdownChevronIcon
|
|
135
|
+
className={cx(
|
|
136
|
+
"transition-transform duration-100",
|
|
137
|
+
internalOpen ? "rotate-180" : "rotate-0"
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
116
140
|
</Button>
|
|
117
141
|
</Select.SelectTrigger>
|
|
118
142
|
),
|
|
119
|
-
[
|
|
143
|
+
[
|
|
144
|
+
onFocus,
|
|
145
|
+
onBlur,
|
|
146
|
+
id,
|
|
147
|
+
style,
|
|
148
|
+
className,
|
|
149
|
+
disabled,
|
|
150
|
+
icon,
|
|
151
|
+
placeholder,
|
|
152
|
+
label,
|
|
153
|
+
internalOpen,
|
|
154
|
+
]
|
|
120
155
|
);
|
|
121
156
|
|
|
122
157
|
if (readOnly) {
|
|
@@ -133,12 +168,28 @@ export const SelectMenu = memoGeneric(function SelectMenu<
|
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
return (
|
|
136
|
-
<Select.Root
|
|
171
|
+
<Select.Root
|
|
172
|
+
value={value}
|
|
173
|
+
onValueChange={onSelect}
|
|
174
|
+
open={open}
|
|
175
|
+
onOpenChange={handleOpenChange}
|
|
176
|
+
>
|
|
137
177
|
{trigger}
|
|
138
178
|
<Select.Portal>
|
|
139
|
-
<Select.Content
|
|
179
|
+
<Select.Content
|
|
180
|
+
className={styles.contentStyle}
|
|
181
|
+
position="popper"
|
|
182
|
+
sideOffset={6}
|
|
183
|
+
collisionPadding={8}
|
|
184
|
+
align="end"
|
|
185
|
+
style={{
|
|
186
|
+
width: "var(--radix-select-trigger-width)",
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
140
189
|
<Select.ScrollUpButton className={scrollButtonStyles}>
|
|
141
|
-
<ChevronUpIcon
|
|
190
|
+
<ChevronUpIcon
|
|
191
|
+
className={cx("transition-transform duration-100")}
|
|
192
|
+
/>
|
|
142
193
|
</Select.ScrollUpButton>
|
|
143
194
|
<Select.Viewport>
|
|
144
195
|
{menuItems.map((menuItem) => {
|
|
@@ -17,6 +17,7 @@ interface Props {
|
|
|
17
17
|
label?: React.ReactNode;
|
|
18
18
|
onFocus?: FocusEventHandler;
|
|
19
19
|
onBlur?: FocusEventHandler;
|
|
20
|
+
readOnly?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const labelStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] pointer-events-none flex items-center z-label`;
|
|
@@ -40,6 +41,7 @@ export const Slider = memo(function Slider({
|
|
|
40
41
|
label,
|
|
41
42
|
onFocus,
|
|
42
43
|
onBlur,
|
|
44
|
+
readOnly = false,
|
|
43
45
|
}: Props) {
|
|
44
46
|
const arrayValue = useMemo(
|
|
45
47
|
() => [Math.min(Math.max(value, min), max)],
|
|
@@ -88,12 +90,13 @@ export const Slider = memo(function Slider({
|
|
|
88
90
|
value={arrayValue}
|
|
89
91
|
onValueChange={handleValueChange}
|
|
90
92
|
className={cx(
|
|
91
|
-
"flex relative items-center select-none touch-none h-[27px] rounded overflow-hidden",
|
|
93
|
+
"flex relative items-center select-none touch-none h-[27px] rounded overflow-hidden w-full",
|
|
92
94
|
className
|
|
93
95
|
)}
|
|
94
96
|
style={style}
|
|
95
97
|
onFocus={onFocus}
|
|
96
98
|
onBlur={onBlur}
|
|
99
|
+
disabled={readOnly}
|
|
97
100
|
>
|
|
98
101
|
<RadixSlider.Track className="relative flex-grow h-full rounded overflow-hidden bg-input-background">
|
|
99
102
|
{label && (
|
|
@@ -117,7 +120,7 @@ export const Slider = memo(function Slider({
|
|
|
117
120
|
<RadixSlider.Thumb
|
|
118
121
|
style={thumbStyle}
|
|
119
122
|
className={cx(
|
|
120
|
-
"block h-[27px] rounded border border-solid bg-slider-thumb-background focus:
|
|
123
|
+
"block h-[27px] rounded border border-solid bg-slider-thumb-background transition-colors focus:border-primary focus:border-2 outline-none",
|
|
121
124
|
colorScheme === undefined && "border-slider-border",
|
|
122
125
|
colorScheme === "primary" && "border-primary",
|
|
123
126
|
colorScheme === "secondary" && "border-secondary"
|
|
@@ -16,17 +16,7 @@ import {
|
|
|
16
16
|
verticalListSortingStrategy,
|
|
17
17
|
} from "@dnd-kit/sortable";
|
|
18
18
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
19
|
-
import React
|
|
20
|
-
createContext,
|
|
21
|
-
ReactNode,
|
|
22
|
-
Ref,
|
|
23
|
-
useCallback,
|
|
24
|
-
useContext,
|
|
25
|
-
useEffect,
|
|
26
|
-
useMemo,
|
|
27
|
-
useRef,
|
|
28
|
-
useState,
|
|
29
|
-
} from "react";
|
|
19
|
+
import * as React from "react";
|
|
30
20
|
import { createPortal } from "react-dom";
|
|
31
21
|
|
|
32
22
|
export type RelativeDropPosition = "above" | "below" | "inside";
|
|
@@ -60,7 +50,7 @@ const defaultAcceptsDrop: DropValidator = (
|
|
|
60
50
|
return true;
|
|
61
51
|
};
|
|
62
52
|
|
|
63
|
-
const SortableItemContext = createContext<{
|
|
53
|
+
const SortableItemContext = React.createContext<{
|
|
64
54
|
keys: string[];
|
|
65
55
|
position: Translate;
|
|
66
56
|
acceptsDrop: DropValidator;
|
|
@@ -119,7 +109,7 @@ interface ItemProps<T> {
|
|
|
119
109
|
disabled?: boolean;
|
|
120
110
|
children: (
|
|
121
111
|
props: {
|
|
122
|
-
ref: Ref<T>;
|
|
112
|
+
ref: React.Ref<T>;
|
|
123
113
|
relativeDropPosition?: RelativeDropPosition;
|
|
124
114
|
[key: string]: any;
|
|
125
115
|
} & UseSortableReturnType["attributes"]
|
|
@@ -132,7 +122,7 @@ function SortableItem<T extends HTMLElement>({
|
|
|
132
122
|
children,
|
|
133
123
|
}: ItemProps<T>) {
|
|
134
124
|
const { keys, position, acceptsDrop, setActivatorEvent, axis } =
|
|
135
|
-
useContext(SortableItemContext);
|
|
125
|
+
React.useContext(SortableItemContext);
|
|
136
126
|
const sortable = useSortable({ id, disabled });
|
|
137
127
|
|
|
138
128
|
const {
|
|
@@ -156,7 +146,7 @@ function SortableItem<T extends HTMLElement>({
|
|
|
156
146
|
const offsetLeft = eventX + position.x;
|
|
157
147
|
const offsetTop = eventY + position.y;
|
|
158
148
|
|
|
159
|
-
const ref = useCallback((node: T) => setNodeRef(node), [setNodeRef]);
|
|
149
|
+
const ref = React.useCallback((node: T) => setNodeRef(node), [setNodeRef]);
|
|
160
150
|
|
|
161
151
|
return children({
|
|
162
152
|
ref,
|
|
@@ -183,8 +173,8 @@ function SortableItem<T extends HTMLElement>({
|
|
|
183
173
|
|
|
184
174
|
interface RootProps {
|
|
185
175
|
keys: string[];
|
|
186
|
-
children: ReactNode;
|
|
187
|
-
renderOverlay?: (index: number) => ReactNode;
|
|
176
|
+
children: React.ReactNode;
|
|
177
|
+
renderOverlay?: (index: number) => React.ReactNode;
|
|
188
178
|
onMoveItem?: (
|
|
189
179
|
sourceIndex: number,
|
|
190
180
|
destinationIndex: number,
|
|
@@ -210,27 +200,27 @@ function SortableRoot({
|
|
|
210
200
|
})
|
|
211
201
|
);
|
|
212
202
|
|
|
213
|
-
const [activeIndex, setActiveIndex] = useState<number | undefined>();
|
|
214
|
-
const activatorEvent = useRef<PointerEvent | null>(null);
|
|
203
|
+
const [activeIndex, setActiveIndex] = React.useState<number | undefined>();
|
|
204
|
+
const activatorEvent = React.useRef<PointerEvent | null>(null);
|
|
215
205
|
|
|
216
|
-
const setActivatorEvent = useCallback((event: PointerEvent) => {
|
|
206
|
+
const setActivatorEvent = React.useCallback((event: PointerEvent) => {
|
|
217
207
|
activatorEvent.current = event;
|
|
218
208
|
}, []);
|
|
219
209
|
|
|
220
|
-
const [position, setPosition] = useState<Translate>({ x: 0, y: 0 });
|
|
210
|
+
const [position, setPosition] = React.useState<Translate>({ x: 0, y: 0 });
|
|
221
211
|
|
|
222
|
-
const handleDragStart = useCallback(
|
|
212
|
+
const handleDragStart = React.useCallback(
|
|
223
213
|
(event: DragStartEvent) => {
|
|
224
214
|
setActiveIndex(keys.indexOf(event.active.id));
|
|
225
215
|
},
|
|
226
216
|
[keys]
|
|
227
217
|
);
|
|
228
218
|
|
|
229
|
-
const handleDragMove = useCallback((event: DragMoveEvent) => {
|
|
219
|
+
const handleDragMove = React.useCallback((event: DragMoveEvent) => {
|
|
230
220
|
setPosition({ ...event.delta });
|
|
231
221
|
}, []);
|
|
232
222
|
|
|
233
|
-
const handleDragEnd = useCallback(
|
|
223
|
+
const handleDragEnd = React.useCallback(
|
|
234
224
|
(event: DragEndEvent) => {
|
|
235
225
|
const { active, over } = event;
|
|
236
226
|
|
|
@@ -263,15 +253,15 @@ function SortableRoot({
|
|
|
263
253
|
[acceptsDrop, axis, keys, onMoveItem, position.x, position.y]
|
|
264
254
|
);
|
|
265
255
|
|
|
266
|
-
const [mounted, setMounted] = useState(false);
|
|
256
|
+
const [mounted, setMounted] = React.useState(false);
|
|
267
257
|
|
|
268
|
-
useEffect(() => {
|
|
258
|
+
React.useEffect(() => {
|
|
269
259
|
setMounted(true);
|
|
270
260
|
}, []);
|
|
271
261
|
|
|
272
262
|
return (
|
|
273
263
|
<SortableItemContext.Provider
|
|
274
|
-
value={useMemo(
|
|
264
|
+
value={React.useMemo(
|
|
275
265
|
() => ({
|
|
276
266
|
keys,
|
|
277
267
|
acceptsDrop,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react/jsx-pascal-case */
|
|
2
|
-
import
|
|
2
|
+
import type { ForwardedRef } from "react";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
size?: number | string;
|
|
@@ -10,12 +11,21 @@ interface Props {
|
|
|
10
11
|
* Vertical
|
|
11
12
|
* ------------------------------------------------------------------------- */
|
|
12
13
|
|
|
13
|
-
const SpacerVertical = forwardRef<HTMLSpanElement, Props>(
|
|
14
|
+
const SpacerVertical = React.forwardRef<HTMLSpanElement, Props>(
|
|
14
15
|
({ size, inline, ...props }, ref: ForwardedRef<HTMLSpanElement>) => {
|
|
15
16
|
return (
|
|
16
|
-
<span
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
<span
|
|
18
|
+
className={`${inline ? "inline-block" : "block"} ${size === undefined ? "flex flex-1" : ""}`}
|
|
19
|
+
style={
|
|
20
|
+
size
|
|
21
|
+
? {
|
|
22
|
+
minHeight: `${size}px`,
|
|
23
|
+
}
|
|
24
|
+
: undefined
|
|
25
|
+
}
|
|
26
|
+
{...props}
|
|
27
|
+
ref={ref}
|
|
28
|
+
/>
|
|
19
29
|
);
|
|
20
30
|
}
|
|
21
31
|
);
|
|
@@ -24,12 +34,21 @@ const SpacerVertical = forwardRef<HTMLSpanElement, Props>(
|
|
|
24
34
|
* Horizontal
|
|
25
35
|
* ------------------------------------------------------------------------- */
|
|
26
36
|
|
|
27
|
-
const SpacerHorizontal = forwardRef<HTMLSpanElement, Props>(
|
|
37
|
+
const SpacerHorizontal = React.forwardRef<HTMLSpanElement, Props>(
|
|
28
38
|
({ size, inline, ...props }, ref: ForwardedRef<HTMLSpanElement>) => {
|
|
29
39
|
return (
|
|
30
|
-
<span
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
<span
|
|
41
|
+
className={`${inline ? "inline-block" : "block"} ${size === undefined ? "flex flex-1" : ""}`}
|
|
42
|
+
style={
|
|
43
|
+
size
|
|
44
|
+
? {
|
|
45
|
+
minWidth: `${size}px`,
|
|
46
|
+
}
|
|
47
|
+
: undefined
|
|
48
|
+
}
|
|
49
|
+
{...props}
|
|
50
|
+
ref={ref}
|
|
51
|
+
/>
|
|
33
52
|
);
|
|
34
53
|
}
|
|
35
54
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
2
|
-
import
|
|
2
|
+
import * as React from "react";
|
|
3
3
|
import { cx } from "../utils/classNames";
|
|
4
4
|
|
|
5
|
-
type SwitchColorScheme = "
|
|
5
|
+
type SwitchColorScheme = "primary" | "secondary";
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
id?: string;
|
|
@@ -11,17 +11,17 @@ interface Props {
|
|
|
11
11
|
/** @default normal */
|
|
12
12
|
colorScheme?: SwitchColorScheme;
|
|
13
13
|
disabled?: boolean;
|
|
14
|
-
onFocus?: FocusEventHandler;
|
|
15
|
-
onBlur?: FocusEventHandler;
|
|
14
|
+
onFocus?: React.FocusEventHandler;
|
|
15
|
+
onBlur?: React.FocusEventHandler;
|
|
16
16
|
className?: string;
|
|
17
|
-
style?: CSSProperties;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const Switch = function Switch({
|
|
21
21
|
id,
|
|
22
22
|
value,
|
|
23
23
|
onChange,
|
|
24
|
-
colorScheme = "
|
|
24
|
+
colorScheme = "primary",
|
|
25
25
|
disabled,
|
|
26
26
|
onFocus,
|
|
27
27
|
onBlur,
|
|
@@ -38,7 +38,7 @@ export const Switch = function Switch({
|
|
|
38
38
|
onChange(newValue);
|
|
39
39
|
}}
|
|
40
40
|
className={cx(
|
|
41
|
-
"all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer [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",
|
|
41
|
+
"all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer [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
42
|
colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
|
|
43
43
|
"focus:z-interactable",
|
|
44
44
|
className
|
|
@@ -46,7 +46,7 @@ export const Switch = function Switch({
|
|
|
46
46
|
onFocus={onFocus}
|
|
47
47
|
onBlur={onBlur}
|
|
48
48
|
>
|
|
49
|
-
<SwitchPrimitive.Thumb className="block w-[15px] h-[15px] bg-
|
|
49
|
+
<SwitchPrimitive.Thumb className="block w-[15px] h-[15px] bg-background rounded-full transition-transform translate-x-[2px] data-[state=checked]:translate-x-[15px]" />
|
|
50
50
|
</SwitchPrimitive.Root>
|
|
51
51
|
);
|
|
52
52
|
};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { assignRef } from "@noya-app/react-utils";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, {
|
|
3
|
+
ComponentProps,
|
|
4
|
+
forwardRef,
|
|
5
|
+
memo,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
} from "react";
|
|
3
10
|
import { cx } from "../utils/classNames";
|
|
4
11
|
|
|
5
|
-
export const useAutoResize = (value: string) => {
|
|
12
|
+
export const useAutoResize = (value: string | undefined) => {
|
|
6
13
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
|
7
14
|
|
|
8
15
|
useEffect(() => {
|
|
16
|
+
if (value === undefined) return;
|
|
9
17
|
if (!textareaRef.current) return;
|
|
10
18
|
|
|
11
19
|
textareaRef.current.style.height = "auto"; // Reset the height
|
|
@@ -15,39 +23,48 @@ export const useAutoResize = (value: string) => {
|
|
|
15
23
|
return textareaRef;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
|
-
export const
|
|
19
|
-
forwardRef(function
|
|
26
|
+
export const TextArea = memo(
|
|
27
|
+
forwardRef(function TextArea(
|
|
20
28
|
{
|
|
21
29
|
value,
|
|
22
30
|
onChangeText,
|
|
23
31
|
className,
|
|
32
|
+
onChange,
|
|
33
|
+
autoResize = false,
|
|
24
34
|
...rest
|
|
25
35
|
}: {
|
|
26
|
-
onChangeText
|
|
36
|
+
onChangeText?: (value: string) => void;
|
|
27
37
|
value?: string;
|
|
38
|
+
autoResize?: boolean;
|
|
28
39
|
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
29
40
|
forwardedRef: React.ForwardedRef<HTMLTextAreaElement>
|
|
30
41
|
) {
|
|
31
|
-
const
|
|
42
|
+
const autoResizeRef = useAutoResize(
|
|
43
|
+
autoResize ? value || rest.placeholder || "" : undefined
|
|
44
|
+
);
|
|
32
45
|
|
|
33
46
|
const handleChange = useCallback(
|
|
34
|
-
(event: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
35
|
-
onChangeText(event.target.value)
|
|
36
|
-
|
|
47
|
+
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
48
|
+
onChangeText?.(event.target.value);
|
|
49
|
+
onChange?.(event);
|
|
50
|
+
},
|
|
51
|
+
[onChangeText, onChange]
|
|
37
52
|
);
|
|
38
53
|
|
|
39
54
|
const handleRef = useCallback(
|
|
40
55
|
(value: HTMLTextAreaElement | null) => {
|
|
41
|
-
|
|
56
|
+
if (autoResize) {
|
|
57
|
+
autoResizeRef.current = value;
|
|
58
|
+
}
|
|
42
59
|
assignRef(forwardedRef, value);
|
|
43
60
|
},
|
|
44
|
-
[
|
|
61
|
+
[autoResize, autoResizeRef, forwardedRef]
|
|
45
62
|
);
|
|
46
63
|
|
|
47
64
|
return (
|
|
48
65
|
<textarea
|
|
49
66
|
className={cx(
|
|
50
|
-
`font-sans text-heading5 font-normal text-text bg-input-background
|
|
67
|
+
`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`,
|
|
51
68
|
className
|
|
52
69
|
)}
|
|
53
70
|
ref={handleRef}
|
|
@@ -58,3 +75,33 @@ export const AutoResizingTextArea = memo(
|
|
|
58
75
|
);
|
|
59
76
|
})
|
|
60
77
|
);
|
|
78
|
+
|
|
79
|
+
export const TextAreaRow = memo(
|
|
80
|
+
forwardRef(function TextAreaRow(
|
|
81
|
+
{
|
|
82
|
+
className,
|
|
83
|
+
textAreaClassName,
|
|
84
|
+
textAreaRef,
|
|
85
|
+
end,
|
|
86
|
+
endClassName,
|
|
87
|
+
...rest
|
|
88
|
+
}: {
|
|
89
|
+
className?: string;
|
|
90
|
+
textAreaClassName?: string;
|
|
91
|
+
textAreaRef?: React.Ref<HTMLTextAreaElement>;
|
|
92
|
+
end?: React.ReactNode;
|
|
93
|
+
endClassName?: string;
|
|
94
|
+
} & ComponentProps<typeof TextArea>,
|
|
95
|
+
forwardedRef: React.ForwardedRef<HTMLDivElement>
|
|
96
|
+
) {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
ref={forwardedRef}
|
|
100
|
+
className={cx("relative w-full h-full", className)}
|
|
101
|
+
>
|
|
102
|
+
<TextArea className={textAreaClassName} {...rest} ref={textAreaRef} />
|
|
103
|
+
<div className={cx("absolute right-1 top-4", endClassName)}>{end}</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
})
|
|
107
|
+
);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { DropdownChevronIcon } from "@noya-app/noya-icons";
|
|
2
|
+
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { BaseToolbar } from "./BaseToolbar";
|
|
5
|
+
import { Button } from "./Button";
|
|
6
|
+
import { DropdownMenu } from "./DropdownMenu";
|
|
7
|
+
import { renderIcon } from "./Icons";
|
|
8
|
+
import type { MenuItem } from "./internal/Menu";
|
|
9
|
+
import { getKeyboardShortcutsForMenuItems } from "./internal/Menu";
|
|
10
|
+
import { Spacer } from "./Spacer";
|
|
11
|
+
|
|
12
|
+
export interface ToolbarProps<T extends string = string> {
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
logo?: React.ReactNode;
|
|
15
|
+
leftMenuItems?: MenuItem<T>[];
|
|
16
|
+
rightMenuItems?: MenuItem<T>[];
|
|
17
|
+
onSelectMenuItem?: (value: T) => void;
|
|
18
|
+
/** Whether to bind keyboard shortcuts for menu items. @default true */
|
|
19
|
+
shouldBindKeyboardShortcuts?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Toolbar<T extends string = string>({
|
|
23
|
+
children,
|
|
24
|
+
logo,
|
|
25
|
+
leftMenuItems = [],
|
|
26
|
+
rightMenuItems = [],
|
|
27
|
+
onSelectMenuItem,
|
|
28
|
+
shouldBindKeyboardShortcuts = true,
|
|
29
|
+
}: ToolbarProps<T>) {
|
|
30
|
+
// Register keyboard shortcuts for all menu items
|
|
31
|
+
const allMenuItems = React.useMemo(
|
|
32
|
+
() => [...leftMenuItems, ...rightMenuItems],
|
|
33
|
+
[leftMenuItems, rightMenuItems]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
useKeyboardShortcuts(
|
|
37
|
+
React.useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
onSelectMenuItem && shouldBindKeyboardShortcuts
|
|
40
|
+
? getKeyboardShortcutsForMenuItems(allMenuItems, onSelectMenuItem)
|
|
41
|
+
: {},
|
|
42
|
+
[allMenuItems, onSelectMenuItem, shouldBindKeyboardShortcuts]
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<BaseToolbar
|
|
48
|
+
logo={logo}
|
|
49
|
+
left={
|
|
50
|
+
leftMenuItems.length > 0 && (
|
|
51
|
+
<div className="flex gap-2">
|
|
52
|
+
<ToolbarMenu
|
|
53
|
+
items={leftMenuItems}
|
|
54
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
right={
|
|
60
|
+
rightMenuItems.length > 0 && (
|
|
61
|
+
<div className="flex gap-2">
|
|
62
|
+
<ToolbarMenu
|
|
63
|
+
items={rightMenuItems}
|
|
64
|
+
onSelectMenuItem={onSelectMenuItem}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</BaseToolbar>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ToolbarMenu<T extends string>({
|
|
76
|
+
items,
|
|
77
|
+
onSelectMenuItem,
|
|
78
|
+
}: {
|
|
79
|
+
items: MenuItem<T>[];
|
|
80
|
+
onSelectMenuItem?: (value: T) => void;
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
{items.map((item, i) => {
|
|
85
|
+
if (typeof item === "string") return null;
|
|
86
|
+
|
|
87
|
+
if (!item.items) {
|
|
88
|
+
return (
|
|
89
|
+
<Button
|
|
90
|
+
key={i}
|
|
91
|
+
disabled={item.disabled}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
if (onSelectMenuItem && item.value) {
|
|
94
|
+
onSelectMenuItem(item.value);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
{item.title}
|
|
99
|
+
{item.icon && (
|
|
100
|
+
<>
|
|
101
|
+
<Spacer.Horizontal inline size={6} />
|
|
102
|
+
{renderIcon(item.icon)}
|
|
103
|
+
</>
|
|
104
|
+
)}
|
|
105
|
+
</Button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<DropdownMenu
|
|
111
|
+
key={i}
|
|
112
|
+
items={item.items}
|
|
113
|
+
onSelect={(value) => {
|
|
114
|
+
if (onSelectMenuItem && value) {
|
|
115
|
+
onSelectMenuItem(value);
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<Button disabled={item.disabled}>
|
|
120
|
+
{item.title}
|
|
121
|
+
<Spacer.Horizontal size={6} />
|
|
122
|
+
<DropdownChevronIcon />
|
|
123
|
+
</Button>
|
|
124
|
+
</DropdownMenu>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import * as TooltipPrimitive from
|
|
2
|
-
import
|
|
3
|
-
import { textStyles } from
|
|
1
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { textStyles } from "./Text";
|
|
4
4
|
|
|
5
5
|
// const Arrow = styled(TooltipPrimitive.Arrow)(({ theme }) => ({
|
|
6
6
|
// fill: theme.colors.popover.background,
|
|
7
7
|
// }));
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
-
children: ReactNode;
|
|
11
|
-
content: ReactNode;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
content: React.ReactNode;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export const Tooltip = memo(function Tooltip({
|
|
14
|
+
export const Tooltip = React.memo(function Tooltip({
|
|
15
|
+
children,
|
|
16
|
+
content,
|
|
17
|
+
}: Props) {
|
|
15
18
|
return (
|
|
16
19
|
<TooltipPrimitive.Provider>
|
|
17
20
|
<TooltipPrimitive.Root>
|
|
@@ -22,7 +25,7 @@ export const Tooltip = memo(function Tooltip({ children, content }: Props) {
|
|
|
22
25
|
align="center"
|
|
23
26
|
sideOffset={2}
|
|
24
27
|
collisionPadding={8}
|
|
25
|
-
className={`${textStyles.small} text-text rounded-[3px] py-small px-medium bg-popover-background shadow-[0_2px_4px_rgba(0,0,0,0.2)_0_0_12px_rgba(0,0,0,0.1)] border border-solid border-divider-strong`}
|
|
28
|
+
className={`${textStyles.small} text-text rounded-[3px] py-small px-medium bg-popover-background shadow-[0_2px_4px_rgba(0,0,0,0.2)_0_0_12px_rgba(0,0,0,0.1)] border border-solid border-divider-strong z-menu`}
|
|
26
29
|
>
|
|
27
30
|
{content}
|
|
28
31
|
{/* <Arrow /> */}
|