Skip to content

Commit 88b8533

Browse files
authoredJan 21, 2025··
refactor(FormControl): update FormControl to use CSS Modules behind flag (#5578)
* Revert "Revert "refactor(FormControl): update FormControl to use CSS Modules …" This reverts commit 43afd36. * update InputLabel * fix css variable:
1 parent d76cd26 commit 88b8533

13 files changed

+440
-167
lines changed
 

‎.changeset/gentle-stingrays-search.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
Update FormControl to use CSS Modules behind feature flag

‎package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.ControlHorizontalLayout {
2+
display: flex;
3+
4+
&:where([data-has-leading-visual]) {
5+
align-items: center;
6+
}
7+
}
8+
9+
.ControlVerticalLayout {
10+
display: flex;
11+
flex-direction: column;
12+
align-items: flex-start;
13+
14+
& > *:not(label) + * {
15+
margin-top: var(--base-size-4);
16+
}
17+
18+
&[data-has-label] > * + * {
19+
margin-top: var(--base-size-4);
20+
}
21+
}
22+
23+
.ControlChoiceInputs > input {
24+
margin-right: 0;
25+
margin-left: 0;
26+
}
27+
28+
.LabelContainer {
29+
> * {
30+
/* stylelint-disable-next-line primer/spacing */
31+
padding-left: var(--stack-gap-condensed);
32+
}
33+
34+
> label {
35+
font-weight: var(--base-text-weight-normal);
36+
}
37+
}
38+
39+
.LeadingVisual {
40+
margin-left: var(--base-size-8);
41+
color: var(--fgColor-muted);
42+
43+
&:where([data-disabled]) {
44+
color: var(--control-fgColor-disabled);
45+
}
46+
47+
> * {
48+
min-width: var(--text-body-size-large);
49+
min-height: var(--text-body-size-large);
50+
fill: currentColor;
51+
}
52+
53+
> *:where([data-has-caption]) {
54+
min-width: var(--base-size-24);
55+
min-height: var(--base-size-24);
56+
}
57+
}

‎packages/react/src/FormControl/FormControl.tsx

+134-52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import {clsx} from 'clsx'
12
import React, {useContext} from 'react'
23
import Autocomplete from '../Autocomplete'
3-
import Box from '../Box'
44
import Checkbox from '../Checkbox'
55
import Radio from '../Radio'
66
import Select from '../Select/Select'
@@ -10,7 +10,6 @@ import TextInputWithTokens from '../TextInputWithTokens'
1010
import Textarea from '../Textarea'
1111
import {CheckboxOrRadioGroupContext} from '../internal/components/CheckboxOrRadioGroup'
1212
import ValidationAnimationContainer from '../internal/components/ValidationAnimationContainer'
13-
import {get} from '../constants'
1413
import {useSlots} from '../hooks/useSlots'
1514
import type {SxProp} from '../sx'
1615
import {useId} from '../hooks/useId'
@@ -20,6 +19,12 @@ import FormControlLeadingVisual from './FormControlLeadingVisual'
2019
import FormControlValidation from './_FormControlValidation'
2120
import {FormControlContextProvider} from './_FormControlContext'
2221
import {warning} from '../utils/warning'
22+
import styled from 'styled-components'
23+
import sx from '../sx'
24+
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
25+
import {cssModulesFlag} from './feature-flags'
26+
import {useFeatureFlag} from '../FeatureFlags'
27+
import classes from './FormControl.module.css'
2328

2429
export type FormControlProps = {
2530
children?: React.ReactNode
@@ -45,6 +50,7 @@ export type FormControlProps = {
4550

4651
const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
4752
({children, disabled: disabledProp, layout = 'vertical', id: idProp, required, sx, className}, ref) => {
53+
const enabled = useFeatureFlag(cssModulesFlag)
4854
const [slots, childrenWithoutSlots] = useSlots(children, {
4955
caption: FormControlCaption,
5056
label: FormControlLabel,
@@ -127,69 +133,62 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
127133
}}
128134
>
129135
{isChoiceInput || layout === 'horizontal' ? (
130-
<Box
136+
<StyledHorizontalLayout
131137
ref={ref}
132-
display="flex"
133-
alignItems={slots.leadingVisual ? 'center' : undefined}
138+
data-has-leading-visual={slots.leadingVisual ? '' : undefined}
134139
sx={sx}
135-
className={className}
140+
className={clsx(className, {
141+
[classes.ControlHorizontalLayout]: enabled,
142+
})}
136143
>
137-
<Box sx={{'> input': {marginLeft: 0, marginRight: 0}}}>
138-
{React.isValidElement(InputComponent) &&
139-
React.cloneElement(
140-
InputComponent as React.ReactElement<{
141-
id: string
142-
disabled: boolean
143-
required: boolean
144-
['aria-describedby']: string
145-
}>,
146-
{
147-
id,
148-
disabled,
149-
// allow checkboxes to be required
150-
required: required && !isRadioInput,
151-
['aria-describedby']: captionId as string,
152-
},
153-
)}
144+
<StyledChoiceInputs className={classes.ControlChoiceInputs}>
145+
{React.isValidElement(InputComponent)
146+
? React.cloneElement(
147+
InputComponent as React.ReactElement<{
148+
id: string
149+
disabled: boolean
150+
required: boolean
151+
['aria-describedby']: string
152+
}>,
153+
{
154+
id,
155+
disabled,
156+
// allow checkboxes to be required
157+
required: required && !isRadioInput,
158+
['aria-describedby']: captionId as string,
159+
},
160+
)
161+
: null}
154162
{childrenWithoutSlots.filter(
155163
child =>
156164
React.isValidElement(child) &&
157165
![Checkbox, Radio].some(inputComponent => child.type === inputComponent),
158166
)}
159-
</Box>
160-
{slots.leadingVisual && (
161-
<Box
162-
color={disabled ? 'fg.muted' : 'fg.default'}
163-
sx={{
164-
'> *': {
165-
minWidth: slots.caption ? get('fontSizes.4') : get('fontSizes.2'),
166-
minHeight: slots.caption ? get('fontSizes.4') : get('fontSizes.2'),
167-
fill: 'currentColor',
168-
},
169-
}}
170-
ml={2}
167+
</StyledChoiceInputs>
168+
{slots.leadingVisual ? (
169+
<StyledLeadingVisual
170+
className={clsx({
171+
[classes.LeadingVisual]: enabled,
172+
})}
173+
data-disabled={disabled ? '' : undefined}
174+
data-has-caption={slots.caption ? '' : undefined}
171175
>
172176
{slots.leadingVisual}
173-
</Box>
174-
)}
175-
<Box
176-
sx={{
177-
'> *': {paddingLeft: 'var(--stack-gap-condensed)'},
178-
'> label': {fontWeight: 'var(--base-text-weight-normal)'},
179-
}}
180-
>
177+
</StyledLeadingVisual>
178+
) : null}
179+
<StyledLabelContainer className={classes.LabelContainer}>
181180
{slots.label}
182181
{slots.caption}
183-
</Box>
184-
</Box>
182+
</StyledLabelContainer>
183+
</StyledHorizontalLayout>
185184
) : (
186-
<Box
185+
<StyledVerticalLayout
187186
ref={ref}
188-
display="flex"
189-
flexDirection="column"
190-
alignItems="flex-start"
191-
sx={{...(isLabelHidden ? {'> *:not(label) + *': {marginTop: 1}} : {'> * + *': {marginTop: 1}}), ...sx}}
192-
className={className}
187+
data-has-label={!isLabelHidden ? '' : undefined}
188+
sx={sx}
189+
className={clsx(className, {
190+
[classes.ControlVerticalLayout]: enabled,
191+
})}
193192
>
194193
{slots.label}
195194
{React.isValidElement(InputComponent) &&
@@ -215,13 +214,96 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
215214
<ValidationAnimationContainer show>{slots.validation}</ValidationAnimationContainer>
216215
) : null}
217216
{slots.caption}
218-
</Box>
217+
</StyledVerticalLayout>
219218
)}
220219
</FormControlContextProvider>
221220
)
222221
},
223222
)
224223

