diff --git a/.changeset/fifty-suns-smoke.md b/.changeset/fifty-suns-smoke.md new file mode 100644 index 00000000000..749aa264863 --- /dev/null +++ b/.changeset/fifty-suns-smoke.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Convert Radio to css modules behind feature flag diff --git a/packages/react/src/Radio/Radio.dev.stories.tsx b/packages/react/src/Radio/Radio.dev.stories.tsx new file mode 100644 index 00000000000..56dcf214241 --- /dev/null +++ b/packages/react/src/Radio/Radio.dev.stories.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import {Box, FormControl, Radio} from '..' + +export default { + title: 'Components/Radio/Dev', + component: Radio, +} + +export const SxProp = () => { + return ( + + + + Label + + + ) +} diff --git a/packages/react/src/Radio/Radio.module.css b/packages/react/src/Radio/Radio.module.css new file mode 100644 index 00000000000..2032cb8a697 --- /dev/null +++ b/packages/react/src/Radio/Radio.module.css @@ -0,0 +1,34 @@ +.Radio { + border-radius: var(--borderRadius-full, 100vh); + transition: + background-color, + border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */ + + &:where(:checked) { + /* stylelint-disable-next-line primer/colors */ + background-color: var(--control-checked-fgColor-rest); + + /* using bgColor here to avoid a border change in dark high contrast */ + /* stylelint-disable-next-line primer/colors */ + border-color: var(--control-checked-bgColor-rest); + border-width: var(--borderWidth-thicker); + + &:disabled { + cursor: not-allowed; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--fgColor-muted); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--fgColor-muted); + } + } + + &:focus, + &:focus-within { + @mixin focusOutline 2px; + } + + @media (forced-colors: active) { + background-color: canvastext; + border-color: canvastext; + } +} diff --git a/packages/react/src/Radio/Radio.tsx b/packages/react/src/Radio/Radio.tsx index 4ddcb346d5d..6040ad4fd20 100644 --- a/packages/react/src/Radio/Radio.tsx +++ b/packages/react/src/Radio/Radio.tsx @@ -8,6 +8,11 @@ import {RadioGroupContext} from '../RadioGroup/RadioGroup' import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles' import {get} from '../constants' import {sharedCheckboxAndRadioStyles} from '../internal/utils/sharedCheckboxAndRadioStyles' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' +import {clsx} from 'clsx' +import classes from './Radio.module.css' +import sharedClasses from '../Checkbox/shared.module.css' export type RadioProps = { /** @@ -42,47 +47,63 @@ export type RadioProps = { } & InputHTMLAttributes & SxProp -const StyledRadio = styled.input` - ${sharedCheckboxAndRadioStyles}; - border-radius: var(--borderRadius-full, 100vh); - transition: - background-color, - border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */ +const StyledRadio = toggleStyledComponent( + 'primer_react_css_modules_team', + 'input', + styled.input` + ${sharedCheckboxAndRadioStyles}; + border-radius: var(--borderRadius-full, 100vh); + transition: + background-color, + border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */ - &:checked { - border-width: var(--base-size-4, 4px); - border-color: var( - --control-checked-bgColor-rest, - ${get('colors.accent.fg')} - ); /* using bgColor here to avoid a border change in dark high contrast */ - background-color: var(--control-checked-fgColor-rest, ${get('colors.fg.onEmphasis')}); + &:checked { + border-width: var(--base-size-4, 4px); + border-color: var( + --control-checked-bgColor-rest, + ${get('colors.accent.fg')} + ); /* using bgColor here to avoid a border change in dark high contrast */ + background-color: var(--control-checked-fgColor-rest, ${get('colors.fg.onEmphasis')}); - &:disabled { - cursor: not-allowed; - border-color: ${get('colors.fg.muted')}; - background-color: ${get('colors.fg.muted')}; + &:disabled { + cursor: not-allowed; + border-color: ${get('colors.fg.muted')}; + background-color: ${get('colors.fg.muted')}; + } } - } - ${getGlobalFocusStyles()}; + ${getGlobalFocusStyles()}; - @media (forced-colors: active) { - background-color: canvastext; - border-color: canvastext; - } + @media (forced-colors: active) { + background-color: canvastext; + border-color: canvastext; + } - ${sx} -` + ${sx} + `, +) /** * An accessible, native radio component for selecting one option from a list. */ const Radio = React.forwardRef( ( - {checked, disabled, name: nameProp, onChange, sx: sxProp, required, validationStatus, value, ...rest}: RadioProps, + { + checked, + disabled, + name: nameProp, + onChange, + sx: sxProp, + required, + validationStatus, + value, + className, + ...rest + }: RadioProps, ref, ): ReactElement => { const radioGroupContext = useContext(RadioGroupContext) + const enabled = useFeatureFlag('primer_react_css_modules_team') const handleOnChange: ChangeEventHandler = e => { radioGroupContext?.onChange && radioGroupContext.onChange(e) onChange && onChange(e) @@ -110,6 +131,10 @@ const Radio = React.forwardRef( aria-invalid={validationStatus === 'error' ? 'true' : 'false'} sx={sxProp} onChange={handleOnChange} + className={clsx(className, { + [sharedClasses.Input]: enabled, + [classes.Radio]: enabled, + })} {...rest} /> )