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
6 changes: 5 additions & 1 deletion docs/content/TextInput.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ TextInput is a form component to add default styling to the native text input.
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="validationStatus"
type="'error' | 'success' | 'warning'"
description="Style the input to match the status"
/>

<PropsTableRow
name="variant"
Expand Down
43 changes: 42 additions & 1 deletion docs/content/TextInputWithTokens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,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 @@ -197,13 +228,23 @@ render(MaxHeightExample)
defaultValue="false"
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="size"
type="'small' | 'medium' | 'large' | 'extralarge'"
defaultValue="extralarge"
description="The size of the tokens and 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="'error' | 'success' | 'warning'"
description="Style the input to match the status"
/>
<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 @@ -42,19 +42,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
Loading