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

Labeled field updates #595

Merged
merged 53 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d868e51
Allows LabeledField to accept optional as prop and updates Checkbox+R…
jhp0621 Aug 1, 2023
3fa079b
Uses css to visually hide label if set to false for Checkbox+Radio Gr…
jhp0621 Aug 1, 2023
49debb5
Displays required indicator and hint if provided
jhp0621 Aug 1, 2023
9873164
Updates dropdown-checkbox-group to match the same semantics as regula…
jhp0621 Aug 1, 2023
2dc1661
Merge branch 'main' of https://github.com/LaunchPadLab/lp-components …
jhp0621 Sep 18, 2023
f71254b
Remove dropdown-checkbox-group component
jhp0621 Sep 18, 2023
950cd7d
Add Dropdown in storybook
jhp0621 Sep 18, 2023
4d1c06c
Remove unused css files
jhp0621 Sep 18, 2023
036cf6d
Update styling
jhp0621 Sep 18, 2023
4fbc017
Define checkbox group container outside of component
jhp0621 Sep 25, 2023
8c73fc3
Remove test suite re: missing legend
jhp0621 Sep 25, 2023
c2df5fe
Set group role on labeled field and simplify classname logic
jhp0621 Sep 25, 2023
04015e1
Move dropdown test suite into checkbox-group test file
jhp0621 Sep 25, 2023
e462dbb
Merge branch 'main' into labeled-field-updates
jhp0621 Sep 25, 2023
4a7ee8c
Update comment
jhp0621 Sep 25, 2023
4be9338
Update version
jhp0621 Sep 25, 2023
62fd746
Update helper comment
jhp0621 Sep 25, 2023
23ffe8a
Undo defining variable outside
jhp0621 Sep 25, 2023
ed0cf8e
use prepare for yarn run build
jhp0621 Sep 26, 2023
c8352b9
update comments
Oct 25, 2023
071721e
explicitly set true on useDropdown
Oct 25, 2023
0d2d6ab
update select component to use fieldset and legend
Oct 25, 2023
4e36817
update tests to use test id for labeled field component
Oct 25, 2023
2a74019
Merge branch 'main' into labeled-field-updates
Oct 25, 2023
f1f35d5
undo updates in select
Oct 26, 2023
e6fe0f7
rename prop
Oct 26, 2023
eadf88b
use option.value for key
Oct 26, 2023
490b9e9
update test
Oct 26, 2023
e57ea98
add dropdown checkboxgroup tests
Oct 26, 2023
9fdab57
add test units for updated legend
Oct 26, 2023
ee51287
rename prop rename
Oct 26, 2023
3d47b42
update proptype
Oct 27, 2023
259978d
add migration guide
Oct 30, 2023
ea1f920
use shorthand assignment
Nov 2, 2023
85ac007
add & update migration guide
Nov 3, 2023
8ad9171
use describe
Nov 3, 2023
dbf4ee8
conditionally set field-wrapper class name and remove data-testid
Nov 6, 2023
0844385
add visually hidden class assertion
Nov 6, 2023
a3bdda3
replace example in test to use grouped elements
Nov 6, 2023
598efdc
update param sentence
Nov 6, 2023
cc3b562
be explicit with allowed proptypes for wrapper element
Nov 6, 2023
63514b0
fix typo
Nov 6, 2023
952d3dd
use markup for components
Nov 6, 2023
efa4fc7
pass in is-active conditional classname on higher level
Nov 13, 2023
d587379
update dropdown select so the labels populate
Nov 14, 2023
f6da097
Add a section about dropdown selection changes in migration guide
jhp0621 Feb 8, 2024
2f4dc31
Update comments
jhp0621 Feb 8, 2024
bcb4729
wrap tests under describe
jhp0621 Feb 8, 2024
9814f6b
Use the correct labels for dropdown test
jhp0621 Feb 8, 2024
6b9c6cf
Pass in id for checkbox label that matches input id
jhp0621 Feb 8, 2024
c687232
Merge branch 'main' into labeled-field-updates
chawes13 Feb 12, 2024
c0dd1e7
Use labels for safe guard check
jhp0621 Feb 12, 2024
5fb9bb7
Merge branch 'labeled-field-updates' of https://github.com/LaunchPadL…
jhp0621 Feb 12, 2024
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
6 changes: 3 additions & 3 deletions .storybook/styles/components/_forms.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*-----------------------
Fieldset
Fieldsets and Field Wrappers
-----------------------*/
.field-wrapper {
fieldset, .field-wrapper {
display: inline-block;
@include rem(margin-top, 15px);
position: relative;
Expand Down Expand Up @@ -149,7 +149,7 @@ Radio
/*-----------------------
Checkbox
-----------------------*/
div.checkbox {
.checkbox {
@include rem(margin-top, 15px);
width: auto;

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Documentation and usage info can be found in [docs.md](docs.md).
- [v7.0.0](migration-guides/v7.0.0.md)
- [v8.0.0](migration-guides/v8.0.0.md)
- [v9.0.0](migration-guides/v9.0.0.md)
- [v10.0.0](migration-guides/v10.0.0.md)

## Contribution

Expand Down
7 changes: 4 additions & 3 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ Clicking an unselected checkbox adds its value to this array, and clicking a sel
* `meta` **[Object][145]** A `redux-form` [meta][147] object
* `options` **[Array][143]** An array of checkbox values (strings, numbers, or key-value pairs)
* `checkboxInputProps` **[Object][145]** An object of key-value pairs representing props to pass down to all checkbox inputs (optional, default `{}`)
* `useDropdown` **[Boolean][141]?** A boolean that determines whether the checkbox options are displayed in a dropdown container or not (optional, default `false`)
* `dropdown` **[Boolean][141]** A flag indicating whether the checkbox options are displayed in a dropdown container or not (optional, default `false`)

### Examples

Expand Down Expand Up @@ -1065,7 +1065,7 @@ function EmailInput ({

## LabeledField

A container wrapper for redux-form controlled inputs. This wrapper adds a label component (defaults to [InputLabel][68])
A container for redux-form controlled inputs. This wrapper adds a label component (defaults to [InputLabel][68])
above the wrapped component and an error component below (defaults to [InputError][65]). Additionally, it adds the class `"error"`
to the container if the input is touched and invalid.

Expand All @@ -1075,9 +1075,10 @@ use the [omitLabelProps][88] helper.

### Parameters

* `hideErrorLabel` **[Boolean][141]?** A boolean determining whether to hide the error label on input error (optional, default `false`)
* `hideErrorLabel` **[Boolean][141]** A boolean determining whether to hide the error label on input error (optional, default `false`)
* `labelComponent` **[Function][140]** A custom label component for the input (optional, default `InputLabel`)
* `errorComponent` **[Function][140]** A custom error component for the input (optional, default `InputError`)
* `as` **[String][139]** A string that determines the element type of the wrapper (optional, default `'div'`)

### Examples

Expand Down
82 changes: 82 additions & 0 deletions migration-guides/v.10.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# v10.0.0 Migration Guide

This version contains the following breaking changes:

1. All inputs, except for `CheckboxGroup` and `RadioGroup`, are now wrapped in a `div`
2. The `LabeledField` component is now rendered as a `div` by default and accepts an optional prop `as` that can overwrite the HTML element
3. The `DropdownCheckboxGroup` component is now `CheckboxGroup` with prop `dropdown`=`true`
4. The `CheckboxGroup` and `RadioGroup` legends now rely on `visually-hidden` class styles to hide the label from the view

jhp0621 marked this conversation as resolved.
Show resolved Hide resolved
Further explanation of each item is detailed below.

---
## 1. All inputs, except for `CheckboxGroup` and `RadioGroup`, are now wrapped in a 'div'
`fieldset` no longer wraps each input unless when grouping related form controls (i.e., `CheckboxGroup` and `RadioGroup`). This affects base styles in _forms.scss and any custom rules your code might have that rely on the outdated structure.

```html
<!-- Input HTML Before -->
<fieldset class="custom-class-name">
<span>
<label for="person.firstName" class="">First Name</label>
</span>
<div class="input-wrapper">
<input id="person.firstName" name="person.firstName" type="text" value="">
</div>
</fieldset>

<!-- Input HTML After -->
<div class="field-wrapper custom-class-name">
<span>
<label for="person.firstName" class="">First Name</label>
</span>
<div class="input-wrapper">
<input id="person.firstName" name="person.firstName" type="text" value="">
</div>
</div>
```

## 2. The `LabeledField` component is now rendered as a `div` by default and accepts an optional prop `as` that can overwrite the HTML element
If you are using `LabeledField` with a non-grouping input, do not overwrite the wrapper's element, as it should not be `fieldset`. You may need to update your code that relies on the outdated structure. If you are using `LabeledField` with grouped related fields, pass in "fieldset" for the prop `as` and make sure the custom label component has `legend` as its first child ([source](https://www.w3.org/TR/WCAG20-TECHS/H71.html#:~:text=The%20first%20element%20inside%20the,related%20radio%20buttons%20and%20checkboxes))- you can reference the `CheckboxGroup` or `RadioGroup` component as an example.
jhp0621 marked this conversation as resolved.
Show resolved Hide resolved

## 3. The `DropdownCheckboxGroup` component is now `CheckboxGroup` with prop `dropdown`=`true`
Due to high functionality overlap between the two components, `DropdownCheckboxGroup` has been removed and instead `CheckboxGroup` now accepts the optional `dropdown` prop that defaults to `false`. When the prop's value is set to `true`, the checkbox options appear in a dropdown container, just like it did in `DropdownCheckboxGroup`.

```jsx
const inputProps = {
name: 'person.checkboxOptions',
value: '',
onChange: action('field changed'),
}
const options = [
{ key: 'First Option', value: '1' },
{ key: 'Second Option', value: '2' },
{ key: 'Third Option', value: '3' },
]

// Before:
import { DropdownCheckboxGroup } from 'lp-components'

<DropdownCheckboxGroup input={inputProps} meta={{}} options={options} />

// After:
import { CheckboxGroup } from 'lp-components'

<CheckboxGroup input={inputProps} meta={{}} options={options} dropdown={true} />
```

## 4. The `CheckboxGroup` and `RadioGroup` legends now rely on `visually-hidden` class styles to hide the label from the view
To follow the accessibility guideline of `fieldset` having one `legend` as its direct child, changes have been made in `CheckboxGroup` and `RadioGroup` to always render the `legend` and if `label=false` is passed into the components, the class name "visually-hidden" is added to the legend in order to visually hide it. Please make sure the following class styles are included in your project's root stylesheet:

```css
.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0) !important;
clip-path: inset(50%) !important;
height: 1px !important;
width: 1px !important;
overflow: hidden !important;
position: absolute !important;
white-space: nowrap !important;
border: 0 !important;
padding: 0 !important;
}
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "10.2.0",
"version": "10.0.0",
"engines": {
"node": "^18.12"
},
Expand Down Expand Up @@ -104,4 +104,4 @@
"src/**/*.{js,jsx}": "eslint --fix --max-warnings=0",
"src/**/*.{js,jsx,json,scss}": "prettier --write"
}
}
}
24 changes: 18 additions & 6 deletions src/forms/helpers/dropdown-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,38 @@ const propTypes = {
children: PropTypes.node,
className: PropTypes.string,
selectedValues: PropTypes.arrayOf(PropTypes.string),
options: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
})
),
}

const defaultProps = {
className: '',
selectedValues: [],
options: [],
}

// Wraps the `CheckboxGroup` component
// Wraps the `CheckboxGroup` component when dropdown is set to true.

function DropdownSelect({ children, className, selectedValues }) {
function DropdownSelect({ children, className, selectedValues, options }) {
const [expanded, toggleExpanded] = useToggle()

return (
<OutsideClickHandler onOutsideClick={() => toggleExpanded(false)}>
<div className="dropdown-select">
<div
className={classnames('dropdown-select', {
'is-active': expanded,
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
})}
>
<button
type="button"
className="select-input"
onClick={() => toggleExpanded()}
>
<p>{getLabel(selectedValues)}</p>
<p>{getLabel(selectedValues, options)}</p>
</button>
<div
className={classnames(className, 'options', {
Expand All @@ -46,8 +57,9 @@ DropdownSelect.propTypes = propTypes

DropdownSelect.defaultProps = defaultProps

function getLabel(values) {
return values.length ? values.join(', ') : 'None'
function getLabel(values, options) {
const labels = values.map((v) => options.find((o) => o.value === v).key)
return values.length ? labels.join(', ') : 'None'
jhp0621 marked this conversation as resolved.
Show resolved Hide resolved
}

export default DropdownSelect
28 changes: 16 additions & 12 deletions src/forms/inputs/checkbox-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import classnames from 'classnames'
* @param {Object} meta - A `redux-form` [meta](http://redux-form.com/6.5.0/docs/api/Field.md/#meta-props) object
* @param {Array} options - An array of checkbox values (strings, numbers, or key-value pairs)
* @param {Object} [checkboxInputProps={}] - An object of key-value pairs representing props to pass down to all checkbox inputs
* @param {Boolean} [useDropdown] - A boolean that determines whether the checkbox options are displayed in a dropdown container or not (optional, default `false`)
* @param {Boolean} [dropdown=false] - A flag indicating whether the checkbox options are displayed in a dropdown container or not
* @example
*
* function TodoForm ({ handleSubmit, pristine, invalid, submitting }) {
Expand Down Expand Up @@ -91,14 +91,14 @@ const propTypes = {
className: PropTypes.string,
checkboxInputProps: PropTypes.object,
options: fieldOptionsType,
useDropdown: PropTypes.bool,
dropdown: PropTypes.bool,
}

const defaultProps = {
className: 'CheckboxGroup',
checkboxInputProps: {},
options: [],
useDropdown: false,
dropdown: false,
}

function CheckboxGroupLegend({
Expand All @@ -121,10 +121,10 @@ function CheckboxGroupLegend({
)
}

function CheckboxOptionContainer({ children, useDropdown, value }) {
if (useDropdown)
function CheckboxOptionsContainer({ children, dropdown, ...rest }) {
if (dropdown)
return (
<DropdownSelect selectedValues={value} className="checkboxes">
<DropdownSelect className="checkboxes" {...rest}>
{children}
</DropdownSelect>
)
Expand All @@ -139,7 +139,7 @@ function CheckboxGroup(props) {
options,
className,
checkboxInputProps,
useDropdown,
dropdown,
...rest
} = props
const inputProps = omitLabelProps(rest)
Expand All @@ -150,7 +150,7 @@ function CheckboxGroup(props) {
return function (checked) {
// Add or remove option value from array of values, depending on whether it's checked
const newValueArray = checked
? addToArray([option.value], value)
? addToArray(value, [option.value])
jhp0621 marked this conversation as resolved.
Show resolved Hide resolved
: removeFromArray([option.value], value)
return onChange(newValueArray)
}
Expand All @@ -163,11 +163,15 @@ function CheckboxGroup(props) {
as="fieldset"
{...props}
>
<CheckboxOptionContainer useDropdown={useDropdown} value={value}>
<CheckboxOptionsContainer
dropdown={dropdown}
selectedValues={value}
options={optionObjects}
>
{optionObjects.map((option) => (
<Checkbox // eslint-disable-line react/jsx-key
<Checkbox
key={option.value}
{...{
key: option.value,
input: {
name: `${name}.${option.value}`,
value: value.includes(option.value),
Expand All @@ -180,7 +184,7 @@ function CheckboxGroup(props) {
}}
/>
))}
</CheckboxOptionContainer>
</CheckboxOptionsContainer>
</LabeledField>
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/forms/inputs/radio-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ function RadioGroup(props) {
as="fieldset"
{...props}
>
{optionObjects.map((option, i) => {
{optionObjects.map((option) => {
return (
<RadioButton // eslint-disable-line react/jsx-key
<RadioButton
key={option.value}
{...{
key: i,
type: 'radio',
input: {
name, // all radio inputs must share the same name
Expand Down
18 changes: 9 additions & 9 deletions src/forms/labels/labeled-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { hasInputError } from '../helpers'

/**
*
* A container wrapper for redux-form controlled inputs. This wrapper adds a label component (defaults to {@link InputLabel})
* A container for redux-form controlled inputs. This wrapper adds a label component (defaults to {@link InputLabel})
* above the wrapped component and an error component below (defaults to {@link InputError}). Additionally, it adds the class `"error"`
* to the container if the input is touched and invalid.
*
Expand All @@ -17,10 +17,10 @@ import { hasInputError } from '../helpers'
*
* @name LabeledField
* @type Function
* @param {Boolean} [hideErrorLabel] - A boolean determining whether to hide the error label on input error (optional, default `false`)
* @param {Boolean} [hideErrorLabel=false] - A boolean determining whether to hide the error label on input error
* @param {Function} [labelComponent=InputLabel] - A custom label component for the input
* @param {Function} [errorComponent=InputError] - A custom error component for the input
* @param {Element Type} [as] - A string that determines the element type of the component (optional, default `div`)
* @param {String} [as='div'] - A string that determines the element type of the wrapper
*
* @example
*
Expand Down Expand Up @@ -73,7 +73,7 @@ const propTypes = {
...InputError.propTypes,
children: PropTypes.node,
hideErrorLabel: PropTypes.bool,
as: PropTypes.elementType,
as: PropTypes.oneOf(['div', 'fieldset']),
}

const defaultProps = {
Expand All @@ -91,25 +91,25 @@ function LabeledField({
children,
hideErrorLabel,
label,
as: Component,
as: Wrapper,
...rest
}) {
const { name } = input
const { touched, invalid } = meta
return (
<Component
className={classnames(className, 'field-wrapper', {
<Wrapper
className={classnames(className, {
error: hasInputError({ touched, invalid }),
disabled: rest.disabled,
'field-wrapper': Wrapper !== 'fieldset',
})}
role="group"
>
<LabelComponent {...{ name, id, label, ...rest }} />
{children}
{!hideErrorLabel && (
<ErrorComponent {...{ ...input, ...meta, ...rest }} />
)}
</Component>
</Wrapper>
)
}

Expand Down
11 changes: 8 additions & 3 deletions stories/forms/inputs/checkbox-group.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ storiesOf('CheckboxGroup', module)
}}
/>
))
.add('with dropdown options', () => (
<CheckboxGroup input={inputProps} meta={{}} options={options} useDropdown />
))
.add('with dropdown', () => (
<CheckboxGroup
input={inputProps}
meta={{}}
options={options}
dropdown={true}
/>
))
Loading