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 field components - text, checkbox, radio #1611

Merged
merged 74 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
a23e32d
adds components and stories for basic usage
mperrotti Nov 12, 2021
779e5fe
supports checkbox and radio fields
mperrotti Nov 15, 2021
0ad2715
styles disabled label styles, adds stories, jsdocs props, cleanups
mperrotti Nov 16, 2021
f8668ce
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Nov 16, 2021
e0addd0
adds missing jsdoc comment
mperrotti Nov 16, 2021
0d3ae10
adds components to render groups of radios or checkboxes
mperrotti Nov 17, 2021
a67c80f
supports LeadingVisual for checkbox and radio inputs
mperrotti Nov 17, 2021
fe9696a
animate in field validation message
mperrotti Nov 17, 2021
dd08828
updates validation API
mperrotti Nov 19, 2021
6054f1c
creates text, checkbox, and radio input field components
mperrotti Nov 22, 2021
910ee68
makes InputField validation props smarter
mperrotti Nov 22, 2021
00ecaba
rm ChoiceFieldset from this changeset
mperrotti Nov 22, 2021
115bb4e
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Nov 22, 2021
fc84152
fixes ts errors
mperrotti Nov 22, 2021
f70f8ad
fixes bad imports
mperrotti Nov 22, 2021
793c00d
cleanup
mperrotti Nov 22, 2021
e50bde8
adds VisuallyHidden component
mperrotti Nov 22, 2021
e9bcee9
require label to be passed
mperrotti Nov 23, 2021
4533f20
fixes typo from last commit
mperrotti Nov 23, 2021
ad1c9e8
adds React component docs for TextInputField, RadioInputField, and Ch…
mperrotti Nov 23, 2021
024b5d3
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Nov 23, 2021
006e976
adds tests, fixes check for Label slot
mperrotti Nov 24, 2021
79c2286
warns if id, required, or disabled props are passed directly to the I…
mperrotti Nov 24, 2021
581b4ca
fix issue where the input still had a top margin when the label was h…
mperrotti Nov 24, 2021
54b43a4
marks new internal components as private
mperrotti Nov 24, 2021
7b7c1f8
adds changeset
mperrotti Nov 24, 2021
0a220ad
updates imports
mperrotti Nov 24, 2021
6ea5201
Merge github.com:primer/react into mp/form-field-component
mperrotti Nov 24, 2021
504a9d9
updates stories and docs
mperrotti Nov 24, 2021
d721895
Merge github.com:primer/react into mp/form-field-component
mperrotti Nov 24, 2021
e5a964f
adds React component docs to doc nav, updates docs
mperrotti Nov 24, 2021
15da266
updates CheckboxInputField to use new Checkbox component
mperrotti Nov 24, 2021
cbab1ae
updates snapshots from using checkbox component
mperrotti Nov 24, 2021
ec1312a
Merge branch 'main' into mp/form-field-component
mperrotti Nov 24, 2021
b092729
Merge github.com:primer/react into mp/form-field-component
mperrotti Nov 30, 2021
6577aa8
Merge branch 'main' into mp/form-field-component
mperrotti Nov 30, 2021
7424440
lightens caption when field is disabled
mperrotti Nov 30, 2021
69334dc
removes unusable export from checkbox and radio input fields
mperrotti Nov 30, 2021
2691adc
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti Nov 30, 2021
4b20f1a
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 3, 2021
b137a07
updates Checkbox docs to point to CheckboxInputField
mperrotti Dec 3, 2021
3c18363
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 6, 2021
4e06ca7
uses new Radio component to render the input
mperrotti Dec 6, 2021
a179e92
Merge branch 'main' into mp/form-field-component
mperrotti Dec 8, 2021
73285ba
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 13, 2021
98a67e8
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti Dec 13, 2021
b6781f4
fixes bad import
mperrotti Dec 13, 2021
ff79a52
uses new package name
mperrotti Dec 13, 2021
f4ff5c8
replaces CheckboxInputField and RadioInputField with 1 component - Ch…
mperrotti Dec 13, 2021
58efa2b
replaces TextInputField component with InputField component
mperrotti Dec 13, 2021
c6c2062
updates documentation formatting
mperrotti Dec 13, 2021
e3f7a6e
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 13, 2021
e283cbe
more docs updates
mperrotti Dec 14, 2021
05f96dd
Merge branch 'main' into mp/form-field-component
mperrotti Dec 14, 2021
c1c4808
Update docs/content/Checkbox.md
mperrotti Dec 15, 2021
ce49a84
addresses PR feedback + misc cleanup
mperrotti Dec 15, 2021
6a7bb75
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 16, 2021
12d8047
TESTING DEPLOYMENT - revert InputField.mdx to last successful build
mperrotti Dec 16, 2021
fd269bf
Merge branch 'main' into mp/form-field-component
mperrotti Dec 16, 2021
73ad10c
attempting to get InputField docs to build
mperrotti Dec 16, 2021
5c56710
attempting to get InputField docs to build
mperrotti Dec 16, 2021
b42dc39
attempting to get InputField docs to build
mperrotti Dec 16, 2021
03156ef
probably fixed deployment
mperrotti Dec 16, 2021
00f5ff2
Merge branch 'main' into mp/form-field-component
mperrotti Dec 16, 2021
50a7f81
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 17, 2021
398e541
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti Dec 17, 2021
d6d3f03
Update docs/content/ChoiceInputField.mdx
mperrotti Dec 17, 2021
c17ec66
addresses more PR feedback
mperrotti Dec 17, 2021
a450bb8
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti Dec 17, 2021
7e6da8a
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 17, 2021
4d04066
try adding componentchecklist import to fix deployment failure
mperrotti Dec 17, 2021
15bf95c
adds comopnentId to docs frontmatter
mperrotti Dec 17, 2021
709d5a9
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti Dec 17, 2021
ad9e954
Merge branch 'main' into mp/form-field-component
mperrotti Dec 17, 2021
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
37,854 changes: 41 additions & 37,813 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"author": "GitHub, Inc.",
"license": "MIT",
"dependencies": {
"@primer/octicons-react": "^16.1.0",
"@primer/octicons-react": "16.1.1",
"@primer/primitives": "6.1.0",
"@radix-ui/react-polymorphic": "0.0.14",
"@react-aria/ssr": "3.1.0",
Expand Down
10 changes: 10 additions & 0 deletions src/InputCaption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import {Text} from '..'

interface Props {
id: string
}

const InputCaption: React.FC<Props> = props => <Text color="fg.muted" fontSize={0} {...props} />

export default InputCaption
76 changes: 76 additions & 0 deletions src/InputField/InputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react'
import {Box} from '..'
import createSlots from '../utils/create-slots'
import {ComponentProps} from '../utils/types'
import InputFieldCaption from './InputFieldCaption'
import InputFieldInput from './InputFieldInput'
import InputFieldLabel from './InputFieldLabel'
import InputFieldValidation from './InputFieldValidation'
export interface Props {
// TODO: limit children to specific components
// children: any;
/**
* Whether the field is ready for user input
*/
disabled?: boolean
/**
* The unique identifier for this field. Used to associate the label, validation text, and caption text
*/
id: string
/**
* Whether this field must have a value for the user to complete their task
*/
required?: boolean
/**
* Styles the field to visually communicate the result of form validation
*/
// TODO: Figure out if we're keeping the 'warning' status
validationStatus?: 'error' | 'warning' | 'success'
}

export interface InputFieldContext extends Pick<Props, 'disabled' | 'id' | 'required' | 'validationStatus'> {
captionId: string
validationMessageId: string
}

export const {Slots, Slot} = createSlots(['Caption', 'Validation', 'Input', 'Label'])

const InputField: React.FC<Props> = ({children, disabled, id, required, validationStatus}) => {
const hasValidationChild = React.Children.toArray(children).some(
child => React.isValidElement(child) && child.type === InputFieldValidation
)

return (
<Slots
context={{
captionId: `${id}-caption`,
disabled,
id,
required,
validationMessageId: hasValidationChild ? `${id}-errorMsg` : undefined,
validationStatus
}}
>
{slots => {
return (
<Box display="flex" flexDirection="column" sx={{'> * + *': {marginTop: 1}}}>
{children}
{slots.Label}
{slots.Input}
{slots.Validation}
{slots.Caption && <Box mt={2}>{slots.Caption}</Box>}
</Box>
)
}}
</Slots>
)
}

export type InputFieldComponentProps = ComponentProps<typeof InputField>
export type {Props as InputFieldInputProps} from './InputFieldInput'
export default Object.assign(InputField, {
Caption: InputFieldCaption,
Input: InputFieldInput,
Label: InputFieldLabel,
Validation: InputFieldValidation
})
11 changes: 11 additions & 0 deletions src/InputField/InputFieldCaption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import InputCaption from '../InputCaption'
import {InputFieldContext, Slot} from './InputField'

const InputFieldCaption: React.FC = ({children}) => (
<Slot name="Caption">
{({captionId}: InputFieldContext) => <InputCaption id={captionId}>{children}</InputCaption>}
</Slot>
)

export default InputFieldCaption
31 changes: 31 additions & 0 deletions src/InputField/InputFieldInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
import {InputFieldContext, Slot} from './InputField'
import {TextInput} from '..'

export interface Props {
// TODO: pass a generic to `React.ReactElement` to limit to children that accept certain props (e.g.: `validationStatus`)
children?: React.ReactElement
}

const InputFieldInput = React.forwardRef(({as: Component = TextInput, ...rest}, ref) => {
return (
<Slot name="Input">
{({disabled, id, required, validationStatus, validationMessageId, captionId}: InputFieldContext) => (
<Component
ref={ref}
aria-describedby={[validationMessageId, captionId].filter(Boolean).join(' ')}
id={id}
required={required}
validationStatus={validationStatus}
disabled={disabled}
{...rest}
/>
)}
</Slot>
)
// TODO: support more than just `<input />`
// e.g.: `<select />`, `<textarea />`
}) as PolymorphicForwardRefComponent<'input', Props>

export default InputFieldInput
28 changes: 28 additions & 0 deletions src/InputField/InputFieldLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import InputLabel from '../InputLabel'
import {InputFieldContext, Slot} from './InputField'

export interface Props {
/**
* Whether the label should be visually hidden
*/
visuallyHidden?: boolean
}

const InputFieldLabel: React.FC<Props> = ({children, visuallyHidden}) => (
<Slot name="Label">
{({disabled, id, required}: InputFieldContext) => (
<InputLabel
htmlFor={id}
visuallyHidden={visuallyHidden}
required={required}
disabled={disabled}
title={required ? 'required field' : undefined}
>
{children}
</InputLabel>
)}
</Slot>
)

export default InputFieldLabel
50 changes: 50 additions & 0 deletions src/InputField/InputFieldValidation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {AlertIcon, CheckCircleFillIcon, IconProps} from '@primer/octicons-react'
import React from 'react'
import {Box, Text} from '..'
import {InputFieldContext, Slot} from './InputField'

const validationIconMap: Record<NonNullable<InputFieldContext['validationStatus']>, React.ComponentType<IconProps>> = {
success: CheckCircleFillIcon,
error: AlertIcon, // TODO: replace with `AlertFillIcon` when it's available
warning: AlertIcon // TODO: replace with `AlertFillIcon` when it's available
}

const validationColorMap: Record<NonNullable<InputFieldContext['validationStatus']>, string> = {
success: 'success.fg',
error: 'danger.fg',
warning: 'attention.fg'
}

const InputFieldValidation: React.FC = ({children}) => (
<Slot name="Validation">
{({validationStatus, validationMessageId}: InputFieldContext) => {
const IconComponent = validationStatus ? validationIconMap[validationStatus] : undefined
const fgColor = validationStatus ? validationColorMap[validationStatus] : undefined

return (
<Text
display="flex"
color={fgColor}
id={validationMessageId}
fontSize={0}
sx={{
alignItems: 'baseline',
a: {
color: 'currentColor',
textDecoration: 'underline'
}
}}
>
{IconComponent && (
<Box as="span" mr={1}>
<IconComponent size={12} fill="currentColor" />
</Box>
)}
<span>{children}</span>
</Text>
)
}}
</Slot>
)

export default InputFieldValidation
74 changes: 74 additions & 0 deletions src/InputField/ToggleInputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react'
import {Box} from '..'
import {ComponentProps} from '../utils/types'
import {Slots} from './InputField'
import InputFieldCaption from './InputFieldCaption'
import InputFieldInput from './InputFieldInput'
import InputFieldLabel from './InputFieldLabel'
import InputFieldValidation from './InputFieldValidation'

//TODO: DRY out - some of this is repeated in the `InputField` Props interface
export interface Props {
// TODO: limit children to specific components
// children: any;
disabled?: boolean
/**
* The unique identifier for this field. Used to associate the label, validation text, and caption text
*/

id: string
/**
* The identifier used to associate this input with other inputs. For example: associating multiple radio inputs
*/
name?: string
/**
* Styles the field to visually communicate the result of form validation
*/
validationStatus?: 'error' | 'warning' | 'success'
}

const InputField: React.FC<Props> = ({children, disabled, id, validationStatus}) => {
return (
<Slots
context={{
captionId: `${id}-caption`,
disabled,
id,
validationStatus
}}
>
{slots => {
return (
// TODO: see if I can just make `children` a child of the `Box`
<>
{children}
<Box display="flex">
<div>{slots.Input}</div>
{/* TODO: fix typescript */}
{!slots.Label?.valueOf().props.visuallyHidden || slots.Caption ? (
<Box ml={1}>
{slots.Label}
{slots.Caption}
</Box>
) : (
<>
{slots.Label}
{slots.Caption}
</>
)}
</Box>
</>
)
}}
</Slots>
)
}

export type InputFieldComponentProps = ComponentProps<typeof InputField>
export type {Props as InputFieldInputProps} from './InputFieldInput'
export default Object.assign(InputField, {
Caption: InputFieldCaption,
Input: InputFieldInput,
Label: InputFieldLabel,
Validation: InputFieldValidation
})
2 changes: 2 additions & 0 deletions src/InputField/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default} from './InputField'
export type {InputFieldInputProps} from './InputField'
49 changes: 49 additions & 0 deletions src/InputLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react'
import {Box} from '.'

interface Props extends React.HTMLProps<HTMLLabelElement> {
disabled?: boolean
required?: boolean
visuallyHidden?: boolean
}

const InputLabel: React.FC<Props> = ({children, disabled, required, visuallyHidden, ...rest}) => {
return (
// TODO: fix typescript errors
<Box
fontWeight="bold"
fontSize={1}
as="label"
display="block"
color={disabled ? 'fg.muted' : 'fg.default'}
sx={
// TODO: create a VisuallyHidden component if this hiding technique is used in any other components
visuallyHidden
mperrotti marked this conversation as resolved.
Show resolved Hide resolved
? {
position: 'absolute',
width: '1px',
height: '1px',
padding: '0',
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
borderWidth: '0'
}
: undefined
}
{...rest}
>
{required ? (
<Box display="flex" as="span">
<Box mr={1}>{children}</Box>
<span>*</span>
</Box>
) : (
children
)}
</Box>
)
}

export default InputLabel
Loading