@noya-app/noya-designsystem 0.1.43 → 0.1.45

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 (44) hide show
  1. package/.turbo/turbo-build.log +12 -9
  2. package/CHANGELOG.md +15 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +323 -80
  5. package/dist/index.d.ts +323 -80
  6. package/dist/index.js +2126 -1373
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2012 -1272
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -3
  11. package/src/components/AnimatePresence.tsx +10 -1
  12. package/src/components/Avatar.tsx +2 -0
  13. package/src/components/Checkbox.tsx +6 -1
  14. package/src/components/Combobox.tsx +28 -14
  15. package/src/components/ComboboxMenu.tsx +30 -15
  16. package/src/components/CommandPalette.tsx +69 -0
  17. package/src/components/ContextMenu.tsx +33 -134
  18. package/src/components/DropdownMenu.tsx +47 -155
  19. package/src/components/Fade.tsx +62 -0
  20. package/src/components/InputField.tsx +109 -133
  21. package/src/components/Label.tsx +81 -7
  22. package/src/components/LabeledField.tsx +112 -0
  23. package/src/components/ListView.tsx +55 -52
  24. package/src/components/Popover.tsx +12 -1
  25. package/src/components/SearchCompletionMenu.tsx +206 -0
  26. package/src/components/SegmentedControl.tsx +5 -2
  27. package/src/components/SelectMenu.tsx +108 -124
  28. package/src/components/SidebarList.tsx +252 -0
  29. package/src/components/Slider.tsx +18 -26
  30. package/src/components/Text.tsx +1 -0
  31. package/src/components/TextArea.tsx +4 -1
  32. package/src/components/Toolbar.tsx +6 -2
  33. package/src/components/TreeView.tsx +3 -0
  34. package/src/components/WorkspaceLayout.tsx +38 -18
  35. package/src/components/internal/Menu.tsx +58 -10
  36. package/src/components/internal/MenuViewport.tsx +151 -0
  37. package/src/components/internal/SelectItem.tsx +111 -0
  38. package/src/hooks/useIndent.ts +12 -0
  39. package/src/hooks/useLabel.ts +51 -0
  40. package/src/hooks/usePreservePanelSize.tsx +50 -19
  41. package/src/index.tsx +12 -5
  42. package/src/utils/combobox.ts +4 -1
  43. package/src/utils/createSectionedMenu.ts +11 -8
  44. package/src/utils/selection.ts +36 -0
@@ -18,20 +18,19 @@ import React, {
18
18
  useRef,
19
19
  useState,
20
20
  } from "react";
21
+ import { useLabel, useLabelPosition } from "../hooks/useLabel";
21
22
  import { cx } from "../utils/classNames";
22
23
  import handleNudge from "../utils/handleNudge";
23
- import { Button } from "./Button";
24
+ import { Button, ButtonRootProps } from "./Button";
24
25
  import { DropdownMenu } from "./DropdownMenu";
25
26
  import { MenuItem } from "./internal/Menu";
26
27
  import TextInput, { TextInputProps } from "./internal/TextInput";
28
+ import { InsetLabel, LabelPosition } from "./Label";
27
29
  import { Popover } from "./Popover";
28
30
 
29
- type LabelPosition = "start" | "end";
30
-
31
31
  export type InputFieldSize = "small" | "medium" | "large";
32
32
 
