From 63bd09c4aa886aaa4cc2029710fe91755f7cec42 Mon Sep 17 00:00:00 2001 From: Marie Lucca <40550942+francinelucca@users.noreply.github.com> Date: Fri, 28 Mar 2025 23:50:12 -0400 Subject: [PATCH] =?UTF-8?q?Revert=20"Remove=20the=20CSS=20modules=20featur?= =?UTF-8?q?e=20flag=20from=20the=20FormControl=20component=20(#=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 926f8f8124bc7c825908dd28b3e4d5673a8f295b. --- .changeset/hungry-pumas-poke.md | 5 - .../react/src/FormControl/FormControl.tsx | 186 ++++++++++++------ .../src/FormControl/FormControlCaption.tsx | 34 +++- .../react/src/FormControl/feature-flags.ts | 1 + .../src/internal/components/InputLabel.tsx | 69 ++++++- .../internal/components/InputValidation.tsx | 63 +++++- .../src/internal/utils/toggleSxComponent.tsx | 2 +- 7 files changed, 269 insertions(+), 91 deletions(-) delete mode 100644 .changeset/hungry-pumas-poke.md create mode 100644 packages/react/src/FormControl/feature-flags.ts diff --git a/.changeset/hungry-pumas-poke.md b/.changeset/hungry-pumas-poke.md deleted file mode 100644 index 1a82f7332bb..00000000000 --- a/.changeset/hungry-pumas-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': minor ---- - -Removes feature flag for FormControl diff --git a/packages/react/src/FormControl/FormControl.tsx b/packages/react/src/FormControl/FormControl.tsx index 91d6b9dc0e5..e66af31eb61 100644 --- a/packages/react/src/FormControl/FormControl.tsx +++ b/packages/react/src/FormControl/FormControl.tsx @@ -20,8 +20,12 @@ import FormControlLeadingVisual from './FormControlLeadingVisual' import FormControlValidation from './_FormControlValidation' import {FormControlContextProvider} from './_FormControlContext' import {warning} from '../utils/warning' +import styled from 'styled-components' +import sx from '../sx' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {cssModulesFlag} from './feature-flags' +import {useFeatureFlag} from '../FeatureFlags' import classes from './FormControl.module.css' -import {defaultSxProp} from '../utils/defaultSxProp' export type FormControlProps = { children?: React.ReactNode @@ -47,6 +51,7 @@ export type FormControlProps = { const FormControl = React.forwardRef( ({children, disabled: disabledProp, layout = 'vertical', id: idProp, required, sx, className}, ref) => { + const enabled = useFeatureFlag(cssModulesFlag) const [slots, childrenWithoutSlots] = useSlots(children, { caption: FormControlCaption, label: FormControlLabel, @@ -117,46 +122,6 @@ const FormControl = React.forwardRef( } const isLabelHidden = slots.label?.props.visuallyHidden - const InputChildren = ( - <> -
- {React.isValidElement(InputComponent) - ? React.cloneElement( - InputComponent as React.ReactElement<{ - id: string - disabled: boolean - required: boolean - ['aria-describedby']: string - }>, - { - id, - disabled, - // allow checkboxes to be required - required: required && !isRadioInput, - ['aria-describedby']: captionId as string, - }, - ) - : null} - {childrenWithoutSlots.filter( - child => - React.isValidElement(child) && ![Checkbox, Radio].some(inputComponent => child.type === inputComponent), - )} -
- {slots.leadingVisual ? ( -
- {slots.leadingVisual} -
- ) : null} -
- {slots.label} - {slots.caption} -
- - ) return ( ( }} > {isChoiceInput || layout === 'horizontal' ? ( - sx !== defaultSxProp ? ( - - {InputChildren} - - ) : ( -
- {InputChildren} -
- ) + + + {React.isValidElement(InputComponent) + ? React.cloneElement( + InputComponent as React.ReactElement<{ + id: string + disabled: boolean + required: boolean + ['aria-describedby']: string + }>, + { + id, + disabled, + // allow checkboxes to be required + required: required && !isRadioInput, + ['aria-describedby']: captionId as string, + }, + ) + : null} + {childrenWithoutSlots.filter( + child => + React.isValidElement(child) && + ![Checkbox, Radio].some(inputComponent => child.type === inputComponent), + )} + + {slots.leadingVisual ? ( + + {slots.leadingVisual} + + ) : null} + + {slots.label} + {slots.caption} + + ) : ( ( display="flex" flexDirection="column" alignItems="flex-start" - sx={sx} - className={clsx(className, classes.ControlVerticalLayout)} + sx={ + enabled + ? sx + : {...(isLabelHidden ? {'> *:not(label) + *': {marginTop: 1}} : {'> * + *': {marginTop: 1}}), ...sx} + } + className={clsx(className, { + [classes.ControlVerticalLayout]: enabled, + })} > {slots.label} {React.isValidElement(InputComponent) && @@ -228,6 +229,69 @@ const FormControl = React.forwardRef( }, ) +const StyledHorizontalLayout = toggleStyledComponent( + cssModulesFlag, + 'div', + styled.div` + display: flex; + + &:where([data-has-leading-visual]) { + align-items: center; + } + + ${sx} + `, +) + +const StyledChoiceInputs = toggleStyledComponent( + cssModulesFlag, + 'div', + styled.div` + > input { + margin-left: 0; + margin-right: 0; + } + `, +) + +const StyledLabelContainer = toggleStyledComponent( + cssModulesFlag, + 'div', + styled.div` + > * { + padding-left: var(--stack-gap-condensed); + } + + > label { + font-weight: var(--base-text-weight-normal); + } + `, +) + +const StyledLeadingVisual = toggleStyledComponent( + cssModulesFlag, + 'div', + styled.div` + color: var(--fgColor-default); + margin-left: var(--base-size-8); + + &:where([data-disabled]) { + color: var(--fgColor-muted); + } + + > * { + fill: currentColor; + min-width: var(--text-body-size-large); + min-height: var(--text-body-size-large); + } + + > *:where([data-has-caption]) { + min-width: var(--base-size-24); + min-height: var(--base-size-24); + } + `, +) + export default Object.assign(FormControl, { Caption: FormControlCaption, Label: FormControlLabel, diff --git a/packages/react/src/FormControl/FormControlCaption.tsx b/packages/react/src/FormControl/FormControlCaption.tsx index c9c29e6c1cd..fa6e36034b6 100644 --- a/packages/react/src/FormControl/FormControlCaption.tsx +++ b/packages/react/src/FormControl/FormControlCaption.tsx @@ -1,10 +1,14 @@ import {clsx} from 'clsx' import React from 'react' +import styled from 'styled-components' +import {cssModulesFlag} from './feature-flags' +import {useFeatureFlag} from '../FeatureFlags' import Text from '../Text' +import sx from '../sx' import type {SxProp} from '../sx' import classes from './FormControlCaption.module.css' import {useFormControlContext} from './_FormControlContext' -import {toggleSxComponent} from '../internal/utils/toggleSxComponent' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' type FormControlCaptionProps = React.PropsWithChildren< { @@ -14,18 +18,36 @@ type FormControlCaptionProps = React.PropsWithChildren< > function FormControlCaption({id, children, sx, className}: FormControlCaptionProps) { + const enabled = useFeatureFlag(cssModulesFlag) const {captionId, disabled} = useFormControlContext() - const Caption = toggleSxComponent(sx, Text) as React.ComponentType - return ( - {children} - + ) } +const StyledCaption = toggleStyledComponent( + cssModulesFlag, + Text, + styled(Text)` + color: var(--fgColor-muted); + display: block; + font-size: var(--text-body-size-small); + + &:where([data-control-disabled]) { + color: var(--control-fgColor-disabled); + } + + ${sx} + `, +) + export {FormControlCaption} diff --git a/packages/react/src/FormControl/feature-flags.ts b/packages/react/src/FormControl/feature-flags.ts new file mode 100644 index 00000000000..32e2cdb02ce --- /dev/null +++ b/packages/react/src/FormControl/feature-flags.ts @@ -0,0 +1 @@ +export const cssModulesFlag = 'primer_react_css_modules_ga' diff --git a/packages/react/src/internal/components/InputLabel.tsx b/packages/react/src/internal/components/InputLabel.tsx index e4b77f039f4..54002f3a3fb 100644 --- a/packages/react/src/internal/components/InputLabel.tsx +++ b/packages/react/src/internal/components/InputLabel.tsx @@ -1,8 +1,11 @@ import {clsx} from 'clsx' import React from 'react' -import {type SxProp} from '../../sx' +import styled from 'styled-components' +import sx, {type SxProp} from '../../sx' +import {cssModulesFlag} from '../../FormControl/feature-flags' +import {useFeatureFlag} from '../../FeatureFlags' import classes from './InputLabel.module.css' -import {toggleSxComponent} from '../utils/toggleSxComponent' +import {toggleStyledComponent} from '../utils/toggleStyledComponent' type BaseProps = SxProp & { disabled?: boolean @@ -40,26 +43,76 @@ function InputLabel({ className, ...props }: Props) { - const Label = toggleSxComponent({sx}, as) as React.ComponentType + const enabled = useFeatureFlag(cssModulesFlag) return ( - + ) } +const StyledLabel = toggleStyledComponent( + cssModulesFlag, + 'label', + styled.label` + align-self: flex-start; + display: block; + color: var(--fgColor-default); + cursor: pointer; + font-weight: 600; + font-size: var(--text-body-size-medium); + + &:where([data-control-disabled]) { + color: var(--fgColor-muted); + cursor: not-allowed; + } + + &:where([data-visually-hidden]) { + border: 0; + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + width: 1px; + } + + ${sx} + `, +) + +const StyledRequiredText = toggleStyledComponent( + cssModulesFlag, + 'span', + styled.span` + display: flex; + column-gap: var(--base-size-4); + `, +) + export {InputLabel} diff --git a/packages/react/src/internal/components/InputValidation.tsx b/packages/react/src/internal/components/InputValidation.tsx index 05542c65571..d7224c429fa 100644 --- a/packages/react/src/internal/components/InputValidation.tsx +++ b/packages/react/src/internal/components/InputValidation.tsx @@ -2,11 +2,14 @@ import type {IconProps} from '@primer/octicons-react' import {AlertFillIcon, CheckCircleFillIcon} from '@primer/octicons-react' import {clsx} from 'clsx' import React from 'react' +import styled from 'styled-components' import Text from '../../Text' +import sx from '../../sx' import type {SxProp} from '../../sx' +import {cssModulesFlag} from '../../FormControl/feature-flags' import type {FormValidationStatus} from '../../utils/types/FormValidationStatus' +import {useFeatureFlag} from '../../FeatureFlags' import classes from './InputValidation.module.css' -import {defaultSxProp} from '../../utils/defaultSxProp' type Props = { id: string @@ -22,6 +25,7 @@ const validationIconMap: Record< } const InputValidation: React.FC> = ({children, id, validationStatus, sx}) => { + const enabled = useFeatureFlag(cssModulesFlag) const IconComponent = validationStatus ? validationIconMap[validationStatus] : undefined // TODO: use `text-caption-lineHeight` token as a custom property when it's available @@ -31,15 +35,19 @@ const InputValidation: React.FC> = ({children, id const iconBoxMinHeight = iconSize * captionLineHeight return ( - {IconComponent ? ( - + ) : null} - {children} - - + + ) } +const StyledInputValidation = styled(Text)` + color: var(--inputValidation-fgColor); + display: flex; + font-size: var(--text-body-size-small); + font-weight: 600; + + & :where(a) { + color: currentColor; + text-dectoration: underline; + } + + &:where([data-validation-status='success']) { + --inputValidation-fgColor: var(--fgColor-success); + } + + &:where([data-validation-status='error']) { + --inputValidation-fgColor: var(--fgColor-danger); + } + + ${sx} +` + +const StyledValidationIcon = styled.span` + align-items: center; + display: flex; + margin-inline-end: var(--base-size-4); + min-height: var(--inputValidation-iconSize); +` + +const StyledValidationText = styled.span` + line-height: var(--inputValidation-lineHeight); +` + export default InputValidation diff --git a/packages/react/src/internal/utils/toggleSxComponent.tsx b/packages/react/src/internal/utils/toggleSxComponent.tsx index aba06239780..44904262ee7 100644 --- a/packages/react/src/internal/utils/toggleSxComponent.tsx +++ b/packages/react/src/internal/utils/toggleSxComponent.tsx @@ -17,7 +17,7 @@ type CSSModulesProps = { * @param defaultAs - the default component to use when `as` is not provided */ export function toggleSxComponent( - sx: BetterSystemStyleObject | undefined, + sx: BetterSystemStyleObject, // eslint-disable-next-line @typescript-eslint/no-explicit-any defaultAs: string | React.ComponentType, ) {