Skip to content

Text Input enhancements #1661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-shrimps-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Text Input enhancements
109 changes: 91 additions & 18 deletions docs/content/TextInput.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,78 @@ TextInput is a form component to add default styling to the native text input.

**Note:** Don't forget to set `aria-label` to make the TextInput accessible to screen reader users.

## Examples
## Default example

```jsx live
<TextInput aria-label="Zipcode" name="zipcode" placeholder="Zipcode" autoComplete="postal-code" />
```

## Text Input with icons

```jsx live
<>
<TextInput
leadingVisual={SearchIcon}
aria-label="Zipcode"
name="zipcode"
placeholder="Zip"
autoComplete="postal-code"
/>

<TextInput
trailingVisual={SearchIcon}
aria-label="Zipcode"
name="zipcode"
placeholder="Zip"
autoComplete="postal-code"
/>
</>
```

## Text Input with text visuals

```jsx live
<>
<TextInput aria-label="Zipcode" name="zipcode" placeholder="Zipcode" autoComplete="postal-code" />
<TextInput leadingVisual="$" aria-label="Cost of the thing" name="cost" placeholder="23.45" />

<TextInput sx={{width: '150px'}} trailingVisual="minutes" aria-label="Time in minutes" name="time" placeholder="25" />
</>
```

<TextInput sx={{ml: 4}} aria-label="City" name="city" placeholder="City" contrast />
## Text Input with error and warning states

```jsx live
<>
<TextInput
validationStatus="error"
aria-label="Zipcode"
name="zipcode"
placeholder="Error"
autoComplete="postal-code"
/>

<TextInput
sx={{ml: 4}}
icon={SearchIcon}
validationStatus="warning"
aria-label="Zipcode"
name="zipcode"
placeholder="Find user"
placeholder="Warning"
autoComplete="postal-code"
/>
</>
```

## Block text input

```jsx live
<TextInput block aria-label="Zipcode" name="zipcode" placeholder="560076" autoComplete="postal-code" />
```

## Contrast text input

```jsx live
<TextInput contrast aria-label="Zipcode" name="zipcode" placeholder="Find user" autoComplete="postal-code" />
```

## Props

### TextInput
Expand All @@ -39,7 +92,7 @@ TextInput is a form component to add default styling to the native text input.
defaultValue="false"
description={
<>
Adds <InlineCode>display: block</InlineCode> to element
Creates a full width input element
</>
}
/>
Expand All @@ -49,19 +102,36 @@ TextInput is a form component to add default styling to the native text input.
defaultValue="false"
description="Changes background color to a higher contrast color"
/>
<PropsTableRow
name="variant"
type="'small' | 'large'"
description="Creates a smaller or larger input than the default."
/>
<PropsTableRow name='size' type="'small' | 'medium' | 'large'" description="Creates a smaller or larger input than the default." />

<PropsTableRow
name="leadingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the left edge inside the input"
/>
<PropsTableRow
name="trailingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the right edge inside the input"
/>
<PropsTableRow name="validationStatus" type="'warning' | 'error'" description="Style the input to match the status" />

<PropsTableRow
name="variant"
type="'small' | 'medium' | 'large'"
description="(Use size) Creates a smaller or larger input than the default."
deprecated
/>

<PropsTableRow
name="width"
type={
<>
string | number | <Link href="https://styled-system.com/guides/array-props">Array&lt;string | number&gt;</Link>
</>
}
description="Set the width of the input"
description="(Use sx prop) Set the width of the input"
deprecated
/>
<PropsTableRow
name="maxWidth"
Expand All @@ -70,7 +140,8 @@ TextInput is a form component to add default styling to the native text input.
string | number | <Link href="https://styled-system.com/guides/array-props">Array&lt;string | number&gt;</Link>
</>
}
description="Set the maximum width of the input"
description="(Use sx prop) Set the maximum width of the input"
deprecated
/>
<PropsTableRow
name="minWidth"
Expand All @@ -79,12 +150,14 @@ TextInput is a form component to add default styling to the native text input.
string | number | <Link href="https://styled-system.com/guides/array-props">Array&lt;string | number&gt;</Link>
</>
}
description="Set the minimum width of the input"
description="(Use sx prop) Set the minimum width of the input"
deprecated
/>
<PropsTableRow
name="icon"
type="React.ComponentType"
description="An Octicon React component. To be used inside of input. Positioned on the left edge."
description="(Use leadingVisual or trailingVisual) An Octicon React component. To be used inside of input. Positioned on the left edge."
deprecated
/>
<PropsTableSxRow />
<PropsTableRefRow
Expand All @@ -110,8 +183,8 @@ TextInput is a form component to add default styling to the native text input.
adaptsToScreenSizes: true,
fullTestCoverage: false,
usedInProduction: true,
usageExamplesDocumented: false,
hasStorybookStories: false,
usageExamplesDocumented: true,
hasStorybookStories: true,
designReviewed: false,
a11yReviewed: false,
stableApi: false,
Expand Down
30 changes: 25 additions & 5 deletions src/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import TextInputWrapper from './_TextInputWrapper'

