@noya-app/noya-designsystem 0.1.47 → 0.1.49
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 +18 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +389 -241
- package/dist/index.d.ts +389 -241
- package/dist/index.js +4802 -3990
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4715 -3915
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/validateDropIndicator.test.ts +263 -0
- package/src/components/ActionMenu.tsx +51 -0
- package/src/components/ActivityIndicator.tsx +7 -1
- package/src/components/BaseToolbar.tsx +2 -2
- package/src/components/Checkbox.tsx +2 -2
- package/src/components/Chip.tsx +7 -6
- package/src/components/Collection.tsx +30 -2
- package/src/components/Combobox.tsx +27 -15
- package/src/components/ComboboxMenu.tsx +15 -6
- package/src/components/Dialog.tsx +17 -12
- package/src/components/DimensionInput.tsx +14 -12
- package/src/components/Drawer.tsx +98 -0
- package/src/components/EditableText.tsx +3 -1
- package/src/components/FillInputField.tsx +2 -2
- package/src/components/Grid.tsx +161 -111
- package/src/components/GridView.tsx +73 -41
- package/src/components/InputField.tsx +148 -208
- package/src/components/Label.tsx +4 -68
- package/src/components/LabeledField.tsx +7 -1
- package/src/components/List.tsx +135 -50
- package/src/components/ListView.tsx +63 -43
- package/src/components/MediaThumbnail.tsx +117 -0
- package/src/components/Message.tsx +8 -8
- package/src/components/NoyaLogo.tsx +41 -0
- package/src/components/SearchCompletionMenu.tsx +30 -16
- package/src/components/SegmentedControl.tsx +1 -1
- package/src/components/SelectMenu.tsx +28 -21
- package/src/components/Slider.tsx +16 -7
- package/src/components/Sortable.tsx +125 -62
- package/src/components/internal/Menu.tsx +45 -25
- package/src/components/internal/MenuViewport.tsx +7 -1
- package/src/components/internal/SelectItem.tsx +53 -28
- package/src/components/internal/__tests__/Menu.test.tsx +12 -0
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +86 -0
- package/src/components/workspace/PanelWorkspaceLayout.tsx +102 -0
- package/src/components/{WorkspaceLayout.tsx → workspace/WorkspaceLayout.tsx} +109 -106
- package/src/components/workspace/types.ts +31 -0
- package/src/contexts/DialogContext.tsx +49 -34
- package/src/hooks/usePreservePanelSize.tsx +1 -5
- package/src/index.css +8 -1
- package/src/index.tsx +6 -1
- package/src/theme/index.ts +2 -0
- package/src/utils/classNames.ts +51 -3
- package/src/utils/inputs.ts +21 -0
- package/tailwind.config.ts +8 -0
|
@@ -1,57 +1,49 @@
|
|
|
1
1
|
import { DropdownChevronIcon } from "@noya-app/noya-icons";
|
|
2
|
-
import {
|
|
2
|
+
import { memoGeneric, useMergeRefs, useSize } from "@noya-app/react-utils";
|
|
3
3
|
import { Property } from "csstype";
|
|
4
4
|
import React, {
|
|
5
5
|
createContext,
|
|
6
6
|
FocusEventHandler,
|
|
7
7
|
ForwardedRef,
|
|
8
8
|
forwardRef,
|
|
9
|
-
LabelHTMLAttributes,
|
|
10
9
|
memo,
|
|
11
10
|
ReactNode,
|
|
12
11
|
useCallback,
|
|
13
12
|
useContext,
|
|
14
|
-
useEffect,
|
|
15
13
|
useId,
|
|
16
|
-
useLayoutEffect,
|
|
17
14
|
useMemo,
|
|
18
|
-
useRef,
|
|
19
15
|
useState,
|
|
20
16
|
} from "react";
|
|
21
17
|
import { useLabel, useLabelPosition } from "../hooks/useLabel";
|
|
22
|
-
import { cx } from "../utils/classNames";
|
|
18
|
+
import { cx, mergeConflictingClassNames } from "../utils/classNames";
|
|
23
19
|
import handleNudge from "../utils/handleNudge";
|
|
20
|
+
import { getInsetEndStyles, InputSize, startBaseStyles } from "../utils/inputs";
|
|
24
21
|
import { Button, ButtonRootProps } from "./Button";
|
|
25
22
|
import { DropdownMenu } from "./DropdownMenu";
|
|
26
23
|
import { MenuItem } from "./internal/Menu";
|
|
27
24
|
import TextInput, { TextInputProps } from "./internal/TextInput";
|
|
28
|
-
import {
|
|
25
|
+
import { Label } from "./Label";
|
|
29
26
|
import { Popover } from "./Popover";
|
|
30
27
|
|
|
31
28
|
export type InputFieldSize = "small" | "medium" | "large";
|
|
32
29
|
|
|
33
30
|
type InputFieldContextValue = {
|
|
34
|
-
labelSize?: number;
|
|
35
|
-
buttonSize?: number;
|
|
36
|
-
/** @default medium */
|
|
37
|
-
size: InputFieldSize;
|
|
38
31
|
isFocused: boolean;
|
|
39
32
|
onFocusChange: (isFocused: boolean) => void;
|
|
40
|
-
inputRef
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
label?: ReactNode;
|
|
46
|
-
labelPosition?: LabelPosition;
|
|
47
|
-
};
|
|
33
|
+
inputRef: ForwardedRef<HTMLInputElement>;
|
|
34
|
+
startRef: ForwardedRef<HTMLSpanElement>;
|
|
35
|
+
endRef: ForwardedRef<HTMLSpanElement>;
|
|
36
|
+
endWidth?: number;
|
|
37
|
+
} & Pick<InputFieldRootProps, "endWidth" | "startWidth" | "size" | "id">;
|
|
48
38
|
|
|
49
39
|
const InputFieldContext = createContext<InputFieldContextValue>({
|
|
50
|
-
|
|
51
|
-
buttonSize: undefined,
|
|
40
|
+
startWidth: undefined,
|
|
52
41
|
size: "medium",
|
|
53
42
|
isFocused: false,
|
|
54
43
|
onFocusChange: () => {},
|
|
44
|
+
inputRef: null,
|
|
45
|
+
startRef: null,
|
|
46
|
+
endRef: null,
|
|
55
47
|
});
|
|
56
48
|
|
|
57
49
|
export const useInputFieldContext = () => useContext(InputFieldContext);
|
|
@@ -59,79 +51,22 @@ export const useInputFieldContext = () => useContext(InputFieldContext);
|
|
|
59
51
|
const noop = () => {};
|
|
60
52
|
|
|
61
53
|
export const getFieldSpacing = ({
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
endWidth = 0,
|
|
55
|
+
startWidth = 0,
|
|
64
56
|
variant,
|
|
65
57
|
}: {
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
endWidth?: number;
|
|
59
|
+
startWidth?: number;
|
|
68
60
|
variant?: InputFieldVariant;
|
|
69
61
|
}) => ({
|
|
70
|
-
paddingLeft: variant === "bare" ?
|
|
71
|
-
paddingRight: `${variant === "bare" ?
|
|
62
|
+
paddingLeft: `${variant === "bare" ? startWidth : startWidth ? startWidth + 12 : 6}px`,
|
|
63
|
+
paddingRight: `${variant === "bare" ? endWidth : endWidth ? endWidth + 12 : 6}px`,
|
|
72
64
|
});
|
|
73
65
|
|
|
74
|
-
/* ----------------------------------------------------------------------------
|
|
75
|
-
* Label
|
|
76
|
-
* ------------------------------------------------------------------------- */
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Inserts a Label at the end of the input, unless overridden by LabeledField.
|
|
80
|
-
*/
|
|
81
|
-
const InputFieldLabel = memo(
|
|
82
|
-
forwardRef(function InputFieldLabel(
|
|
83
|
-
props: LabelHTMLAttributes<HTMLLabelElement>,
|
|
84
|
-
forwardedRef: ForwardedRef<HTMLLabelElement>
|
|
85
|
-
) {
|
|
86
|
-
const { setLabelWidth, size, buttonSize, label, id, labelPosition } =
|
|
87
|
-
useInputFieldContext();
|
|
88
|
-
|
|
89
|
-
const ref = useRef<HTMLLabelElement | null>(null);
|
|
90
|
-
|
|
91
|
-
useLayoutEffect(() => {
|
|
92
|
-
if (!setLabelWidth) return;
|
|
93
|
-
|
|
94
|
-
const width = ref.current?.getBoundingClientRect().width;
|
|
95
|
-
|
|
96
|
-
if (!width) return;
|
|
97
|
-
|
|
98
|
-
setLabelWidth(width);
|
|
99
|
-
}, [setLabelWidth]);
|
|
100
|
-
|
|
101
|
-
if (labelPosition !== "inset") return null;
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<InsetLabel
|
|
105
|
-
ref={(element) => {
|
|
106
|
-
ref.current = element;
|
|
107
|
-
assignRef(forwardedRef, element);
|
|
108
|
-
}}
|
|
109
|
-
size={size}
|
|
110
|
-
buttonSize={buttonSize}
|
|
111
|
-
htmlFor={id}
|
|
112
|
-
{...props}
|
|
113
|
-
>
|
|
114
|
-
{label}
|
|
115
|
-
</InsetLabel>
|
|
116
|
-
);
|
|
117
|
-
})
|
|
118
|
-
);
|
|
119
|
-
|
|
120
66
|
/* ----------------------------------------------------------------------------
|
|
121
67
|
* Dropdown
|
|
122
68
|
* ------------------------------------------------------------------------- */
|
|
123
69
|
|
|
124
|
-
const DropdownContainer = forwardRef<
|
|
125
|
-
HTMLSpanElement,
|
|
126
|
-
{ className?: string; children?: React.ReactNode }
|
|
127
|
-
>(({ className, ...props }, ref) => (
|
|
128
|
-
<span
|
|
129
|
-
ref={ref}
|
|
130
|
-
className={cx("absolute inset-y-0 right-0 flex flex-col", className)}
|
|
131
|
-
{...props}
|
|
132
|
-
/>
|
|
133
|
-
));
|
|
134
|
-
|
|
135
70
|
interface InputFieldDropdownProps<T extends string> {
|
|
136
71
|
id?: string;
|
|
137
72
|
items: MenuItem<T>[];
|
|
@@ -143,20 +78,18 @@ const InputFieldDropdownMenu = memoGeneric(function InputFieldDropdownMenu<
|
|
|
143
78
|
T extends string,
|
|
144
79
|
>({ id, items, onSelect, children }: InputFieldDropdownProps<T>) {
|
|
145
80
|
return (
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</DropdownMenu>
|
|
159
|
-
</DropdownContainer>
|
|
81
|
+
<DropdownMenu<T>
|
|
82
|
+
items={items}
|
|
83
|
+
onSelect={onSelect}
|
|
84
|
+
align="end"
|
|
85
|
+
sideOffset={8}
|
|
86
|
+
>
|
|
87
|
+
{children || (
|
|
88
|
+
<InputFieldButton variant="floating" id={id}>
|
|
89
|
+
<DropdownChevronIcon />
|
|
90
|
+
</InputFieldButton>
|
|
91
|
+
)}
|
|
92
|
+
</DropdownMenu>
|
|
160
93
|
);
|
|
161
94
|
});
|
|
162
95
|
|
|
@@ -169,7 +102,6 @@ const InputFieldButton = memo(
|
|
|
169
102
|
forwardRef(function InputFieldButton(
|
|
170
103
|
props: React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
171
104
|
ButtonRootProps & {
|
|
172
|
-
buttonClassName?: string;
|
|
173
105
|
variant?: "floating" | "normal";
|
|
174
106
|
},
|
|
175
107
|
forwardedRef: ForwardedRef<HTMLButtonElement>
|
|
@@ -178,24 +110,12 @@ const InputFieldButton = memo(
|
|
|
178
110
|
children,
|
|
179
111
|
onClick,
|
|
180
112
|
className,
|
|
181
|
-
|
|
113
|
+
style,
|
|
182
114
|
variant = "normal",
|
|
183
115
|
as = "button",
|
|
184
116
|
...rest
|
|
185
117
|
} = props;
|
|
186
|
-
const { size, inputRef
|
|
187
|
-
|
|
188
|
-
const ref = useRef<HTMLButtonElement | null>(null);
|
|
189
|
-
|
|
190
|
-
useLayoutEffect(() => {
|
|
191
|
-
if (!setButtonWidth) return;
|
|
192
|
-
|
|
193
|
-
const width = ref.current?.getBoundingClientRect().width;
|
|
194
|
-
|
|
195
|
-
if (!width) return;
|
|
196
|
-
|
|
197
|
-
setButtonWidth(width);
|
|
198
|
-
}, [setButtonWidth]);
|
|
118
|
+
const { size, inputRef } = useInputFieldContext();
|
|
199
119
|
|
|
200
120
|
const defaultHandleClick = useCallback(
|
|
201
121
|
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
@@ -219,42 +139,35 @@ const InputFieldButton = memo(
|
|
|
219
139
|
[]
|
|
220
140
|
);
|
|
221
141
|
|
|
142
|
+
const memoizedStyle = useMemo(
|
|
143
|
+
() => ({
|
|
144
|
+
...(variant === "floating" && {
|
|
145
|
+
paddingLeft: "0px",
|
|
146
|
+
paddingRight: "0px",
|
|
147
|
+
}),
|
|
148
|
+
...style,
|
|
149
|
+
}),
|
|
150
|
+
[style, variant]
|
|
151
|
+
);
|
|
152
|
+
|
|
222
153
|
return (
|
|
223
|
-
<
|
|
154
|
+
<Button
|
|
155
|
+
as={as}
|
|
156
|
+
style={memoizedStyle}
|
|
157
|
+
ref={forwardedRef}
|
|
158
|
+
variant="floating"
|
|
159
|
+
onClick={onClick ?? defaultHandleClick}
|
|
160
|
+
onPointerDown={handlePointerDown}
|
|
161
|
+
tabIndex={-1}
|
|
162
|
+
size={size === "medium" ? "normal" : size}
|
|
224
163
|
className={cx(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
? "right-1 top-2.5"
|
|
228
|
-
: size === "medium"
|
|
229
|
-
? "right-1 top-1"
|
|
230
|
-
: size === "small"
|
|
231
|
-
? "right-0.5 top-0.5"
|
|
232
|
-
: "",
|
|
233
|
-
"z-label",
|
|
234
|
-
variant === "floating" && "right-0",
|
|
235
|
-
className
|
|
164
|
+
className,
|
|
165
|
+
variant === "floating" && "bg-transparent shadow-none"
|
|
236
166
|
)}
|
|
167
|
+
{...rest}
|
|
237
168
|
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
ref={(element) => {
|
|
241
|
-
ref.current = element;
|
|
242
|
-
assignRef(forwardedRef, element);
|
|
243
|
-
}}
|
|
244
|
-
variant="floating"
|
|
245
|
-
onClick={onClick ?? defaultHandleClick}
|
|
246
|
-
onPointerDown={handlePointerDown}
|
|
247
|
-
tabIndex={-1}
|
|
248
|
-
size={size === "medium" ? "normal" : size}
|
|
249
|
-
className={cx(
|
|
250
|
-
buttonClassName,
|
|
251
|
-
variant === "floating" && "bg-transparent shadow-none"
|
|
252
|
-
)}
|
|
253
|
-
{...rest}
|
|
254
|
-
>
|
|
255
|
-
{children}
|
|
256
|
-
</Button>
|
|
257
|
-
</span>
|
|
169
|
+
{children}
|
|
170
|
+
</Button>
|
|
258
171
|
);
|
|
259
172
|
})
|
|
260
173
|
);
|
|
@@ -300,14 +213,16 @@ const InputElement = forwardRef(
|
|
|
300
213
|
style,
|
|
301
214
|
...props
|
|
302
215
|
}: InputElementProps,
|
|
303
|
-
|
|
216
|
+
forwardedRef: ForwardedRef<any>
|
|
304
217
|
) => {
|
|
305
|
-
const {
|
|
218
|
+
const { endWidth, inputRef, size, id, startWidth } = useInputFieldContext();
|
|
219
|
+
|
|
220
|
+
const ref = useMergeRefs(inputRef, forwardedRef);
|
|
306
221
|
|
|
307
222
|
const spacingStyles = getFieldSpacing({
|
|
308
|
-
|
|
309
|
-
labelSize,
|
|
223
|
+
endWidth,
|
|
310
224
|
variant: $variant,
|
|
225
|
+
startWidth,
|
|
311
226
|
});
|
|
312
227
|
|
|
313
228
|
const inputClassName = cx(
|
|
@@ -329,12 +244,20 @@ const InputElement = forwardRef(
|
|
|
329
244
|
className
|
|
330
245
|
);
|
|
331
246
|
|
|
247
|
+
const mergedClassName = useMemo(
|
|
248
|
+
() =>
|
|
249
|
+
mergeConflictingClassNames(inputClassName, {
|
|
250
|
+
categories: ["text"],
|
|
251
|
+
}),
|
|
252
|
+
[inputClassName]
|
|
253
|
+
);
|
|
254
|
+
|
|
332
255
|
if (as === "span") {
|
|
333
256
|
return (
|
|
334
257
|
<span
|
|
335
258
|
ref={ref}
|
|
336
259
|
{...props}
|
|
337
|
-
className={
|
|
260
|
+
className={mergedClassName}
|
|
338
261
|
style={{ ...spacingStyles, ...style }}
|
|
339
262
|
>
|
|
340
263
|
{children}
|
|
@@ -349,7 +272,7 @@ const InputElement = forwardRef(
|
|
|
349
272
|
id={id}
|
|
350
273
|
onSubmit={onSubmit ?? (() => {})}
|
|
351
274
|
{...props}
|
|
352
|
-
className={
|
|
275
|
+
className={mergedClassName}
|
|
353
276
|
style={{ ...spacingStyles, ...style }}
|
|
354
277
|
/>
|
|
355
278
|
);
|
|
@@ -367,7 +290,9 @@ const InputFieldInput = forwardRef(function InputFieldInput(
|
|
|
367
290
|
{ textAlign, variant, ...rest }: InputFieldInputProps,
|
|
368
291
|
forwardedRef: ForwardedRef<HTMLInputElement>
|
|
369
292
|
) {
|
|
370
|
-
const { onFocusChange,
|
|
293
|
+
const { onFocusChange, inputRef } = useInputFieldContext();
|
|
294
|
+
|
|
295
|
+
const ref = useMergeRefs(inputRef, forwardedRef);
|
|
371
296
|
|
|
372
297
|
const handleFocusChange = useCallback(
|
|
373
298
|
(isFocused: boolean) => {
|
|
@@ -376,13 +301,9 @@ const InputFieldInput = forwardRef(function InputFieldInput(
|
|
|
376
301
|
[onFocusChange]
|
|
377
302
|
);
|
|
378
303
|
|
|
379
|
-
useLayoutEffect(() => {
|
|
380
|
-
setInputRef?.(forwardedRef);
|
|
381
|
-
}, [forwardedRef, setInputRef]);
|
|
382
|
-
|
|
383
304
|
return (
|
|
384
305
|
<InputElement
|
|
385
|
-
ref={
|
|
306
|
+
ref={ref}
|
|
386
307
|
$variant={variant}
|
|
387
308
|
$textAlign={textAlign}
|
|
388
309
|
onFocusChange={handleFocusChange}
|
|
@@ -566,10 +487,14 @@ const RootContainer = forwardRef<
|
|
|
566
487
|
interface InputFieldRootProps {
|
|
567
488
|
id?: string;
|
|
568
489
|
children?: ReactNode;
|
|
490
|
+
start?: ReactNode;
|
|
491
|
+
end?: ReactNode;
|
|
492
|
+
label?: ReactNode;
|
|
569
493
|
width?: number;
|
|
570
|
-
|
|
494
|
+
startWidth?: number;
|
|
495
|
+
endWidth?: number;
|
|
571
496
|
/** @default medium */
|
|
572
|
-
size?:
|
|
497
|
+
size?: InputSize;
|
|
573
498
|
renderPopoverContent?: (options: { width: number }) => ReactNode;
|
|
574
499
|
/**
|
|
575
500
|
* if there is a popover, this is the offset from the trigger
|
|
@@ -582,26 +507,36 @@ interface InputFieldRootProps {
|
|
|
582
507
|
}
|
|
583
508
|
|
|
584
509
|
function InputFieldRoot({
|
|
585
|
-
id
|
|
510
|
+
id,
|
|
586
511
|
children,
|
|
587
512
|
width,
|
|
588
513
|
sideOffset = 3,
|
|
589
|
-
|
|
514
|
+
startWidth: startWidthProp,
|
|
515
|
+
endWidth: endWidthProp,
|
|
590
516
|
size = "medium",
|
|
591
517
|
renderPopoverContent,
|
|
592
518
|
onFocusChange,
|
|
593
519
|
style,
|
|
594
520
|
className,
|
|
521
|
+
start,
|
|
522
|
+
end,
|
|
523
|
+
label: labelProp,
|
|
595
524
|
}: InputFieldRootProps) {
|
|
596
|
-
const { fieldId: id, label } = useLabel({ fieldId: idProp });
|
|
597
525
|
const randomId = useId();
|
|
598
|
-
const labelPosition = useLabelPosition();
|
|
599
526
|
|
|
600
527
|
const [isFocused, setIsFocused] = React.useState(false);
|
|
601
528
|
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
529
|
+
const startRef = React.useRef<HTMLSpanElement>(null);
|
|
530
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
531
|
+
const endRef = React.useRef<HTMLSpanElement>(null);
|
|
532
|
+
|
|
533
|
+
const measuredInputSize = useSize(inputRef, "width");
|
|
534
|
+
const measuredStartSize = useSize(startRef, "width");
|
|
535
|
+
const measuredEndSize = useSize(endRef, "width");
|
|
536
|
+
const labelPosition = useLabelPosition();
|
|
537
|
+
const { label } = useLabel({ fieldId: id });
|
|
538
|
+
|
|
539
|
+
const endStyles = getInsetEndStyles(size);
|
|
605
540
|
|
|
606
541
|
const handleFocusChange = useCallback(
|
|
607
542
|
(isFocused: boolean) => {
|
|
@@ -611,58 +546,56 @@ function InputFieldRoot({
|
|
|
611
546
|
[onFocusChange]
|
|
612
547
|
);
|
|
613
548
|
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// eslint-disable-next-line @shopify/prefer-early-return
|
|
618
|
-
useEffect(() => {
|
|
619
|
-
if (inputRef && typeof inputRef !== "function") {
|
|
620
|
-
setMeasuredWidth?.(
|
|
621
|
-
isFocused ? inputRef?.current?.getBoundingClientRect().width : undefined
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
}, [inputRef, isFocused]);
|
|
549
|
+
const startWidth = measuredStartSize?.width ?? startWidthProp;
|
|
550
|
+
const endWidth = measuredEndSize?.width ?? endWidthProp;
|
|
625
551
|
|
|
626
552
|
const contextValue = useMemo(
|
|
627
553
|
(): InputFieldContextValue => ({
|
|
628
|
-
|
|
629
|
-
|
|
554
|
+
startWidth,
|
|
555
|
+
endWidth,
|
|
556
|
+
inputRef,
|
|
557
|
+
startRef,
|
|
558
|
+
endRef,
|
|
630
559
|
size,
|
|
631
560
|
isFocused,
|
|
632
561
|
onFocusChange: handleFocusChange,
|
|
633
|
-
inputRef,
|
|
634
|
-
setInputRef,
|
|
635
|
-
setLabelWidth: setMeasuredLabelSize,
|
|
636
|
-
setButtonWidth: setMeasuredButtonSize,
|
|
637
562
|
id: id ?? randomId,
|
|
638
|
-
label,
|
|
639
|
-
labelPosition,
|
|
640
563
|
}),
|
|
641
|
-
[
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
label,
|
|
652
|
-
labelPosition,
|
|
653
|
-
]
|
|
564
|
+
[startWidth, endWidth, size, isFocused, handleFocusChange, id, randomId]
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const insetLabel = useMemo(
|
|
568
|
+
() => (
|
|
569
|
+
<Label htmlFor={id} className="!text-text-disabled">
|
|
570
|
+
{labelProp ?? label}
|
|
571
|
+
</Label>
|
|
572
|
+
),
|
|
573
|
+
[id, labelProp, label]
|
|
654
574
|
);
|
|
655
575
|
|
|
656
576
|
const rootElement = (
|
|
657
577
|
<RootContainer $width={width} style={style} className={className}>
|
|
658
578
|
{children}
|
|
659
|
-
{
|
|
579
|
+
{start && (
|
|
580
|
+
<span ref={startRef} className={startBaseStyles}>
|
|
581
|
+
{start}
|
|
582
|
+
</span>
|
|
583
|
+
)}
|
|
584
|
+
{(end || label) && (
|
|
585
|
+
<span ref={endRef} className={cx(endStyles, label && end && "gap-1.5")}>
|
|
586
|
+
{labelProp
|
|
587
|
+
? insetLabel
|
|
588
|
+
: label && labelPosition === "inset" && insetLabel}
|
|
589
|
+
{end}
|
|
590
|
+
</span>
|
|
591
|
+
)}
|
|
660
592
|
</RootContainer>
|
|
661
593
|
);
|
|
662
594
|
|
|
663
595
|
const measuredWidthObject = useMemo(
|
|
664
|
-
() =>
|
|
665
|
-
|
|
596
|
+
() =>
|
|
597
|
+
measuredInputSize ? { width: measuredInputSize.width + 4 } : undefined,
|
|
598
|
+
[measuredInputSize]
|
|
666
599
|
);
|
|
667
600
|
|
|
668
601
|
return (
|
|
@@ -711,11 +644,10 @@ const PrimitiveInputField = ({
|
|
|
711
644
|
React.InputHTMLAttributes<HTMLInputElement>,
|
|
712
645
|
HTMLInputElement
|
|
713
646
|
>) => {
|
|
714
|
-
const { id,
|
|
647
|
+
const { id, endWidth } = useInputFieldContext();
|
|
715
648
|
|
|
716
649
|
const spacingStyles = getFieldSpacing({
|
|
717
|
-
|
|
718
|
-
labelSize,
|
|
650
|
+
endWidth,
|
|
719
651
|
});
|
|
720
652
|
const memoizedStyles = useMemo(
|
|
721
653
|
() => ({ ...spacingStyles, ...style }),
|
|
@@ -750,16 +682,20 @@ const InputFieldSubcomponents = {
|
|
|
750
682
|
NumberInput: memo(InputFieldNumberInput),
|
|
751
683
|
DropdownMenu: InputFieldDropdownMenu,
|
|
752
684
|
Button: InputFieldButton,
|
|
753
|
-
Label: InputFieldLabel,
|
|
754
685
|
PrimitiveElement: PrimitiveInputField,
|
|
755
686
|
} as const;
|
|
756
687
|
|
|
757
688
|
// Create the main InputField component
|
|
758
689
|
const InputFieldComponent = forwardRef<HTMLInputElement, InputFieldProps>(
|
|
759
|
-
function InputField(
|
|
690
|
+
function InputField(props: InputFieldProps, ref) {
|
|
760
691
|
const {
|
|
692
|
+
children,
|
|
761
693
|
width,
|
|
762
|
-
|
|
694
|
+
endWidth,
|
|
695
|
+
startWidth,
|
|
696
|
+
label,
|
|
697
|
+
start,
|
|
698
|
+
end,
|
|
763
699
|
size,
|
|
764
700
|
renderPopoverContent,
|
|
765
701
|
sideOffset,
|
|
@@ -772,7 +708,11 @@ const InputFieldComponent = forwardRef<HTMLInputElement, InputFieldProps>(
|
|
|
772
708
|
return (
|
|
773
709
|
<InputFieldSubcomponents.Root
|
|
774
710
|
width={width}
|
|
775
|
-
|
|
711
|
+
endWidth={endWidth}
|
|
712
|
+
startWidth={startWidth}
|
|
713
|
+
label={label}
|
|
714
|
+
start={start}
|
|
715
|
+
end={end}
|
|
776
716
|
size={size}
|
|
777
717
|
renderPopoverContent={renderPopoverContent}
|
|
778
718
|
sideOffset={sideOffset}
|
package/src/components/Label.tsx
CHANGED
|
@@ -1,72 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
CSSProperties,
|
|
3
|
-
LabelHTMLAttributes,
|
|
4
|
-
forwardRef,
|
|
5
|
-
memo,
|
|
6
|
-
useMemo,
|
|
7
|
-
} from "react";
|
|
1
|
+
import React, { LabelHTMLAttributes, forwardRef, memo } from "react";
|
|
8
2
|
import { cx } from "../utils/classNames";
|
|
9
|
-
import { InputFieldSize } from "./InputField";
|
|
10
3
|
|
|
11
4
|
export type LabelPosition = "inset" | "start" | "end" | "above";
|
|
12
5
|
|
|
13
|
-
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
|
|
14
|
-
/** @default start */
|
|
15
|
-
position?: Exclude<LabelPosition, "inset">;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type InsetLabelProps = {
|
|
19
|
-
/** @default medium */
|
|
20
|
-
size?: InputFieldSize;
|
|
21
|
-
/** when used with an InputField.Root and label position is inset, it will add padding to the right of the label to account for the button */
|
|
22
|
-
buttonSize?: number;
|
|
23
|
-
} & LabelHTMLAttributes<HTMLLabelElement>;
|
|
24
|
-
|
|
25
|
-
// apply to <label
|
|
26
|
-
export const insetLabelTextStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] truncate pointer-events-none text-right`;
|
|
27
|
-
|
|
28
|
-
// InsetLabelContainer
|
|
29
|
-
export const insetLabelStyles = `flex items-center absolute top-0 bottom-0 z-label`;
|
|
30
|
-
const getInsetLabelPosition = ({
|
|
31
|
-
buttonSize,
|
|
32
|
-
}: {
|
|
33
|
-
buttonSize?: number;
|
|
34
|
-
}): CSSProperties => ({
|
|
35
|
-
right: `${6 + (buttonSize ?? 0)}px`,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export const InsetLabel = memo(
|
|
39
|
-
forwardRef<HTMLLabelElement, InsetLabelProps>(function InsetLabel(
|
|
40
|
-
{ children, buttonSize, size = "medium", className, style, ...props },
|
|
41
|
-
ref
|
|
42
|
-
) {
|
|
43
|
-
const styles = useMemo(
|
|
44
|
-
() => ({
|
|
45
|
-
...getInsetLabelPosition({
|
|
46
|
-
buttonSize,
|
|
47
|
-
}),
|
|
48
|
-
...style,
|
|
49
|
-
}),
|
|
50
|
-
[buttonSize, style]
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<label
|
|
55
|
-
ref={ref}
|
|
56
|
-
style={styles}
|
|
57
|
-
className={cx(
|
|
58
|
-
insetLabelStyles,
|
|
59
|
-
insetLabelTextStyles,
|
|
60
|
-
size === "small" ? "min-h-[19px]" : "min-h-[27px]",
|
|
61
|
-
className
|
|
62
|
-
)}
|
|
63
|
-
{...props}
|
|
64
|
-
>
|
|
65
|
-
{children}
|
|
66
|
-
</label>
|
|
67
|
-
);
|
|
68
|
-
})
|
|
69
|
-
);
|
|
6
|
+
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
|
|
70
7
|
|
|
71
8
|
const labelStyles = "flex items-center select-none z-label relative";
|
|
72
9
|
const labelTextStyles =
|
|
@@ -74,7 +11,7 @@ const labelTextStyles =
|
|
|
74
11
|
|
|
75
12
|
export const Label = memo(
|
|
76
13
|
forwardRef<HTMLLabelElement, LabelProps>(function Label(
|
|
77
|
-
{ className,
|
|
14
|
+
{ className, children, ...props },
|
|
78
15
|
ref
|
|
79
16
|
) {
|
|
80
17
|
return (
|
|
@@ -83,8 +20,7 @@ export const Label = memo(
|
|
|
83
20
|
className={cx(
|
|
84
21
|
labelStyles,
|
|
85
22
|
labelTextStyles,
|
|
86
|
-
"text-left min-h-
|
|
87
|
-
(position === "start" || position === "end") && "self-start",
|
|
23
|
+
"text-left min-h-input-height",
|
|
88
24
|
className
|
|
89
25
|
)}
|
|
90
26
|
{...props}
|
|
@@ -91,7 +91,13 @@ export const LabeledField = memo(function LabeledField({
|
|
|
91
91
|
<div className={labeledFieldClasses}>
|
|
92
92
|
<Label
|
|
93
93
|
htmlFor={fieldId}
|
|
94
|
-
position
|
|
94
|
+
// we do this so presence avatars are aligned to the right when position is above
|
|
95
|
+
// and when start/end position with textarea it aligns label to the top of the field
|
|
96
|
+
className={
|
|
97
|
+
labelPosition === "start" || labelPosition === "end"
|
|
98
|
+
? "self-start"
|
|
99
|
+
: undefined
|
|
100
|
+
}
|
|
95
101
|
style={labelStyle}
|
|
96
102
|
{...props}
|
|
97
103
|
>
|