Skip to content

Commit a7e5e3a

Browse files
ShreyasGit51283cursoragent
andcommitted
feat(components): [DSYS-194] add description and errorMessage props to field components
Introduces a shared renderFieldHelpers() helper and adds ergonomic `description`/`errorMessage` props to TextField, NumberField, SearchField, Select, ComboBox, RadioGroup, CheckboxGroup, DateField, and TimeField. The props render the React Aria description slot and FieldError internally, matching the Figma library's flat `description` property and resolving the Code Connect drift. Additive and non-breaking: existing slotted children keep working. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 51b495e commit a7e5e3a

18 files changed

Lines changed: 217 additions & 49 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@launchpad-ui/components": minor
3+
---
4+
5+
Add ergonomic `description` and `errorMessage` props to field components: `TextField`, `NumberField`, `SearchField`, `Select`, `ComboBox`, `RadioGroup`, `CheckboxGroup`, `DateField`, and `TimeField`.
6+
7+
These render the React Aria `description` slot (`<Text slot="description">`) and `FieldError` internally, matching the Figma library's flat `description` property and resolving the Code Connect drift. The change is additive and non-breaking: passing helper text as `<Text slot="description">` / `<FieldError>` children remains fully supported.

packages/components/figma/CheckboxGroup.figma.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ figma.connect(
2525
example: (props) => (
2626
<>
2727
{props.label}
28-
<CheckboxGroup>{props.children}</CheckboxGroup>
28+
<CheckboxGroup description={props.description}>{props.children}</CheckboxGroup>
2929
</>
3030
),
3131
},

packages/components/figma/DateField.figma.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ figma.connect(
1111
true: figma.children(['Label']),
1212
false: undefined,
1313
}),
14-
helpText: figma.boolean('Help text?', {
14+
description: figma.boolean('Help text?', {
1515
true: figma.string('Help text'),
1616
false: undefined,
1717
}),
1818
isInvalid: figma.enum('State', { Invalid: true }),
1919
isDisabled: figma.enum('State', { Disabled: true }),
2020
},
21-
example: ({ label, isInvalid, isDisabled }) => (
21+
example: ({ label, isInvalid, isDisabled, description }) => (
2222
<>
2323
{label}
24-
<DateField isInvalid={isInvalid} isDisabled={isDisabled} />
24+
<DateField isInvalid={isInvalid} isDisabled={isDisabled} description={description} />
2525
</>
2626
),
2727
},

packages/components/figma/NumberField.figma.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ figma.connect(
1111
true: 10,
1212
false: undefined,
1313
}),
14+
description: figma.boolean('Description?', {
15+
true: figma.string('Description'),
16+
false: undefined,
17+
}),
1418
isInvalid: figma.enum('State', { Invalid: true }),
1519
isDisabled: figma.enum('State', { Disabled: true }),
1620
},
17-
example: ({ isInvalid, isDisabled, defaultValue }) => (
18-
<NumberField isInvalid={isInvalid} isDisabled={isDisabled} defaultValue={defaultValue} />
21+
example: ({ isInvalid, isDisabled, defaultValue, description }) => (
22+
<NumberField
23+
isInvalid={isInvalid}
24+
isDisabled={isDisabled}
25+
defaultValue={defaultValue}
26+
description={description}
27+
/>
1928
),
2029
},
2130
);
@@ -34,13 +43,22 @@ figma.connect(
3443
true: 10,
3544
false: undefined,
3645
}),
46+
description: figma.boolean('Description?', {
47+
true: figma.string('Description'),
48+
false: undefined,
49+
}),
3750
isInvalid: figma.enum('State', { Invalid: true }),
3851
isDisabled: figma.enum('State', { Disabled: true }),
3952
},
40-
example: ({ isInvalid, isDisabled, defaultValue, label }) => (
53+
example: ({ isInvalid, isDisabled, defaultValue, label, description }) => (
4154
<>
4255
{label}
43-
<NumberField isInvalid={isInvalid} isDisabled={isDisabled} defaultValue={defaultValue} />
56+
<NumberField
57+
isInvalid={isInvalid}
58+
isDisabled={isDisabled}
59+
defaultValue={defaultValue}
60+
description={description}
61+
/>
4462
</>
4563
),
4664
},

