@moda/om 21.7.1 → 21.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,3 +6,5 @@ export default _default;
6
6
  export declare const Default: () => React.JSX.Element;
7
7
  export declare const AribitraryChildren: () => React.JSX.Element;
8
8
  export declare const SelectChildren: () => React.JSX.Element;
9
+ export declare const AccessibilityErrorState: () => React.JSX.Element;
10
+ export declare const AccessibilityDynamicValidation: () => React.JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda/om",
3
- "version": "21.7.1",
3
+ "version": "21.7.2",
4
4
  "description": "Moda Operandi design system",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { action } from 'storybook/actions';
3
3
  import { States } from '../../utilities';
4
4
  import { Textarea } from '../Textarea';
@@ -75,3 +75,44 @@ export const SelectChildren = () => {
75
75
  </States>
76
76
  );
77
77
  };
78
+
79
+ export const AccessibilityErrorState = () => (
80
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '300px' }}>
81
+ <Field
82
+ label='Email address'
83
+ placeholder='Enter your email'
84
+ error='Please enter a valid email address'
85
+ defaultValue='invalid-email'
86
+ />
87
+ <p style={{ fontSize: '12px', color: '#666' }}>
88
+ Inspect the input to see: aria-invalid=&quot;true&quot;, aria-describedby linking to the
89
+ error, and role=&quot;alert&quot; on the error message for screen reader announcements.
90
+ </p>
91
+ </div>
92
+ );
93
+
94
+ export const AccessibilityDynamicValidation = () => {
95
+ const [value, setValue] = useState('');
96
+ const [touched, setTouched] = useState(false);
97
+
98
+ const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
99
+ const error =
100
+ touched && value && !isValidEmail(value) ? 'Please enter a valid email address' : undefined;
101
+
102
+ return (
103
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '300px' }}>
104
+ <Field
105
+ label='Email address'
106
+ placeholder='Enter your email'
107
+ error={error}
108
+ value={value}
109
+ onChange={setValue}
110
+ onBlur={() => setTouched(true)}
111
+ />
112
+ <p style={{ fontSize: '12px', color: '#666' }}>
113
+ Type an invalid email and blur the field. Screen readers will announce the error via
114
+ role=&quot;alert&quot; when it appears.
115
+ </p>
116
+ </div>
117
+ );
118
+ };
@@ -1,4 +1,4 @@
1
- import React, { JSX, useMemo } from 'react';
1
+ import React, { JSX, useId, useMemo } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import WarningIcon from '@moda/icons/warning-16';
4
4
  import { TextInput, TextInputProps } from '../TextInput';
@@ -16,8 +16,14 @@ export const Field = React.forwardRef(
16
16
  { className, children, error, label, placeholder, ...rest }: FieldProps,
17
17
  ref: React.Ref<HTMLInputElement>
18
18
  ) => {
19
+ const id = useId();
20
+ const errorId = `${id}-error`;
19
21
  const fieldContextValue = useMemo(() => ({ displaysError: Boolean(error) }), [error]);
20
22
 
23
+ // Combine any existing aria-describedby with our error ID
24
+ const ariaDescribedBy =
25
+ [rest['aria-describedby'], error ? errorId : null].filter(Boolean).join(' ') || undefined;
26
+
21
27
  return (
22
28
  <FormControlContext.Provider value={fieldContextValue}>
23
29
  <label className={classNames('Field', className)}>
@@ -33,6 +39,7 @@ export const Field = React.forwardRef(
33
39
  placeholder,
34
40
  label,
35
41
  shiftIconLeftwards: error && children.type === Select,
42
+ 'aria-describedby': ariaDescribedBy,
36
43
  ...rest,
37
44
  ...children.props
38
45
  })}
@@ -50,6 +57,7 @@ export const Field = React.forwardRef(
50
57
  placeholder={placeholder}
51
58
  label={label}
52
59
  error={error}
60
+ aria-describedby={ariaDescribedBy}
53
61
  {...rest}
54
62
  />
55
63
  {error && (
@@ -61,7 +69,11 @@ export const Field = React.forwardRef(
61
69
  )}
62
70
  </span>
63
71
 
64
- {error && <span className='Field__error'>{error}</span>}
72
+ {error && (
73
+ <span id={errorId} className='Field__error' role='alert'>
74
+ {error}
75
+ </span>
76
+ )}
65
77
  </label>
66
78
  </FormControlContext.Provider>
67
79
  );
@@ -39,6 +39,7 @@ export const TextInput = React.forwardRef(
39
39
  disabled={disabled}
40
40
  placeholder={placeholder}
41
41
  aria-label={label || placeholder}
42
+ aria-invalid={error ? true : undefined}
42
43
  ref={ref}
43
44
  onChange={event => {
44
45
  onChange?.(event.target.value);