diff --git a/.changeset/tall-cameras-unite.md b/.changeset/tall-cameras-unite.md new file mode 100644 index 00000000000..f42cd2a8bad --- /dev/null +++ b/.changeset/tall-cameras-unite.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Migrate TextInputAction & TextInputInnerVisualSlot to CSS Modules behind feature flag diff --git a/packages/react/src/internal/components/TextInputInnerAction.module.css b/packages/react/src/internal/components/TextInputInnerAction.module.css new file mode 100644 index 00000000000..f312c132f3e --- /dev/null +++ b/packages/react/src/internal/components/TextInputInnerAction.module.css @@ -0,0 +1,31 @@ +.Invisible { + position: relative; + padding-top: var(--base-size-2); + padding-right: var(--base-size-4); + padding-bottom: var(--base-size-2); + padding-left: var(--base-size-4); + color: var(--fgColor-muted); + background-color: transparent; + + &:hover, + &:focus { + color: var(--fgColor-default); + } + + &[data-component='IconButton'] { + width: var(--inner-action-size); + height: var(--inner-action-size); + } + + @media (pointer: coarse) { + ::after { + position: absolute; + top: 50%; + right: 0; + left: 0; + min-height: 44px; + content: ''; + transform: translateY(-50%); + } + } +} diff --git a/packages/react/src/internal/components/TextInputInnerAction.tsx b/packages/react/src/internal/components/TextInputInnerAction.tsx index 75fcb22349f..6a24a24f4c2 100644 --- a/packages/react/src/internal/components/TextInputInnerAction.tsx +++ b/packages/react/src/internal/components/TextInputInnerAction.tsx @@ -6,6 +6,11 @@ import {Tooltip} from '../../TooltipV2' import type {ButtonProps} from '../../Button' import type {BetterSystemStyleObject, SxProp} from '../../sx' import {merge} from '../../sx' +import {clsx} from 'clsx' + +import styles from './TextInputInnerAction.module.css' +import {useFeatureFlag} from '../../FeatureFlags' +import {TEXT_INPUT_CSS_MODULES_FEATURE_FLAG} from './UnstyledTextInput' type TextInputActionProps = Omit< React.ButtonHTMLAttributes, @@ -89,15 +94,23 @@ const TextInputAction = forwardRef( children, icon, sx: sxProp, + className, variant = 'invisible', ...rest }, forwardedRef, ) => { - const sx = - variant === 'invisible' - ? merge(invisibleButtonStyleOverrides, sxProp || {}) - : sxProp || {} + const enabled = useFeatureFlag(TEXT_INPUT_CSS_MODULES_FEATURE_FLAG) + + const styleProps = enabled + ? {className: clsx(variant === 'invisible' && styles.Invisible, className), sx: sxProp || {}} + : { + className, + sx: + variant === 'invisible' + ? merge(invisibleButtonStyleOverrides, sxProp || {}) + : sxProp || {}, + } if ((icon && !ariaLabel) || (!children && !ariaLabel)) { // eslint-disable-next-line no-console @@ -122,13 +135,13 @@ const TextInputAction = forwardRef( type="button" icon={icon} size="small" - sx={sx} + {...styleProps} {...rest} ref={forwardedRef} /> ) : ( - diff --git a/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css b/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css new file mode 100644 index 00000000000..acf4539155a --- /dev/null +++ b/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css @@ -0,0 +1,19 @@ +.Spinner { + position: absolute; + top: 0; + right: 0; + max-width: 100%; + height: 100%; +} + +.SpinnerLeading { + left: 0; +} + +.SpinnerHidden { + visibility: hidden; +} + +.SpinnerVisible { + visibility: visible; +} diff --git a/packages/react/src/internal/components/TextInputInnerVisualSlot.tsx b/packages/react/src/internal/components/TextInputInnerVisualSlot.tsx index 13e6342a6fc..44cd3758c76 100644 --- a/packages/react/src/internal/components/TextInputInnerVisualSlot.tsx +++ b/packages/react/src/internal/components/TextInputInnerVisualSlot.tsx @@ -1,8 +1,13 @@ import React from 'react' import Box from '../../Box' import Spinner from '../../Spinner' +import {clsx} from 'clsx' import type {TextInputNonPassthroughProps} from '../../TextInput' +import styles from './TextInputInnerVisualSlot.module.css' +import {useFeatureFlag} from '../../FeatureFlags' +import {TEXT_INPUT_CSS_MODULES_FEATURE_FLAG} from './UnstyledTextInput' + const TextInputInnerVisualSlot: React.FC< React.PropsWithChildren<{ /** Whether the input is expected to ever show a loading indicator */ @@ -15,7 +20,9 @@ const TextInputInnerVisualSlot: React.FC< id?: string }> > = ({children, hasLoadingIndicator, showLoadingIndicator, visualPosition, id}) => { - if ((!children && !hasLoadingIndicator) || (visualPosition === 'leading' && !children && !showLoadingIndicator)) { + const enabled = useFeatureFlag(TEXT_INPUT_CSS_MODULES_FEATURE_FLAG) + const isLeading = visualPosition === 'leading' + if ((!children && !hasLoadingIndicator) || (isLeading && !children && !showLoadingIndicator)) { return null } @@ -27,26 +34,40 @@ const TextInputInnerVisualSlot: React.FC< ) } + const boxStyleProps = enabled + ? {className: clsx(showLoadingIndicator ? styles.SpinnerHidden : styles.SpinnerVisible)} + : { + sx: { + visibility: showLoadingIndicator ? 'hidden' : 'visible', + }, + } + + const spinnerStyleProps = enabled + ? { + className: clsx( + showLoadingIndicator ? styles.SpinnerVisible : styles.SpinnerHidden, + children && styles.Spinner, + children && isLeading && styles.SpinnerLeading, + ), + } + : { + sx: children + ? { + position: 'absolute', + top: 0, + height: '100%', + maxWidth: '100%', + visibility: showLoadingIndicator ? 'visible' : 'hidden', + ...(isLeading ? {left: 0} : {right: 0}), + } + : {visibility: showLoadingIndicator ? 'visible' : 'hidden'}, + } + return ( - {children && {children}} - + {children && {children}} + )