packages/components/figma/RadioGroup.figma.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ figma.connect(
1111
true: figma.children(['Label']),
1212
false: undefined,
1313
}),
14+
description: figma.boolean('Description?', {
15+
true: figma.string('Description'),
16+
false: undefined,
17+
}),
1418
radios: figma.children(['.Radio']),
1519
},
16-
example: ({ label, radios }) => (
17-
<RadioGroup defaultValue="1">
20+
example: ({ label, radios, description }) => (
21+
<RadioGroup defaultValue="1" description={description}>
1822
{label}
1923
<ButtonGroup role="presentation" spacing="compact">
2024
{radios}

packages/components/figma/SearchField.figma.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ figma.connect(
1111
true: figma.children(['Label']),
1212
false: undefined,
1313
}),
14+
description: figma.boolean('Description?', {
15+
true: figma.string('Description'),
16+
false: undefined,
17+
}),
1418
isInvalid: figma.enum('State', { Invalid: true }),
1519
isDisabled: figma.enum('State', { Disabled: true }),
1620
},
17-
example: ({ label, isInvalid, isDisabled }) => (
18-
<SearchField isInvalid={isInvalid} isDisabled={isDisabled}>
21+
example: ({ label, isInvalid, isDisabled, description }) => (
22+
<SearchField isInvalid={isInvalid} isDisabled={isDisabled} description={description}>
1923
{label}
2024
<Group>
2125
{/* <Icon name="search" size="small" /> */}

packages/components/figma/Select.figma.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@ figma.connect(
1414
true: 'Select an option',
1515
false: undefined,
1616
}),
17+
description: figma.boolean('Description?', {
18+
true: figma.string('Description'),
19+
false: undefined,
20+
}),
1721
},
18-
example: ({ label }) => (
19-
<Select selectedKey="1" onSelectionChange={(key) => console.log(key)}>
22+
example: ({ label, description }) => (
23+
<Select
24+
selectedKey="1"
25+
onSelectionChange={(key) => console.log(key)}
26+
description={description}
27+
>
2028
{label}
2129
<Button>
2230
<SelectValue />

packages/components/figma/TextField.figma.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ figma.connect(
1919
true: figma.textContent('Value'),
2020
false: undefined,
2121
}),
22+
description: figma.boolean('Description?', {
23+
true: figma.textContent('Description'),
24+
false: undefined,
25+
}),
2226
multiline: figma.boolean('Multi Line'),
2327
variant: figma.boolean('Minimal', {
2428
true: 'minimal',
@@ -32,9 +36,9 @@ figma.connect(
3236
isDisabled: figma.enum('State', { Disabled: true }),
3337
validationMessage: figma.textContent('Validation message'),
3438
},
35-
example: ({ children, variant, placeholder, value, isDisabled }) => {
39+
example: ({ children, variant, placeholder, value, isDisabled, description }) => {
3640
return (
37-
<TextField isDisabled={isDisabled}>
41+
<TextField isDisabled={isDisabled} description={description}>
3842
{children}
3943
<Input variant={variant} placeholder={placeholder} value={value} />
4044
</TextField>
@@ -66,6 +70,10 @@ figma.connect(
6670
true: figma.textContent('Value'),
6771
false: undefined,
6872
}),
73+
description: figma.boolean('Description?', {
74+
true: figma.textContent('Description'),
75+
false: undefined,
76+
}),
6977
isDisabled: figma.enum('State', { Disabled: true }),
7078
validationMessage: figma.textContent('Validation message'),
7179
},
@@ -77,9 +85,10 @@ figma.connect(
7785
isDisabled,
7886
isInvalid,
7987
validationMessage,
88+
description,
8089
}) => {
8190
return (
82-
<TextField isDisabled={isDisabled} isInvalid={isInvalid}>
91+
<TextField isDisabled={isDisabled} isInvalid={isInvalid} description={description}>
8392
{children}
8493
<Input variant={variant} placeholder={placeholder} value={value} />
8594
<FieldError>{validationMessage}</FieldError>
@@ -107,11 +116,15 @@ figma.connect(
107116
true: figma.textContent('Value'),
108117
false: undefined,
109118
}),
119+
description: figma.boolean('Description?', {
120+
true: figma.textContent('Description'),
121+
false: undefined,
122+
}),
110123
isDisabled: figma.enum('State', { Disabled: true }),
111124
},
112-
example: ({ children, placeholder, value, isDisabled }) => {
125+
example: ({ children, placeholder, value, isDisabled, description }) => {
113126
return (
114-
<TextField isDisabled={isDisabled}>
127+
<TextField isDisabled={isDisabled} description={description}>
115128
{children}
116129
<TextArea placeholder={placeholder} value={value} />
117130
</TextField>
@@ -136,12 +149,24 @@ figma.connect(
136149
false: undefined,
137150
}),
138151
value: figma.textContent('Value'),
152+
description: figma.boolean('Description?', {
153+
true: figma.textContent('Description'),
154+
false: undefined,
155+
}),
139156
isDisabled: figma.enum('State', { Disabled: true }),
140157
validationMessage: figma.textContent('Validation message'),
141158
},
142-
example: ({ children, placeholder, value, isDisabled, isInvalid, validationMessage }) => {
159+
example: ({
160+
children,
161+
placeholder,
162+
value,
163+
isDisabled,
164+
isInvalid,
165+
validationMessage,
166+
description,
167+
}) => {
143168
return (
144-
<TextField isDisabled={isDisabled} isInvalid={isInvalid}>
169+
<TextField isDisabled={isDisabled} isInvalid={isInvalid} description={description}>
145170
{children}
146171
<TextArea placeholder={placeholder} value={value} />
147172
<FieldError>{validationMessage}</FieldError>

packages/components/src/CheckboxGroup.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import { createContext } from 'react';
77
import { CheckboxGroup as AriaCheckboxGroup } from 'react-aria-components/CheckboxGroup';
88
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
99

10+
import { type FieldHelperProps, renderFieldHelpers } from './fieldHelpers';
1011
import styles from './styles/CheckboxGroup.module.css';
1112
import { useLPContextProps } from './utils';
1213

1314
const checkboxGroupStyles = cva(styles.group);
1415

15-
interface CheckboxGroupProps extends AriaCheckboxGroupProps {
16+
interface CheckboxGroupProps extends AriaCheckboxGroupProps, FieldHelperProps {
1617
ref?: Ref<HTMLDivElement>;
1718
}
1819

@@ -23,7 +24,7 @@ const CheckboxGroupContext = createContext<ContextValue<CheckboxGroupProps, HTML
2324
*
2425
* https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html
2526
*/
26-
const CheckboxGroup = ({ ref, ...props }: CheckboxGroupProps) => {
27+
const CheckboxGroup = ({ ref, description, errorMessage, ...props }: CheckboxGroupProps) => {
2728
[props, ref] = useLPContextProps(props, ref, CheckboxGroupContext);
2829
return (
2930
<AriaCheckboxGroup
@@ -32,7 +33,11 @@ const CheckboxGroup = ({ ref, ...props }: CheckboxGroupProps) => {
3233
className={composeRenderProps(props.className, (className, renderProps) =>
3334
checkboxGroupStyles({ ...renderProps, className }),
3435
)}
35-
/>
36+
>
37+
{composeRenderProps(props.children, (children) =>
38+
renderFieldHelpers(children, { description, errorMessage }),
39+
)}
40+
</AriaCheckboxGroup>
3641
);
3742
};
3843

packages/components/src/ComboBox.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { composeRenderProps } from 'react-aria-components/composeRenderProps';
1111
import { GroupContext } from 'react-aria-components/Group';
1212
import { Provider } from 'react-aria-components/slots';
1313

14+
import { type FieldHelperProps, renderFieldHelpers } from './fieldHelpers';
1415
import { IconButton } from './IconButton';
1516
import { PopoverContext } from './Popover';
1617
import styles from './styles/ComboBox.module.css';
1718
import { useLPContextProps } from './utils';
1819

1920
const comboBoxStyles = cva(styles.box);
2021

21-
interface ComboBoxProps<T extends object> extends AriaComboBoxProps<T> {
22+
interface ComboBoxProps<T extends object> extends AriaComboBoxProps<T>, FieldHelperProps {
2223
ref?: Ref<HTMLDivElement>;
2324
}
2425

@@ -32,7 +33,12 @@ const ComboBoxContext = createContext<ContextValue<ComboBoxProps<any>, HTMLDivEl
3233
*
3334
* https://react-spectrum.adobe.com/react-aria/ComboBox.html
3435
*/
35-
const ComboBox = <T extends object>({ ref, ...props }: ComboBoxProps<T>) => {
36+
const ComboBox = <T extends object>({
37+
ref,
38+
description,
39+
errorMessage,
40+
...props
41+
}: ComboBoxProps<T>) => {
3642
[props, ref] = useLPContextProps(props, ref, ComboBoxContext);
3743
const { menuTrigger = 'focus' } = props;
3844
const groupRef = useRef<HTMLDivElement>(null);
@@ -72,7 +78,7 @@ const ComboBox = <T extends object>({ ref, ...props }: ComboBoxProps<T>) => {
7278
],
7379
]}
7480
>
75-
{children}
81+
{renderFieldHelpers(children, { description, errorMessage })}
7682
</Provider>
7783
))}
7884
</AriaComboBox>

0 commit comments

Comments
 (0)