@transferwise/components 0.0.0-experimental-2fa5e3f → 0.0.0-experimental-c09cb11
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.
- package/build/dateLookup/dateTrigger/DateTrigger.js +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.mjs +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
- package/build/field/Field.js +34 -7
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +34 -7
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/inlineAlert/InlineAlert.js +13 -6
- package/build/inlineAlert/InlineAlert.js.map +1 -1
- package/build/inlineAlert/InlineAlert.mjs +13 -6
- package/build/inlineAlert/InlineAlert.mjs.map +1 -1
- package/build/label/Label.js +49 -5
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.messages.js +12 -0
- package/build/label/Label.messages.js.map +1 -0
- package/build/label/Label.messages.mjs +10 -0
- package/build/label/Label.messages.mjs.map +1 -0
- package/build/label/Label.mjs +50 -6
- package/build/label/Label.mjs.map +1 -1
- package/build/main.css +4 -8
- package/build/styles/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/build/styles/field/Field.css +4 -0
- package/build/styles/main.css +4 -8
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +4 -2
- package/build/types/field/Field.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inlineAlert/InlineAlert.d.ts +9 -0
- package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
- package/build/types/label/Label.d.ts +39 -3
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/label/Label.messages.d.ts +8 -0
- package/build/types/label/Label.messages.d.ts.map +1 -0
- package/build/types/label/index.d.ts +3 -0
- package/build/types/label/index.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/dateInput/DateInput.tests.story.tsx +6 -41
- package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.less +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.tsx +9 -4
- package/src/field/Field.css +4 -0
- package/src/field/Field.less +5 -0
- package/src/field/Field.spec.tsx +41 -5
- package/src/field/Field.story.tsx +105 -7
- package/src/field/Field.tsx +33 -9
- package/src/i18n/en.json +1 -0
- package/src/index.ts +1 -1
- package/src/inlineAlert/InlineAlert.story.tsx +7 -72
- package/src/inlineAlert/InlineAlert.tsx +14 -3
- package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.js +5 -10
- package/src/inputs/InputGroup.spec.tsx +1 -1
- package/src/inputs/SearchInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.spec.tsx +1 -1
- package/src/label/Label.messages.tsx +8 -0
- package/src/label/Label.spec.tsx +42 -7
- package/src/label/Label.story.tsx +30 -25
- package/src/label/Label.tsx +66 -8
- package/src/label/index.ts +2 -0
- package/src/main.css +4 -8
- package/src/main.less +1 -0
- package/src/moneyInput/MoneyInput.story.tsx +11 -11
- package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
- package/src/select/Select.rtl.spec.tsx +1 -1
- package/src/switch/Switch.spec.tsx +1 -1
- package/src/switch/Switch.story.tsx +19 -21
- package/src/field/Field.tests.story.tsx +0 -33
|
@@ -3,6 +3,12 @@ import { useState } from 'react';
|
|
|
3
3
|
import { Input } from '../inputs/Input';
|
|
4
4
|
import { Field } from './Field';
|
|
5
5
|
import { Sentiment } from '../common';
|
|
6
|
+
import DateInput from '../dateInput';
|
|
7
|
+
import { fn } from '@storybook/test';
|
|
8
|
+
import { lorem10, lorem40 } from '../test-utils';
|
|
9
|
+
import { TextArea } from '../inputs/TextArea';
|
|
10
|
+
import { SearchInput } from '../inputs/SearchInput';
|
|
11
|
+
import Info from '../info';
|
|
6
12
|
|
|
7
13
|
export default {
|
|
8
14
|
component: Field,
|
|
@@ -13,9 +19,66 @@ export default {
|
|
|
13
19
|
export const Basic = () => {
|
|
14
20
|
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
15
21
|
return (
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
22
|
+
<div className="row">
|
|
23
|
+
<div className="col-md-8 col-md-offset-2">
|
|
24
|
+
<Field label="Required Text Input" required>
|
|
25
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
26
|
+
</Field>
|
|
27
|
+
<Field label="Text Input">
|
|
28
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
29
|
+
</Field>
|
|
30
|
+
<Field label="Text Input with Description" description="This a field Description">
|
|
31
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
32
|
+
</Field>
|
|
33
|
+
<Field
|
|
34
|
+
label="Text Input with Validation Error"
|
|
35
|
+
description="This a field Description"
|
|
36
|
+
sentiment={Sentiment.NEGATIVE}
|
|
37
|
+
message="Validation error, please take a look"
|
|
38
|
+
>
|
|
39
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
40
|
+
</Field>
|
|
41
|
+
|
|
42
|
+
<Field label="Date Of Birth">
|
|
43
|
+
<DateInput onChange={fn} />
|
|
44
|
+
</Field>
|
|
45
|
+
|
|
46
|
+
<Field label="Date Of Birth with Description" description={lorem10}>
|
|
47
|
+
<DateInput onChange={fn} />
|
|
48
|
+
</Field>
|
|
49
|
+
|
|
50
|
+
<Field label="Search business">
|
|
51
|
+
<SearchInput />
|
|
52
|
+
</Field>
|
|
53
|
+
|
|
54
|
+
<Field label="Textarea">
|
|
55
|
+
<TextArea />
|
|
56
|
+
</Field>
|
|
57
|
+
|
|
58
|
+
<Field label="Textarea with Description" description="This a TextArea Description">
|
|
59
|
+
<TextArea />
|
|
60
|
+
</Field>
|
|
61
|
+
|
|
62
|
+
<Field
|
|
63
|
+
label="Textarea with Validation Error"
|
|
64
|
+
description="This a TextArea Description"
|
|
65
|
+
message={lorem10}
|
|
66
|
+
sentiment="negative"
|
|
67
|
+
>
|
|
68
|
+
<TextArea />
|
|
69
|
+
</Field>
|
|
70
|
+
|
|
71
|
+
<Field
|
|
72
|
+
label={
|
|
73
|
+
<>
|
|
74
|
+
Label with Suffix <Info content={lorem40} />
|
|
75
|
+
</>
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
<TextArea />
|
|
79
|
+
</Field>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
19
82
|
);
|
|
20
83
|
};
|
|
21
84
|
|
|
@@ -23,16 +86,51 @@ export const WithStatusMessages = () => {
|
|
|
23
86
|
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
24
87
|
return (
|
|
25
88
|
<div>
|
|
26
|
-
<Field
|
|
89
|
+
<Field
|
|
90
|
+
label="Text Input with Positive Message"
|
|
91
|
+
sentiment={Sentiment.POSITIVE}
|
|
92
|
+
message="Positive message"
|
|
93
|
+
>
|
|
27
94
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
28
95
|
</Field>
|
|
29
|
-
|
|
96
|
+
|
|
97
|
+
<Field
|
|
98
|
+
label="Text Input with Warning Message"
|
|
99
|
+
sentiment={Sentiment.WARNING}
|
|
100
|
+
message="Warning message"
|
|
101
|
+
>
|
|
30
102
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
31
103
|
</Field>
|
|
32
|
-
|
|
104
|
+
|
|
105
|
+
<Field
|
|
106
|
+
label="Text Input with Validation Error"
|
|
107
|
+
sentiment={Sentiment.NEGATIVE}
|
|
108
|
+
message="This is a required field"
|
|
109
|
+
>
|
|
110
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
111
|
+
</Field>
|
|
112
|
+
|
|
113
|
+
{/* instance with deprecated `hint` prop */}
|
|
114
|
+
<Field
|
|
115
|
+
label="Text Input with deprecated `hint` prop"
|
|
116
|
+
hint="This is a helpful message"
|
|
117
|
+
sentiment={Sentiment.NEGATIVE}
|
|
118
|
+
message="Validation error, please take a look"
|
|
119
|
+
>
|
|
33
120
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
34
121
|
</Field>
|
|
35
|
-
|
|
122
|
+
|
|
123
|
+
{/* instance with deprecated `error` & `hint` props */}
|
|
124
|
+
<Field
|
|
125
|
+
label="Text Input with deprecated `error` & `hint` props"
|
|
126
|
+
hint="This is a helpful message"
|
|
127
|
+
error="Validation error, please take a look"
|
|
128
|
+
>
|
|
129
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
130
|
+
</Field>
|
|
131
|
+
|
|
132
|
+
{/* instance of info via `message` property, this is rare case (e.g MoneyInput) */}
|
|
133
|
+
<Field label="Text Input with Info under the field" message="This is a helpful message">
|
|
36
134
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
37
135
|
</Field>
|
|
38
136
|
</div>
|
package/src/field/Field.tsx
CHANGED
|
@@ -9,16 +9,18 @@ import {
|
|
|
9
9
|
InputIdContextProvider,
|
|
10
10
|
InputInvalidProvider,
|
|
11
11
|
} from '../inputs/contexts';
|
|
12
|
-
import { Label } from '../label
|
|
12
|
+
import { Label } from '../label';
|
|
13
13
|
|
|
14
14
|
export type FieldProps = {
|
|
15
15
|
/** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */
|
|
16
16
|
id?: string | null;
|
|
17
17
|
/** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */
|
|
18
18
|
label?: React.ReactNode;
|
|
19
|
-
|
|
19
|
+
required?: boolean;
|
|
20
|
+
/** @deprecated use `description` prop instead */
|
|
20
21
|
hint?: React.ReactNode;
|
|
21
22
|
message?: React.ReactNode;
|
|
23
|
+
description?: React.ReactNode;
|
|
22
24
|
/** @deprecated use `message` and `type={Sentiment.NEGATIVE}` prop instead */
|
|
23
25
|
error?: React.ReactNode;
|
|
24
26
|
sentiment?: `${Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.POSITIVE | Sentiment.WARNING}`;
|
|
@@ -29,14 +31,17 @@ export type FieldProps = {
|
|
|
29
31
|
export const Field = ({
|
|
30
32
|
id,
|
|
31
33
|
label,
|
|
34
|
+
required = false,
|
|
32
35
|
message: propMessage,
|
|
36
|
+
hint,
|
|
37
|
+
description = hint,
|
|
33
38
|
sentiment: propType = Sentiment.NEUTRAL,
|
|
34
39
|
className,
|
|
35
40
|
children,
|
|
36
41
|
...props
|
|
37
42
|
}: FieldProps) => {
|
|
38
43
|
const sentiment = props.error ? Sentiment.NEGATIVE : propType;
|
|
39
|
-
const message =
|
|
44
|
+
const message = propMessage || props.error;
|
|
40
45
|
const hasError = sentiment === Sentiment.NEGATIVE;
|
|
41
46
|
|
|
42
47
|
const labelId = useId();
|
|
@@ -44,16 +49,32 @@ export const Field = ({
|
|
|
44
49
|
const fallbackInputId = useId();
|
|
45
50
|
const inputId = id !== null ? (id ?? fallbackInputId) : undefined;
|
|
46
51
|
|
|
52
|
+
const messageId = useId();
|
|
47
53
|
const descriptionId = useId();
|
|
48
54
|
|
|
55
|
+
/**
|
|
56
|
+
* form control can have multiple messages to describe it,
|
|
57
|
+
* e.g the description underneath the label and inline alert
|
|
58
|
+
*/
|
|
59
|
+
function ariaDescribedbyByIds() {
|
|
60
|
+
const messageIds = [];
|
|
61
|
+
if (description) {
|
|
62
|
+
messageIds.push(descriptionId);
|
|
63
|
+
}
|
|
64
|
+
if (message) {
|
|
65
|
+
messageIds.push(messageId);
|
|
66
|
+
}
|
|
67
|
+
return messageIds.length > 0 ? messageIds.join(' ') : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
return (
|
|
50
71
|
<FieldLabelIdContextProvider value={labelId}>
|
|
51
72
|
<InputIdContextProvider value={inputId}>
|
|
52
|
-
<InputDescribedByProvider value={
|
|
73
|
+
<InputDescribedByProvider value={ariaDescribedbyByIds()}>
|
|
53
74
|
<InputInvalidProvider value={hasError}>
|
|
54
75
|
<div
|
|
55
76
|
className={clsx(
|
|
56
|
-
'form-group d-block',
|
|
77
|
+
'np-field form-group d-block',
|
|
57
78
|
{
|
|
58
79
|
'has-success': sentiment === Sentiment.POSITIVE,
|
|
59
80
|
'has-warning': sentiment === Sentiment.WARNING,
|
|
@@ -64,16 +85,19 @@ export const Field = ({
|
|
|
64
85
|
)}
|
|
65
86
|
>
|
|
66
87
|
{label != null ? (
|
|
67
|
-
<Label
|
|
68
|
-
{
|
|
69
|
-
|
|
88
|
+
<Label>
|
|
89
|
+
<Label.Text id={labelId} htmlFor={inputId} required={required}>
|
|
90
|
+
{label}
|
|
91
|
+
</Label.Text>
|
|
92
|
+
<Label.Description id={descriptionId}>{description}</Label.Description>
|
|
93
|
+
<div className="np-field-control">{children}</div>
|
|
70
94
|
</Label>
|
|
71
95
|
) : (
|
|
72
96
|
children
|
|
73
97
|
)}
|
|
74
98
|
|
|
75
99
|
{message && (
|
|
76
|
-
<InlineAlert type={sentiment} id={
|
|
100
|
+
<InlineAlert type={sentiment} id={messageId}>
|
|
77
101
|
{message}
|
|
78
102
|
</InlineAlert>
|
|
79
103
|
)}
|
package/src/i18n/en.json
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"neptune.DateLookup.year": "year",
|
|
19
19
|
"neptune.FlowNavigation.back": "back to previous step",
|
|
20
20
|
"neptune.Info.ariaLabel": "More information",
|
|
21
|
+
"neptune.Label.optional": "(Optional)",
|
|
21
22
|
"neptune.Link.opensInNewTab": "(opens in new tab)",
|
|
22
23
|
"neptune.MoneyInput.Select.placeholder": "Select an option...",
|
|
23
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Select an option...",
|
package/src/index.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type {
|
|
|
44
44
|
} from './inputs/SelectInput';
|
|
45
45
|
export type { TextAreaProps } from './inputs/TextArea';
|
|
46
46
|
export type { InstructionsListProps } from './instructionsList';
|
|
47
|
-
export type { LabelProps } from './label/Label';
|
|
47
|
+
export type { LabelProps, LabelOptionalProps, LabelDescriptionProps } from './label/Label';
|
|
48
48
|
export type { LoaderProps } from './loader';
|
|
49
49
|
export type { MarkdownProps } from './markdown';
|
|
50
50
|
export type { ModalProps } from './modal';
|
|
@@ -2,9 +2,9 @@ import { select, text } from '@storybook/addon-knobs';
|
|
|
2
2
|
import { Meta } from '@storybook/react';
|
|
3
3
|
|
|
4
4
|
import { Sentiment } from '../common';
|
|
5
|
-
import { Input } from '../inputs/Input';
|
|
6
5
|
|
|
7
6
|
import InlineAlert, { InlineAlertProps } from './InlineAlert';
|
|
7
|
+
import { lorem40 } from '../test-utils';
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
component: InlineAlert,
|
|
@@ -29,81 +29,16 @@ export const Basic = () => {
|
|
|
29
29
|
|
|
30
30
|
const message = text('message', 'Please enter a password over 5 characters');
|
|
31
31
|
|
|
32
|
-
let typeClass = '';
|
|
33
|
-
switch (type) {
|
|
34
|
-
case Sentiment.ERROR:
|
|
35
|
-
case Sentiment.NEGATIVE:
|
|
36
|
-
typeClass = 'has-error';
|
|
37
|
-
break;
|
|
38
|
-
case Sentiment.SUCCESS:
|
|
39
|
-
case Sentiment.POSITIVE:
|
|
40
|
-
typeClass = 'has-success';
|
|
41
|
-
break;
|
|
42
|
-
case Sentiment.INFO:
|
|
43
|
-
case Sentiment.NEUTRAL:
|
|
44
|
-
typeClass = 'has-info';
|
|
45
|
-
break;
|
|
46
|
-
case Sentiment.WARNING:
|
|
47
|
-
typeClass = 'has-warning';
|
|
48
|
-
break;
|
|
49
|
-
case Sentiment.PENDING:
|
|
50
|
-
}
|
|
51
|
-
|
|
52
32
|
return (
|
|
53
33
|
<>
|
|
54
|
-
{/* eslint-disable-next-line react/no-adjacent-inline-elements */}
|
|
55
|
-
<p>
|
|
56
|
-
The styling for the input (the coloured border) and the visibility of the inline alert is
|
|
57
|
-
controlled through the use of <code>has-***</code> classes which are applied to the{' '}
|
|
58
|
-
<code>form-group</code> element. For example, to display an inline alert of type error, you
|
|
59
|
-
must also apply the class <code>has-error</code> to the parent <code>form-group</code>{' '}
|
|
60
|
-
element. The available classes are <code>has-error</code>, <code>has-info</code>,{' '}
|
|
61
|
-
<code>has-warning</code> and <code>has-success</code>.
|
|
62
|
-
</p>
|
|
63
34
|
<p>
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
|
|
35
|
+
Avoid using <code>InlineAlert</code> directly (unless for some custom use cases), for form
|
|
36
|
+
control validation, info messaging please use <code>Field</code> component
|
|
37
|
+
<pre>{'<Field sentiment={..} message={..}>'}</pre>
|
|
67
38
|
</p>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
</label>
|
|
72
|
-
<Input id="id0" value="Neptune is cool" />
|
|
73
|
-
<InlineAlert type={type}>{message}</InlineAlert>
|
|
74
|
-
</div>
|
|
75
|
-
<div className="form-group has-error">
|
|
76
|
-
<label className="control-label" htmlFor="id1">
|
|
77
|
-
Negative
|
|
78
|
-
</label>
|
|
79
|
-
<Input id="id1" value="Neptune is cool" />
|
|
80
|
-
<InlineAlert type="negative">{message}</InlineAlert>
|
|
81
|
-
</div>
|
|
82
|
-
<div className="form-group has-success">
|
|
83
|
-
<label className="control-label" htmlFor="id2">
|
|
84
|
-
Positive
|
|
85
|
-
</label>
|
|
86
|
-
<Input id="id2" value="Neptune is cool" />
|
|
87
|
-
<InlineAlert type="positive">
|
|
88
|
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
89
|
-
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
90
|
-
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
91
|
-
</InlineAlert>
|
|
92
|
-
</div>
|
|
93
|
-
<div className="form-group has-neutral">
|
|
94
|
-
<label className="control-label" htmlFor="id3">
|
|
95
|
-
Neutral
|
|
96
|
-
</label>
|
|
97
|
-
<Input id="id3" value="Neptune is cool" />
|
|
98
|
-
<InlineAlert type="neutral">{message}</InlineAlert>
|
|
99
|
-
</div>
|
|
100
|
-
<div className="form-group">
|
|
101
|
-
<label className="control-label" htmlFor="id4">
|
|
102
|
-
No has-* class
|
|
103
|
-
</label>
|
|
104
|
-
<Input id="id4" value="Neptune is cool" />
|
|
105
|
-
<InlineAlert type="negative">{message}</InlineAlert>
|
|
106
|
-
</div>
|
|
39
|
+
<InlineAlert type={type}>{message}</InlineAlert>
|
|
40
|
+
<InlineAlert type="negative">{message}</InlineAlert>
|
|
41
|
+
<InlineAlert type="positive">{lorem40}</InlineAlert>
|
|
107
42
|
</>
|
|
108
43
|
);
|
|
109
44
|
};
|
|
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { Sentiment, Size } from '../common';
|
|
5
5
|
import StatusIcon from '../statusIcon';
|
|
6
|
+
import Body from '../body';
|
|
6
7
|
|
|
7
8
|
export interface InlineAlertProps {
|
|
8
9
|
id?: string;
|
|
@@ -19,6 +20,15 @@ const iconTypes = new Set<NonNullable<InlineAlertProps['type']>>([
|
|
|
19
20
|
Sentiment.WARNING,
|
|
20
21
|
]);
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Avoid using `<InlineAlert>` component directly
|
|
25
|
+
* it's for edge cases when `<Field />` isn't suitable for some reasons.
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
* ```
|
|
29
|
+
* <Field sentiment={..} message={..}>..</Field>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
22
32
|
export default function InlineAlert({
|
|
23
33
|
id,
|
|
24
34
|
type = 'neutral',
|
|
@@ -26,17 +36,18 @@ export default function InlineAlert({
|
|
|
26
36
|
children,
|
|
27
37
|
}: InlineAlertProps) {
|
|
28
38
|
return (
|
|
29
|
-
<
|
|
39
|
+
<Body
|
|
30
40
|
role="alert"
|
|
31
41
|
id={id}
|
|
32
42
|
className={clsx(
|
|
33
43
|
'alert alert-detach',
|
|
34
44
|
`alert-${type === Sentiment.NEGATIVE || type === Sentiment.ERROR ? 'danger' : type}`,
|
|
45
|
+
'd-flex',
|
|
35
46
|
className,
|
|
36
47
|
)}
|
|
37
48
|
>
|
|
38
49
|
{iconTypes.has(type) && <StatusIcon sentiment={type} size={Size.SMALL} />}
|
|
39
|
-
|
|
40
|
-
</
|
|
50
|
+
{children}
|
|
51
|
+
</Body>
|
|
41
52
|
);
|
|
42
53
|
}
|
|
@@ -2,6 +2,7 @@ import { text } from '@storybook/addon-knobs';
|
|
|
2
2
|
import { userEvent, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import InputWithDisplayFormat from '.';
|
|
5
|
+
import { Field } from '../field/Field';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
component: InputWithDisplayFormat,
|
|
@@ -13,10 +14,7 @@ export const Basic = () => {
|
|
|
13
14
|
const displayPattern = text('DisplayPattern', '**-**-**');
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
|
-
|
|
17
|
-
<label id="template" htmlFor="Basic">
|
|
18
|
-
Display pattern is controlled via knobs
|
|
19
|
-
</label>
|
|
17
|
+
<Field label="Display pattern is controlled via knobs" id="Basic">
|
|
20
18
|
<InputWithDisplayFormat
|
|
21
19
|
id="Basic"
|
|
22
20
|
placeholder={placeholder}
|
|
@@ -26,7 +24,7 @@ export const Basic = () => {
|
|
|
26
24
|
onBlur={(v) => console.log(v)}
|
|
27
25
|
onFocus={(v) => console.log(v)}
|
|
28
26
|
/>
|
|
29
|
-
|
|
27
|
+
</Field>
|
|
30
28
|
);
|
|
31
29
|
};
|
|
32
30
|
|
|
@@ -37,10 +35,7 @@ const Template = (args) => {
|
|
|
37
35
|
const id = label.replaceAll(' ', '-').toLowerCase();
|
|
38
36
|
|
|
39
37
|
return (
|
|
40
|
-
|
|
41
|
-
<label id="template" htmlFor={id}>
|
|
42
|
-
{label}
|
|
43
|
-
</label>
|
|
38
|
+
<Field label={label} id={id}>
|
|
44
39
|
<InputWithDisplayFormat
|
|
45
40
|
id={id}
|
|
46
41
|
placeholder={placeholder}
|
|
@@ -50,7 +45,7 @@ const Template = (args) => {
|
|
|
50
45
|
onBlur={(v) => console.log(v)}
|
|
51
46
|
onFocus={(v) => console.log(v)}
|
|
52
47
|
/>
|
|
53
|
-
|
|
48
|
+
</Field>
|
|
54
49
|
);
|
|
55
50
|
};
|
|
56
51
|
|
|
@@ -214,6 +214,6 @@ describe('SelectInput', () => {
|
|
|
214
214
|
<SelectInput items={[{ type: 'option', value: 'USD' }]} value="USD" />
|
|
215
215
|
</Field>,
|
|
216
216
|
);
|
|
217
|
-
expect(screen.getByLabelText(
|
|
217
|
+
expect(screen.getByLabelText(/Currency/)).toHaveAttribute('aria-haspopup');
|
|
218
218
|
});
|
|
219
219
|
});
|
package/src/label/Label.spec.tsx
CHANGED
|
@@ -1,26 +1,61 @@
|
|
|
1
1
|
import { Input } from '../inputs/Input';
|
|
2
|
-
import { render, screen } from '../test-utils';
|
|
2
|
+
import { lorem10, render, screen } from '../test-utils';
|
|
3
3
|
import { Label } from './Label';
|
|
4
4
|
|
|
5
5
|
describe('Label', () => {
|
|
6
6
|
it('renders string labels', () => {
|
|
7
7
|
render(
|
|
8
8
|
<Label>
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
<Label.Text htmlFor="test" required>
|
|
10
|
+
Phone number
|
|
11
|
+
</Label.Text>
|
|
12
|
+
<Input id="test" readOnly />
|
|
11
13
|
</Label>,
|
|
12
14
|
);
|
|
13
15
|
|
|
14
16
|
expect(screen.getByLabelText('Phone number')).toBeInTheDocument();
|
|
15
17
|
});
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
it('renders string labels with (Optional) suffix', () => {
|
|
17
20
|
render(
|
|
18
21
|
<Label>
|
|
19
|
-
<
|
|
20
|
-
<Input readOnly />
|
|
22
|
+
<Label.Text htmlFor="test">Phone number</Label.Text>
|
|
23
|
+
<Input id="test" readOnly />
|
|
21
24
|
</Label>,
|
|
22
25
|
);
|
|
23
26
|
|
|
24
|
-
expect(screen.getByLabelText(
|
|
27
|
+
expect(screen.getByLabelText(/Phone number/)).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByLabelText(/(Optional)/)).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders description', () => {
|
|
32
|
+
const descriptionId = 'desc-test';
|
|
33
|
+
render(
|
|
34
|
+
<Label>
|
|
35
|
+
<Label.Text htmlFor="test">Phone number</Label.Text>
|
|
36
|
+
<Label.Description id={descriptionId}>{lorem10}</Label.Description>
|
|
37
|
+
<Input id="test" readOnly aria-describedby={descriptionId} />
|
|
38
|
+
</Label>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByLabelText(/Phone number/)).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText(lorem10)).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByText(lorem10)).toHaveAttribute('id', descriptionId);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('connects label with form control via `id` + `aria-labelledby`', () => {
|
|
47
|
+
const labelId = 'label-id';
|
|
48
|
+
render(
|
|
49
|
+
<Label>
|
|
50
|
+
<Label.Text id={labelId} required>
|
|
51
|
+
Phone number
|
|
52
|
+
</Label.Text>
|
|
53
|
+
<div role="group" aria-labelledby={labelId}>
|
|
54
|
+
Custom complex component
|
|
55
|
+
</div>
|
|
56
|
+
</Label>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByLabelText('Phone number')).toHaveAttribute('aria-labelledby', labelId);
|
|
25
60
|
});
|
|
26
61
|
});
|
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import Info from '../info/Info';
|
|
4
|
-
import { Input } from '../inputs/Input';
|
|
1
|
+
import Info from '../info';
|
|
5
2
|
import { Label } from './Label';
|
|
6
3
|
|
|
7
4
|
export default {
|
|
8
5
|
component: Label,
|
|
9
6
|
title: 'Label',
|
|
7
|
+
tags: ['autodocs'],
|
|
10
8
|
};
|
|
11
9
|
|
|
12
10
|
export const Basic = () => {
|
|
13
|
-
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
14
11
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
12
|
+
<>
|
|
13
|
+
<p>
|
|
14
|
+
Avoid using <code>Label</code> component directly (unless for some custom use cases), for
|
|
15
|
+
form control labels and descriptions / info messages, please use <code>Field</code>{' '}
|
|
16
|
+
component
|
|
17
|
+
<pre>{'<Field label={..} description={..} required={..}>'}</pre>
|
|
18
|
+
</p>
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
<Label className="m-b-2">
|
|
21
|
+
<Label.Text required>Text Input</Label.Text>
|
|
22
|
+
</Label>
|
|
23
|
+
|
|
24
|
+
<Label className="m-b-2">
|
|
25
|
+
<Label.Text>Text Input</Label.Text>
|
|
26
|
+
</Label>
|
|
27
|
+
|
|
28
|
+
<Label className="m-b-2">
|
|
29
|
+
<Label.Text>Text Input with Description</Label.Text>
|
|
30
|
+
<Label.Description>This a field Description</Label.Description>
|
|
31
|
+
</Label>
|
|
32
|
+
|
|
33
|
+
<Label className="m-b-2">
|
|
34
|
+
<Label.Text>
|
|
35
|
+
Text Input with Description{' '}
|
|
36
|
+
<Info content="This is some help in popover" aria-label="The aria label" />{' '}
|
|
37
|
+
</Label.Text>
|
|
38
|
+
<Label.Description>This a field Description</Label.Description>
|
|
39
|
+
</Label>
|
|
40
|
+
</>
|
|
36
41
|
);
|
|
37
42
|
};
|