Skip to content

Commit

Permalink
Add trailingAction to TextInput (#1947)
Browse files Browse the repository at this point in the history
* adds loading indicator to text inputs

* removes style debugging div

* updates stories and docs

* updates tests

* fixes silly test mistakes

* revert default textinput story

* adds changeset

* Update src/TextInput.tsx

Co-authored-by: Pavithra Kodmad <pksjce@github.com>

* updates component API

* adds trailing action to text input, tweaks formcontrol

* addresses PR feedback

* fixes linting error

* indicate a busy status to assistive technology

* updates snaps

* renames 'isLoading' prop to 'loading'

* Update docs/content/TextInput.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* Update docs/content/TextInput.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* Update docs/content/TextInput.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* Update docs/content/TextInput.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* Update docs/content/TextInput.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* updates snapshots

* nests examples under an 'Examples' heading

* export TextInput non-pass-through props

* fix undefined type usage

* fixes story types

* rm unnecessary width style from formcontrol, fix formcontrol import

* rm forgotten comment

* adds changeset

* silences linting error and rms bad imports

* addresses PR feedback

* rms redundant stories

* removes excess right padding when we pass 'trailingAction' and 'loading'

* change iconLabel to aria-label in docs example

* empy commit to make CI run

* disable html-addon to prevent Storybook from timing out Chromatic

Co-authored-by: Pavithra Kodmad <pksjce@github.com>
Co-authored-by: Cole Bemis <colebemis@github.com>
  • Loading branch information
3 people committed Mar 29, 2022
1 parent 310d215 commit edc85c9
Show file tree
Hide file tree
Showing 13 changed files with 1,703 additions and 194 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-moose-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Adds the option to render a trailing action inside of the TextInput component
159 changes: 110 additions & 49 deletions docs/content/TextInput.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ const WithIconAndLoadingIndicator = () => {
render(<WithIconAndLoadingIndicator />)
```

### With trailing action

```jsx live
<Box display="grid" sx={{gap: 3}}>
<FormControl>
<FormControl.Label>Icon action</FormControl.Label>
<TextInput
trailingAction={
<TextInput.Action
onClick={() => {
alert('clear input')
}}
icon={XIcon}
aria-label="Clear input"
sx={{color: 'fg.subtle'}}
/>
}
/>
</FormControl>
<FormControl>
<FormControl.Label>Text action</FormControl.Label>
<TextInput
trailingAction={
<TextInput.Action
onClick={() => {
alert('clear input')
}}
>
Clear
</TextInput.Action>
}
/>
</FormControl>
</Box>
```

### With error and warning states

```jsx live
Expand Down Expand Up @@ -160,61 +196,63 @@ render(<WithIconAndLoadingIndicator />)

<PropsTable>
<PropsTableRow name="aria-label" type="string" description="Allows input to be accessible." />
<PropsTableRow
name="block"
type="boolean"
defaultValue="false"
description="Creates a full-width input element"
/>
<PropsTableRow name="block" type="boolean" defaultValue="false" description="Creates a full-width input element" />
<PropsTableRow
name="contrast"
type="boolean"
defaultValue="false"
description="Changes background color to a higher contrast color"
/>
<PropsTableRow name='size' type="'small' | 'medium' | 'large'" description="Creates a smaller or larger input than the default." />

<PropsTableRow name="loading" type="boolean" description="Whether to show a loading indicator in the input" />
<PropsTableRow
name="loaderPosition"
type="'auto' | 'leading' | 'trailing'"
description={
<>
<div>Which position to render the loading indicator</div>
<ul>
<li>
'auto' (default): at the end of the input, unless a `leadingVisual` is passed. Then, it will render at the
beginning
</li>
<li>'leading': at the beginning of the input</li>
<li>'trailing': at the end of the input</li>
</ul>
</>
}
/>
<PropsTableRow
name="leadingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the left edge inside the input"
/>
<PropsTableRow name="monospace" type="boolean" defaultValue="false" description="Shows input in monospace font" />
<PropsTableRow
name="trailingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the right edge inside the input"
/>
<PropsTableRow
name="validationStatus"
type="'error' | 'success' | 'warning'"
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="size"
type="'small' | 'medium' | 'large'"
description="Creates a smaller or larger input than the default."
/>
<PropsTableRow name="loading" type="boolean" description="Whether to show a loading indicator in the input" />
<PropsTableRow
name="loaderPosition"
type="'auto' | 'leading' | 'trailing'"
description={
<>
<div>Which position to render the loading indicator</div>
<ul>
<li>
'auto' (default): at the end of the input, unless a `leadingVisual` is passed. Then, it will render at the
beginning
</li>
<li>'leading': at the beginning of the input</li>
<li>'trailing': at the end of the input</li>
</ul>
</>
}
/>
<PropsTableRow
name="leadingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the left edge inside the input"
/>
<PropsTableRow name="monospace" type="boolean" defaultValue="false" description="Shows input in monospace font" />
<PropsTableRow
name="trailingVisual"
type={<>string | React.ComponentType</>}
description="Visual positioned on the right edge inside the input"
/>
<PropsTableRow
name="trailingAction"
type="React.ReactElement<HTMLProps<HTMLButtonElement>>"
description="A visual that renders inside the input after the typing area"
/>
<PropsTableRow
name="validationStatus"
type="'error' | 'success' | 'warning'"
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={
Expand Down Expand Up @@ -265,6 +303,29 @@ render(<WithIconAndLoadingIndicator />)
/>
</PropsTable>

### TextInput.Action

<PropsTable>
<PropsTableRow
name="aria-label"
type="string"
description="Text that appears in a tooltip. If an icon is passed, this is also used as the label used by assistive technologies."
/>
<PropsTableRow name="icon" type="React.FunctionComponent" description="The icon to render inside the button" />
<PropsTableRow
name="variant"
type="'default' | 'primary' | 'invisible' | 'danger'"
description="Determine's the styles on a button"
/>
<PropsTableBasePropRows
elementType="button"
refType="HTMLButtonElement"
passthroughPropsLink={
<Link href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes">MDN</Link>
}
/>
</PropsTable>

## Status

<ComponentChecklist
Expand Down
4 changes: 2 additions & 2 deletions src/FormControl/FormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
ref={ref}
display="flex"
flexDirection="column"
width="100%"
sx={{...(isLabelHidden ? {'> *:not(label) + *': {marginTop: 1}} : {'> * + *': {marginTop: 1}}), ...sx}}
>
{slots.Label}
Expand All @@ -184,7 +183,8 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
required,
disabled,
validationStatus,
['aria-describedby']: [validationMessageId, captionId].filter(Boolean).join(' ')
['aria-describedby']: [validationMessageId, captionId].filter(Boolean).join(' '),
...InputComponent.props
})}
{React.Children.toArray(children).filter(
child =>
Expand Down
41 changes: 38 additions & 3 deletions src/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {MouseEventHandler} from 'react'
import React, {MouseEventHandler, useCallback, useState} from 'react'
import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
import classnames from 'classnames'

Expand All @@ -7,6 +7,7 @@ import {useProvidedRefOrCreate} from './hooks'
import {Merge} from './utils/types'
import TextInputWrapper, {StyledWrapperProps} from './_TextInputWrapper'
import UnstyledTextInput from './_UnstyledTextInput'
import TextInputAction from './_TextInputInnerAction'

export type TextInputNonPassthroughProps = {
/** @deprecated Use `leadingVisual` or `trailingVisual` prop instead */
Expand All @@ -28,6 +29,10 @@ export type TextInputNonPassthroughProps = {
* A visual that renders inside the input after the typing area
*/
trailingVisual?: string | React.ComponentType<{className?: string}>
/**
* A visual that renders inside the input after the typing area
*/
trailingAction?: React.ReactElement<React.HTMLProps<HTMLButtonElement>>
} & Pick<
StyledWrapperProps,
| 'block'
Expand All @@ -52,6 +57,7 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
icon: IconComponent,
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
trailingAction,
block,
className,
contrast,
Expand All @@ -62,6 +68,8 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
validationStatus,
sx: sxProp,
size: sizeProp,
onFocus,
onBlur,
// start deprecated props
width: widthProp,
minWidth: minWidthProp,
Expand All @@ -72,6 +80,7 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
},
ref
) => {
const [isInputFocused, setIsInputFocused] = useState<boolean>(false)
const inputRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement>)
// this class is necessary to style FilterSearch, plz no touchy!
const wrapperClasses = classnames(className, 'TextInput-wrapper')
Expand All @@ -82,6 +91,20 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
const focusInput: MouseEventHandler = () => {
inputRef.current?.focus()
}
const handleInputFocus = useCallback(
e => {
setIsInputFocused(true)
onFocus && onFocus(e)
},
[onFocus]
)
const handleInputBlur = useCallback(
e => {
setIsInputFocused(false)
onBlur && onBlur(e)
},
[onBlur]
)

return (
<TextInputWrapper
Expand All @@ -99,6 +122,8 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
variant={variantProp}
hasLeadingVisual={Boolean(LeadingVisual || showLeadingLoadingIndicator)}
hasTrailingVisual={Boolean(TrailingVisual || showTrailingLoadingIndicator)}
hasTrailingAction={Boolean(trailingAction)}
isInputFocused={isInputFocused}
onClick={focusInput}
aria-live="polite"
aria-busy={Boolean(loading)}
Expand All @@ -111,14 +136,22 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
>
{typeof LeadingVisual === 'function' ? <LeadingVisual /> : LeadingVisual}
</TextInputInnerVisualSlot>
<UnstyledTextInput ref={inputRef} disabled={disabled} {...inputProps} data-component="input" />
<UnstyledTextInput
ref={inputRef}
disabled={disabled}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
{...inputProps}
data-component="input"
/>
<TextInputInnerVisualSlot
visualPosition="trailing"
showLoadingIndicator={showTrailingLoadingIndicator}
hasLoadingIndicator={typeof loading === 'boolean'}
>
{typeof TrailingVisual === 'function' ? <TrailingVisual /> : TrailingVisual}
</TextInputInnerVisualSlot>
{trailingAction}
</TextInputWrapper>
)
}
Expand All @@ -131,4 +164,6 @@ TextInput.defaultProps = {

TextInput.displayName = 'TextInput'

export default TextInput
export default Object.assign(TextInput, {
Action: TextInputAction
})
1 change: 1 addition & 0 deletions src/_InputLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const InputLabel: React.FC<Props & SxProp> = ({children, disabled, required, vis
display: 'block',
color: disabled ? 'fg.muted' : 'fg.default',
cursor: disabled ? 'default' : 'pointer',
alignSelf: 'flex-start',
...sx
}}
>
Expand Down
Loading

0 comments on commit edc85c9

Please sign in to comment.