@a-type/ui 6.0.22 → 6.0.24

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 (50) hide show
  1. package/dist/components/forms/CheckboxField.d.ts +3 -1
  2. package/dist/components/forms/CheckboxField.js +5 -6
  3. package/dist/components/forms/CheckboxField.js.map +1 -1
  4. package/dist/components/forms/EmojiField.d.ts +3 -1
  5. package/dist/components/forms/EmojiField.js +4 -5
  6. package/dist/components/forms/EmojiField.js.map +1 -1
  7. package/dist/components/forms/Field.d.ts +21 -0
  8. package/dist/components/forms/Field.js +71 -0
  9. package/dist/components/forms/Field.js.map +1 -0
  10. package/dist/components/forms/Field.module.css +62 -0
  11. package/dist/components/forms/Form.module.css +1 -1
  12. package/dist/components/forms/FormikForm.d.ts +2 -2
  13. package/dist/components/forms/FormikForm.stories.d.ts +2 -2
  14. package/dist/components/forms/FormikForm.stories.js +1 -1
  15. package/dist/components/forms/FormikForm.stories.js.map +1 -1
  16. package/dist/components/forms/NumberStepperField.d.ts +3 -1
  17. package/dist/components/forms/NumberStepperField.js +6 -9
  18. package/dist/components/forms/NumberStepperField.js.map +1 -1
  19. package/dist/components/forms/SwitchField.d.ts +3 -1
  20. package/dist/components/forms/SwitchField.js +5 -6
  21. package/dist/components/forms/SwitchField.js.map +1 -1
  22. package/dist/components/forms/TextField.d.ts +7 -5
  23. package/dist/components/forms/TextField.js +5 -7
  24. package/dist/components/forms/TextField.js.map +1 -1
  25. package/dist/components/forms/ToggleGroupField.d.ts +3 -1
  26. package/dist/components/forms/ToggleGroupField.js +12 -11
  27. package/dist/components/forms/ToggleGroupField.js.map +1 -1
  28. package/dist/components/forms/index.d.ts +1 -1
  29. package/dist/components/forms/index.js +1 -1
  30. package/dist/components/forms/index.js.map +1 -1
  31. package/dist/components/switch/Switch.module.css +1 -1
  32. package/package.json +1 -1
  33. package/src/components/forms/CheckboxField.tsx +19 -15
  34. package/src/components/forms/EmojiField.tsx +15 -9
  35. package/src/components/forms/Field.module.css +62 -0
  36. package/src/components/forms/Field.tsx +125 -0
  37. package/src/components/forms/Form.module.css +1 -1
  38. package/src/components/forms/FormikForm.stories.tsx +25 -5
  39. package/src/components/forms/NumberStepperField.tsx +19 -16
  40. package/src/components/forms/SwitchField.tsx +19 -15
  41. package/src/components/forms/TextField.tsx +37 -25
  42. package/src/components/forms/ToggleGroupField.tsx +27 -19
  43. package/src/components/forms/index.tsx +1 -1
  44. package/src/components/switch/Switch.module.css +1 -1
  45. package/dist/components/forms/FieldLabel.d.ts +0 -2
  46. package/dist/components/forms/FieldLabel.js +0 -5
  47. package/dist/components/forms/FieldLabel.js.map +0 -1
  48. package/dist/components/forms/FieldLabel.module.css +0 -20
  49. package/src/components/forms/FieldLabel.module.css +0 -20
  50. package/src/components/forms/FieldLabel.tsx +0 -9
@@ -1,5 +1,5 @@
1
1
  export * from './CheckboxField.js';
2
- export * from './FieldLabel.js';
2
+ export * from './Field.js';
3
3
  export * from './Form.js';
4
4
  export * from './FormikForm.js';
5
5
  export * from './hooks.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/forms/index.tsx"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/forms/index.tsx"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
@@ -15,7 +15,7 @@
15
15
  --mx-shadow-value: inset var(--m-shadow-sm);
16
16
 
17
17
  @apply --mx-bg(var(--m-control-bg));
18
- @apply --mx-borderColor(var(--m-control-border));
18
+ @apply --mx-borderColor(var(--m-control-borderColor));
19
19
  border-radius: var(--m-radius-lg);
20
20
 
21
21
  &[data-checked] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "6.0.22",
3
+ "version": "6.0.24",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "url": "https://github.com/a-type/ui"
@@ -1,8 +1,8 @@
1
1
  import { useField } from 'formik';
