@noya-app/noya-designsystem 0.1.48 → 0.1.50

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 +19 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +347 -173
  5. package/dist/index.d.ts +347 -173
  6. package/dist/index.js +3602 -2811
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4761 -3993
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -5
  11. package/src/__tests__/validateDropIndicator.test.ts +4 -4
  12. package/src/components/ActionMenu.tsx +15 -4
  13. package/src/components/Avatar.tsx +2 -2
  14. package/src/components/Banner.tsx +29 -0
  15. package/src/components/BaseToolbar.tsx +2 -2
  16. package/src/components/Button.tsx +2 -2
  17. package/src/components/Checkbox.tsx +2 -2
  18. package/src/components/Chip.tsx +15 -14
  19. package/src/components/Collection.tsx +25 -2
  20. package/src/components/Combobox.tsx +22 -23
  21. package/src/components/ComboboxMenu.tsx +1 -1
  22. package/src/components/Dialog.tsx +2 -2
  23. package/src/components/DimensionInput.tsx +14 -12
  24. package/src/components/EditableText.tsx +3 -1
  25. package/src/components/FillInputField.tsx +2 -2
  26. package/src/components/Grid.tsx +131 -113
  27. package/src/components/GridView.tsx +36 -18
  28. package/src/components/InputField.tsx +116 -171
  29. package/src/components/Label.tsx +14 -73
  30. package/src/components/LabeledField.tsx +13 -2
  31. package/src/components/List.tsx +106 -47
  32. package/src/components/ListView.tsx +52 -31
  33. package/src/components/MediaThumbnail.tsx +14 -6
  34. package/src/components/Message.tsx +8 -8
  35. package/src/components/NoyaLogo.tsx +41 -0
  36. package/src/components/Popover.tsx +9 -6
  37. package/src/components/Section.tsx +172 -0
  38. package/src/components/SegmentedControl.tsx +1 -1
  39. package/src/components/SelectMenu.tsx +9 -4
  40. package/src/components/Slider.tsx +16 -7
  41. package/src/components/Sortable.tsx +186 -47
  42. package/src/components/UserPointer.tsx +1 -1
  43. package/src/components/ai-assistant/AIAssistantLayout.tsx +102 -0
  44. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +71 -0
  45. package/src/components/file-explorer/FileExplorerLayout.tsx +71 -0
  46. package/src/components/internal/Menu.tsx +20 -10
  47. package/src/components/internal/SelectItem.tsx +3 -2
  48. package/src/components/pipeline/PipelineResultLayout.tsx +32 -0
  49. package/src/index.css +118 -112
  50. package/src/index.tsx +9 -0
  51. package/src/theme/index.ts +2 -0
  52. package/src/utils/classNames.ts +76 -3
  53. package/src/utils/inputs.ts +21 -0
  54. package/src/utils/moveTreeItem.ts +99 -0
  55. package/tailwind.config.ts +82 -75
