Skip to content

Commit 1cfb2c8

Browse files
authored
Create reusable FieldsetLegend component (#640)
* Create a component for fieldset legend * Replace logic in Checkbox and Radio Groups * Create fieldset legend storybook * Increase minor v * Add unit tests * Provide default value inside bracket * Add a note re: visually-hidden class * Remove confusing stories * Remove commented out code * fix version * Rename component + files
1 parent 052921a commit 1cfb2c8

File tree

11 files changed

+499
-337
lines changed

11 files changed

+499
-337
lines changed

docs.md

Lines changed: 331 additions & 285 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@launchpadlab/lp-components",
3-
"version": "10.0.1",
3+
"version": "10.1.0",
44
"engines": {
55
"node": "^18.12 || ^20.0"
66
},

src/controls/tab-bar/tab-bar.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import manageFocus from './focus'
99
* @name TabBar
1010
* @type Function
1111
* @description A control component for navigating among multiple tabs
12-
* @param {Boolean} [vertical] - A boolean setting the `className` of the `ul` to 'horizontal' (default), or 'vertical', which determines the alignment of the tabs (optional, default `false`)
12+
* @param {Boolean} [vertical=false] - A boolean setting the `className` of the `ul` to 'horizontal' (default), or 'vertical', which determines the alignment of the tabs
1313
* @param {Array} options - An array of tab values (strings or key-value pairs)
1414
* @param {String|Number} value - The value of the current tab
1515
* @param {Function} [onChange] - A function called with the new value when a tab is clicked
16-
* @param {String} [activeClassName] - The class of the active tab, (optional, default `active`)
16+
* @param {String} [activeClassName='active'] - The class of the active tab
1717
* @example
1818
*
1919
* function ShowTabs ({ currentTab, setCurrentTab }) {

src/forms/inputs/checkbox-group.js

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ import {
66
fieldOptionsType,
77
omitLabelProps,
88
replaceEmptyStringValue,
9-
convertNameToLabel,
109
DropdownSelect,
1110
} from '../helpers'
12-
import { LabeledField } from '../labels'
11+
import { LabeledField, FieldsetLegend } from '../labels'
1312
import {
1413
addToArray,
1514
removeFromArray,
1615
serializeOptions,
1716
compose,
1817
} from '../../utils'
19-
import classnames from 'classnames'
2018

2119
/**
2220
*
@@ -101,26 +99,6 @@ const defaultProps = {
10199
dropdown: false,
102100
}
103101

104-
function CheckboxGroupLegend({
105-
label,
106-
name,
107-
required,
108-
requiredIndicator,
109-
hint,
110-
}) {
111-
return (
112-
<legend className={classnames({ 'visually-hidden': label === false })}>
113-
{label || convertNameToLabel(name)}
114-
{required && requiredIndicator && (
115-
<span className="required-indicator" aria-hidden="true">
116-
{requiredIndicator}
117-
</span>
118-
)}
119-
{hint && <i>{hint}</i>}
120-
</legend>
121-
)
122-
}
123-
124102
function CheckboxOptionsContainer({ children, dropdown, ...rest }) {
125103
if (dropdown)
126104
return (
@@ -159,7 +137,7 @@ function CheckboxGroup(props) {
159137
return (
160138
<LabeledField
161139
className={className}
162-
labelComponent={CheckboxGroupLegend}
140+
labelComponent={FieldsetLegend}
163141
as="fieldset"
164142
{...props}
165143
>

src/forms/inputs/radio-group.js

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
33
import {
4-
convertNameToLabel,
54
radioGroupPropTypes,
65
fieldOptionsType,
76
omitLabelProps,
87
} from '../helpers'
9-
import { LabeledField } from '../labels'
8+
import { LabeledField, FieldsetLegend } from '../labels'
109
import { serializeOptions, filterInvalidDOMProps } from '../../utils'
11-
import classnames from 'classnames'
1210

1311
/**
1412
*
@@ -90,20 +88,6 @@ const defaultProps = {
9088
radioInputProps: {},
9189
}
9290

93-
function RadioGroupLegend({ label, name, required, requiredIndicator, hint }) {
94-
return (
95-
<legend className={classnames({ 'visually-hidden': label === false })}>
96-
{label || convertNameToLabel(name)}
97-
{required && requiredIndicator && (
98-
<span className="required-indicator" aria-hidden="true">
99-
{requiredIndicator}
100-
</span>
101-
)}
102-
{hint && <i>{hint}</i>}
103-
</legend>
104-
)
105-
}
106-
10791
// This should never be used by itself, so it does not exist as a separate export
10892
function RadioButton(props) {
10993
const {
@@ -146,7 +130,7 @@ function RadioGroup(props) {
146130
return (
147131
<LabeledField
148132
className={className}
149-
labelComponent={RadioGroupLegend}
133+
labelComponent={FieldsetLegend}
150134
as="fieldset"
151135
{...props}
152136
>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import classnames from 'classnames'
4+
import { convertNameToLabel } from '../helpers'
5+
6+
/**
7+
*
8+
* A legend representing a caption for the content of its parent field set element
9+
*
10+
* This component must be used as a direct child and the only legend of the <fieldset> element that groups related controls
11+
*
12+
*
13+
* The text of the legend is set using the following rules:
14+
* - If the `label` prop is set to `false`, the legend is hidden visually via a class. _Note: It's your responsibility to make sure your styling rules respect the `visually-hidden` class_
15+
* - Else If the `label` prop is set to a string, the label will display that text
16+
* - Otherwise, the label will be set using the `name` prop.
17+
*
18+
*
19+
* @name FieldsetLegend
20+
* @type Function
21+
* @param {String} name - The name of the associated group
22+
* @param {String} [hint] - A usage hint for the associated input
23+
* @param {String|Boolean} [label] - Custom text for the legend
24+
* @param {Boolean} [required=false] - A boolean value to indicate whether the field is required
25+
* @param {String} [requiredIndicator=''] - Custom character to denote a field is required
26+
27+
* @example
28+
*
29+
*
30+
* function ShippingAddress (props) {
31+
* const name = 'shippingAddress'
32+
* return (
33+
* <fieldset>
34+
* <FieldsetLegend name={name} />
35+
* <Input id={`${name}.name`} input={{name: 'name'}} />
36+
* <Input id={`${name}.street`} input={{name: 'street'}} />
37+
* <Input id={`${name}.city`}" input={{name: 'city'}} />
38+
* <Input id={`${name}.state`} input={{name: 'state'}} />
39+
* </fieldset>
40+
* )
41+
* }
42+
*
43+
*/
44+
45+
const propTypes = {
46+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
47+
name: PropTypes.string.isRequired,
48+
required: PropTypes.bool,
49+
requiredIndicator: PropTypes.string,
50+
className: PropTypes.string,
51+
hint: PropTypes.string,
52+
}
53+
54+
const defaultProps = {
55+
children: null,
56+
hint: '',
57+
label: '',
58+
required: false,
59+
requiredIndicator: '',
60+
className: '',
61+
}
62+
63+
function FieldsetLegend({
64+
label,
65+
name,
66+
required,
67+
requiredIndicator,
68+
className,
69+
hint,
70+
}) {
71+
return (
72+
<legend
73+
className={classnames(className, { 'visually-hidden': label === false })}
74+
>
75+
{label || convertNameToLabel(name)}
76+
{required && requiredIndicator && (
77+
<span className="required-indicator" aria-hidden="true">
78+
{requiredIndicator}
79+
</span>
80+
)}
81+
{hint && <i>{hint}</i>}
82+
</legend>
83+
)
84+
}
85+
86+
FieldsetLegend.propTypes = propTypes
87+
FieldsetLegend.defaultProps = defaultProps
88+
89+
export default FieldsetLegend

src/forms/labels/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export ErrorLabel from './error-label'
22
export InputError from './input-error'
33
export InputLabel from './input-label'
44
export LabeledField from './labeled-field'
5+
export FieldsetLegend from './fieldset-legend'

src/forms/labels/input-label.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import { useToggle } from '../../utils'
2525
* @name InputLabel
2626
* @type Function
2727
* @param {String} name - The name of the associated input
28-
* @param {String} [id=name] - The id of the associated input (defaults to name)
28+
* @param {String} [id=name] - The id of the associated input
2929
* @param {String} [hint] - A usage hint for the associated input
3030
* @param {String|Boolean} [label] - Custom text for the label
3131
* @param {String} [tooltip] - A message to display in a tooltip
32-
* @param {Boolean} [required] - A boolean value to indicate whether the field is required
33-
* @param {String} [requiredIndicator] - Custom character to denote a field is required (optional, default `''`)
32+
* @param {Boolean} [required=false] - A boolean value to indicate whether the field is required
33+
* @param {String} [requiredIndicator=''] - Custom character to denote a field is required
3434
3535
* @example
3636
*
@@ -74,6 +74,7 @@ const defaultProps = {
7474
id: '',
7575
label: '',
7676
tooltip: '',
77+
required: false,
7778
requiredIndicator: '',
7879
className: '',
7980
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react'
2+
import { storiesOf } from '@storybook/react'
3+
import { FieldsetLegend } from 'src'
4+
5+
storiesOf('FieldsetLegend', module)
6+
.add('with default label', () => <FieldsetLegend name="nameOfInput" />)
7+
.add('with custom label', () => (
8+
<FieldsetLegend name="nameOfInput" label="Custom Label" />
9+
))
10+
.add('with no label', () => (
11+
<FieldsetLegend name="nameOfInput" label={false} />
12+
))
13+
.add('with required true custom indicator', () => (
14+
<FieldsetLegend
15+
name="nameOfInput"
16+
label="Custom Label"
17+
required={true}
18+
requiredIndicator={'*'}
19+
/>
20+
))
21+
.add('with hint', () => <FieldsetLegend name="nameOfInput" hint="hint" />)

stories/forms/labels/input-label.story.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ storiesOf('InputLabel', module)
88
<InputLabel name="nameOfInput" label="Custom Label" />
99
))
1010
.add('with no label', () => <InputLabel name="nameOfInput" label={false} />)
11-
.add('with required true default indicator', () => (
12-
<InputLabel name="nameOfInput" label="Custom Label" required />
13-
))
1411
.add('with required true custom indicator', () => (
1512
<InputLabel
1613
name="nameOfInput"
1714
label="Custom Label"
18-
required
15+
required={true}
1916
requiredIndicator={'*'}
2017
/>
2118
))

0 commit comments

Comments
 (0)