type NonPassthroughProps = {
className?: string
/** @deprecated Use `leadingVisual` or `trailingVisual` prop instead */
icon?: React.ComponentType<{className?: string}>
leadingVisual?: string | React.ComponentType<{className?: string}>
trailingVisual?: string | React.ComponentType<{className?: string}>
} & Pick<
ComponentProps<typeof TextInputWrapper>,
'block' | 'contrast' | 'disabled' | 'sx' | 'theme' | 'width' | 'maxWidth' | 'minWidth' | 'variant'
'block' | 'contrast' | 'disabled' | 'sx' | 'width' | 'maxWidth' | 'minWidth' | 'variant' | 'size'
>

// Note: using ComponentProps instead of ComponentPropsWithoutRef here would cause a type issue where `css` is a required prop.
Expand All @@ -20,16 +23,21 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputInternalProps>(
(
{
icon: IconComponent,
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
block,
className,
contrast,
disabled,
validationStatus,
sx: sxProp,
theme,
size: sizeProp,
// start deprecated props
width: widthProp,
minWidth: minWidthProp,
maxWidth: maxWidthProp,
variant: variantProp,
// end deprecated props
...inputProps
},
ref
Expand All @@ -41,18 +49,30 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputInternalProps>(
<TextInputWrapper
block={block}
className={wrapperClasses}
validationStatus={validationStatus}
contrast={contrast}
disabled={disabled}
hasIcon={!!IconComponent}
sx={sxProp}
theme={theme}
size={sizeProp}
width={widthProp}
minWidth={minWidthProp}
maxWidth={maxWidthProp}
variant={variantProp}
hasLeadingVisual={Boolean(LeadingVisual)}
hasTrailingVisual={Boolean(TrailingVisual)}
>
{IconComponent && <IconComponent className="TextInput-icon" />}
<UnstyledTextInput ref={ref} disabled={disabled} {...inputProps} />
{LeadingVisual && (
<span className="TextInput-icon">
{typeof LeadingVisual === 'function' ? <LeadingVisual /> : LeadingVisual}
</span>
)}
<UnstyledTextInput ref={ref} disabled={disabled} {...inputProps} data-component="input" />
{TrailingVisual && (
<span className="TextInput-icon">
{typeof TrailingVisual === 'function' ? <TrailingVisual /> : TrailingVisual}
</span>
)}
</TextInputWrapper>
)
}
Expand Down
8 changes: 5 additions & 3 deletions src/TextInputWithTokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {TokenSizeKeys} from './Token/TokenBase'
import {TextInputProps} from './TextInput'
import {useProvidedRefOrCreate} from './hooks'
import UnstyledTextInput from './_UnstyledTextInput'
import TextInputWrapper from './_TextInputWrapper'
import TextInputWrapper, {textInputHorizPadding} from './_TextInputWrapper'
import Box from './Box'
import Text from './Text'
import {isFocusable} from '@primer/behaviors/utils'
Expand Down Expand Up @@ -241,14 +241,16 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
className={className}
contrast={contrast}
disabled={disabled}
hasIcon={!!IconComponent}
hasLeadingVisual={Boolean(IconComponent)}
theme={theme}
width={widthProp}
minWidth={minWidthProp}
maxWidth={maxWidthProp}
variant={variantProp}
onClick={focusInput}
sx={{
paddingLeft: textInputHorizPadding,
py: `calc(${textInputHorizPadding} / 2)`,
...(block
? {
display: 'flex',
Expand Down Expand Up @@ -289,13 +291,13 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
}
}}
>
{IconComponent && <IconComponent className="TextInput-icon" />}
<Box
sx={{
order: 1,
flexGrow: 1
}}
>
{IconComponent && <IconComponent className="TextInput-icon" />}
<UnstyledTextInput
ref={combinedInputRef}
disabled={disabled}
Expand Down
Loading