@@ -6,7 +6,6 @@ import React, {
6
6
  FocusEventHandler,
7
7
  ForwardedRef,
8
8
  forwardRef,
9
- LabelHTMLAttributes,
10
9
  memo,
11
10
  ReactNode,
12
11
  useCallback,
@@ -16,45 +15,35 @@ import React, {
16
15
  useState,
17
16
  } from "react";
18
17
  import { useLabel, useLabelPosition } from "../hooks/useLabel";
19
- import { cx } from "../utils/classNames";
18
+ import { cx, mergeConflictingClassNames } from "../utils/classNames";
20
19
  import handleNudge from "../utils/handleNudge";
20
+ import { getInsetEndStyles, InputSize, startBaseStyles } from "../utils/inputs";
21
21
  import { Button, ButtonRootProps } from "./Button";
22
22
  import { DropdownMenu } from "./DropdownMenu";
23
23
  import { MenuItem } from "./internal/Menu";
24
24
  import TextInput, { TextInputProps } from "./internal/TextInput";
25
- import { InsetLabel, LabelPosition } from "./Label";
25
+ import { Label } from "./Label";
26
26
  import { Popover } from "./Popover";
27
27
 
28
28
  export type InputFieldSize = "small" | "medium" | "large";
29
29
 
30
30
  type InputFieldContextValue = {
31
- labelWidth?: number;
32
- buttonWidth?: number;
33
- startWidth?: number;
34
- /** @default medium */
35
- size: InputFieldSize;
36
31
  isFocused: boolean;
37
32
  onFocusChange: (isFocused: boolean) => void;
38
33
  inputRef: ForwardedRef<HTMLInputElement>;
39
- buttonRef: ForwardedRef<HTMLButtonElement>;
40
- labelRef: ForwardedRef<HTMLLabelElement>;
41
34
  startRef: ForwardedRef<HTMLSpanElement>;
42
- id?: string;
43
- label?: ReactNode;
44
- labelPosition?: LabelPosition;
45
- };
35
+ endRef: ForwardedRef<HTMLSpanElement>;
36
+ endWidth?: number;
37
+ } & Pick<InputFieldRootProps, "endWidth" | "startWidth" | "size" | "id">;
46
38
 
47
39
  const InputFieldContext = createContext<InputFieldContextValue>({
48
- labelWidth: undefined,
49
- buttonWidth: undefined,
50
40
  startWidth: undefined,
51
41
  size: "medium",
52
42
  isFocused: false,
53
43
  onFocusChange: () => {},
54
44
  inputRef: null,
55
- buttonRef: null,
56
- labelRef: null,
57
45
  startRef: null,
46
+ endRef: null,
58
47
  });
59
48
 
60
49
  export const useInputFieldContext = () => useContext(InputFieldContext);
@@ -62,68 +51,22 @@ export const useInputFieldContext = () => useContext(InputFieldContext);
62
51
  const noop = () => {};
63
52
 
64
53
  export const getFieldSpacing = ({
65
- buttonWidth = 0,
66
- labelWidth = 0,
67
- startWidth,
54
+ endWidth = 0,
55
+ startWidth = 0,
68
56
  variant,
69
57
  }: {
70
- buttonWidth?: number;
71
- labelWidth?: number;
58
+ endWidth?: number;
72
59
  startWidth?: number;
73
60
  variant?: InputFieldVariant;
74
61
  }) => ({
75
62
  paddingLeft: `${variant === "bare" ? startWidth : startWidth ? startWidth + 12 : 6}px`,
76
- paddingRight: `${variant === "bare" ? 0 : labelWidth + buttonWidth + (labelWidth || buttonWidth ? 12 : 6)}px`,
63
+ paddingRight: `${variant === "bare" ? endWidth : endWidth ? endWidth + 12 : 6}px`,
77
64
  });
78
65
 
79
- /* ----------------------------------------------------------------------------
80
- * Label
81
- * ------------------------------------------------------------------------- */
82
-
83
- /**
84
- * Inserts a Label at the end of the input, unless overridden by LabeledField.
85
- */
86
- const InputFieldLabel = memo(
87
- forwardRef(function InputFieldLabel(
88
- props: LabelHTMLAttributes<HTMLLabelElement>,
89
- forwardedRef: ForwardedRef<HTMLLabelElement>
90
- ) {
91
- const { labelRef, size, buttonWidth, label, id, labelPosition } =
92
- useInputFieldContext();
93
-
94
- const ref = useMergeRefs(labelRef, forwardedRef);
95
-
96
- if (labelPosition !== "inset") return null;
97
-
98
- return (
99
- <InsetLabel
100
- ref={ref}
101
- size={size}
102
- buttonWidth={buttonWidth}
103
- htmlFor={id}
104
- {...props}
105
- >
106
- {label}
107
- </InsetLabel>
108
- );
109
- })
110
- );
111
-
112
66
  /* ----------------------------------------------------------------------------
113
67
  * Dropdown
114
68
  * ------------------------------------------------------------------------- */
115
69
 
