-
Notifications
You must be signed in to change notification settings - Fork 616
Adds a component to support checkbox and radio groups #1657
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
Changes from all commits
Commits
Show all changes
119 commits
Select commit
Hold shift + click to select a range
a23e32d
adds components and stories for basic usage
mperrotti 779e5fe
supports checkbox and radio fields
mperrotti 0ad2715
styles disabled label styles, adds stories, jsdocs props, cleanups
mperrotti f8668ce
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti e0addd0
adds missing jsdoc comment
mperrotti 0d3ae10
adds components to render groups of radios or checkboxes
mperrotti a67c80f
supports LeadingVisual for checkbox and radio inputs
mperrotti fe9696a
animate in field validation message
mperrotti dd08828
updates validation API
mperrotti 6054f1c
creates text, checkbox, and radio input field components
mperrotti 910ee68
makes InputField validation props smarter
mperrotti 00ecaba
rm ChoiceFieldset from this changeset
mperrotti 115bb4e
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti fc84152
fixes ts errors
mperrotti f70f8ad
fixes bad imports
mperrotti 793c00d
cleanup
mperrotti e50bde8
adds VisuallyHidden component
mperrotti e9bcee9
require label to be passed
mperrotti 4533f20
fixes typo from last commit
mperrotti ad1c9e8
adds React component docs for TextInputField, RadioInputField, and Ch…
mperrotti 024b5d3
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 006e976
adds tests, fixes check for Label slot
mperrotti 79c2286
warns if id, required, or disabled props are passed directly to the I…
mperrotti 581b4ca
fix issue where the input still had a top margin when the label was h…
mperrotti 54b43a4
marks new internal components as private
mperrotti 7b7c1f8
adds changeset
mperrotti 0a220ad
updates imports
mperrotti 6ea5201
Merge github.com:primer/react into mp/form-field-component
mperrotti 504a9d9
updates stories and docs
mperrotti d721895
Merge github.com:primer/react into mp/form-field-component
mperrotti e5a964f
adds React component docs to doc nav, updates docs
mperrotti 15da266
updates CheckboxInputField to use new Checkbox component
mperrotti cbab1ae
updates snapshots from using checkbox component
mperrotti 9880c53
adds ChoiceFieldset components and stories
mperrotti ec1312a
Merge branch 'main' into mp/form-field-component
mperrotti 69e99f6
fixes ts error
mperrotti b092729
Merge github.com:primer/react into mp/form-field-component
mperrotti 6577aa8
Merge branch 'main' into mp/form-field-component
mperrotti 7424440
lightens caption when field is disabled
mperrotti 69334dc
removes unusable export from checkbox and radio input fields
mperrotti 2691adc
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 6af2f8c
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 3869fff
rename ChoiceField to Item
mperrotti 689ace4
adds a prop to disable the fieldset
mperrotti f33ff51
exports ChoiceFieldset list item component
mperrotti 14af744
rm ChoiceFieldInput component, improve dev experience for onSelect ha…
mperrotti 2684d58
rm unused imports and exports
mperrotti 06c5f3b
replaced fieldset caption with fieldset description
mperrotti 3c5351a
API improvements and storybook story updates
mperrotti cfc2e86
adds and updates docs, little fixes and improvments
mperrotti 2f2a998
adds tests
mperrotti a78625f
adds changeset
mperrotti d0b19a2
fix lint errors
mperrotti 4b20f1a
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 9c17471
updates docs, adds ChoiceFieldset to docs sidebar
mperrotti b137a07
updates Checkbox docs to point to CheckboxInputField
mperrotti 72e886d
adds ComponentChecklst to component docs
mperrotti 3c18363
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 4e06ca7
uses new Radio component to render the input
mperrotti 3c312f9
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti a0465ee
ensures name prop cannot be undefined when passed to input
mperrotti 18767e1
wraps snapshot renders in SSRProvider
mperrotti 8ef90f2
addresses PR feedback
mperrotti 55aa8e3
rm unused imports
mperrotti ee2f9bf
Merge branch 'main' of github.com:primer/react into mp/choice-group
mperrotti a179e92
Merge branch 'main' into mp/form-field-component
mperrotti ea439d3
wraps snapshot tests in SSRProvider
mperrotti 1aa9277
fixes typo
mperrotti c33fe78
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 73285ba
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 98a67e8
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti b6781f4
fixes bad import
mperrotti 8699756
Merge branch 'main' of github.com:primer/react into mp/choice-group
mperrotti ef2ae4f
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti ff79a52
uses new package name
mperrotti 90f40e0
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti bfe3b65
uses new package name
mperrotti f4ff5c8
replaces CheckboxInputField and RadioInputField with 1 component - Ch…
mperrotti 58efa2b
replaces TextInputField component with InputField component
mperrotti c6c2062
updates documentation formatting
mperrotti e3f7a6e
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 95dacc4
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 3421225
uses ChoiceInputField instead of checkbox and radio field components
mperrotti 1974d98
updates documentation formatting
mperrotti e283cbe
more docs updates
mperrotti fc0b75f
merges from InputField branch, updates more docs
mperrotti c4c6ac3
fixes copy/paste mistake in docs
mperrotti 05f96dd
Merge branch 'main' into mp/form-field-component
mperrotti c1c4808
Update docs/content/Checkbox.md
mperrotti ce49a84
addresses PR feedback + misc cleanup
mperrotti 6a7bb75
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 2a77582
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 12d8047
TESTING DEPLOYMENT - revert InputField.mdx to last successful build
mperrotti fd269bf
Merge branch 'main' into mp/form-field-component
mperrotti 73ad10c
attempting to get InputField docs to build
mperrotti 5c56710
attempting to get InputField docs to build
mperrotti b42dc39
attempting to get InputField docs to build
mperrotti 03156ef
probably fixed deployment
mperrotti 7e1cc93
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 00f5ff2
Merge branch 'main' into mp/form-field-component
mperrotti 50a7f81
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 398e541
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti d6d3f03
Update docs/content/ChoiceInputField.mdx
mperrotti c17ec66
addresses more PR feedback
mperrotti a450bb8
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 7e6da8a
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti 4d04066
try adding componentchecklist import to fix deployment failure
mperrotti 15bf95c
adds comopnentId to docs frontmatter
mperrotti d18effc
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 709d5a9
Merge branch 'main' of github.com:primer/react into mp/form-field-com…
mperrotti ad9e954
Merge branch 'main' into mp/form-field-component
mperrotti 2c4f769
Merge branch 'mp/form-field-component' of github.com:primer/react int…
mperrotti 78c12fe
Merge branch 'main' of github.com:primer/react into mp/choice-group
mperrotti ea2f769
Merge branch 'main' of github.com:primer/react into mp/choice-group
mperrotti ce577f2
Merge branch 'main' into mp/choice-group
mperrotti fdb2147
Merge branch 'main' into mp/choice-group
mperrotti 8dbcfed
docs fixes
mperrotti da725b8
Merge branch 'mp/choice-group' of github.com:primer/react into mp/cho…
mperrotti 42d1bce
Merge branch 'main' into mp/choice-group
mperrotti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@primer/react': minor | ||
--- | ||
|
||
Adds ChoiceFieldset component |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import React from 'react' | ||
import {ChoiceInputField} from '..' | ||
|
||
const ChoiceFieldCaption: React.FC = ({children}) => <ChoiceInputField.Caption>{children}</ChoiceInputField.Caption> | ||
|
||
export default ChoiceFieldCaption |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import React from 'react' | ||
import {ChoiceInputField} from '..' | ||
|
||
const ChoiceFieldLabel: React.FC = ({children}) => <ChoiceInputField.Label>{children}</ChoiceInputField.Label> | ||
|
||
export default ChoiceFieldLabel |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import React, {ComponentProps} from 'react' | ||
import {Box, useSSRSafeId} from '..' | ||
import createSlots from '../utils/create-slots' | ||
import {FormValidationStatus} from '../utils/types/FormValidationStatus' | ||
import ValidationAnimationContainer from '../_ValidationAnimationContainer' | ||
import InputValidation from '../_InputValidation' | ||
import ChoiceFieldsetListItem from './ChoiceFieldsetListItem' | ||
import ChoiceFieldsetDescription from './ChoiceFieldsetDescription' | ||
import ChoiceFieldsetLegend from './ChoiceFieldsetLegend' | ||
import ChoiceFieldsetList from './ChoiceFieldsetList' | ||
import ChoiceFieldsetValidation from './ChoiceFieldsetValidation' | ||
|
||
export interface ChoiceFieldsetProps<T = Record<string, FormValidationStatus>> { | ||
children?: React.ReactNode | ||
/** | ||
* Whether the fieldset is NOT ready for user input | ||
*/ | ||
disabled?: boolean | ||
/** | ||
* The unique identifier for this fieldset. Used to associate the validation text with the fieldset | ||
* If an ID is not passed, one will be automatically generated | ||
*/ | ||
id?: string | ||
/** | ||
* The unique identifier used to associate radio inputs with eachother | ||
* If a name is not passed and the fieldset renders radio inputs, a name will be automatically generated | ||
*/ | ||
name?: string | ||
/** | ||
* The callback that is called when a user toggles a choice on or off | ||
*/ | ||
onSelect?: (selectedValues: string[]) => void | ||
/** | ||
* Whether this field must have a value for the user to complete their task | ||
*/ | ||
required?: boolean | ||
/** | ||
* The selected values | ||
*/ | ||
selected?: string[] | ||
/** | ||
* A map of validation statuses and their associated validation keys. When one of the validation keys is passed to the `validationResult` prop, | ||
* the associated validation message will be rendered in the correct style | ||
*/ | ||
validationMap?: T | ||
/** | ||
* The key of the validation message to show | ||
*/ | ||
validationResult?: keyof T | ||
} | ||
|
||
export interface ChoiceFieldsetContext extends ChoiceFieldsetProps { | ||
validationMessageId: string | ||
} | ||
|
||
const {Slots, Slot} = createSlots(['Description', 'ChoiceList', 'Legend', 'Validation']) | ||
export {Slot} | ||
|
||
const ChoiceFieldset = <T extends Record<string, FormValidationStatus>>({ | ||
children, | ||
disabled, | ||
id, | ||
name, | ||
onSelect, | ||
required, | ||
selected, | ||
validationMap, | ||
validationResult | ||
}: ChoiceFieldsetProps<T>) => { | ||
const fieldsetId = useSSRSafeId(id) | ||
const validationChildren: React.ReactElement[] | undefined | null = React.Children.map(children, child => | ||
React.isValidElement(child) && child.type === ChoiceFieldsetValidation ? child : null | ||
)?.filter(Boolean) | ||
const validationChildToRender = validationChildren?.find(child => child.props.validationKey === validationResult) | ||
const validationMessageId = validationChildToRender ? `${fieldsetId}-validationMsg` : undefined | ||
|
||
return ( | ||
<Slots | ||
context={{ | ||
disabled, | ||
name, | ||
onSelect, | ||
required, | ||
selected, | ||
validationMessageId | ||
}} | ||
> | ||
{slots => { | ||
const isLegendVisible = React.isValidElement(slots.Legend) && slots.Legend.props.isVisible | ||
|
||
return ( | ||
<div> | ||
<Box | ||
as="fieldset" | ||
border="none" | ||
margin={0} | ||
padding={0} | ||
aria-describedby={[validationMessageId].filter(Boolean).join(' ')} | ||
> | ||
{React.Children.toArray(children).filter( | ||
child => React.isValidElement(child) && child.type !== ChoiceFieldsetValidation | ||
)} | ||
<Box mb={isLegendVisible ? 3 : undefined}> | ||
{slots.Legend} | ||
{slots.Description} | ||
</Box> | ||
{slots.ChoiceList} | ||
</Box> | ||
{validationChildToRender && ( | ||
<Box mt={3}> | ||
{validationMap && validationResult && validationMessageId && ( | ||
<ValidationAnimationContainer show> | ||
<InputValidation validationStatus={validationMap[validationResult]} id={validationMessageId}> | ||
{validationChildToRender} | ||
</InputValidation> | ||
</ValidationAnimationContainer> | ||
)} | ||
</Box> | ||
)} | ||
</div> | ||
) | ||
}} | ||
</Slots> | ||
) | ||
} | ||
|
||
export type InputFieldComponentProps = ComponentProps<typeof ChoiceFieldset> | ||
export type {ChoiceFieldsetListProps} from './ChoiceFieldsetList' | ||
export type {ChoiceFieldsetLegendProps} from './ChoiceFieldsetLegend' | ||
export type {ChoiceFieldProps} from './ChoiceFieldsetListItem' | ||
export default Object.assign(ChoiceFieldset, { | ||
Description: ChoiceFieldsetDescription, | ||
Item: ChoiceFieldsetListItem, | ||
Legend: ChoiceFieldsetLegend, | ||
List: ChoiceFieldsetList, | ||
Validation: ChoiceFieldsetValidation | ||
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from 'react' | ||
import {Text} from '..' | ||
import {ChoiceFieldsetContext, Slot} from './ChoiceFieldset' | ||
|
||
const ChoiceFieldsetDescription: React.FC = ({children}) => ( | ||
<Slot name="Description"> | ||
{({disabled}: ChoiceFieldsetContext) => ( | ||
<Text color={disabled ? 'fg.muted' : 'fg.default'} fontSize={1}> | ||
{children} | ||
</Text> | ||
)} | ||
</Slot> | ||
) | ||
|
||
export default ChoiceFieldsetDescription |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from 'react' | ||
import {Box} from '..' | ||
import VisuallyHidden from '../_VisuallyHidden' | ||
import {ChoiceFieldsetContext, Slot} from './ChoiceFieldset' | ||
|
||
export interface ChoiceFieldsetLegendProps { | ||
/** | ||
* Whether to visually hide the fieldset legend | ||
*/ | ||
visuallyHidden?: boolean | ||
} | ||
|
||
const ChoiceFieldsetLegend: React.FC<ChoiceFieldsetLegendProps> = ({children, visuallyHidden}) => ( | ||
<Slot name="Legend"> | ||
{({required, disabled}: ChoiceFieldsetContext) => ( | ||
<VisuallyHidden | ||
as="legend" | ||
isVisible={!visuallyHidden} | ||
title={required ? 'required field' : undefined} | ||
sx={{ | ||
color: disabled ? 'fg.muted' : undefined, | ||
fontSize: 2, | ||
padding: 0 | ||
}} | ||
> | ||
{required ? ( | ||
<Box display="flex" as="span"> | ||
mperrotti marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Box mr={1}>{children}</Box> | ||
<span>*</span> | ||
</Box> | ||
) : ( | ||
children | ||
)} | ||
</VisuallyHidden> | ||
)} | ||
</Slot> | ||
) | ||
|
||
export default ChoiceFieldsetLegend |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import React from 'react' | ||
import styled from 'styled-components' | ||
import {useSSRSafeId} from '..' | ||
import {get} from '../constants' | ||
import {Slot, ChoiceFieldsetContext} from './ChoiceFieldset' | ||
import ChoiceFieldsetListContext from './ChoiceFieldsetListContext' | ||
|
||
export interface ChoiceFieldsetListProps { | ||
/** | ||
* Whether multiple items or a single item can be selected | ||
*/ | ||
selectionVariant?: 'single' | 'multiple' | ||
} | ||
|
||
const List = styled.ul` | ||
display: flex; | ||
flex-direction: column; | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
|
||
> li + li { | ||
margin-top: ${get('space.2')}; | ||
} | ||
` | ||
|
||
const getSelectedCheckboxes = ( | ||
value: string, | ||
checked: boolean, | ||
selectedValues: string[], | ||
selectionVariant?: ChoiceFieldsetListProps['selectionVariant'] | ||
): string[] => { | ||
if (checked) { | ||
return selectionVariant === 'multiple' ? [...selectedValues, value] : [value] | ||
} | ||
|
||
return selectedValues.filter(selectedValue => selectedValue !== value) | ||
} | ||
|
||
const ChoiceFieldsetList: React.FC<ChoiceFieldsetListProps> = ({selectionVariant, children}) => { | ||
const ssrSafeUniqueName = useSSRSafeId() | ||
|
||
return ( | ||
<Slot name="ChoiceList"> | ||
{({name, onSelect, disabled, selected = []}: ChoiceFieldsetContext) => { | ||
return ( | ||
<ChoiceFieldsetListContext.Provider | ||
value={{ | ||
disabled, | ||
selected, | ||
name: name || ssrSafeUniqueName, | ||
onChange: e => { | ||
const updatedSelections = getSelectedCheckboxes( | ||
e.currentTarget.value, | ||
e.currentTarget.checked, | ||
selected, | ||
selectionVariant | ||
) | ||
onSelect && onSelect(updatedSelections) | ||
}, | ||
selectionVariant | ||
}} | ||
> | ||
<List> | ||
{React.Children.map(children, (child, i) => ( | ||
<li key={i}>{child}</li> | ||
))} | ||
</List> | ||
</ChoiceFieldsetListContext.Provider> | ||
) | ||
}} | ||
</Slot> | ||
) | ||
} | ||
|
||
ChoiceFieldsetList.defaultProps = { | ||
selectionVariant: 'single' | ||
} | ||
|
||
export default ChoiceFieldsetList |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {ChangeEventHandler, createContext} from 'react' | ||
|
||
const ChoiceFieldsetListContext = createContext<{ | ||
disabled?: boolean | ||
name: string | ||
onChange: ChangeEventHandler<HTMLInputElement> | ||
selected?: string[] | ||
selectionVariant?: 'single' | 'multiple' | ||
} | null>(null) | ||
|
||
export default ChoiceFieldsetListContext |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch to fragment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally wrapped this in a
div
so that everything is grouped in 1 DOM node. This way, if this is in a flex or grid container, the whole component would turn into a flex item or grid cell.