33
33
  type InputFieldContextValue = {
34
- labelPosition: LabelPosition;
35
34
  labelSize?: number;
36
35
  buttonSize?: number;
37
36
  /** @default medium */
@@ -43,51 +42,11 @@ type InputFieldContextValue = {
43
42
  setLabelWidth?: (width: number) => void;
44
43
  setButtonWidth?: (width: number) => void;
45
44
  id?: string;
45
+ label?: ReactNode;
46
+ labelPosition?: LabelPosition;
46
47
  };
47
48
 
48
- const getLabelPosition = ({
49
- labelPosition,
50
- size,
51
- buttonSize,
52
- }: {
53
- labelPosition: LabelPosition;
54
- size: InputFieldSize;
55
- buttonSize?: number;
56
- }) => ({
57
- left:
58
- labelPosition === "start"
59
- ? size === "large"
60
- ? "6px"
61
- : size === "medium"
62
- ? "2px"
63
- : "0px"
64
- : "",
65
- right:
66
- labelPosition === "end"
67
- ? size === "large"
68
- ? `${6 + (buttonSize ?? 0)}px`
69
- : size === "medium"
70
- ? `${2 + (buttonSize ?? 0)}px`
71
- : `${buttonSize ?? 0}px`
72
- : "",
73
- });
74
-
75
- const getInputFieldSpacing = ({
76
- labelPosition,
77
- buttonSize,
78
- labelSize,
79
- }: {
80
- labelPosition: LabelPosition;
81
- size?: InputFieldSize;
82
- buttonSize?: number;
83
- labelSize?: number;
84
- }) => ({
85
- paddingLeft: `${labelSize && labelPosition === "start" ? 6 + labelSize : 6}px`,
86
- paddingRight: `${labelPosition === "end" ? 6 + (labelSize ?? 0) + (buttonSize ? buttonSize + 6 : 0) : 6}px`,
87
- });
88
-
89
49
  const InputFieldContext = createContext<InputFieldContextValue>({
90
- labelPosition: "end",
91
50
  labelSize: undefined,
92
51
  buttonSize: undefined,
93
52
  size: "medium",
@@ -95,69 +54,37 @@ const InputFieldContext = createContext<InputFieldContextValue>({
95
54
  onFocusChange: () => {},
96
55
  });
97
56
 
57
+ export const useInputFieldContext = () => useContext(InputFieldContext);
58
+
98
59
  const noop = () => {};
99
60
 
61
+ export const getFieldSpacing = ({
62
+ buttonSize = 0,
63
+ labelSize = 0,
64
+ variant,
65
+ }: {
66
+ buttonSize?: number;
67
+ labelSize?: number;
68
+ variant?: InputFieldVariant;
69
+ }) => ({
70
+ paddingLeft: variant === "bare" ? "0px" : "6px",
71
+ paddingRight: `${variant === "bare" ? 0 : labelSize + buttonSize + (labelSize || buttonSize ? 12 : 6)}px`,
72
+ });
73
+
100
74
  /* ----------------------------------------------------------------------------
101
75
  * Label
102
76
  * ------------------------------------------------------------------------- */
103
77
 
104
- interface LabelContainerProps {
105
- labelPosition: LabelPosition;
106
- size: InputFieldSize;
107
- className?: string;
108
- children?: React.ReactNode;
109
- style?: React.CSSProperties;
110
- htmlFor?: string;
111
- }
112
-
113
- const LabelContainer = forwardRef(function LabelContainer(
114
- {
115
- labelPosition: $labelPosition,
116
- size: $size,
117
- className,
118
- style,
119
- ...props
120
- }: LabelContainerProps,
121
- ref: ForwardedRef<HTMLLabelElement>
122
- ) {
123
- const { buttonSize } = useContext(InputFieldContext);
124
- const spacingStyles = getLabelPosition({
125
- labelPosition: $labelPosition,
126
- size: $size,
127
- buttonSize,
128
- });
129
-
130
- const memoizedStyles = useMemo(
131
- () => ({ ...spacingStyles, ...style }),
132
- [spacingStyles, style]
133
- );
134
-
135
- return (
136
- <label
137
- ref={ref}
138
- className={cx(
139
- "absolute top-0 bottom-0 flex items-center font-sans text-label font-bold uppercase text-text-disabled select-none z-label pointer-events-none",
140
- $labelPosition === "start"
141
- ? `justify-start pl-1.5`
142
- : $labelPosition === "end" && "justify-end pr-1.5",
143
- className
144
- )}
145
- style={memoizedStyles}
146
- {...props}
147
- />
148
- );
149
- });
150
-
151
78
  /**
152
- * Inserts a Label at the start or end of the input given a `labelPosition`on InputField.Root.
79
+ * Inserts a Label at the end of the input, unless overridden by LabeledField.
153
80
  */
154
81
  const InputFieldLabel = memo(
155
82
  forwardRef(function InputFieldLabel(
156
83
  props: LabelHTMLAttributes<HTMLLabelElement>,
157
84
  forwardedRef: ForwardedRef<HTMLLabelElement>
158
85
  ) {
159
- const { labelPosition, setLabelWidth, size, id } =
160
- useContext(InputFieldContext);
86
+ const { setLabelWidth, size, buttonSize, label, id, labelPosition } =
87
+ useInputFieldContext();
161
88
 
162
89
  const ref = useRef<HTMLLabelElement | null>(null);
163
90
 
@@ -171,17 +98,21 @@ const InputFieldLabel = memo(
171
98
  setLabelWidth(width);
172
99
  }, [setLabelWidth]);
173
100
 
101
+ if (labelPosition !== "inset") return null;
102
+
174
103
  return (
175
- <LabelContainer
104
+ <InsetLabel
176
105
  ref={(element) => {
177
106
  ref.current = element;
178
107
  assignRef(forwardedRef, element);
179
108
  }}
180
109
  size={size}
181
- labelPosition={labelPosition}
110
+ buttonSize={buttonSize}
182
111
  htmlFor={id}
183
112
  {...props}
184
- />
113
+ >
114
+ {label}
115
+ </InsetLabel>
185
116
  );
186
117
  })
187
118
  );
@@ -236,10 +167,11 @@ const InputFieldDropdownMenu = memoGeneric(function InputFieldDropdownMenu<
236
167
  /** Inserts a Button at the end of the input. Should not be used with an InputField.Label */
237
168
  const InputFieldButton = memo(
238
169
  forwardRef(function InputFieldButton(
239
- props: React.ButtonHTMLAttributes<HTMLButtonElement> & {
240
- buttonClassName?: string;
241
- variant?: "floating" | "normal";
242
- },
170
+ props: React.ButtonHTMLAttributes<HTMLButtonElement> &
171
+ ButtonRootProps & {
172
+ buttonClassName?: string;
173
+ variant?: "floating" | "normal";
174
+ },
243
175
  forwardedRef: ForwardedRef<HTMLButtonElement>
244
176
  ) {
245
177
  const {
@@ -248,9 +180,10 @@ const InputFieldButton = memo(
248
180
  className,
249
181
  buttonClassName,
250
182
  variant = "normal",
183
+ as = "button",
251
184
  ...rest
252
185
  } = props;
253
- const { size, inputRef, setButtonWidth } = useContext(InputFieldContext);
186
+ const { size, inputRef, setButtonWidth } = useInputFieldContext();
254
187
 
255
188
  const ref = useRef<HTMLButtonElement | null>(null);
256
189
 
@@ -291,7 +224,7 @@ const InputFieldButton = memo(
291
224
  className={cx(
292
225
  "absolute",
293
226
  size === "large"
294
- ? "right-[9px] top-2.5"
227
+ ? "right-1 top-2.5"
295
228
  : size === "medium"
296
229
  ? "right-1 top-1"
297
230
  : size === "small"
@@ -303,6 +236,7 @@ const InputFieldButton = memo(
303
236
  )}
304
237
  >
305
238
  <Button
239
+ as={as}
306
240
  ref={(element) => {
307
241
  ref.current = element;
308
242
  assignRef(forwardedRef, element);
@@ -314,7 +248,7 @@ const InputFieldButton = memo(
314
248
  size={size === "medium" ? "normal" : size}
315
249
  className={cx(
316
250
  buttonClassName,
317
- variant === "floating" && "bg-transparent shadow-none !px-0.5"
251
+ variant === "floating" && "bg-transparent shadow-none"
318
252
  )}
319
253
  {...rest}
320
254
  >
@@ -368,14 +302,12 @@ const InputElement = forwardRef(
368
302
  }: InputElementProps,
369
303
  ref: ForwardedRef<any>
370
304
  ) => {
371
- const { buttonSize, labelSize, labelPosition, size } =
372
- useContext(InputFieldContext);
305
+ const { buttonSize, labelSize, size, id } = useInputFieldContext();
373
306
 
374
- const spacingStyles = getInputFieldSpacing({
375
- labelPosition,
376
- size,
307
+ const spacingStyles = getFieldSpacing({
377
308
  buttonSize,
378
309
  labelSize,
310
+ variant: $variant,
379
311
  });
380
312
 
381
313
  const inputClassName = cx(
@@ -414,6 +346,7 @@ const InputElement = forwardRef(
414
346
  <TextInput
415
347
  ref={ref}
416
348
  value={value ?? ""}
349
+ id={id}
417
350
  onSubmit={onSubmit ?? (() => {})}
418
351
  {...props}
419
352
  className={inputClassName}
@@ -434,7 +367,7 @@ const InputFieldInput = forwardRef(function InputFieldInput(
434
367
  { textAlign, variant, ...rest }: InputFieldInputProps,
435
368
  forwardedRef: ForwardedRef<HTMLInputElement>
436
369
  ) {
437
- const { onFocusChange, setInputRef, id } = useContext(InputFieldContext);
370
+ const { onFocusChange, setInputRef } = useInputFieldContext();
438
371
 
439
372
  const handleFocusChange = useCallback(
440
373
  (isFocused: boolean) => {
@@ -453,7 +386,6 @@ const InputFieldInput = forwardRef(function InputFieldInput(
453
386
  $variant={variant}
454
387
  $textAlign={textAlign}
455
388
  onFocusChange={handleFocusChange}
456
- id={id}
457
389
  {...rest}
458
390
  />
459
391
  );
@@ -505,6 +437,7 @@ type InputFieldNumberInputProps = Omit<
505
437
  onFocus?: FocusEventHandler;
506
438
  onBlur?: FocusEventHandler;
507
439
  readOnly?: boolean;
440
+ className?: string;
508
441
  } & (
509
442
  | {
510
443
  onChange: (value: number) => void;
@@ -634,8 +567,6 @@ interface InputFieldRootProps {
634
567
  id?: string;
635
568
  children?: ReactNode;
636
569
  width?: number;
637
- /** @default end */
638
- labelPosition?: LabelPosition;
639
570
  labelSize?: number;
640
571
  /** @default medium */
641
572
  size?: InputFieldSize;
@@ -651,10 +582,9 @@ interface InputFieldRootProps {
651
582
  }
652
583
 
653
584
  function InputFieldRoot({
654
- id,
585
+ id: idProp,
655
586
  children,
656
587
  width,
657
- labelPosition = "end",
658
588
  sideOffset = 3,
659
589
  labelSize,
660
590
  size = "medium",
@@ -663,7 +593,9 @@ function InputFieldRoot({
663
593
  style,
664
594
  className,
665
595
  }: InputFieldRootProps) {
596
+ const { fieldId: id, label } = useLabel({ fieldId: idProp });
666
597
  const randomId = useId();
598
+ const labelPosition = useLabelPosition();
667
599
 
668
600
  const [isFocused, setIsFocused] = React.useState(false);
669
601
 
@@ -693,7 +625,6 @@ function InputFieldRoot({
693
625
 
694
626
  const contextValue = useMemo(
695
627
  (): InputFieldContextValue => ({
696
- labelPosition,
697
628
  labelSize: measuredLabelSize ?? labelSize,
698
629
  buttonSize: measuredButtonSize,
699
630
  size,
@@ -704,9 +635,10 @@ function InputFieldRoot({
704
635
  setLabelWidth: setMeasuredLabelSize,
705
636
  setButtonWidth: setMeasuredButtonSize,
706
637
  id: id ?? randomId,
638
+ label,
639
+ labelPosition,
707
640
  }),
708
641
  [
709
- labelPosition,
710
642
  measuredLabelSize,
711
643
  labelSize,
712
644
  measuredButtonSize,
@@ -716,12 +648,15 @@ function InputFieldRoot({
716
648
  inputRef,
717
649
  id,
718
650
  randomId,
651
+ label,
652
+ labelPosition,
719
653
  ]
720
654
  );
721
655
 
722
656
  const rootElement = (
723
657
  <RootContainer $width={width} style={style} className={className}>
724
658
  {children}
659
+ {label && <InputFieldLabel />}
725
660
  </RootContainer>
726
661
  );
727
662
 
@@ -776,11 +711,9 @@ const PrimitiveInputField = ({
776
711
  React.InputHTMLAttributes<HTMLInputElement>,
777
712
  HTMLInputElement
778
713
  >) => {
779
- const { labelSize, labelPosition, buttonSize } =
780
- useContext(InputFieldContext);
714
+ const { id, labelSize, buttonSize } = useInputFieldContext();
781
715
 
782
- const spacingStyles = getInputFieldSpacing({
783
- labelPosition,
716
+ const spacingStyles = getFieldSpacing({
784
717
  buttonSize,
785
718
  labelSize,
786
719
  });
@@ -791,6 +724,7 @@ const PrimitiveInputField = ({
791
724
  return (
792
725
  <input
793
726
  style={memoizedStyles}
727
+ id={id}
794
728
  className={cx(
795
729
  "flex w-0 flex-1 relative border-none outline-none min-w-0 self-stretch rounded py-1 bg-input-background select-all pointer-events-[all] focus:ring-2 focus:ring-primary font-sans font-normal text-heading5 placeholder:text-text-disabled focus:z-interactable transition-all",
796
730
  readOnly
@@ -805,13 +739,55 @@ const PrimitiveInputField = ({
805
739
  );
806
740
  };
807
741
 
808
- export namespace InputField {
809
- export const Root = memo(InputFieldRoot);
810
- export const Input = memo(InputFieldInput);
811
- export const Typeahead = memo(InputFieldTypeahead);
812
- export const NumberInput = memo(InputFieldNumberInput);
813
- export const DropdownMenu = InputFieldDropdownMenu;
814
- export const Button = InputFieldButton;
815
- export const Label = InputFieldLabel;
816
- export const PrimitiveElement = PrimitiveInputField;
817
- }
742
+ // Create a type for the main InputField component that combines Root and Input props
743
+ export type InputFieldProps = InputFieldRootProps & InputFieldInputProps;
744
+
745
+ // Create a map of all subcomponents
746
+ const InputFieldSubcomponents = {
747
+ Root: memo(InputFieldRoot),
748
+ Input: memo(InputFieldInput),
749
+ Typeahead: memo(InputFieldTypeahead),
750
+ NumberInput: memo(InputFieldNumberInput),
751
+ DropdownMenu: InputFieldDropdownMenu,
752
+ Button: InputFieldButton,
753
+ Label: InputFieldLabel,
754
+ PrimitiveElement: PrimitiveInputField,
755
+ } as const;
756
+
757
+ // Create the main InputField component
758
+ const InputFieldComponent = forwardRef<HTMLInputElement, InputFieldProps>(
759
+ function InputField({ children, ...props }: InputFieldProps, ref) {
760
+ const {
761
+ width,
762
+ labelSize,
763
+ size,
764
+ renderPopoverContent,
765
+ sideOffset,
766
+ onFocusChange,
767
+ style,
768
+ className,
769
+ ...inputProps
770
+ } = props;
771
+
772
+ return (
773
+ <InputFieldSubcomponents.Root
774
+ width={width}
775
+ labelSize={labelSize}
776
+ size={size}
777
+ renderPopoverContent={renderPopoverContent}
778
+ sideOffset={sideOffset}
779
+ onFocusChange={onFocusChange}
780
+ style={style}
781
+ className={className}
782
+ >
783
+ <InputFieldSubcomponents.Input ref={ref} {...inputProps} />
784
+ {children}
785
+ </InputFieldSubcomponents.Root>
786
+ );
787
+ }
788
+ );
789
+
790
+ // Export both the namespace and the component
791
+ const InputField = Object.assign(InputFieldComponent, InputFieldSubcomponents);
792
+
793
+ export { InputField };
@@ -1,22 +1,96 @@
1
- import * as React from "react";
1
+ import React, {
2
+ CSSProperties,
3
+ LabelHTMLAttributes,
4
+ forwardRef,
5
+ memo,
6
+ useMemo,
7
+ } from "react";
2
8
  import { cx } from "../utils/classNames";
9
+ import { InputFieldSize } from "./InputField";
3
10
 
4
- interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
11
+ export type LabelPosition = "inset" | "start" | "end" | "above";
5
12
 
6
- export const Label = React.memo(
7
- React.forwardRef<HTMLLabelElement, LabelProps>(function Label(
8
- { className, ...props },
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
+ );
70
+
71
+ const labelStyles = "flex items-center select-none z-label relative";
72
+ const labelTextStyles =
73
+ "font-sans text-label uppercase leading-3 font-bold text-text-muted";
74
+
75
+ export const Label = memo(
76
+ forwardRef<HTMLLabelElement, LabelProps>(function Label(
77
+ { className, position = "start", children, ...props },
9
78
  ref
10
79
  ) {
11
80
  return (
12
81
  <label
13
82
  ref={ref}
14
83
  className={cx(
15
- `font-sans text-label uppercase flex items-center leading-[19px] select-none font-bold text-text-muted z-label `,
84
+ labelStyles,
85
+ labelTextStyles,
86
+ "text-left min-h-[27px]",
87
+ (position === "start" || position === "end") && "self-start",
16
88
  className
17
89
  )}
18
90
  {...props}
19
- />
91
+ >
92
+ {children}
93
+ </label>
20
94
  );
21
95
  })
22
96
  );
@@ -0,0 +1,112 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { useIndent } from "../hooks/useIndent";
3
+ import {
4
+ LabelContext,
5
+ LabelPositionContext,
6
+ LabelPositionContextValue,
7
+ useLabelWidth,
8
+ } from "../hooks/useLabel";
9
+ import { cx } from "../utils/classNames";
10
+ import { Label, LabelPosition } from "./Label";
11
+
12
+ export const labeledFieldStyles = (labelPosition?: LabelPosition) =>
13
+ cx(
14
+ "flex flex-row flex-1 flex-wrap items-center gap-x-1.5",
15
+ labelPosition === "end" && "flex-row-reverse justify-end",
16
+ labelPosition === "above" && "flex-col items-stretch justify-start"
17
+ );
18
+
19
+ const labelPositionInset: LabelPositionContextValue = {
20
+ position: "inset",
21
+ };
22
+
23
+ export const LabeledField = memo(function LabeledField({
24
+ children,
25
+ label,
26
+ labelPosition = "start",
27
+ fieldId,
28
+ minWidth = 100,
29
+ className,
30
+ style,
31
+ ...props
32
+ }: {
33
+ children: React.ReactNode;
34
+ label: React.ReactNode;
35
+ labelPosition?: LabelPosition;
36
+ /** Used for both the `id` of the field and the `htmlFor` of the label, unless `id` and `htmlFor` are explicitly provided */
37
+ fieldId?: string;
38
+ className?: string;
39
+ style?: React.CSSProperties;
40
+ /** @default 100 */
41
+ minWidth?: number;
42
+ }) {
43
+ const indentLevel = useIndent();
44
+ const labelWidthFromContext = useLabelWidth();
45
+ const labelWidth = labelWidthFromContext ?? minWidth;
46
+
47
+ const labeledFieldClasses = useMemo(
48
+ () => labeledFieldStyles(labelPosition),
49
+ [labelPosition]
50
+ );
51
+
52
+ const labelContextValue = useMemo(
53
+ () => ({ fieldId, label }),
54
+ [fieldId, label]
55
+ );
56
+
57
+ const indentStyle = useMemo(
58
+ () => ({
59
+ width: indentLevel * 8,
60
+ flexBasis: indentLevel * 8,
61
+ flexShrink: 0,
62
+ flexGrow: 0,
63
+ }),
64
+ [indentLevel]
65
+ );
66
+
67
+ const fieldStyle = useMemo(
68
+ () => ({
69
+ minWidth: `calc(100% - ${labelWidth + indentLevel * 8 + 6}px)`,
70
+ }),
71
+ [labelWidth, indentLevel]
72
+ );
73
+
74
+ const labelStyle = useMemo(
75
+ () => ({
76
+ minWidth: `calc(${labelWidth}px - ${indentLevel * 8}px)`,
77
+ }),
78
+ [labelWidth, indentLevel]
79
+ );
80
+
81
+ return (
82
+ <LabelContext.Provider value={labelContextValue}>
83
+ {/* let components individually handle inset labels using info from context*/}
84
+ {labelPosition === "inset" ? (
85
+ <LabelPositionContext.Provider value={labelPositionInset}>
86
+ {children}
87
+ </LabelPositionContext.Provider>
88
+ ) : (
89
+ <div className={cx("flex relative flex-1", className)} style={style}>
90
+ {Boolean(indentLevel) && <div style={indentStyle} />}
91
+ <div className={labeledFieldClasses}>
92
+ <Label
93
+ htmlFor={fieldId}
94
+ position={labelPosition}
95
+ style={labelStyle}
96
+ {...props}
97
+ >
98
+ {label}
99
+ </Label>
100
+ {labelPosition === "start" ? (
101
+ <div className="flex-auto flex" style={fieldStyle}>
102
+ {children}
103
+ </div>
104
+ ) : (
105
+ children
106
+ )}
107
+ </div>
108
+ </div>
109
+ )}
110
+ </LabelContext.Provider>
111
+ );
112
+ });