Skip to content

Commit ea3a5b2

Browse files
authored
DRYD-2130: Accessibility > Missing form label (Criteria 3.3.2) (#27)
1 parent 67724ad commit ea3a5b2

18 files changed

Lines changed: 188 additions & 6 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## 2.2.0
4+
5+
* Resolve orphaned/missing form labels by forwarding id and aria attributes to rendered inputs, and adding label props to QuickSearchInput, RepeatingInput, TabularCompoundInput, and UploadInput
6+
37
## 2.1.2
48

59
* Add htmlFor/id attribute to labels associating them to inputs

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cspace-input",
3-
"version": "2.1.2",
3+
"version": "2.2.0",
44
"description": "CollectionSpace input components",
55
"author": "Ray Lee <ray.lee@lyrasis.org>",
66
"license": "ECL-2.0",

src/components/DateInput.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,11 @@ export default class DateInput extends Component {
261261
if (readOnly) {
262262
const {
263263
embedded,
264+
id,
264265
} = remainingProps;
265266

266267
return (
267-
<LineInput readOnly value={value} embedded={embedded} />
268+
<LineInput id={id} readOnly value={value} embedded={embedded} />
268269
);
269270
}
270271

src/components/DateTimeInput.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { pathPropType } from '../helpers/pathHelpers';
88
*/
99

1010
const propTypes = {
11+
id: PropTypes.string,
1112
name: PropTypes.string,
1213
// TODO: Stop using propTypes in isInput. Until then, these unused props need to be declared so
1314
// this component is recognized as an input.
@@ -20,6 +21,7 @@ const propTypes = {
2021
};
2122

2223
const defaultProps = {
24+
id: undefined,
2325
name: undefined,
2426
parentPath: undefined,
2527
subpath: undefined,
@@ -29,13 +31,15 @@ const defaultProps = {
2931

3032
export default function DateTimeInput(props) {
3133
const {
34+
id,
3235
name,
3336
value,
3437
formatValue,
3538
} = props;
3639

3740
return (
3841
<LineInput
42+
id={id}
3943
name={name}
4044
readOnly
4145
value={formatValue ? formatValue(value) : value}

src/components/DropdownMenuInput.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,18 @@ export default class DropdownMenuInput extends Component {
248248
const inputValue = valueLabel;
249249

250250
if (readOnly) {
251-
const { embedded } = remainingProps;
251+
const {
252+
embedded,
253+
id,
254+
'aria-label': ariaLabel,
255+
'aria-labelledby': ariaLabelledby,
256+
} = remainingProps;
252257

253258
return (
254259
<LineInput
260+
aria-label={ariaLabel}
261+
aria-labelledby={ariaLabelledby}
262+
id={id}
255263
readOnly
256264
value={inputValue}
257265
embedded={embedded}

src/components/FileInput.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import styles from '../../styles/cspace-input/FileInput.css';
66

77
const propTypes = {
88
accept: PropTypes.string,
9+
id: PropTypes.string,
910
name: PropTypes.string,
1011
formatFileInfo: PropTypes.func,
1112
onCommit: PropTypes.func,
@@ -88,6 +89,7 @@ export default class FileInput extends Component {
8889
const {
8990
accept,
9091
formatValue,
92+
id,
9193
name,
9294
...remainingProps
9395
} = this.props;
@@ -104,6 +106,7 @@ export default class FileInput extends Component {
104106
<input
105107
accept={accept}
106108
data-name={name}
109+
id={id}
107110
ref={this.handleFileInputRef}
108111
tabIndex="-1"
109112
type="file"

src/components/IDGeneratorInput.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,15 @@ export default class IDGeneratorInput extends Component {
141141

142142
const {
143143
embedded,
144+
id,
144145
value,
145146
} = remainingProps;
146147

147148
if (readOnly) {
148149
return (
149150
<LineInput
150151
embedded={embedded}
152+
id={id}
151153
readOnly
152154
value={value}
153155
/>

src/components/QuickSearchInput.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import styles from '../../styles/cspace-input/QuickSearchInput.css';
1111
const propTypes = {
1212
formatRecordTypeLabel: PropTypes.func,
1313
formatVocabularyLabel: PropTypes.func,
14+
keywordInputLabel: PropTypes.string,
1415
keywordValue: PropTypes.string,
1516
placeholder: PropTypes.string,
17+
recordTypeInputLabel: PropTypes.string,
1618
searchButtonLabel: PropTypes.string,
19+
vocabularyInputLabel: PropTypes.string,
1720
// eslint-disable-next-line react/forbid-prop-types
1821
recordTypes: PropTypes.objectOf(PropTypes.object),
1922
recordTypeValue: PropTypes.string,
@@ -27,9 +30,12 @@ const propTypes = {
2730
const defaultProps = {
2831
formatRecordTypeLabel: undefined,
2932
formatVocabularyLabel: undefined,
33+
keywordInputLabel: undefined,
3034
keywordValue: undefined,
3135
placeholder: undefined,
36+
recordTypeInputLabel: undefined,
3237
searchButtonLabel: undefined,
38+
vocabularyInputLabel: undefined,
3339
recordTypes: undefined,
3440
recordTypeValue: undefined,
3541
vocabularyValue: undefined,
@@ -119,12 +125,14 @@ export default class QuickSearchInput extends Component {
119125
renderRecordTypeDropdown() {
120126
const {
121127
formatRecordTypeLabel,
128+
recordTypeInputLabel,
122129
recordTypes,
123130
recordTypeValue,
124131
} = this.props;
125132

126133
return (
127134
<RecordTypeInput
135+
aria-label={recordTypeInputLabel}
128136
embedded
129137
formatRecordTypeLabel={formatRecordTypeLabel}
130138
recordTypes={recordTypes}
@@ -140,11 +148,13 @@ export default class QuickSearchInput extends Component {
140148
formatVocabularyLabel,
141149
recordTypes,
142150
recordTypeValue,
151+
vocabularyInputLabel,
143152
vocabularyValue,
144153
} = this.props;
145154

146155
return (
147156
<VocabularyInput
157+
aria-label={vocabularyInputLabel}
148158
embedded
149159
formatVocabularyLabel={formatVocabularyLabel}
150160
recordTypes={recordTypes}
@@ -160,12 +170,14 @@ export default class QuickSearchInput extends Component {
160170

161171
renderKeywordInput() {
162172
const {
173+
keywordInputLabel,
163174
keywordValue,
164175
placeholder,
165176
} = this.props;
166177

167178
return (
168179
<LineInput
180+
aria-label={keywordInputLabel}
169181
embedded
170182
placeholder={placeholder}
171183
value={keywordValue}

src/components/RepeatingInput.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,15 @@ export default class RepeatingInput extends Component {
257257
const normalizedValue = normalizeValue(value);
258258
const templateId = template.props.id;
259259

260+
// The label is rendered once as the group header, so each instance input loses its own
261+
// label. Point each instance at the header label via aria-labelledby, so it still has an
262+
// accessible name. The header Label carries id `${templateId}-label` (set by the consumer,
263+
// e.g. the cspace-ui Field component).
264+
const templateLabel = template.props.label;
265+
const labelId = (React.isValidElement(templateLabel) && templateLabel.props.id)
266+
? templateLabel.props.id
267+
: undefined;
268+
260269
return normalizedValue.map((instanceValue, index, list) => {
261270
const instanceName = `${index}`;
262271

@@ -265,6 +274,7 @@ export default class RepeatingInput extends Component {
265274
readOnly,
266275
embedded: true,
267276
label: undefined,
277+
'aria-labelledby': labelId,
268278
name: instanceName,
269279
parentPath: getPath(this.props),
270280
value: instanceValue,

0 commit comments

Comments
 (0)