2
+ import { ReactNode } from 'react';
2
3
  import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
3
- import { Box } from '../box/Box.js';
4
4
  import { Checkbox } from '../checkbox/index.js';
5
- import { HorizontalFieldLabel } from './FieldLabel.js';
5
+ import { Field } from './Field.js';
6
6
 
7
7
  export interface CheckboxFieldProps {
8
8
  name: string;
@@ -10,6 +10,7 @@ export interface CheckboxFieldProps {
10
10
  required?: boolean;
11
11
  className?: string;
12
12
  id?: string;
13
+ description?: ReactNode;
13
14
  }
14
15
 
15
16
  export function CheckboxField({
@@ -18,24 +19,27 @@ export function CheckboxField({
18
19
  className,
19
20
  required,
20
21
  id: providedId,
22
+ description,
21
23
  ...rest
22
24
  }: CheckboxFieldProps) {
23
25
  const [props, _, tools] = useField({ name, required, type: 'checkbox' });
24
26
  const id = useIdOrGenerated(providedId);
25
27
  return (
26
- <Box gap="md" className={className}>
27
- <Checkbox
28
- {...props}
29
- checked={props.checked}
30
- onCheckedChange={(v) => {
31
- tools.setValue(!!v);
32
- }}
33
- id={id}
34
- {...rest}
28
+ <Field horizontal className={className} id={id}>
29
+ <Field.Control
30
+ render={
31
+ <Checkbox
32
+ {...props}
33
+ checked={props.checked}
34
+ onCheckedChange={(v) => {
35
+ tools.setValue(!!v);
36
+ }}
37
+ {...rest}
38
+ />
39
+ }
35
40
  />
36
- {label && (
37
- <HorizontalFieldLabel htmlFor={id}>{label}</HorizontalFieldLabel>
38
- )}
39
- </Box>
41
+ {label && <Field.Label>{label}</Field.Label>}
42
+ {description && <Field.Description>{description}</Field.Description>}
43
+ </Field>
40
44
  );
41
45
  }
@@ -1,19 +1,19 @@
1
1
  import { useField } from 'formik';
2
- import { useState } from 'react';
2
+ import { ReactNode, useState } from 'react';
3
3
  import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
4
- import { Box } from '../box/Box.js';
5
4
  import { Button, ButtonProps } from '../button/Button.js';
6
5
  import { EmojiPicker } from '../emojiPicker/EmojiPicker.js';
7
6
  import { Icon } from '../icon/Icon.js';
8
7
  import { Popover } from '../popover/Popover.js';
9
8
  import cls from './EmojiField.module.css';
10
- import { HorizontalFieldLabel } from './FieldLabel.js';
9
+ import { Field } from './Field.js';
11
10
 
12
11
  export type EmojiFieldProps = Omit<ButtonProps, 'className'> & {
13
12
  name: string;
14
13
  label?: string;
15
14
  required?: string;
16
15
  className?: string;
16
+ description?: ReactNode;
17
17
  };
18
18
 
19
19
  export function EmojiField({
@@ -22,17 +22,24 @@ export function EmojiField({
22
22
  className,
23
23
  required,
24
24
  id: providedId,
25
+ description,
25
26
  ...rest
26
27
  }: EmojiFieldProps) {
27
28
  const [props, _, tools] = useField({ name });
28
29
  const id = useIdOrGenerated(providedId);
29
30
  const [open, setOpen] = useState(false);
30
31
  return (
31
- <Box gap="md" className={className}>
32
+ <Field horizontal className={className} id={id}>
32
33
  <Popover open={open} onOpenChange={setOpen}>
34
+ <Field.Control render={<input type="hidden" {...props} />} />
33
35
  <Popover.Trigger
34
36
  render={
35
- <Button id={id} aria-label="Select emoji" size="wrapper" {...rest}>
37
+ <Button
38
+ id={`${id}-trigger`}
39
+ aria-label="Select emoji"
40
+ size="wrapper"
41
+ {...rest}
42
+ >
36
43
  <Button.Icon className={cls.triggerIcon}>
37
44
  {props.value || <Icon name="smile" />}
38
45
  </Button.Icon>
@@ -57,9 +64,8 @@ export function EmojiField({
57
64
  />
58
65
  </Popover.Content>
59
66
  </Popover>
60
- {label && (
61
- <HorizontalFieldLabel htmlFor={id}>{label}</HorizontalFieldLabel>
62
- )}
63
- </Box>
67
+ {label && <Field.Label>{label}</Field.Label>}
68
+ {description && <Field.Description>{description}</Field.Description>}
69
+ </Field>
64
70
  );
65
71
  }
@@ -0,0 +1,62 @@
1
+ .root {
2
+ display: grid;
3
+ grid-template-areas: 'label' 'control' 'description';
4
+ justify-content: start;
5
+
6
+ &[data-horizontal] {
7
+ flex-direction: row;
8
+ align-items: center;
9
+ grid-template-areas: 'control label' 'description description';
10
+ grid-template-columns: auto 1fr;
11
+ &[data-reverse] {
12
+ grid-template-areas: 'label control' 'description description';
13
+ grid-template-columns: auto 1fr;
14
+ }
15
+ }
16
+
17
+ &[data-stretch] {
18
+ align-self: stretch;
19
+ justify-content: stretch;
20
+ }
21
+ }
22
+
23
+ .control {
24
+ grid-area: control;
25
+
26
+ [data-stretch] & {
27
+ align-self: stretch;
28
+ justify-content: stretch;
29
+ width: 100%;
30
+ }
31
+ }
32
+
33
+ .label {
34
+ grid-area: label;
35
+ @apply --mx-prose-ambient;
36
+ @apply --mx-fg(var(--m-color-neutral-heavy));
37
+ font-weight: var(--m-text-weight-bold);
38
+ margin-bottom: var(--m-space-xs);
39
+ margin-left: calc(var(--m-global-shape-roundness) * var(--m-space-md));
40
+
41
+ [data-horizontal] & {
42
+ @apply --mx-fg(var(--m-color-neutral-ink));
43
+ font-weight: var(--m-text-weight-normal);
44
+ margin-block-start: --fn-literal(5px);
45
+
46
+ [data-reverse] & {
47
+ margin-inline-start: 0;
48
+ margin-inline-end: calc(
49
+ var(--m-global-shape-roundness) * var(--m-space-md)
50
+ );
51
+ }
52
+ }
53
+ }
54
+
55
+ .description {
56
+ grid-area: description;
57
+ @apply --mx-prose-ambient;
58
+ @apply --mx-fg(var(--m-color-neutral-heavy));
59
+ font-style: italic;
60
+ margin-top: var(--m-space-sm);
61
+ margin-left: calc(var(--m-global-shape-roundness) * var(--m-space-md));
62
+ }
@@ -0,0 +1,125 @@
1
+ import clsx from 'clsx';
2
+ import {
3
+ ComponentProps,
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import useMergedRef from '../../hooks/useMergedRef.js';
11
+ import { SlotDiv } from '../utility/SlotDiv.js';
12
+ import cls from './Field.module.css';
13
+
14
+ export interface FieldRootProps extends ComponentProps<'div'> {
15
+ horizontal?: boolean;
16
+ stretch?: boolean;
17
+ reverse?: boolean;
18
+ /**
19
+ * This ID will be passed to Control, and Label will be associated.
20
+ */
21
+ id?: string;
22
+ }
23
+
24
+ const FieldContext = createContext<{ id?: string; events: EventTarget }>({
25
+ events: new EventTarget(),
26
+ });
27
+
28
+ function FieldRoot({
29
+ className,
30
+ horizontal,
31
+ stretch,
32
+ reverse,
33
+ id,
34
+ ...props
35
+ }: FieldRootProps) {
36
+ const [events] = useState(() => new EventTarget());
37
+ return (
38
+ <FieldContext.Provider value={{ id, events }}>
39
+ <SlotDiv
40
+ className={clsx(cls.root, className)}
41
+ data-horizontal={horizontal ? '' : undefined}
42
+ data-stretch={stretch ? '' : undefined}
43
+ data-reverse={reverse ? '' : undefined}
44
+ {...props}
45
+ />
46
+ </FieldContext.Provider>
47
+ );
48
+ }
49
+
50
+ function FieldControl({
51
+ className,
52
+ ref,
53
+ ...props
54
+ }: ComponentProps<typeof SlotDiv>) {
55
+ const innerRef = useRef<HTMLElement>(null);
56
+ const finalRef = useMergedRef(ref, innerRef);
57
+ const { id, events } = useContext(FieldContext);
58
+
59
+ useEffect(() => {
60
+ if (!id) return;
61
+
62
+ function onDescriptionVisible(event: CustomEvent) {
63
+ if (event.detail === true) {
64
+ innerRef.current?.setAttribute('aria-describedby', `${id}-description`);
65
+ } else {
66
+ innerRef.current?.removeAttribute('aria-describedby');
67
+ }
68
+ }
69
+
70
+ events.addEventListener(
71
+ 'description-visible',
72
+ onDescriptionVisible as EventListener,
73
+ );
74
+ return () => {
75
+ events.removeEventListener(
76
+ 'description-visible',
77
+ onDescriptionVisible as EventListener,
78
+ );
79
+ };
80
+ }, [events, id]);
81
+
82
+ return (
83
+ <SlotDiv
84
+ className={clsx(cls.control, className)}
85
+ id={id}
86
+ ref={finalRef}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ function FieldLabel({ className, ...props }: ComponentProps<'label'>) {
93
+ const { id } = useContext(FieldContext);
94
+ return (
95
+ <label className={clsx(cls.label, className)} htmlFor={id} {...props} />
96
+ );
97
+ }
98
+
99
+ function FieldDescription({ className, ...props }: ComponentProps<'div'>) {
100
+ const { id, events } = useContext(FieldContext);
101
+
102
+ useEffect(() => {
103
+ events.dispatchEvent(
104
+ new CustomEvent('description-visible', { detail: true }),
105
+ );
106
+ return () => {
107
+ events.dispatchEvent(
108
+ new CustomEvent('description-visible', { detail: false }),
109
+ );
110
+ };
111
+ }, [events]);
112
+ return (
113
+ <div
114
+ className={clsx('@mode-dense', cls.description, className)}
115
+ id={id ? `${id}-description` : undefined}
116
+ {...props}
117
+ />
118
+ );
119
+ }
120
+
121
+ export const Field = Object.assign(FieldRoot, {
122
+ Label: FieldLabel,
123
+ Description: FieldDescription,
124
+ Control: FieldControl,
125
+ });
@@ -2,5 +2,5 @@
2
2
  display: flex;
3
3
  align-items: start;
4
4
  flex-direction: column;
5
- gap: var(--m-space-md);
5
+ gap: var(--m-space-lg);
6
6
  }
@@ -47,19 +47,39 @@ export const Default: Story = {
47
47
  label="Email"
48
48
  placeholder="you@example.com"
49
49
  />
50
- <TextField name="password" type="password" label="Password" />
51
- <NumberStepperField name="age" label="Age" min={13} max={100} />
52
- <CheckboxField name="tos" label="I agree" />
53
- <SwitchField name="newsletter" label="Subscribe to newsletter" />
50
+ <TextField
51
+ name="password"
52
+ type="password"
53
+ label="Password"
54
+ description="Make it a good one"
55
+ />
56
+ <NumberStepperField
57
+ name="age"
58
+ label="Age"
59
+ min={13}
60
+ max={100}
61
+ description="You can mail your government ID to us, don't worry, we're the good guys"
62
+ />
63
+ <CheckboxField
64
+ name="tos"
65
+ label="I agree"
66
+ description="To what? Not telling."
67
+ />
68
+ <SwitchField
69
+ name="newsletter"
70
+ label="Subscribe to newsletter"
71
+ description="Don't worry, we don't bother you much"
72
+ />
54
73
  <ToggleGroupField
55
74
  type="single"
56
75
  name="plan"
57
76
  label="Select your plan"
58
77
  required
78
+ description="Please do the expensive one!!!"
59
79
  >
60
80
  <ToggleGroupField.Item value="basic">Basic</ToggleGroupField.Item>
61
81
  <ToggleGroupField.Item value="pro">Pro</ToggleGroupField.Item>
62
- <ToggleGroupField.Item value="enterprise">
82
+ <ToggleGroupField.Item value="enterprise" className="@mode-accent">
63
83
  Enterprise
64
84
  </ToggleGroupField.Item>
65
85
  </ToggleGroupField>
@@ -1,12 +1,11 @@
1
1
  import { useField } from 'formik';
2
- import { withClassName } from '../../hooks.js';
2
+ import { ReactNode } from 'react';
3
3
  import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
4
4
  import {
5
5
  NumberStepper,
6
6
  NumberStepperProps,
7
7
  } from '../numberStepper/NumberStepper.js';
8
- import { FieldLabel } from './FieldLabel.js';
9
- import cls from './NumberStepperField.module.css';
8
+ import { Field } from './Field.js';
10
9
 
11
10
  export interface NumberStepperFieldProps
12
11
  extends Omit<NumberStepperProps, 'value' | 'onChange'> {
@@ -15,6 +14,7 @@ export interface NumberStepperFieldProps
15
14
  className?: string;
16
15
  id?: string;
17
16
  onChange?: (value: number) => void;
17
+ description?: ReactNode;
18
18
  }
19
19
 
20
20
  export function NumberStepperField({
@@ -23,24 +23,27 @@ export function NumberStepperField({
23
23
  className,
24
24
  id: providedId,
25
25
  onChange,
26
+ description,
26
27
  ...rest
27
28
  }: NumberStepperFieldProps) {
28
29
  const [_, field, tools] = useField({ name });
29
30
  const id = useIdOrGenerated(providedId);
30
31
  return (
31
- <FieldRoot className={className}>
32
- {label && <FieldLabel htmlFor={id}>{label}</FieldLabel>}
33
- <NumberStepper
34
- value={field.value}
35
- onChange={(v) => {
36
- tools.setValue(v);
37
- onChange?.(v);
38
- }}
39
- id={id}
40
- {...rest}
32
+ <Field className={className} id={id}>
33
+ {label && <Field.Label>{label}</Field.Label>}
34
+ <Field.Control
35
+ render={
36
+ <NumberStepper
37
+ value={field.value}
38
+ onChange={(v) => {
39
+ tools.setValue(v);
40
+ onChange?.(v);
41
+ }}
42
+ {...rest}
43
+ />
44
+ }
41
45
  />
42
- </FieldRoot>
46
+ {description && <Field.Description>{description}</Field.Description>}
47
+ </Field>
43
48
  );
44
49
  }
45
-
46
- const FieldRoot = withClassName('div', cls.root);
@@ -1,8 +1,8 @@
1
1
  import { useField } from 'formik';
2
+ import { ReactNode } from 'react';
2
3
  import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
3
- import { Box } from '../box/Box.js';
4
4
  import { Switch } from '../switch/Switch.js';
5
- import { HorizontalFieldLabel } from './FieldLabel.js';
5
+ import { Field } from './Field.js';
6
6
 
7
7
  export interface SwitchFieldProps {
8
8
  name: string;
@@ -10,6 +10,7 @@ export interface SwitchFieldProps {
10
10
  required?: boolean;
11
11
  className?: string;
12
12
  id?: string;
13
+ description?: ReactNode;
13
14
  }
14
15
 
15
16
  export function SwitchField({
@@ -18,24 +19,27 @@ export function SwitchField({
18
19
  className,
19
20
  required,
20
21
  id: providedId,
22
+ description,
21
23
  ...rest
22
24
  }: SwitchFieldProps) {
23
25
  const [props, _, tools] = useField({ name, required, type: 'checkbox' });
24
26
  const id = useIdOrGenerated(providedId);
25
27
  return (
26
- <Box gap="md" className={className}>
27
- <Switch
28
- {...props}
29
- checked={props.checked}
30
- onCheckedChange={(v) => {
31
- tools.setValue(!!v);
32
- }}
33
- id={id}
34
- {...rest}
28
+ <Field horizontal className={className} id={id}>
29
+ <Field.Control
30
+ render={
31
+ <Switch
32
+ {...props}
33
+ checked={props.checked}
34
+ onCheckedChange={(v) => {
35
+ tools.setValue(!!v);
36
+ }}
37
+ {...rest}
38
+ />
39
+ }
35
40
  />
36
- {label && (
37
- <HorizontalFieldLabel htmlFor={id}>{label}</HorizontalFieldLabel>
38
- )}
39
- </Box>
41
+ {label && <Field.Label>{label}</Field.Label>}
42
+ {description && <Field.Description>{description}</Field.Description>}
43
+ </Field>
40
44
  );
41
45
  }
@@ -3,17 +3,17 @@ import { useField, useFormikContext } from 'formik';
3
3
  import {
4
4
  InputHTMLAttributes,
5
5
  KeyboardEvent,
6
+ ReactNode,
6
7
  Ref,
7
8
  useCallback,
8
9
  useEffect,
9
10
  useRef,
10
11
  } from 'react';
11
- import { withClassName } from '../../hooks.js';
12
12
  import { useIdOrGenerated } from '../../hooks/useIdOrGenerated.js';
13
13
  import useMergedRef from '../../hooks/useMergedRef.js';
14
14
  import { Input, InputProps } from '../input/Input.js';
15
15
  import { TextArea, TextAreaProps } from '../textArea/TextArea.js';
16
- import { FieldLabel } from './FieldLabel.js';
16
+ import { Field } from './Field.js';
17
17
  import cls from './TextField.module.css';
18
18
 
19
19
  export type TextFieldProps = {
@@ -24,6 +24,7 @@ export type TextFieldProps = {
24
24
  inputRef?: Ref<HTMLInputElement>;
25
25
  inputClassName?: string;
26
26
  ref?: Ref<HTMLDivElement>;
27
+ description?: ReactNode;
27
28
  } & Omit<InputProps, 'ref'>;
28
29
 
29
30
  const emptyRef = (() => {}) as any;
@@ -41,6 +42,7 @@ export const TextField = function TextField({
41
42
  onBlur,
42
43
  id: providedId,
43
44
  inputClassName,
45
+ description,
44
46
  ...rest
45
47
  }: TextFieldProps) {
46
48
  const [props] = useField({
@@ -61,17 +63,21 @@ export const TextField = function TextField({
61
63
  }, [autoFocusDelay]);
62
64
 
63
65
  return (
64
- <FieldRoot className={className} ref={ref}>
65
- {label && <FieldLabel htmlFor={id}>{label}</FieldLabel>}
66
- <Input
67
- {...props}
68
- {...rest}
69
- id={id}
70
- autoFocus={autoFocus}
71
- className={clsx(cls.input, inputClassName)}
72
- ref={useMergedRef(innerInputRef, inputRef || emptyRef)}
66
+ <Field className={className} stretch ref={ref} id={id}>
67
+ {label && <Field.Label>{label}</Field.Label>}
68
+ <Field.Control
69
+ render={
70
+ <Input
71
+ {...props}
72
+ {...rest}
73
+ autoFocus={autoFocus}
74
+ className={clsx(cls.input, inputClassName)}
75
+ ref={useMergedRef(innerInputRef, inputRef || emptyRef)}
76
+ />
77
+ }
73
78
  />
74
- </FieldRoot>
79
+ {description && <Field.Description>{description}</Field.Description>}
80
+ </Field>
75
81
  );
76
82
  };
77
83
 
@@ -85,7 +91,9 @@ export type TextAreaFieldProps = {
85
91
  inputRef?: Ref<HTMLTextAreaElement>;
86
92
  submitOnEnter?: boolean;
87
93
  textAreaClassName?: string;
88
- } & TextAreaProps;
94
+ description?: ReactNode;
95
+ ref?: Ref<HTMLDivElement>;
96
+ } & Omit<TextAreaProps, 'ref'>;
89
97
 
90
98
  export function TextAreaField({
91
99
  name,
@@ -96,6 +104,8 @@ export function TextAreaField({
96
104
  submitOnEnter,
97
105
  id: providedId,
98
106
  textAreaClassName,
107
+ description,
108
+ ref,
99
109
  ...rest
100
110
  }: TextAreaFieldProps) {
101
111
  const [props] = useField(name);
@@ -113,18 +123,20 @@ export function TextAreaField({
113
123
  const id = useIdOrGenerated(providedId);
114
124
 
115
125
  return (
116
- <FieldRoot className={className}>
117
- {label && <FieldLabel htmlFor={id}>{label}</FieldLabel>}
118
- <TextArea
119
- ref={inputRef as any}
120
- {...props}
121
- {...rest}
122
- className={clsx('layer-composed:w-full', textAreaClassName)}
123
- id={id}
124
- onKeyDown={onKeyDownInner}
126
+ <Field stretch className={className} ref={ref} id={id}>
127
+ {label && <Field.Label>{label}</Field.Label>}
128
+ <Field.Control
129
+ render={
130
+ <TextArea
131
+ ref={inputRef as any}
132
+ {...props}
133
+ {...rest}
134
+ className={clsx(cls.input, textAreaClassName)}
135
+ onKeyDown={onKeyDownInner}
136
+ />
137
+ }
125
138
  />
126
- </FieldRoot>
139
+ {description && <Field.Description>{description}</Field.Description>}
140
+ </Field>
127
141
  );
128
142
  }
129
-
130
- export const FieldRoot = withClassName('div', cls.root);