diff --git a/.changeset/dry-seals-count.md b/.changeset/dry-seals-count.md new file mode 100644 index 00000000000..87b384272ce --- /dev/null +++ b/.changeset/dry-seals-count.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Convert TextInput to CSS module behind feature flag diff --git a/packages/react/src/TextInputWithTokens/TextInputWithTokens.module.css b/packages/react/src/TextInputWithTokens/TextInputWithTokens.module.css index 77f34f28978..b3186ad8f86 100644 --- a/packages/react/src/TextInputWithTokens/TextInputWithTokens.module.css +++ b/packages/react/src/TextInputWithTokens/TextInputWithTokens.module.css @@ -1,3 +1,18 @@ +.TextInputWrapper { + padding-top: var(--base-size-6); + padding-bottom: var(--base-size-6); + padding-left: var(--base-size-12); + + &:where([data-block]) { + display: flex; + width: 100%; + } + + &:where([data-token-wrapping]) { + overflow: auto; + } +} + .UnstyledTextInput { height: 100%; } diff --git a/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx b/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx index 4ded926373d..54df57ca385 100644 --- a/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx +++ b/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx @@ -17,8 +17,9 @@ import TextInputWrapper from '../internal/components/TextInputWrapper' import UnstyledTextInput, {TEXT_INPUT_CSS_MODULES_FEATURE_FLAG} from '../internal/components/UnstyledTextInput' import TextInputInnerVisualSlot from '../internal/components/TextInputInnerVisualSlot' import {useFeatureFlag} from '../FeatureFlags' - import styles from './TextInputWithTokens.module.css' +import {clsx} from 'clsx' +import type {BetterSystemStyleObject} from '../sx' // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyReactComponent = React.ComponentType> @@ -96,6 +97,7 @@ function TextInputWithTokensInnerComponent, forwardedRef: React.ForwardedRef, @@ -255,10 +257,49 @@ function TextInputWithTokensInnerComponent + enabled + ? { + className: clsx(className, styles.TextInputWrapper), + style: maxHeight ? {maxHeight, ...style} : style, + sx: sxProp, + } + : { + className, + style, + sx: { + paddingLeft: '12px', + py: '6px', + ...(block + ? { + display: 'flex', + width: '100%', + } + : {}), + + ...(maxHeight + ? { + maxHeight, + overflow: 'auto', + } + : {}), + + ...(preventTokenWrapping + ? { + overflow: 'auto', + } + : {}), + + ...sxProp, + } as BetterSystemStyleObject, + }, + [block, className, enabled, maxHeight, preventTokenWrapping, style, sxProp], + ) + return ( {IconComponent && !LeadingVisual && } select { padding-left: var(--base-size-12); } - -.c1:where(:not([data-trailing-visual]):not([data-trailing-action])) > input, -.c1:where(:not([data-trailing-visual]):not([data-trailing-action])) > select { - padding-right: var(--base-size-12); -} - -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; + +.c1:where(:not([data-trailing-visual]):not([data-trailing-action])) > input, +.c1:where(:not([data-trailing-visual]):not([data-trailing-action])) > select { + padding-right: var(--base-size-12); } @media (min-width:768px) { @@ -7447,6 +7447,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c5 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c5:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -7645,20 +7659,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -7846,6 +7846,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c5 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c5:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -8044,20 +8058,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -8240,6 +8240,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c2 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c2:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -8438,20 +8452,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c2 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c2:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -8595,6 +8595,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c4 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c4:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -8793,20 +8807,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c4 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c4:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -8989,6 +8989,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c2 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c2:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -9187,20 +9201,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c2 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c2:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -9353,6 +9353,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c5 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c5:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -9551,20 +9565,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -9793,6 +9793,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c5 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c5:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -9991,20 +10005,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -10232,6 +10232,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` border-width: 0; } +.c5 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c5:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -10430,20 +10444,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` padding-right: var(--base-size-12); } -.c5 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c5:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -10625,6 +10625,20 @@ exports[`TextInput renders with a loading indicator 1`] = ` `; exports[`TextInput should render a password input 1`] = ` +.c2 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; +} + +.c2:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -10823,20 +10837,6 @@ exports[`TextInput should render a password input 1`] = ` padding-right: var(--base-size-12); } -.c2 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c2:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); diff --git a/packages/react/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap index 782b2826cb6..d06043e9a98 100644 --- a/packages/react/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap @@ -52,6 +52,21 @@ exports[`TextInputWithTokens renders a leadingVisual and trailingVisual 1`] = ` border-width: 0; } +.c4 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; + height: 100%; +} + +.c4:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -206,8 +221,8 @@ exports[`TextInputWithTokens renders a leadingVisual and trailingVisual 1`] = ` .c0 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; } .c1 { @@ -258,23 +273,8 @@ exports[`TextInputWithTokens renders a leadingVisual and trailingVisual 1`] = ` .c1 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); -} - -.c4 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; - height: 100%; -} - -.c4:focus { - outline: 0; + padding-top: 6px; + padding-bottom: 6px; } .c5 { @@ -992,6 +992,21 @@ exports[`TextInputWithTokens renders a truncated set of tokens 1`] = ` border-width: 0; } +.c4 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; + height: 100%; +} + +.c4:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -1146,8 +1161,8 @@ exports[`TextInputWithTokens renders a truncated set of tokens 1`] = ` .c0 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; } .c1 { @@ -1198,23 +1213,8 @@ exports[`TextInputWithTokens renders a truncated set of tokens 1`] = ` .c1 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); -} - -.c4 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; - height: 100%; -} - -.c4:focus { - outline: 0; + padding-top: 6px; + padding-bottom: 6px; } .c5 { @@ -1525,6 +1525,21 @@ exports[`TextInputWithTokens renders as block layout 1`] = ` flex-grow: 1; } +.c4 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; + height: 100%; +} + +.c4:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -1679,8 +1694,8 @@ exports[`TextInputWithTokens renders as block layout 1`] = ` .c0 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1736,8 +1751,8 @@ exports[`TextInputWithTokens renders as block layout 1`] = ` .c1 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1745,21 +1760,6 @@ exports[`TextInputWithTokens renders as block layout 1`] = ` width: 100%; } -.c4 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; - height: 100%; -} - -.c4:focus { - outline: 0; -} - @media (min-width:768px) { .c0 { font-size: var(--text-body-size-medium); @@ -1844,6 +1844,21 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = ` border-width: 0; } +.c4 { + border: 0; + font-size: inherit; + font-family: inherit; + background-color: transparent; + -webkit-appearance: none; + color: inherit; + width: 100%; + height: 100%; +} + +.c4:focus { + outline: 0; +} + .c0 { font-size: 14px; line-height: var(--base-size-20); @@ -1998,8 +2013,8 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = ` .c0 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; max-height: 20px; overflow: auto; } @@ -2052,27 +2067,12 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = ` .c1 { padding-left: 12px; - padding-top: calc(12px / 2); - padding-bottom: calc(12px / 2); + padding-top: 6px; + padding-bottom: 6px; max-height: 20px; overflow: auto; } -.c4 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; - height: 100%; -} - -.c4:focus { - outline: 0; -} - .c5 { -webkit-align-items: center; -webkit-box-align: center; @@ -2208,6 +2208,7 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = `
textarea { + padding: var(--base-size-12); + } + + &:where([data-contrast]) { + background-color: var(--bgColor-inset); + } + + &:where([data-disabled]) { + color: var(--fgColor-disabled); + background-color: var(--control-bgColor-disabled); + border-color: var(--control-borderColor-disabled); + box-shadow: none; + + input, + textarea, + select { + cursor: not-allowed; + } + } + + &:where([data-monospace]) { + font-family: var(--fontStack-monospace); + } + + &:where([data-validation='error']) { + border-color: var(--borderColor-danger-emphasis); + + &:where([data-trailing-action][data-focused]), + &:where(:not([data-trailing-action])):focus-within { + /* stylelint-disable-next-line primer/colors */ + border-color: var(--fgColor-accent); + outline: 2px solid var(--fgColor-accent); + outline-offset: -1px; + } + } + + &:where([data-validation='success']) { + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-success-emphasis); + } + + &:where([data-block]) { + display: flex; + width: 100%; + align-self: stretch; + } + + /* Ensures inputs don't zoom on mobile but are body-font size on desktop */ + @media screen and (--viewportRange-regular) { + font-size: var(--text-body-size-medium); + } + + --inner-action-size: var(--base-size-24); /* Default size */ + + &:where([data-size='small']) { + --inner-action-size: var(--base-size-20); + + min-height: var(--base-size-28); + /* stylelint-disable-next-line primer/spacing */ + padding-top: 3px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 3px; + padding-left: var(--base-size-8); + font-size: var(--text-body-size-small); + /* stylelint-disable-next-line primer/typography */ + line-height: var(--base-size-20); + } + + &:where([data-size='large']) { + --inner-action-size: var(--base-size-28); + + height: var(--base-size-40); + /* stylelint-disable-next-line primer/spacing */ + padding-top: 10px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 10px; + padding-left: var(--base-size-8); + } + + /* Deprecated */ + &:where([data-variant='small']) { + min-height: 28px; + /* stylelint-disable-next-line primer/spacing */ + padding-top: 3px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 3px; + padding-left: var(--base-size-8); + font-size: (--text-body-size-small); + /* stylelint-disable-next-line primer/typography */ + line-height: var(--base-size-20); + } + + /* Deprecated */ + &:where([data-variant='large']) { + /* stylelint-disable-next-line primer/spacing */ + padding-top: 10px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 10px; + padding-left: var(--base-size-8); + font-size: var(--text-title-size-medium); + } +} + +.TextInputWrapper { + padding-right: 0; + padding-left: 0; + + > input, + > select { + padding-right: 0; + padding-left: 0; + } + + /* Repeat and position set for form states (success, error, etc) */ + background-repeat: no-repeat; + + /* For form validation. This keeps images 8px from right and centered vertically. */ + background-position: right 8px center; + + & > :not(:last-child) { + margin-right: var(--base-size-8); + } + + & :global(.TextInput-icon), + & :global(.TextInput-action) { + align-self: center; + color: var(--fgColor-muted); + flex-shrink: 0; + } + + &:where([data-leading-visual]) { + padding-left: var(--base-size-12); + } + + &:where([data-trailing-visual]:not([data-trailing-action])) { + padding-right: var(--base-size-12); + } + + &:where(:not([data-leading-visual])) > input, + &:where(:not([data-leading-visual])) > select { + padding-left: var(--base-size-12); + } + + &:where(:not([data-trailing-visual]):not([data-trailing-action])) > input, + &:where(:not([data-trailing-visual]):not([data-trailing-action])) > select { + padding-right: var(--base-size-12); + } +} diff --git a/packages/react/src/internal/components/TextInputWrapper.tsx b/packages/react/src/internal/components/TextInputWrapper.tsx index c12ea54094e..e8075f19106 100644 --- a/packages/react/src/internal/components/TextInputWrapper.tsx +++ b/packages/react/src/internal/components/TextInputWrapper.tsx @@ -1,14 +1,20 @@ -import React from 'react' +import React, {type ComponentProps} from 'react' import styled from 'styled-components' -import {maxWidth, minWidth, width, type ResponsiveValue} from 'styled-system' +import {maxWidth as maxWidthFn, minWidth as minWidthFn, width as widthFn, type ResponsiveValue} from 'styled-system' import {get} from '../../constants' import type {SxProp} from '../../sx' import sx from '../../sx' import type {FormValidationStatus} from '../../utils/types/FormValidationStatus' +import {TEXT_INPUT_CSS_MODULES_FEATURE_FLAG} from './UnstyledTextInput' +import {toggleStyledComponent} from '../utils/toggleStyledComponent' +import {useFeatureFlag} from '../../FeatureFlags' +import {clsx} from 'clsx' + +import styles from './TextInputWrapper.module.css' export type TextInputSizes = 'small' | 'medium' | 'large' -export type StyledBaseWrapperProps = { +type StyledTextInputBaseWrapperProps = { block?: boolean contrast?: boolean disabled?: boolean @@ -22,6 +28,7 @@ export type StyledBaseWrapperProps = { className?: string style?: React.CSSProperties onClick?: React.MouseEventHandler + children?: React.ReactNode /** @deprecated Update `width` using CSS modules or style. */ width?: string | number | ResponsiveValue /** @deprecated Update `min-width` using CSS modules or style. */ @@ -30,241 +37,286 @@ export type StyledBaseWrapperProps = { maxWidth?: string | number | ResponsiveValue } & SxProp -export type StyledWrapperProps = { +type StyledTextInputWrapperProps = { hasLeadingVisual?: boolean hasTrailingVisual?: boolean -} & StyledBaseWrapperProps - -export const StyledTextInputBaseWrapper = styled.span` - font-size: ${get('fontSizes.1')}; - line-height: var(--base-size-20); - color: ${get('colors.fg.default')}; - vertical-align: middle; - background-color: ${get('colors.canvas.default')}; - border: 1px solid var(--control-borderColor-rest, ${get('colors.border.default')}); - border-radius: ${get('radii.2')}; - outline: none; - box-shadow: ${get('shadows.primer.shadow.inset')}; - display: inline-flex; - align-items: stretch; - min-height: var(--base-size-32); - overflow: hidden; - - input, - textarea { - cursor: text; - } +} & StyledTextInputBaseWrapperProps - select { - cursor: pointer; - } +const StyledTextInputBaseWrapper = toggleStyledComponent( + TEXT_INPUT_CSS_MODULES_FEATURE_FLAG, + 'span', + styled.span` + font-size: ${get('fontSizes.1')}; + line-height: var(--base-size-20); + color: ${get('colors.fg.default')}; + vertical-align: middle; + background-color: ${get('colors.canvas.default')}; + border: 1px solid var(--control-borderColor-rest, ${get('colors.border.default')}); + border-radius: ${get('radii.2')}; + outline: none; + box-shadow: ${get('shadows.primer.shadow.inset')}; + display: inline-flex; + align-items: stretch; + min-height: var(--base-size-32); + overflow: hidden; - input, - textarea, - select { - &::placeholder { - color: var(---control-fgColor-placeholder, ${get('colors.fg.muted')}); + input, + textarea { + cursor: text; } - } - - &:where([data-trailing-action][data-focused]), - &:where(:not([data-trailing-action]):focus-within) { - border-color: ${get('colors.accent.fg')}; - outline: 2px solid ${get('colors.accent.fg')}; - outline-offset: -1px; - } - > textarea { - padding: var(--base-size-12); - } - - &:where([data-contrast]) { - background-color: ${get('colors.canvas.inset')}; - } - - &:where([data-disabled]) { - color: ${get('colors.primer.fg.disabled')}; - background-color: ${get('colors.input.disabledBg')}; - box-shadow: none; - border-color: var(--control-borderColor-disabled, ${get('colors.border.default')}); + select { + cursor: pointer; + } input, textarea, select { - cursor: not-allowed; + &::placeholder { + color: var(---control-fgColor-placeholder, ${get('colors.fg.muted')}); + } } - } - - &:where([data-monospace]) { - font-family: ${get('fonts.mono')}; - } - - &:where([data-validation='error']) { - border-color: ${get('colors.danger.emphasis')}; &:where([data-trailing-action][data-focused]), - &:where(:not([data-trailing-action])):focus-within { + &:where(:not([data-trailing-action]):focus-within) { border-color: ${get('colors.accent.fg')}; outline: 2px solid ${get('colors.accent.fg')}; outline-offset: -1px; } - } - &:where([data-validation='success']) { - border-color: ${get('colors.success.emphasis')}; - } + > textarea { + padding: var(--base-size-12); + } - &:where([data-block]) { - width: 100%; - display: flex; - align-self: stretch; - } + &:where([data-contrast]) { + background-color: ${get('colors.canvas.inset')}; + } - /* Ensures inputs don' t zoom on mobile but are body-font size on desktop */ - @media (min-width: ${get('breakpoints.1')}) { - font-size: var(--text-body-size-medium); - } + &:where([data-disabled]) { + color: ${get('colors.primer.fg.disabled')}; + background-color: ${get('colors.input.disabledBg')}; + box-shadow: none; + border-color: var(--control-borderColor-disabled, ${get('colors.border.default')}); + + input, + textarea, + select { + cursor: not-allowed; + } + } - --inner-action-size: var(--base-size-24); /* Default size */ + &:where([data-monospace]) { + font-family: ${get('fonts.mono')}; + } - &:where([data-size='small']) { - --inner-action-size: var(--base-size-20); + &:where([data-validation='error']) { + border-color: ${get('colors.danger.emphasis')}; - min-height: var(--base-size-28); - padding-top: 3px; - padding-right: var(--base-size-8); - padding-bottom: 3px; - padding-left: var(--base-size-8); - font-size: var(--text-body-size-small); - line-height: var(--base-size-20); - } + &:where([data-trailing-action][data-focused]), + &:where(:not([data-trailing-action])):focus-within { + border-color: ${get('colors.accent.fg')}; + outline: 2px solid ${get('colors.accent.fg')}; + outline-offset: -1px; + } + } - &:where([data-size='large']) { - --inner-action-size: var(--base-size-28); + &:where([data-validation='success']) { + border-color: ${get('colors.success.emphasis')}; + } - height: var(--base-size-40); - padding-top: 10px; - padding-right: var(--base-size-8); - padding-bottom: 10px; - padding-left: var(--base-size-8); - } + &:where([data-block]) { + width: 100%; + display: flex; + align-self: stretch; + } - /* Deprecated */ - &:where([data-variant='small']) { - min-height: 28px; - padding-top: 3px; - padding-right: var(--base-size-8); - padding-bottom: 3px; - padding-left: var(--base-size-8); - font-size: (--text-body-size-small); - line-height: var(--base-size-20); - } + /* Ensures inputs don' t zoom on mobile but are body-font size on desktop */ + @media (min-width: ${get('breakpoints.1')}) { + font-size: var(--text-body-size-medium); + } - /* Deprecated */ - &:where([data-variant='large']) { - padding-top: 10px; - padding-right: var(--base-size-8); - padding-bottom: 10px; - padding-left: var(--base-size-8); - font-size: var(--text-title-size-medium); - } + --inner-action-size: var(--base-size-24); /* Default size */ - & { - ${width} - ${minWidth} - ${maxWidth} - ${sx} - } -` - -export function TextInputBaseWrapper(props: React.PropsWithChildren) { - const { - variant, - size, - isInputFocused, - hasTrailingAction, - validationStatus, - disabled, - contrast, - monospace, - block, - ...restProps - } = props - return ( - - ) -} - -const StyledTextInputWrapper = styled(TextInputBaseWrapper)` - /* Repeat and position set for form states (success, error, etc) */ - background-repeat: no-repeat; - - /* For form validation. This keeps images 8px from right and centered vertically. */ - background-position: right 8px center; - - & > :not(:last-child) { - margin-right: ${get('space.2')}; - } + &:where([data-size='small']) { + --inner-action-size: var(--base-size-20); - .TextInput-icon, - .TextInput-action { - align-self: center; - color: ${get('colors.fg.muted')}; - flex-shrink: 0; - } + min-height: var(--base-size-28); + padding-top: 3px; + padding-right: var(--base-size-8); + padding-bottom: 3px; + padding-left: var(--base-size-8); + font-size: var(--text-body-size-small); + line-height: var(--base-size-20); + } - padding-right: 0; - padding-left: 0; + &:where([data-size='large']) { + --inner-action-size: var(--base-size-28); + + height: var(--base-size-40); + padding-top: 10px; + padding-right: var(--base-size-8); + padding-bottom: 10px; + padding-left: var(--base-size-8); + } + + /* Deprecated */ + &:where([data-variant='small']) { + min-height: 28px; + padding-top: 3px; + padding-right: var(--base-size-8); + padding-bottom: 3px; + padding-left: var(--base-size-8); + font-size: (--text-body-size-small); + line-height: var(--base-size-20); + } + + /* Deprecated */ + &:where([data-variant='large']) { + padding-top: 10px; + padding-right: var(--base-size-8); + padding-bottom: 10px; + padding-left: var(--base-size-8); + font-size: var(--text-title-size-medium); + } + + & { + ${widthFn} + ${minWidthFn} + ${maxWidthFn} + ${sx} + } + `, +) + +export const TextInputBaseWrapper = React.forwardRef( + function TextInputBaseWrapper( + { + className, + style, + variant, + size, + isInputFocused, + hasTrailingAction, + validationStatus, + disabled, + contrast, + monospace, + block, + width, + minWidth, + maxWidth, + ...restProps + }, + forwardRef, + ) { + const enabled = useFeatureFlag(TEXT_INPUT_CSS_MODULES_FEATURE_FLAG) + const widthProps = enabled ? {} : {width, minWidth, maxWidth} + + return ( + + ) + }, +) +TextInputBaseWrapper.displayName = 'TextInputBaseWrapper' + +const StyledTextInputWrapper = toggleStyledComponent( + TEXT_INPUT_CSS_MODULES_FEATURE_FLAG, + TextInputBaseWrapper, + styled(TextInputBaseWrapper)` + /* Repeat and position set for form states (success, error, etc) */ + background-repeat: no-repeat; + + /* For form validation. This keeps images 8px from right and centered vertically. */ + background-position: right 8px center; + + & > :not(:last-child) { + margin-right: ${get('space.2')}; + } + + .TextInput-icon, + .TextInput-action { + align-self: center; + color: ${get('colors.fg.muted')}; + flex-shrink: 0; + } - > input, - > select { padding-right: 0; padding-left: 0; - } - &:where([data-leading-visual]) { - padding-left: var(--base-size-12); - } + > input, + > select { + padding-right: 0; + padding-left: 0; + } - &:where([data-trailing-visual]:not([data-trailing-action])) { - padding-right: var(--base-size-12); - } + &:where([data-leading-visual]) { + padding-left: var(--base-size-12); + } - &:where(:not([data-leading-visual])) > input, - &:where(:not([data-leading-visual])) > select { - padding-left: var(--base-size-12); - } + &:where([data-trailing-visual]:not([data-trailing-action])) { + padding-right: var(--base-size-12); + } - &:where(:not([data-trailing-visual]):not([data-trailing-action])) > input, - &:where(:not([data-trailing-visual]):not([data-trailing-action])) > select { - padding-right: var(--base-size-12); - } + &:where(:not([data-leading-visual])) > input, + &:where(:not([data-leading-visual])) > select { + padding-left: var(--base-size-12); + } - & { - ${sx} + &:where(:not([data-trailing-visual]):not([data-trailing-action])) > input, + &:where(:not([data-trailing-visual]):not([data-trailing-action])) > select { + padding-right: var(--base-size-12); + } + + & { + ${sx} + } + `, +) + +export const TextInputWrapper = React.forwardRef(function TextInputWrapper( + {className, hasLeadingVisual, hasTrailingVisual, ...restProps}, + forwardRef, +) { + const enabled = useFeatureFlag(TEXT_INPUT_CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return ( + + ) + } else { + return ( + + ) } -` - -export function TextInputWrapper(props: React.PropsWithChildren) { - const {hasLeadingVisual, hasTrailingVisual, ...restProps} = props - return ( - - ) -} +}) +export type StyledBaseWrapperProps = ComponentProps +export type StyledWrapperProps = ComponentProps export default TextInputWrapper