@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.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +18 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +389 -241
  5. package/dist/index.d.ts +389 -241
  6. package/dist/index.js +4802 -3990
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4715 -3915
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +7 -7
  11. package/src/__tests__/validateDropIndicator.test.ts +263 -0
  12. package/src/components/ActionMenu.tsx +51 -0
  13. package/src/components/ActivityIndicator.tsx +7 -1
  14. package/src/components/BaseToolbar.tsx +2 -2
  15. package/src/components/Checkbox.tsx +2 -2
  16. package/src/components/Chip.tsx +7 -6
  17. package/src/components/Collection.tsx +30 -2
  18. package/src/components/Combobox.tsx +27 -15
  19. package/src/components/ComboboxMenu.tsx +15 -6
  20. package/src/components/Dialog.tsx +17 -12
  21. package/src/components/DimensionInput.tsx +14 -12
  22. package/src/components/Drawer.tsx +98 -0
  23. package/src/components/EditableText.tsx +3 -1
  24. package/src/components/FillInputField.tsx +2 -2
  25. package/src/components/Grid.tsx +161 -111
  26. package/src/components/GridView.tsx +73 -41
  27. package/src/components/InputField.tsx +148 -208
  28. package/src/components/Label.tsx +4 -68
  29. package/src/components/LabeledField.tsx +7 -1
  30. package/src/components/List.tsx +135 -50
  31. package/src/components/ListView.tsx +63 -43
  32. package/src/components/MediaThumbnail.tsx +117 -0
  33. package/src/components/Message.tsx +8 -8
  34. package/src/components/NoyaLogo.tsx +41 -0
  35. package/src/components/SearchCompletionMenu.tsx +30 -16
  36. package/src/components/SegmentedControl.tsx +1 -1
  37. package/src/components/SelectMenu.tsx +28 -21
  38. package/src/components/Slider.tsx +16 -7
  39. package/src/components/Sortable.tsx +125 -62
  40. package/src/components/internal/Menu.tsx +45 -25
  41. package/src/components/internal/MenuViewport.tsx +7 -1
  42. package/src/components/internal/SelectItem.tsx +53 -28
  43. package/src/components/internal/__tests__/Menu.test.tsx +12 -0
  44. package/src/components/workspace/DrawerWorkspaceLayout.tsx +86 -0
  45. package/src/components/workspace/PanelWorkspaceLayout.tsx +102 -0
  46. package/src/components/{WorkspaceLayout.tsx → workspace/WorkspaceLayout.tsx} +109 -106
  47. package/src/components/workspace/types.ts +31 -0
  48. package/src/contexts/DialogContext.tsx +49 -34
  49. package/src/hooks/usePreservePanelSize.tsx +1 -5
  50. package/src/index.css +8 -1
  51. package/src/index.tsx +6 -1
  52. package/src/theme/index.ts +2 -0
  53. package/src/utils/classNames.ts +51 -3
  54. package/src/utils/inputs.ts +21 -0
  55. package/tailwind.config.ts +8 -0
@@ -1,57 +1,49 @@
1
1
  import { DropdownChevronIcon } from "@noya-app/noya-icons";
2
- import { assignRef, memoGeneric } from "@noya-app/react-utils";
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 { InsetLabel, LabelPosition } from "./Label";
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?: ForwardedRef<HTMLInputElement>;
41
- setInputRef?: (ref: ForwardedRef<HTMLInputElement>) => void;
42
- setLabelWidth?: (width: number) => void;
43
- setButtonWidth?: (width: number) => void;
44
- id?: string;
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
- labelSize: undefined,
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
- buttonSize = 0,
63
- labelSize = 0,
54
+ endWidth = 0,
55
+ startWidth = 0,
64
56
  variant,
65
57
  }: {
66
- buttonSize?: number;
67
- labelSize?: number;
58
+ endWidth?: number;
59
+ startWidth?: number;
68
60
  variant?: InputFieldVariant;
69
61
  }) => ({
70
- paddingLeft: variant === "bare" ? "0px" : "6px",
71
- paddingRight: `${variant === "bare" ? 0 : labelSize + buttonSize + (labelSize || buttonSize ? 12 : 6)}px`,
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
- <DropdownContainer>
147
- <DropdownMenu<T>
148
- items={items}
149
- onSelect={onSelect}
150
- align="end"
151
- sideOffset={8}
152
- >
153
- {children || (
154
- <InputFieldButton variant="floating" id={id}>
155
- <DropdownChevronIcon />
156
- </InputFieldButton>
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
- buttonClassName,
113
+ style,
182
114
  variant = "normal",
183
115
  as = "button",
184
116
  ...rest
185
117
  } = props;
186
- const { size, inputRef, setButtonWidth } = useInputFieldContext();
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
- <span
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
- "absolute",
226
- size === "large"
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
- <Button
239
- as={as}
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
- ref: ForwardedRef<any>
216
+ forwardedRef: ForwardedRef<any>
304
217
  ) => {
305
- const { buttonSize, labelSize, size, id } = useInputFieldContext();
218
+ const { endWidth, inputRef, size, id, startWidth } = useInputFieldContext();
219
+
220
+ const ref = useMergeRefs(inputRef, forwardedRef);
306
221
 
307
222
  const spacingStyles = getFieldSpacing({
308
- buttonSize,
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={inputClassName}
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={inputClassName}
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, setInputRef } = useInputFieldContext();
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={forwardedRef}
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
- labelSize?: number;
494
+ startWidth?: number;
495
+ endWidth?: number;
571
496
  /** @default medium */
572
- size?: InputFieldSize;
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: idProp,
510
+ id,
586
511
  children,
587
512
  width,
588
513
  sideOffset = 3,
589
- labelSize,
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 [measuredLabelSize, setMeasuredLabelSize] = React.useState<number>();
603
- const [measuredButtonSize, setMeasuredButtonSize] = React.useState<number>();
604
- const [measuredWidth, setMeasuredWidth] = React.useState<number>();
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 [inputRef, setInputRef] =
615
- React.useState<ForwardedRef<HTMLInputElement>>();
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
- labelSize: measuredLabelSize ?? labelSize,
629
- buttonSize: measuredButtonSize,
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
- measuredLabelSize,
643
- labelSize,
644
- measuredButtonSize,
645
- size,
646
- isFocused,
647
- handleFocusChange,
648
- inputRef,
649
- id,
650
- randomId,
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
- {label && <InputFieldLabel />}
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
- () => (measuredWidth ? { width: measuredWidth + 4 } : undefined),
665
- [measuredWidth]
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, labelSize, buttonSize } = useInputFieldContext();
647
+ const { id, endWidth } = useInputFieldContext();
715
648
 
716
649
  const spacingStyles = getFieldSpacing({
717
- buttonSize,
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({ children, ...props }: InputFieldProps, ref) {
690
+ function InputField(props: InputFieldProps, ref) {
760
691
  const {
692
+ children,
761
693
  width,
762
- labelSize,
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
- labelSize={labelSize}
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}
@@ -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, position = "start", children, ...props },
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-[27px]",
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={labelPosition}
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
  >