116
- const DropdownContainer = forwardRef<
117
- HTMLSpanElement,
118
- { className?: string; children?: React.ReactNode }
119
- >(({ className, ...props }, ref) => (
120
- <span
121
- ref={ref}
122
- className={cx("absolute inset-y-0 right-0 flex flex-col", className)}
123
- {...props}
124
- />
125
- ));
126
-
127
70
  interface InputFieldDropdownProps<T extends string> {
128
71
  id?: string;
129
72
  items: MenuItem<T>[];
@@ -135,20 +78,18 @@ const InputFieldDropdownMenu = memoGeneric(function InputFieldDropdownMenu<
135
78
  T extends string,
136
79
  >({ id, items, onSelect, children }: InputFieldDropdownProps<T>) {
137
80
  return (
138
- <DropdownContainer>
139
- <DropdownMenu<T>
140
- items={items}
141
- onSelect={onSelect}
142
- align="end"
143
- sideOffset={8}
144
- >
145
- {children || (
146
- <InputFieldButton variant="floating" id={id}>
147
- <DropdownChevronIcon />
148
- </InputFieldButton>
149
- )}
150
- </DropdownMenu>
151
- </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>
152
93
  );
153
94
  });
154
95
 
@@ -161,8 +102,6 @@ const InputFieldButton = memo(
161
102
  forwardRef(function InputFieldButton(
162
103
  props: React.ButtonHTMLAttributes<HTMLButtonElement> &
163
104
  ButtonRootProps & {
164
- buttonClassName?: string;
165
- buttonStyle?: React.CSSProperties;
166
105
  variant?: "floating" | "normal";
167
106
  },
168
107
  forwardedRef: ForwardedRef<HTMLButtonElement>
@@ -172,15 +111,11 @@ const InputFieldButton = memo(
172
111
  onClick,
173
112
  className,
174
113
  style,
175
- buttonClassName,
176
- buttonStyle,
177
114
  variant = "normal",
178
115
  as = "button",
179
116
  ...rest
180
117
  } = props;
181
- const { size, inputRef, buttonRef } = useInputFieldContext();
182
-
183
- const ref = useMergeRefs(buttonRef, forwardedRef);
118
+ const { size, inputRef } = useInputFieldContext();
184
119
 
185
120
  const defaultHandleClick = useCallback(
186
121
  (event: React.MouseEvent<HTMLButtonElement>) => {
@@ -204,41 +139,35 @@ const InputFieldButton = memo(
204
139
  []
205
140
  );
206
141
 
142
+ const memoizedStyle = useMemo(
143
+ () => ({
144
+ ...(variant === "floating" && {
145
+ paddingLeft: "0px",
146
+ paddingRight: "0px",
147
+ }),
148
+ ...style,
149
+ }),
150
+ [style, variant]
151
+ );
152
+
207
153
  return (
208
- <span
209
- style={style}
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}
210
163
  className={cx(
211
- "absolute",
212
- size === "large"
213
- ? "right-1 top-2.5"
214
- : size === "medium"
215
- ? "right-1 top-1"
216
- : size === "small"
217
- ? "right-0.5 top-0.5"
218
- : "",
219
- "z-label",
220
- variant === "floating" && "right-0",
221
- className
164
+ className,
165
+ variant === "floating" && "bg-transparent shadow-none"
222
166
  )}
167
+ {...rest}
223
168
  >
224
- <Button
225
- as={as}
226
- ref={ref}
227
- variant="floating"
228
- onClick={onClick ?? defaultHandleClick}
229
- onPointerDown={handlePointerDown}
230
- tabIndex={-1}
231
- size={size === "medium" ? "normal" : size}
232
- className={cx(
233
- buttonClassName,
234
- variant === "floating" && "bg-transparent shadow-none"
235
- )}
236
- style={buttonStyle}
237
- {...rest}
238
- >
239
- {children}
240
- </Button>
241
- </span>
169
+ {children}
170
+ </Button>
242
171
  );
243
172
  })
244
173
  );
@@ -286,14 +215,12 @@ const InputElement = forwardRef(
286
215
  }: InputElementProps,
287
216
  forwardedRef: ForwardedRef<any>
288
217
  ) => {
289
- const { buttonWidth, inputRef, labelWidth, size, id, startWidth } =
290
- useInputFieldContext();
218
+ const { endWidth, inputRef, size, id, startWidth } = useInputFieldContext();
291
219
 
292
220
  const ref = useMergeRefs(inputRef, forwardedRef);
293
221
 
294
222
  const spacingStyles = getFieldSpacing({
295
- buttonWidth,
296
- labelWidth,
223
+ endWidth,
297
224
  variant: $variant,
298
225
  startWidth,
299
226
  });
@@ -317,12 +244,20 @@ const InputElement = forwardRef(
317
244
  className
318
245
  );
319
246
 
247
+ const mergedClassName = useMemo(
248
+ () =>
249
+ mergeConflictingClassNames(inputClassName, {
250
+ categories: ["text"],
251
+ }),
252
+ [inputClassName]
253
+ );
254
+
320
255
  if (as === "span") {
321
256
  return (
322
257
  <span
323
258
  ref={ref}
324
259
  {...props}
325
- className={inputClassName}
260
+ className={mergedClassName}
326
261
  style={{ ...spacingStyles, ...style }}
327
262
  >
328
263
  {children}
@@ -337,7 +272,7 @@ const InputElement = forwardRef(
337
272
  id={id}
338
273
  onSubmit={onSubmit ?? (() => {})}
339
274
  {...props}
340
- className={inputClassName}
275
+ className={mergedClassName}
341
276
  style={{ ...spacingStyles, ...style }}
342
277
  />
343
278
  );
@@ -549,16 +484,17 @@ const RootContainer = forwardRef<
549
484
  />
550
485
  ));
551
486
 
552
- const startStyles = "absolute left-1.5 inset-y-0 flex items-center";
553
-
554
487
  interface InputFieldRootProps {
555
488
  id?: string;
556
489
  children?: ReactNode;
490
+ start?: ReactNode;
491
+ end?: ReactNode;
492
+ label?: ReactNode;
557
493
  width?: number;
558
- labelWidth?: number;
559
494
  startWidth?: number;
495
+ endWidth?: number;
560
496
  /** @default medium */
561
- size?: InputFieldSize;
497
+ size?: InputSize;
562
498
  renderPopoverContent?: (options: { width: number }) => ReactNode;
563
499
  /**
564
500
  * if there is a popover, this is the offset from the trigger
@@ -568,38 +504,39 @@ interface InputFieldRootProps {
568
504
  onFocusChange?: (isFocused: boolean) => void;
569
505
  style?: React.CSSProperties;
570
506
  className?: string;
571
- start?: ReactNode;
572
507
  }
573
508
 
574
509
  function InputFieldRoot({
575
- id: idProp,
510
+ id,
576
511
  children,
577
512
  width,
578
513
  sideOffset = 3,
579
- labelWidth,
580
- startWidth,
514
+ startWidth: startWidthProp,
515
+ endWidth: endWidthProp,
581
516
  size = "medium",
582
517
  renderPopoverContent,
583
518
  onFocusChange,
584
519
  style,
585
520
  className,
586
521
  start,
522
+ end,
523
+ label: labelProp,
587
524
  }: InputFieldRootProps) {
588
- const { fieldId: id, label } = useLabel({ fieldId: idProp });
589
525
  const randomId = useId();
590
- const labelPosition = useLabelPosition();
591
526
 
592
527
  const [isFocused, setIsFocused] = React.useState(false);
593
528
 
594
529
  const startRef = React.useRef<HTMLSpanElement>(null);
595
- const labelRef = React.useRef<HTMLLabelElement>(null);
596
- const buttonRef = React.useRef<HTMLButtonElement>(null);
597
530
  const inputRef = React.useRef<HTMLInputElement>(null);
531
+ const endRef = React.useRef<HTMLSpanElement>(null);
598
532
 
599
- const measuredLabelSize = useSize(labelRef, "width");
600
- const measuredButtonSize = useSize(buttonRef, "width");
601
533
  const measuredInputSize = useSize(inputRef, "width");
602
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);
603
540
 
604
541
  const handleFocusChange = useCallback(
605
542
  (isFocused: boolean) => {
@@ -609,48 +546,49 @@ function InputFieldRoot({
609
546
  [onFocusChange]
610
547
  );
611
548
 
549
+ const startWidth = measuredStartSize?.width ?? startWidthProp;
550
+ const endWidth = measuredEndSize?.width ?? endWidthProp;
551
+
612
552
  const contextValue = useMemo(
613
553
  (): InputFieldContextValue => ({
614
- labelWidth: measuredLabelSize?.width ?? labelWidth,
615
- buttonWidth: measuredButtonSize?.width,
616
- startWidth: measuredStartSize?.width ?? startWidth,
554
+ startWidth,
555
+ endWidth,
617
556
  inputRef,
618
- buttonRef,
619
- labelRef,
620
557
  startRef,
558
+ endRef,
621
559
  size,
622
560
  isFocused,
623
561
  onFocusChange: handleFocusChange,
624
562
  id: id ?? randomId,
625
- label,
626
- labelPosition,
627
563
  }),
628
- [
629
- measuredLabelSize,
630
- labelWidth,
631
- measuredButtonSize,
632
- size,
633
- isFocused,
634
- handleFocusChange,
635
- inputRef,
636
- id,
637
- randomId,
638
- label,
639
- labelPosition,
640
- measuredStartSize,
641
- startWidth,
642
- ]
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]
643
574
  );
644
575
 
645
576
  const rootElement = (
646
577
  <RootContainer $width={width} style={style} className={className}>
647
578
  {children}
648
579
  {start && (
649
- <span ref={startRef} className={startStyles}>
580
+ <span ref={startRef} className={startBaseStyles}>
650
581
  {start}
651
582
  </span>
652
583
  )}
653
- {label && <InputFieldLabel />}
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
+ )}
654
592
  </RootContainer>
655
593
  );
656
594
 
@@ -706,11 +644,10 @@ const PrimitiveInputField = ({
706
644
  React.InputHTMLAttributes<HTMLInputElement>,
707
645
  HTMLInputElement
708
646
  >) => {
709
- const { id, labelWidth, buttonWidth } = useInputFieldContext();
647
+ const { id, endWidth } = useInputFieldContext();
710
648
 
711
649
  const spacingStyles = getFieldSpacing({
712
- buttonWidth,
713
- labelWidth,
650
+ endWidth,
714
651
  });
715
652
  const memoizedStyles = useMemo(
716
653
  () => ({ ...spacingStyles, ...style }),
@@ -745,16 +682,20 @@ const InputFieldSubcomponents = {
745
682
  NumberInput: memo(InputFieldNumberInput),
746
683
  DropdownMenu: InputFieldDropdownMenu,
747
684
  Button: InputFieldButton,
748
- Label: InputFieldLabel,
749
685
  PrimitiveElement: PrimitiveInputField,
750
686
  } as const;
751
687
 
752
688
  // Create the main InputField component
753
689
  const InputFieldComponent = forwardRef<HTMLInputElement, InputFieldProps>(
754
- function InputField({ children, ...props }: InputFieldProps, ref) {
690
+ function InputField(props: InputFieldProps, ref) {
755
691
  const {
692
+ children,
756
693
  width,
757
- labelWidth,
694
+ endWidth,
695
+ startWidth,
696
+ label,
697
+ start,
698
+ end,
758
699
  size,
759
700
  renderPopoverContent,
760
701
  sideOffset,
@@ -767,7 +708,11 @@ const InputFieldComponent = forwardRef<HTMLInputElement, InputFieldProps>(
767
708
  return (
768
709
  <InputFieldSubcomponents.Root
769
710
  width={width}
770
- labelWidth={labelWidth}
711
+ endWidth={endWidth}
712
+ startWidth={startWidth}
713
+ label={label}
714
+ start={start}
715
+ end={end}
771
716
  size={size}
772
717
  renderPopoverContent={renderPopoverContent}
773
718
  sideOffset={sideOffset}
@@ -1,72 +1,9 @@
1
- import React, {
2
- CSSProperties,
3
- LabelHTMLAttributes,
4
- forwardRef,
5
- memo,
6
- useMemo,
7
- } from "react";
8
- import { cx } from "../utils/classNames";
9
- import { InputFieldSize } from "./InputField";
1
+ import React, { LabelHTMLAttributes, forwardRef, memo } from "react";
2
+ import { mergeConflictingClassNames } from "../utils/classNames";
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
- buttonWidth?: 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
- buttonWidth,
32
- }: {
33
- buttonWidth?: number;
34
- }): CSSProperties => ({
35
- right: `${6 + (buttonWidth ?? 0)}px`,
36
- });
37
-
38
- export const InsetLabel = memo(
39
- forwardRef<HTMLLabelElement, InsetLabelProps>(function InsetLabel(
40
- { children, buttonWidth, size = "medium", className, style, ...props },
41
- ref
42
- ) {
43
- const styles = useMemo(
44
- () => ({
45
- ...getInsetLabelPosition({
46
- buttonWidth,
47
- }),
48
- ...style,
49
- }),
50
- [buttonWidth, 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,18 +11,22 @@ 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 (
81
18
  <label
82
19
  ref={ref}
83
- className={cx(
84
- labelStyles,
85
- labelTextStyles,
86
- "text-left min-h-[27px]",
87
- (position === "start" || position === "end") && "self-start",
88
- className
20
+ className={mergeConflictingClassNames(
21
+ [
22
+ labelStyles,
23
+ labelTextStyles,
24
+ "text-left min-h-input-height",
25
+ className,
26
+ ],
27
+ {
28
+ categories: ["color", "font"],
29
+ }
89
30
  )}
90
31
  {...props}
91
32
  >
@@ -4,6 +4,7 @@ import {
4
4
  LabelContext,
5
5
  LabelPositionContext,
6
6
  LabelPositionContextValue,
7
+ useLabelPosition,
7
8
  useLabelWidth,
8
9
  } from "../hooks/useLabel";
9
10
  import { cx } from "../utils/classNames";
@@ -23,7 +24,7 @@ const labelPositionInset: LabelPositionContextValue = {
23
24
  export const LabeledField = memo(function LabeledField({
24
25
  children,
25
26
  label,
26
- labelPosition = "start",
27
+ labelPosition: labelPositionProp,
27
28
  fieldId,
28
29
  minWidth = 100,
29
30
  className,
@@ -44,6 +45,10 @@ export const LabeledField = memo(function LabeledField({
44
45
  const labelWidthFromContext = useLabelWidth();
45
46
  const labelWidth = labelWidthFromContext ?? minWidth;
46
47
 
48
+ const labelPositionFromContext = useLabelPosition();
49
+ const labelPosition =
50
+ labelPositionProp ?? labelPositionFromContext ?? "start";
51
+
47
52
  const labeledFieldClasses = useMemo(
48
53
  () => labeledFieldStyles(labelPosition),
49
54
  [labelPosition]
@@ -91,7 +96,13 @@ export const LabeledField = memo(function LabeledField({
91
96
  <div className={labeledFieldClasses}>
92
97
  <Label
93
98
  htmlFor={fieldId}
94
- position={labelPosition}
99
+ // we do this so presence avatars are aligned to the right when position is above
100
+ // and when start/end position with textarea it aligns label to the top of the field
101
+ className={
102
+ labelPosition === "start" || labelPosition === "end"
103
+ ? "self-start"
104
+ : undefined
105
+ }
95
106
  style={labelStyle}
96
107
  {...props}
97
108
  >