224+
const StyledHorizontalLayout = toggleStyledComponent(
225+
cssModulesFlag,
226+
'div',
227+
styled.div`
228+
display: flex;
229+
230+
&:where([data-has-leading-visual]) {
231+
align-items: center;
232+
}
233+
234+
${sx}
235+
`,
236+
)
237+
238+
const StyledChoiceInputs = toggleStyledComponent(
239+
cssModulesFlag,
240+
'div',
241+
styled.div`
242+
> input {
243+
margin-left: 0;
244+
margin-right: 0;
245+
}
246+
`,
247+
)
248+
249+
const StyledLabelContainer = toggleStyledComponent(
250+
cssModulesFlag,
251+
'div',
252+
styled.div`
253+
> * {
254+
padding-left: var(--stack-gap-condensed);
255+
}
256+
257+
> label {
258+
font-weight: var(--base-text-weight-normal);
259+
}
260+
`,
261+
)
262+
263+
const StyledVerticalLayout = toggleStyledComponent(
264+
cssModulesFlag,
265+
'div',
266+
styled.div`
267+
display: flex;
268+
flex-direction: column;
269+
align-items: flex-start;
270+
271+
& > *:not(label) + * {
272+
margin-top: var(--base-size-4);
273+
}
274+
275+
&:where([data-has-label]) > * + * {
276+
margin-top: var(--base-size-4);
277+
}
278+
279+
${sx}
280+
`,
281+
)
282+
283+
const StyledLeadingVisual = toggleStyledComponent(
284+
cssModulesFlag,
285+
'div',
286+
styled.div`
287+
color: var(--fgColor-default);
288+
margin-left: var(--base-size-8);
289+
290+
&:where([data-disabled]) {
291+
color: var(--fgColor-muted);
292+
}
293+
294+
> * {
295+
fill: currentColor;
296+
min-width: var(--text-body-size-large);
297+
min-height: var(--text-body-size-large);
298+
}
299+
300+
> *:where([data-has-caption]) {
301+
min-width: var(--base-size-24);
302+
min-height: var(--base-size-24);
303+
}
304+
`,
305+
)
306+
225307
export default Object.assign(FormControl, {
226308
Caption: FormControlCaption,
227309
Label: FormControlLabel,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.Caption {
2+
display: block;
3+
font-size: var(--text-body-size-small);
4+
color: var(--fgColor-muted);
5+
6+
&:where([data-control-disabled]) {
7+
color: var(--control-fgColor-disabled);
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1+
import {clsx} from 'clsx'
12
import React from 'react'
2-
import type {SxProp} from '../sx'
3-
import {useFormControlContext} from './_FormControlContext'
4-
import Text from '../Text'
53
import styled from 'styled-components'
6-
import {get} from '../constants'
4+
import {cssModulesFlag} from './feature-flags'
5+
import {useFeatureFlag} from '../FeatureFlags'
6+
import Text from '../Text'
77
import sx from '../sx'
8-
9-
const StyledCaption = styled(Text)`
10-
color: var(--fgColor-muted);
11-
display: block;
12-
font-size: ${get('fontSizes.0')};
13-
14-
&:where([data-control-disabled]) {
15-
color: var(--control-fgColor-disabled);
16-
}
17-
18-
${sx}
19-
`
8+
import type {SxProp} from '../sx'
9+
import classes from './FormControlCaption.module.css'
10+
import {useFormControlContext} from './_FormControlContext'
11+
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
2012

2113
type FormControlCaptionProps = React.PropsWithChildren<
2214
{
@@ -25,12 +17,36 @@ type FormControlCaptionProps = React.PropsWithChildren<
2517
>
2618

2719
function FormControlCaption({id, children, sx}: FormControlCaptionProps) {
20+
const enabled = useFeatureFlag(cssModulesFlag)
2821
const {captionId, disabled} = useFormControlContext()
2922
return (
30-
<StyledCaption id={id ?? captionId} data-control-disabled={disabled ? '' : undefined} sx={sx}>
23+
<StyledCaption
24+
id={id ?? captionId}
25+
className={clsx({
26+
[classes.Caption]: enabled,
27+
})}
28+
data-control-disabled={disabled ? '' : undefined}
29+
sx={sx}
30+
>
3131
{children}
3232
</StyledCaption>
3333
)
3434
}
3535

36+
const StyledCaption = toggleStyledComponent(
37+
cssModulesFlag,
38+
Text,
39+
styled(Text)`
40+
color: var(--fgColor-muted);
41+
display: block;
42+
font-size: var(--text-body-size-small);
43+
44+
&:where([data-control-disabled]) {
45+
color: var(--control-fgColor-disabled);
46+
}
47+
48+
${sx}
49+
`,
50+
)
51+
3652
export {FormControlCaption}

‎packages/react/src/__tests__/FormControl.test.tsx ‎packages/react/src/FormControl/__tests__/FormControl.test.tsx

+1-53
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react'
22
import {render} from '@testing-library/react'
3-
import {renderHook} from '@testing-library/react-hooks'
43
import axe from 'axe-core'
54
import {
65
Autocomplete,
@@ -12,8 +11,7 @@ import {
1211
Textarea,
1312
TextInput,
1413
TextInputWithTokens,
15-
useFormControlForwardedProps,
16-
} from '..'
14+
} from '../..'
1715
import {MarkGithubIcon} from '@primer/octicons-react'
1816

1917
const LABEL_TEXT = 'Form control'
@@ -454,53 +452,3 @@ describe('FormControl', () => {
454452
})
455453
})
456454
})
457-
458-
describe('useFormControlForwardedProps', () => {
459-
describe('when used outside FormControl', () => {
460-
test('returns empty object when no props object passed', () => {
461-
const result = renderHook(() => useFormControlForwardedProps({}))
462-
expect(result.result.current).toEqual({})
463-
})
464-
465-
test('returns passed props object instance when passed', () => {
466-
const props = {id: 'test-id'}
467-
const result = renderHook(() => useFormControlForwardedProps(props))
468-
expect(result.result.current).toBe(props)
469-
})
470-
})
471-
472-
test('provides context value when no props object is passed', () => {
473-
const id = 'test-id'
474-
475-
const {result} = renderHook(() => useFormControlForwardedProps({}), {
476-
wrapper: ({children}: {children: React.ReactNode}) => (
477-
<FormControl id={id} disabled required>
478-
<FormControl.Label>Label</FormControl.Label>
479-
{children}
480-
</FormControl>
481-
),
482-
})
483-
484-
expect(result.current.disabled).toBe(true)
485-
expect(result.current.id).toBe(id)
486-
expect(result.current.required).toBe(true)
487-
})
488-
489-
test('merges with props object, overriding to prioritize props when conflicting', () => {
490-
const props = {id: 'override-id', xyz: 'someValue'}
491-
492-
const {result} = renderHook(() => useFormControlForwardedProps(props), {
493-
wrapper: ({children}: {children: React.ReactNode}) => (
494-
<FormControl id="form-control-id" disabled>
495-
<FormControl.Label>Label</FormControl.Label>
496-
{children}
497-
</FormControl>
498-
),
499-
})
500-
501-
expect(result.current.disabled).toBe(true)
502-
expect(result.current.id).toBe(props.id)
503-
expect(result.current.required).toBeFalsy()
504-
expect(result.current.xyz).toBe(props.xyz)
505-
})
506-
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react'
2+
import {renderHook} from '@testing-library/react-hooks'
3+
import FormControl, {useFormControlForwardedProps} from '../../FormControl'
4+
5+
describe('useFormControlForwardedProps', () => {
6+
describe('when used outside FormControl', () => {
7+
test('returns empty object when no props object passed', () => {
8+
const result = renderHook(() => useFormControlForwardedProps({}))
9+
expect(result.result.current).toEqual({})
10+
})
11+
12+
test('returns passed props object instance when passed', () => {
13+
const props = {id: 'test-id'}
14+
const result = renderHook(() => useFormControlForwardedProps(props))
15+
expect(result.result.current).toBe(props)
16+
})
17+
})
18+
19+
test('provides context value when no props object is passed', () => {
20+
const id = 'test-id'
21+
22+
const {result} = renderHook(() => useFormControlForwardedProps({}), {
23+
wrapper: ({children}: {children: React.ReactNode}) => (
24+
<FormControl id={id} disabled required>
25+
<FormControl.Label>Label</FormControl.Label>
26+
{children}
27+
</FormControl>
28+
),
29+
})
30+
31+
expect(result.current.disabled).toBe(true)
32+
expect(result.current.id).toBe(id)
33+
expect(result.current.required).toBe(true)
34+
})
35+
36+
test('merges with props object, overriding to prioritize props when conflicting', () => {
37+
const props = {id: 'override-id', xyz: 'someValue'}
38+
39+
const {result} = renderHook(() => useFormControlForwardedProps(props), {
40+
wrapper: ({children}: {children: React.ReactNode}) => (
41+
<FormControl id="form-control-id" disabled>
42+
<FormControl.Label>Label</FormControl.Label>
43+
{children}
44+
</FormControl>
45+
),
46+
})
47+
48+
expect(result.current.disabled).toBe(true)
49+
expect(result.current.id).toBe(props.id)
50+
expect(result.current.required).toBeFalsy()
51+
expect(result.current.xyz).toBe(props.xyz)
52+
})
53+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const cssModulesFlag = 'primer_react_css_modules_team'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.Label {
2+
display: block;
3+
font-size: var(--text-body-size-medium);
4+
font-weight: var(--base-text-weight-semibold);
5+
color: var(--fgColor-default);
6+
cursor: pointer;
7+
align-self: flex-start;
8+
9+
&:where([data-control-disabled]) {
10+
color: var(--fgColor-muted);
11+
cursor: not-allowed;
12+
}
13+
14+
&:where([data-visually-hidden]) {
15+
position: absolute;
16+
width: 1px;
17+
height: 1px;
18+
padding: 0;
19+
/* stylelint-disable-next-line primer/spacing */
20+
margin: -1px;
21+
overflow: hidden;
22+
clip: rect(0 0 0 0);
23+
white-space: nowrap;
24+
border: 0;
25+
clip-path: inset(50%);
26+
}
27+
}
28+
29+
.RequiredText {
30+
display: flex;
31+
column-gap: var(--base-size-4);
32+
}

‎packages/react/src/internal/components/InputLabel.tsx

+51-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import {clsx} from 'clsx'
12
import React from 'react'
23
import styled from 'styled-components'
3-
import {get} from '../../constants'
44
import sx, {type SxProp} from '../../sx'
5+
import {cssModulesFlag} from '../../FormControl/feature-flags'
6+
import {useFeatureFlag} from '../../FeatureFlags'
7+
import classes from './InputLabel.module.css'
8+
import {toggleStyledComponent} from '../utils/toggleStyledComponent'
59

610
type BaseProps = SxProp & {
711
disabled?: boolean
@@ -39,19 +43,26 @@ function InputLabel({
3943
className,
4044
...props
4145
}: Props) {
46+
const enabled = useFeatureFlag(cssModulesFlag)
4247
return (
4348
<StyledLabel
4449
as={as}
4550
data-control-disabled={disabled ? '' : undefined}
4651
data-visually-hidden={visuallyHidden ? '' : undefined}
4752
htmlFor={htmlFor}
4853
id={id}
49-
className={className}
54+
className={clsx(className, {
55+
[classes.Label]: enabled,
56+
})}
5057
sx={sx}
5158
{...props}
5259
>
5360
{required || requiredText ? (
54-
<StyledRequiredText>
61+
<StyledRequiredText
62+
className={clsx({
63+
[classes.RequiredText]: enabled,
64+
})}
65+
>
5566
<span>{children}</span>
5667
<span aria-hidden={requiredIndicator ? undefined : true}>{requiredText ?? '*'}</span>
5768
</StyledRequiredText>
@@ -62,38 +73,46 @@ function InputLabel({
6273
)
6374
}
6475

65-
const StyledRequiredText = styled.span`
66-
display: flex;
67-
column-gap: ${get('space.1')};
68-
`
76+
const StyledLabel = toggleStyledComponent(
77+
cssModulesFlag,
78+
'label',
79+
styled.label`
80+
align-self: flex-start;
81+
display: block;
82+
color: var(--fgColor-default);
83+
cursor: pointer;
84+
font-weight: 600;
85+
font-size: var(--text-body-size-medium);
6986
70-
const StyledLabel = styled.label`
71-
align-self: flex-start;
72-
display: block;
73-
color: var(--fgColor-default);
74-
cursor: pointer;
75-
font-weight: 600;
76-
font-size: ${get('fontSizes.1')};
87+
&:where([data-control-disabled]) {
88+
color: var(--fgColor-muted);
89+
cursor: not-allowed;
90+
}
7791
78-
&:where([data-control-disabled]) {
79-
color: var(--fgColor-muted);
80-
cursor: not-allowed;
81-
}
92+
&:where([data-visually-hidden]) {
93+
border: 0;
94+
clip: rect(0 0 0 0);
95+
clip-path: inset(50%);
96+
height: 1px;
97+
margin: -1px;
98+
overflow: hidden;
99+
padding: 0;
100+
position: absolute;
101+
white-space: nowrap;
102+
width: 1px;
103+
}
82104
83-
&:where([data-visually-hidden]) {
84-
border: 0;
85-
clip: rect(0 0 0 0);
86-
clip-path: inset(50%);
87-
height: 1px;
88-
margin: -1px;
89-
overflow: hidden;
90-
padding: 0;
91-
position: absolute;
92-
white-space: nowrap;
93-
width: 1px;
94-
}
105+
${sx}
106+
`,
107+
)
95108

96-
${sx}
97-
`
109+
const StyledRequiredText = toggleStyledComponent(
110+
cssModulesFlag,
111+
'span',
112+
styled.span`
113+
display: flex;
114+
column-gap: var(--base-size-4);
115+
`,
116+
)
98117

99118
export {InputLabel}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.InputValidation {
2+
display: flex;
3+
font-size: var(--text-body-size-small);
4+
font-weight: var(--base-text-weight-semibold);
5+
/* stylelint-disable-next-line primer/colors */
6+
color: var(--inputValidation-fgColor);
7+
8+
& :where(a) {
9+
color: currentColor;
10+
text-decoration: underline;
11+
}
12+
13+
&:where([data-validation-status='success']) {
14+
--inputValidation-fgColor: var(--fgColor-success);
15+
}
16+
17+
&:where([data-validation-status='error']) {
18+
--inputValidation-fgColor: var(--fgColor-danger);
19+
}
20+
}
21+
22+
.ValidationIcon {
23+
align-items: center;
24+
display: flex;
25+
margin-inline-end: var(--base-size-4);
26+
min-height: var(--inputValidation-iconSize);
27+
}
28+
29+
.ValidationText {
30+
/* stylelint-disable-next-line primer/typography */
31+
line-height: var(--inputValidation-lineHeight);
32+
}

‎packages/react/src/internal/components/InputValidation.tsx

+28-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import type {IconProps} from '@primer/octicons-react'
22
import {AlertFillIcon, CheckCircleFillIcon} from '@primer/octicons-react'
3+
import {clsx} from 'clsx'
34
import React from 'react'
5+
import styled from 'styled-components'
46
import Text from '../../Text'
7+
import sx from '../../sx'
58
import type {SxProp} from '../../sx'
9+
import {cssModulesFlag} from '../../FormControl/feature-flags'
610
import type {FormValidationStatus} from '../../utils/types/FormValidationStatus'
7-
import styled from 'styled-components'
8-
import {get} from '../../constants'
9-
import sx from '../../sx'
11+
import {useFeatureFlag} from '../../FeatureFlags'
12+
import classes from './InputValidation.module.css'
1013

1114
type Props = {
1215
id: string
@@ -22,6 +25,7 @@ const validationIconMap: Record<
2225
}
2326

2427
const InputValidation: React.FC<React.PropsWithChildren<Props>> = ({children, id, validationStatus, sx}) => {
28+
const enabled = useFeatureFlag(cssModulesFlag)
2529
const IconComponent = validationStatus ? validationIconMap[validationStatus] : undefined
2630

2731
// TODO: use `text-caption-lineHeight` token as a custom property when it's available
@@ -31,10 +35,19 @@ const InputValidation: React.FC<React.PropsWithChildren<Props>> = ({children, id
3135
const iconBoxMinHeight = iconSize * captionLineHeight
3236

3337
return (
34-
<StyledInputValidation data-validation-status={validationStatus} sx={sx}>
38+
<StyledInputValidation
39+
className={clsx({
40+
[classes.InputValidation]: enabled,
41+
})}
42+
data-validation-status={validationStatus}
43+
sx={sx}
44+
>
3545
{IconComponent ? (
3646
<StyledValidationIcon
3747
aria-hidden="true"
48+
className={clsx({
49+
[classes.ValidationIcon]: enabled,
50+
})}
3851
style={
3952
{
4053
'--inputValidation-iconSize': iconBoxMinHeight,
@@ -44,7 +57,13 @@ const InputValidation: React.FC<React.PropsWithChildren<Props>> = ({children, id
4457
<IconComponent size={iconSize} fill="currentColor" />
4558
</StyledValidationIcon>
4659
) : null}
47-
<StyledValidationText id={id} style={{'--inputValidation-lineHeight': captionLineHeight} as React.CSSProperties}>
60+
<StyledValidationText
61+
id={id}
62+
className={clsx({
63+
[classes.ValidationText]: enabled,
64+
})}
65+
style={{'--inputValidation-lineHeight': captionLineHeight} as React.CSSProperties}
66+
>
4867
{children}
4968
</StyledValidationText>
5069
</StyledInputValidation>
@@ -54,7 +73,7 @@ const InputValidation: React.FC<React.PropsWithChildren<Props>> = ({children, id
5473
const StyledInputValidation = styled(Text)`
5574
color: var(--inputValidation-fgColor);
5675
display: flex;
57-
font-size: ${get('fontSizes.0')};
76+
font-size: var(--text-body-size-small);
5877
font-weight: 600;
5978
6079
& :where(a) {
@@ -63,11 +82,11 @@ const StyledInputValidation = styled(Text)`
6382
}
6483
6584
&:where([data-validation-status='success']) {
66-
--inputValidation-fgColor: ${get('colors.success.fg')};
85+
--inputValidation-fgColor: var(--fgColor-success);
6786
}
6887
6988
&:where([data-validation-status='error']) {
70-
--inputValidation-fgColor: ${get('colors.danger.fg')};
89+
--inputValidation-fgColor: var(--fgColor-danger);
7190
}
7291
7392
${sx}
@@ -76,7 +95,7 @@ const StyledInputValidation = styled(Text)`
7695
const StyledValidationIcon = styled.span`
7796
align-items: center;
7897
display: flex;
79-
margin-inline-end: ${get('space.1')};
98+
margin-inline-end: var(--base-size-4);
8099
min-height: var(--inputValidation-iconSize);
81100
`
82101

0 commit comments

Comments
 (0)
Please sign in to comment.