Skip to content
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

Input validation styles #1831

Merged
merged 11 commits into from
Feb 10, 2022
5 changes: 5 additions & 0 deletions .changeset/lemon-stingrays-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Makes validation styling available for Select and TextInputWithTokens
38 changes: 38 additions & 0 deletions docs/content/TextInputWithTokens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,37 @@ const MaxHeightExample = () => {
render(MaxHeightExample)
```

### With an error validation status

```javascript live noinline
const BasicExample = () => {
const [tokens, setTokens] = React.useState([
{text: 'zero', id: 0},
{text: 'one', id: 1},
{text: 'two', id: 2}
])
const onTokenRemove = tokenId => {
setTokens(tokens.filter(token => token.id !== tokenId))
}

return (
<>
<Box as="label" display="block" htmlFor="inputWithTokens-basic">
Basic example tokens
</Box>
<TextInputWithTokens
tokens={tokens}
onTokenRemove={onTokenRemove}
id="inputWithTokens-basic"
validationStatus="error"
/>
</>
)
}

render(BasicExample)
```

## Props

<PropsTable>
Expand Down Expand Up @@ -213,12 +244,19 @@ render(MaxHeightExample)
description="Whether tokens should render inline horizontally. By default, tokens wrap to new lines"
/>
<PropsTable.Row name="size" type="TokenSizeKeys" defaultValue="extralarge" description="The size of the tokens" />
<PropsTable.Row
name="inputSize"
type="'small' | 'large'"
defaultValue="'small'"
description="The size of the text input"
/>
mperrotti marked this conversation as resolved.
Show resolved Hide resolved
<PropsTable.Row
name="hideTokenRemoveButtons"
type="boolean"
defaultValue="false"
description="Whether the remove buttons should be rendered in the tokens"
/>
<PropsTableRow name="validationStatus" type="'warning' | 'error'" description="Style the input to match the status" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're not removing 'warning' as part of this issue, do we need to create another one or does it already exist?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think you're missing 'success' from here too?

Suggested change
<PropsTableRow name="validationStatus" type="'warning' | 'error'" description="Style the input to match the status" />
<PropsTableRow name="validationStatus" type="'warning' | 'error' | 'success'" description="Style the input to match the status" />

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one docs related one (sorry): Can we add an example showing validationStatus being applied?

Copy link
Contributor Author

@mperrotti mperrotti Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're not removing 'warning' as part of this issue, do we need to create another one or does it already exist?

We need to create one. I'm going to add this as a topic of conversation for the Primer patterns working session.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one docs related one (sorry): Can we add an example showing validationStatus being applied?

Yes

<PropsTable.Row
name="visibleTokenCount"
type="number"
Expand Down
6 changes: 4 additions & 2 deletions src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {get} from './constants'
import TextInputWrapper, {StyledWrapperProps} from './_TextInputWrapper'

type SelectProps = Omit<
Omit<React.HTMLProps<HTMLSelectElement>, 'size'> & StyledWrapperProps,
Omit<React.HTMLProps<HTMLSelectElement>, 'size'> & Omit<StyledWrapperProps, 'variant'>,
'multiple' | 'hasLeadingVisual' | 'hasTrailingVisual' | 'as'
>

Expand Down Expand Up @@ -41,19 +41,21 @@ const ArrowIndicator = styled(ArrowIndicatorSVG)`
`

const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
({children, disabled, placeholder, size, required, ref: _propsRef, ...rest}: SelectProps, ref) => (
({children, disabled, placeholder, size, required, validationStatus, ref: _propsRef, ...rest}: SelectProps, ref) => (
<TextInputWrapper
sx={{
position: 'relative'
}}
size={size}
validationStatus={validationStatus}
>
<StyledSelect
ref={ref}
required={required || Boolean(placeholder)}
disabled={disabled}
aria-required={required}
aria-disabled={disabled}
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
{...rest}
>
{placeholder && (
Expand Down
18 changes: 14 additions & 4 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, {textInputHorizPadding} from './_TextInputWrapper'
import TextInputWrapper, {textInputHorizPadding, TextInputSizes} from './_TextInputWrapper'
import Box from './Box'
import Text from './Text'
import {isFocusable} from '@primer/behaviors/utils'
Expand Down Expand Up @@ -42,7 +42,7 @@ type TextInputWithTokensInternalProps<TokenComponentType extends AnyReactCompone
*/
preventTokenWrapping?: boolean
/**
* The size of the tokens
* The size of the tokens and text input
*/
size?: TokenSizeKeys
/**
Expand Down Expand Up @@ -82,7 +82,8 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
width: widthProp,
minWidth: minWidthProp,
maxWidth: maxWidthProp,
variant: variantProp,
validationStatus,
variant: variantProp, // deprecated. use `size` instead
visibleTokenCount,
...rest
}: TextInputWithTokensInternalProps<TokenComponentType> & {
Expand Down Expand Up @@ -234,6 +235,12 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
}

const visibleTokens = tokensAreTruncated ? tokens.slice(0, visibleTokenCount) : tokens
const inputSizeMap: Record<TokenSizeKeys, TextInputSizes> = {
small: 'small',
medium: 'small',
large: 'medium',
extralarge: 'medium'
}

return (
<TextInputWrapper
Expand All @@ -246,7 +253,9 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
width={widthProp}
minWidth={minWidthProp}
maxWidth={maxWidthProp}
variant={variantProp}
size={size && inputSizeMap[size]}
validationStatus={validationStatus}
mperrotti marked this conversation as resolved.
Show resolved Hide resolved
variant={variantProp} // deprecated. use `size` prop instead
onClick={focusInput}
sx={{
paddingLeft: textInputHorizPadding,
Expand Down Expand Up @@ -306,6 +315,7 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
onKeyDown={handleInputKeyDown}
type="text"
sx={{height: '100%'}}
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
{...inputPropsRest}
/>
</Box>
Expand Down
6 changes: 4 additions & 2 deletions src/_TextInputWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {get} from './constants'
import sx, {SxProp} from './sx'
import {FormValidationStatus} from './utils/types/FormValidationStatus'

export type TextInputSizes = 'small' | 'medium' | 'large'

const sizeDeprecatedVariants = variant({
variants: {
small: {
Expand Down Expand Up @@ -53,8 +55,8 @@ export type StyledWrapperProps = {
hasLeadingVisual?: boolean
hasTrailingVisual?: boolean
/** @deprecated Use `size` prop instead */
variant?: 'small' | 'large'
size?: 'small' | 'large'
variant?: TextInputSizes
size?: TextInputSizes
} & StyledBaseWrapperProps

const textInputBasePadding = '12px'
Expand Down
38 changes: 38 additions & 0 deletions src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ exports[`TextInputWithTokens renders a truncated set of tokens 1`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -282,6 +283,7 @@ exports[`TextInputWithTokens renders a truncated set of tokens 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -552,6 +554,7 @@ exports[`TextInputWithTokens renders as block layout 1`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -561,6 +564,7 @@ exports[`TextInputWithTokens renders as block layout 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -844,6 +848,7 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -853,6 +858,7 @@ exports[`TextInputWithTokens renders at a maximum height when specified 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -1322,6 +1328,13 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 1`] = `
-ms-flex-align: stretch;
align-items: stretch;
min-height: 32px;
min-height: 28px;
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
line-height: 20px;
padding-left: 12px;
padding-top: calc(12px / 2);
padding-bottom: calc(12px / 2);
Expand Down Expand Up @@ -1516,6 +1529,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 1`] = `
<span
className="c0"
onClick={[Function]}
size="small"
>
<div
className="c1"
Expand All @@ -1525,6 +1539,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -1994,6 +2009,13 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 2`] = `
-ms-flex-align: stretch;
align-items: stretch;
min-height: 32px;
min-height: 28px;
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
line-height: 20px;
padding-left: 12px;
padding-top: calc(12px / 2);
padding-bottom: calc(12px / 2);
Expand Down Expand Up @@ -2188,6 +2210,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 2`] = `
<span
className="c0"
onClick={[Function]}
size="small"
>
<div
className="c1"
Expand All @@ -2197,6 +2220,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 2`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -2860,6 +2884,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 3`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -2869,6 +2894,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 3`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -3532,6 +3558,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 4`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -3541,6 +3568,7 @@ exports[`TextInputWithTokens renders tokens at the specified sizes 4`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -4206,6 +4234,7 @@ exports[`TextInputWithTokens renders tokens on a single line when specified 1`]
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -4215,6 +4244,7 @@ exports[`TextInputWithTokens renders tokens on a single line when specified 1`]
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -4826,6 +4856,7 @@ exports[`TextInputWithTokens renders tokens without a remove button when specifi
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -4835,6 +4866,7 @@ exports[`TextInputWithTokens renders tokens without a remove button when specifi
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -5258,6 +5290,7 @@ exports[`TextInputWithTokens renders with tokens 1`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -5267,6 +5300,7 @@ exports[`TextInputWithTokens renders with tokens 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -5935,6 +5969,7 @@ exports[`TextInputWithTokens renders with tokens using a custom token component
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -5944,6 +5979,7 @@ exports[`TextInputWithTokens renders with tokens using a custom token component
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down Expand Up @@ -6479,6 +6515,7 @@ exports[`TextInputWithTokens renders without tokens 1`] = `
<span
className="c0"
onClick={[Function]}
size="medium"
>
<div
className="c1"
Expand All @@ -6488,6 +6525,7 @@ exports[`TextInputWithTokens renders without tokens 1`] = `
className="c2"
>
<input
aria-invalid="false"
className="c3"
onBlur={[Function]}
onFocus={[Function]}
Expand Down
Loading