@transferwise/components 0.0.0-experimental-f74f39e → 0.0.0-experimental-74f93db
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/alert/Alert.js +1 -1
- package/build/alert/Alert.js.map +1 -1
- package/build/alert/Alert.mjs +1 -1
- package/build/alert/Alert.mjs.map +1 -1
- package/build/checkbox/Checkbox.js +1 -1
- package/build/checkbox/Checkbox.js.map +1 -1
- package/build/checkbox/Checkbox.mjs +1 -1
- package/build/checkbox/Checkbox.mjs.map +1 -1
- package/build/common/initials.js +20 -7
- package/build/common/initials.js.map +1 -1
- package/build/common/initials.mjs +20 -7
- package/build/common/initials.mjs.map +1 -1
- package/build/field/Field.js +8 -4
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +8 -4
- package/build/field/Field.mjs.map +1 -1
- package/build/inlineAlert/InlineAlert.js +1 -7
- package/build/inlineAlert/InlineAlert.js.map +1 -1
- package/build/inlineAlert/InlineAlert.mjs +1 -7
- package/build/inlineAlert/InlineAlert.mjs.map +1 -1
- package/build/main.css +17 -1
- package/build/radioGroup/RadioGroup.js +1 -0
- package/build/radioGroup/RadioGroup.js.map +1 -1
- package/build/radioGroup/RadioGroup.mjs +1 -0
- package/build/radioGroup/RadioGroup.mjs.map +1 -1
- package/build/styles/field/Field.css +10 -1
- package/build/styles/main.css +17 -1
- package/build/styles/radioGroup/RadioGroup.css +3 -0
- package/build/styles/typeahead/Typeahead.css +4 -0
- package/build/typeahead/Typeahead.js +20 -7
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.mjs +20 -7
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/types/components/src/alert/Alert.d.ts +1 -1
- package/build/types/components/src/alert/Alert.d.ts.map +1 -1
- package/build/types/components/src/common/initials.d.ts.map +1 -1
- package/build/types/components/src/field/Field.d.ts +8 -4
- package/build/types/components/src/field/Field.d.ts.map +1 -1
- package/build/types/components/src/inlineAlert/InlineAlert.d.ts +1 -7
- package/build/types/components/src/inlineAlert/InlineAlert.d.ts.map +1 -1
- package/build/types/components/src/radioGroup/RadioGroup.d.ts.map +1 -1
- package/build/types/components/src/typeahead/Typeahead.d.ts +8 -4
- package/build/types/components/src/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/components/src/upload/Upload.d.ts +1 -1
- package/build/types/components/src/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
- package/build/upload/Upload.js.map +1 -1
- package/build/upload/Upload.mjs.map +1 -1
- package/build/upload/steps/uploadImageStep/uploadImageStep.js +5 -4
- package/build/upload/steps/uploadImageStep/uploadImageStep.js.map +1 -1
- package/build/upload/steps/uploadImageStep/uploadImageStep.mjs +5 -4
- package/build/upload/steps/uploadImageStep/uploadImageStep.mjs.map +1 -1
- package/package.json +8 -8
- package/src/alert/Alert.spec.tsx +1 -1
- package/src/alert/Alert.tsx +2 -2
- package/src/checkbox/Checkbox.story.tsx +11 -3
- package/src/checkbox/Checkbox.tsx +1 -1
- package/src/checkbox/__snapshots__/Checkbox.spec.tsx.snap +1 -1
- package/src/common/initials.spec.tsx +31 -0
- package/src/common/initials.ts +19 -8
- package/src/field/Field.css +10 -1
- package/src/field/Field.less +13 -2
- package/src/field/Field.spec.tsx +19 -3
- package/src/field/Field.story.tsx +18 -0
- package/src/field/Field.tsx +17 -5
- package/src/inlineAlert/InlineAlert.story.tsx +4 -0
- package/src/inlineAlert/InlineAlert.tsx +1 -7
- package/src/main.css +17 -1
- package/src/main.less +1 -0
- package/src/radioGroup/RadioGroup.css +3 -0
- package/src/radioGroup/RadioGroup.less +3 -0
- package/src/radioGroup/RadioGroup.story.tsx +2 -0
- package/src/radioGroup/RadioGroup.test.story.tsx +62 -0
- package/src/radioGroup/RadioGroup.tsx +6 -1
- package/src/typeahead/Typeahead.css +4 -0
- package/src/typeahead/Typeahead.less +5 -1
- package/src/typeahead/Typeahead.spec.tsx +1 -1
- package/src/typeahead/Typeahead.story.tsx +151 -3
- package/src/typeahead/Typeahead.tsx +33 -9
- package/src/upload/Upload.story.tsx +1 -1
- package/src/upload/Upload.tests.story.tsx +36 -1
- package/src/upload/Upload.tsx +1 -1
- package/src/upload/steps/uploadImageStep/uploadImageStep.tsx +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-74f93db",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@babel/preset-env": "^7.28.5",
|
|
44
44
|
"@babel/preset-react": "^7.28.5",
|
|
45
45
|
"@babel/preset-typescript": "^7.28.5",
|
|
46
|
-
"@formatjs/cli": "^6.
|
|
46
|
+
"@formatjs/cli": "^6.8.4",
|
|
47
47
|
"@rollup/plugin-babel": "^6.1.0",
|
|
48
48
|
"@rollup/plugin-json": "^6.1.0",
|
|
49
49
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
"@types/babel__core": "^7.20.5",
|
|
64
64
|
"@types/commonmark": "^0.27.10",
|
|
65
65
|
"@types/jest": "^30.0.0",
|
|
66
|
-
"@types/lodash": "4.17.
|
|
66
|
+
"@types/lodash": "4.17.21",
|
|
67
67
|
"@types/lodash.clamp": "^4.0.9",
|
|
68
68
|
"@types/lodash.debounce": "^4.0.9",
|
|
69
|
-
"@types/react": "^18.3.
|
|
69
|
+
"@types/react": "^18.3.23",
|
|
70
70
|
"@types/react-dom": "^18.3.7",
|
|
71
71
|
"@types/react-transition-group": "4.4.12",
|
|
72
72
|
"@wise/art": "^2.25.0",
|
|
73
73
|
"@wise/eslint-config": "^13.3.0",
|
|
74
|
-
"babel-plugin-formatjs": "^10.5.
|
|
74
|
+
"babel-plugin-formatjs": "^10.5.39",
|
|
75
75
|
"eslint": "^9.39.2",
|
|
76
76
|
"eslint-plugin-storybook": "^10.2.0-alpha.13",
|
|
77
77
|
"gulp": "^5.0.1",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"jest-environment-jsdom": "^29.7.0",
|
|
80
80
|
"jest-fetch-mock": "^3.0.3",
|
|
81
81
|
"lodash.times": "^4.3.2",
|
|
82
|
-
"react-intl": "^7.1.
|
|
83
|
-
"rollup": "^4.
|
|
82
|
+
"react-intl": "^7.1.11",
|
|
83
|
+
"rollup": "^4.54.0",
|
|
84
84
|
"rollup-preserve-directives": "^1.1.3",
|
|
85
85
|
"storybook": "^10.2.0-alpha.13",
|
|
86
86
|
"storybook-addon-tag-badges": "^3.0.4",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"clsx": "^2.1.1",
|
|
114
114
|
"commonmark": "^0.31.2",
|
|
115
115
|
"core-js": "^3.47.0",
|
|
116
|
-
"framer-motion": "^12.
|
|
116
|
+
"framer-motion": "^12.23.26",
|
|
117
117
|
"lodash.clamp": "^4.0.3",
|
|
118
118
|
"lodash.debounce": "^4.0.8",
|
|
119
119
|
"merge-props": "^6.0.0",
|
package/src/alert/Alert.spec.tsx
CHANGED
|
@@ -93,7 +93,7 @@ describe('Alert', () => {
|
|
|
93
93
|
expect(component).toHaveClass('arrow-bottom');
|
|
94
94
|
expect(mockedWarn).toHaveBeenCalledWith(
|
|
95
95
|
expect.stringContaining(
|
|
96
|
-
"Alert component doesn't support 'arrow' anymore, use '
|
|
96
|
+
"Alert component doesn't support 'arrow' anymore, use 'InlinePrompt' instead.",
|
|
97
97
|
),
|
|
98
98
|
);
|
|
99
99
|
});
|
package/src/alert/Alert.tsx
CHANGED
|
@@ -67,7 +67,7 @@ export interface AlertProps {
|
|
|
67
67
|
onDismiss?: React.MouseEventHandler<HTMLButtonElement>;
|
|
68
68
|
/** The type dictates which icon and colour will be used */
|
|
69
69
|
type?: AlertType;
|
|
70
|
-
/** @deprecated Use `
|
|
70
|
+
/** @deprecated Use `InlinePrompt` instead. */
|
|
71
71
|
arrow?: `${AlertArrowPosition}`;
|
|
72
72
|
/** @deprecated Use `message` instead. Be aware `message` only accepts plain text or text with **bold** markdown. */
|
|
73
73
|
children?: React.ReactNode;
|
|
@@ -107,7 +107,7 @@ export default function Alert({
|
|
|
107
107
|
useEffect(() => {
|
|
108
108
|
if (arrow !== undefined) {
|
|
109
109
|
logActionRequired(
|
|
110
|
-
"Alert component doesn't support 'arrow' anymore, use '
|
|
110
|
+
"Alert component doesn't support 'arrow' anymore, use 'InlinePrompt' instead.",
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
}, [arrow]);
|
|
@@ -57,9 +57,17 @@ export const MultipleMobile: Story = {
|
|
|
57
57
|
export const WithinField = {
|
|
58
58
|
decorators: [
|
|
59
59
|
(Story) => (
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
60
|
+
<>
|
|
61
|
+
<Field label="Field label">
|
|
62
|
+
<Story />
|
|
63
|
+
</Field>
|
|
64
|
+
<Field label="Field label" message="Something went wrong" sentiment="negative">
|
|
65
|
+
<Story />
|
|
66
|
+
</Field>
|
|
67
|
+
<Field label="Field label" message="Something went great" sentiment="positive">
|
|
68
|
+
<Story />
|
|
69
|
+
</Field>
|
|
70
|
+
</>
|
|
63
71
|
),
|
|
64
72
|
],
|
|
65
73
|
} satisfies Story;
|
|
@@ -44,7 +44,7 @@ export default function Checkbox({
|
|
|
44
44
|
const innerDisabled = disabled || readOnly;
|
|
45
45
|
return (
|
|
46
46
|
<div id={id} className={classList}>
|
|
47
|
-
<label className={clsx({ disabled })}>
|
|
47
|
+
<label className={clsx('np-checkbox-label', { disabled })}>
|
|
48
48
|
<CheckboxButton
|
|
49
49
|
className="p-r-2"
|
|
50
50
|
checked={checked}
|
|
@@ -52,4 +52,35 @@ describe('getInitials', () => {
|
|
|
52
52
|
expect(getInitials('(Super) Fantastic Squirrel')).toBe('FS');
|
|
53
53
|
expect(getInitials('Super (Fantastic) Squirrel')).toBe('SS');
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
it('handles emojis correctly', () => {
|
|
57
|
+
expect(getInitials('😀')).toBe('😀');
|
|
58
|
+
expect(getInitials('😀 Christian Dalby')).toBe('😀D');
|
|
59
|
+
expect(getInitials('Christian 😀')).toBe('C😀');
|
|
60
|
+
expect(getInitials('😎 Christian 😎')).toBe('😎😎');
|
|
61
|
+
expect(getInitials('🎉 Party 🎊 Time')).toBe('🎉T');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('handles unicode strings correctly', () => {
|
|
65
|
+
expect(getInitials('\u{1F600}')).toBe('\u{1F600}');
|
|
66
|
+
expect(getInitials('\u{1F600} Christian Dalby')).toBe('\u{1F600}D');
|
|
67
|
+
expect(getInitials('Christian \u{1F600}')).toBe('C\u{1F600}');
|
|
68
|
+
expect(getInitials('\u{1F60E} Christian \u{1F60E}')).toBe('\u{1F60E}\u{1F60E}');
|
|
69
|
+
expect(getInitials('\u{1F389} Party \u{1F38A} Time')).toBe('\u{1F389}T');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('handles multi-byte emojis with skin tones and ZWJ sequences', () => {
|
|
73
|
+
expect(getInitials('👨👩👧👦')).toBe('👨👩👧👦');
|
|
74
|
+
expect(getInitials('👨👩👧👦 Family')).toBe('👨👩👧👦F');
|
|
75
|
+
expect(getInitials('👋🏽')).toBe('👋🏽');
|
|
76
|
+
expect(getInitials('Christian 👋🏽')).toBe('C👋🏽');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('handles diacritical marks correctly', () => {
|
|
80
|
+
expect(getInitials('André Müller')).toBe('AM');
|
|
81
|
+
expect(getInitials('Ángel Muñoz')).toBe('ÁM');
|
|
82
|
+
expect(getInitials('François Élis')).toBe('FÉ');
|
|
83
|
+
expect(getInitials('Łukasz Ślusarczyk')).toBe('ŁŚ');
|
|
84
|
+
expect(getInitials('Ömer Özdemir')).toBe('ÖÖ');
|
|
85
|
+
});
|
|
55
86
|
});
|
package/src/common/initials.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
function startsWithParenthesis(part: string) {
|
|
2
|
+
return /^[({[<]/.test(part);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// Reuse a single Intl.Segmenter instance to avoid repeated allocations
|
|
6
|
+
const GRAPHEME_SEGMENTER = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
7
|
+
|
|
8
|
+
// Helper to split the string into grapheme clusters, which handles complex characters correctly
|
|
9
|
+
function getGraphemes(str: string): string[] {
|
|
10
|
+
return Array.from(GRAPHEME_SEGMENTER.segment(str), (s) => s.segment);
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
export function getInitials(name: string) {
|
|
2
14
|
if (name.length === 2 && /^[A-Z]{2}$/.test(name)) {
|
|
3
15
|
return name;
|
|
@@ -6,17 +18,16 @@ export function getInitials(name: string) {
|
|
|
6
18
|
const allInitials = name
|
|
7
19
|
.split(' ')
|
|
8
20
|
.filter((part) => !startsWithParenthesis(part))
|
|
9
|
-
.map((part) => part[0])
|
|
21
|
+
.map((part) => getGraphemes(part)[0])
|
|
10
22
|
.join('')
|
|
11
23
|
.toUpperCase();
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return allInitials[0] + allInitials.slice(-1);
|
|
25
|
+
// Get graphemes of the initials string to handle complex characters correctly
|
|
26
|
+
const graphemes = getGraphemes(allInitials);
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
return
|
|
28
|
+
if (graphemes.length === 1) {
|
|
29
|
+
return graphemes[0];
|
|
21
30
|
}
|
|
31
|
+
|
|
32
|
+
return graphemes[0] + graphemes[graphemes.length - 1];
|
|
22
33
|
}
|
package/src/field/Field.css
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
.np-field-control
|
|
1
|
+
.np-field-control,
|
|
2
|
+
.np-field__prompt {
|
|
2
3
|
margin-top: 4px;
|
|
3
4
|
margin-top: var(--size-4);
|
|
4
5
|
}
|
|
6
|
+
.np-field .form-group--typeahead[class],
|
|
7
|
+
.np-field .np-checkbox-label[class] {
|
|
8
|
+
margin-bottom: 0;
|
|
9
|
+
}
|
|
10
|
+
.np-field:has(.wds-radio-group) .np-field__prompt {
|
|
11
|
+
margin-top: 12px;
|
|
12
|
+
margin-top: var(--size-12);
|
|
13
|
+
}
|
package/src/field/Field.less
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
.np-field {
|
|
2
|
-
&-control
|
|
3
|
-
|
|
2
|
+
&-control,
|
|
3
|
+
&__prompt {
|
|
4
|
+
margin-top: var(--size-4);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// @FIXME space between individual fields should be 24px, while some older inputs
|
|
8
|
+
// inject extraneous space.
|
|
9
|
+
.form-group--typeahead[class],
|
|
10
|
+
.np-checkbox-label[class] {
|
|
11
|
+
margin-bottom: 0;
|
|
12
|
+
}
|
|
13
|
+
&:has(.wds-radio-group) &__prompt {
|
|
14
|
+
margin-top: var(--size-12);
|
|
4
15
|
}
|
|
5
16
|
}
|
package/src/field/Field.spec.tsx
CHANGED
|
@@ -71,7 +71,7 @@ describe('Field', () => {
|
|
|
71
71
|
expect(screen.getByLabelText(/Phone number/)).toHaveAttribute('aria-describedby');
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it("should allow for
|
|
74
|
+
it("should allow for InlinePrompt's StatusIcon override via `messageIconLabel` prop", () => {
|
|
75
75
|
const customIconLabel = 'My custom icon label';
|
|
76
76
|
|
|
77
77
|
render(
|
|
@@ -86,8 +86,8 @@ describe('Field', () => {
|
|
|
86
86
|
</Field>,
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
expect(screen.
|
|
90
|
-
expect(screen.
|
|
89
|
+
expect(screen.queryByLabelText('Error:')).not.toBeInTheDocument();
|
|
90
|
+
expect(screen.getByLabelText(customIconLabel)).toBeInTheDocument();
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('should show or hide (Optional) suffix depending on required prop', () => {
|
|
@@ -147,4 +147,20 @@ describe('Field', () => {
|
|
|
147
147
|
const label = screen.getByText('Phone number');
|
|
148
148
|
await userEvent.click(label);
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
it('should show loading spinner when messageLoading is true', () => {
|
|
152
|
+
render(
|
|
153
|
+
<Field
|
|
154
|
+
label="Phone number"
|
|
155
|
+
message="Processing your request"
|
|
156
|
+
sentiment="neutral"
|
|
157
|
+
messageLoading
|
|
158
|
+
>
|
|
159
|
+
<Input />
|
|
160
|
+
</Field>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(screen.getByTestId('InlinePrompt_ProcessIndicator')).toBeInTheDocument();
|
|
164
|
+
expect(screen.getByText('Processing your request')).toBeInTheDocument();
|
|
165
|
+
});
|
|
150
166
|
});
|
|
@@ -46,6 +46,7 @@ export const Basic = (args: FieldProps) => {
|
|
|
46
46
|
sentiment={Sentiment.NEGATIVE}
|
|
47
47
|
message="Validation error, please take a look"
|
|
48
48
|
messageIconLabel={args.messageIconLabel}
|
|
49
|
+
messageLoading={args.messageLoading}
|
|
49
50
|
>
|
|
50
51
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
51
52
|
</Field>
|
|
@@ -81,6 +82,7 @@ export const Basic = (args: FieldProps) => {
|
|
|
81
82
|
message={lorem10}
|
|
82
83
|
sentiment="negative"
|
|
83
84
|
messageIconLabel={args.messageIconLabel}
|
|
85
|
+
messageLoading={args.messageLoading}
|
|
84
86
|
>
|
|
85
87
|
<TextArea />
|
|
86
88
|
</Field>
|
|
@@ -109,6 +111,17 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
109
111
|
sentiment={Sentiment.POSITIVE}
|
|
110
112
|
message="Positive message"
|
|
111
113
|
messageIconLabel={args.messageIconLabel}
|
|
114
|
+
messageLoading={args.messageLoading}
|
|
115
|
+
>
|
|
116
|
+
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
117
|
+
</Field>
|
|
118
|
+
|
|
119
|
+
<Field
|
|
120
|
+
label="Text Input with Proposition Message"
|
|
121
|
+
sentiment="proposition"
|
|
122
|
+
message="Proposition message"
|
|
123
|
+
messageIconLabel={args.messageIconLabel}
|
|
124
|
+
messageLoading={args.messageLoading}
|
|
112
125
|
>
|
|
113
126
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
114
127
|
</Field>
|
|
@@ -118,6 +131,7 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
118
131
|
sentiment={Sentiment.WARNING}
|
|
119
132
|
message="Warning message"
|
|
120
133
|
messageIconLabel={args.messageIconLabel}
|
|
134
|
+
messageLoading={args.messageLoading}
|
|
121
135
|
>
|
|
122
136
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
123
137
|
</Field>
|
|
@@ -127,6 +141,7 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
127
141
|
sentiment={Sentiment.NEGATIVE}
|
|
128
142
|
message="This is a required field"
|
|
129
143
|
messageIconLabel={args.messageIconLabel}
|
|
144
|
+
messageLoading={args.messageLoading}
|
|
130
145
|
>
|
|
131
146
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
132
147
|
</Field>
|
|
@@ -138,6 +153,7 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
138
153
|
sentiment={Sentiment.NEGATIVE}
|
|
139
154
|
message="Validation error, please take a look"
|
|
140
155
|
messageIconLabel={args.messageIconLabel}
|
|
156
|
+
messageLoading={args.messageLoading}
|
|
141
157
|
>
|
|
142
158
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
143
159
|
</Field>
|
|
@@ -148,6 +164,7 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
148
164
|
hint="This is a helpful message"
|
|
149
165
|
error="Validation error, please take a look"
|
|
150
166
|
messageIconLabel={args.messageIconLabel}
|
|
167
|
+
messageLoading={args.messageLoading}
|
|
151
168
|
>
|
|
152
169
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
153
170
|
</Field>
|
|
@@ -157,6 +174,7 @@ export const WithStatusMessages = (args: FieldProps) => {
|
|
|
157
174
|
label="Text Input with Info under the field"
|
|
158
175
|
message="This is a helpful message"
|
|
159
176
|
messageIconLabel={args.messageIconLabel}
|
|
177
|
+
messageLoading={args.messageLoading}
|
|
160
178
|
>
|
|
161
179
|
<Input value={value} onChange={({ target }) => setValue(target.value)} />
|
|
162
180
|
</Field>
|
package/src/field/Field.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { clsx } from 'clsx';
|
|
|
2
2
|
import { useId, useRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { Sentiment } from '../common';
|
|
5
|
-
import
|
|
5
|
+
import { InlinePrompt, type InlinePromptProps } from '../prompt';
|
|
6
6
|
import {
|
|
7
7
|
FieldLabelContextProvider,
|
|
8
8
|
InputDescribedByProvider,
|
|
@@ -21,14 +21,18 @@ export type FieldProps = {
|
|
|
21
21
|
hint?: React.ReactNode;
|
|
22
22
|
message?: React.ReactNode;
|
|
23
23
|
/**
|
|
24
|
-
* Override for the [
|
|
24
|
+
* Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)
|
|
25
25
|
* announced by the screen readers
|
|
26
26
|
* */
|
|
27
27
|
messageIconLabel?: string;
|
|
28
|
+
/**
|
|
29
|
+
* If true, shows a loading spinner in place of the message icon of the InlinePrompt
|
|
30
|
+
*/
|
|
31
|
+
messageLoading?: boolean;
|
|
28
32
|
description?: React.ReactNode;
|
|
29
33
|
/** @deprecated use `message` and `type={Sentiment.NEGATIVE}` prop instead */
|
|
30
34
|
error?: React.ReactNode;
|
|
31
|
-
sentiment?:
|
|
35
|
+
sentiment?: InlinePromptProps['sentiment'];
|
|
32
36
|
className?: string;
|
|
33
37
|
children?: React.ReactNode;
|
|
34
38
|
};
|
|
@@ -39,6 +43,7 @@ export const Field = ({
|
|
|
39
43
|
required = true,
|
|
40
44
|
message: propMessage,
|
|
41
45
|
messageIconLabel,
|
|
46
|
+
messageLoading,
|
|
42
47
|
hint,
|
|
43
48
|
description = hint,
|
|
44
49
|
sentiment: propType = Sentiment.NEUTRAL,
|
|
@@ -104,9 +109,16 @@ export const Field = ({
|
|
|
104
109
|
)}
|
|
105
110
|
|
|
106
111
|
{message && (
|
|
107
|
-
<
|
|
112
|
+
<InlinePrompt
|
|
113
|
+
sentiment={sentiment}
|
|
114
|
+
id={messageId}
|
|
115
|
+
mediaLabel={messageIconLabel}
|
|
116
|
+
className="np-field__prompt"
|
|
117
|
+
loading={messageLoading}
|
|
118
|
+
width="full"
|
|
119
|
+
>
|
|
108
120
|
{message}
|
|
109
|
-
</
|
|
121
|
+
</InlinePrompt>
|
|
110
122
|
)}
|
|
111
123
|
</div>
|
|
112
124
|
</InputInvalidProvider>
|
|
@@ -4,9 +4,13 @@ import InlineAlert, { InlineAlertProps } from './InlineAlert';
|
|
|
4
4
|
import { lorem10, lorem40 } from '../test-utils';
|
|
5
5
|
import Link from '../link';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* > **DEPRECATED** Use [InlinePrompt](?path=/docs/prompts-inlineprompt--docs) or [Field](?path=/docs/forms-field--docs) instead.
|
|
9
|
+
*/
|
|
7
10
|
export default {
|
|
8
11
|
component: InlineAlert,
|
|
9
12
|
title: 'Prompts/InlineAlert',
|
|
13
|
+
tags: ['deprecated'],
|
|
10
14
|
} as Meta<InlineAlertProps>;
|
|
11
15
|
|
|
12
16
|
export const Basic = () => {
|
|
@@ -22,13 +22,7 @@ const iconTypes = new Set<NonNullable<InlineAlertProps['type']>>([
|
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* it's for edge cases when `<Field />` isn't suitable for some reasons.
|
|
27
|
-
*
|
|
28
|
-
* Example:
|
|
29
|
-
* ```
|
|
30
|
-
* <Field sentiment={..} message={..}>..</Field>
|
|
31
|
-
* ```
|
|
25
|
+
* @deprecated Use [<InlinePrompt />](https://storybook.wise.design/?path=/docs/prompts-inlineprompt--docs) or [<Field />](https://storybook.wise.design/?path=/docs/forms-field--docs) instead.
|
|
32
26
|
*/
|
|
33
27
|
export default function InlineAlert({
|
|
34
28
|
id,
|
package/src/main.css
CHANGED
|
@@ -3825,10 +3825,19 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
3825
3825
|
stroke-dasharray: calc(12px * 0.5) calc(12px * 0.5);
|
|
3826
3826
|
stroke-dasharray: var(--wds-list-item-spotlight-strokeDashSize) var(--wds-list-item-spotlight-strokeDashSize);
|
|
3827
3827
|
}
|
|
3828
|
-
.np-field-control
|
|
3828
|
+
.np-field-control,
|
|
3829
|
+
.np-field__prompt {
|
|
3829
3830
|
margin-top: 4px;
|
|
3830
3831
|
margin-top: var(--size-4);
|
|
3831
3832
|
}
|
|
3833
|
+
.np-field .form-group--typeahead[class],
|
|
3834
|
+
.np-field .np-checkbox-label[class] {
|
|
3835
|
+
margin-bottom: 0;
|
|
3836
|
+
}
|
|
3837
|
+
.np-field:has(.wds-radio-group) .np-field__prompt {
|
|
3838
|
+
margin-top: 12px;
|
|
3839
|
+
margin-top: var(--size-12);
|
|
3840
|
+
}
|
|
3832
3841
|
.np-input-group {
|
|
3833
3842
|
display: inline-grid;
|
|
3834
3843
|
width: 100%;
|
|
@@ -5397,6 +5406,9 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5397
5406
|
.np-CardGroup .np-Card.np-Card--promoCard {
|
|
5398
5407
|
max-width: 100%;
|
|
5399
5408
|
}
|
|
5409
|
+
.wds-radio-group .np-radio:last-child label {
|
|
5410
|
+
margin-bottom: 0;
|
|
5411
|
+
}
|
|
5400
5412
|
.np-section {
|
|
5401
5413
|
margin-top: 32px;
|
|
5402
5414
|
margin-top: var(--size-32);
|
|
@@ -6923,6 +6935,10 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
6923
6935
|
padding: 0;
|
|
6924
6936
|
margin: 0;
|
|
6925
6937
|
}
|
|
6938
|
+
.typeahead--prompt {
|
|
6939
|
+
margin-top: 4px;
|
|
6940
|
+
margin-top: var(--size-4);
|
|
6941
|
+
}
|
|
6926
6942
|
.typeahead-sm.typeahead--multiple .typeahead__input-container {
|
|
6927
6943
|
min-height: 32px;
|
|
6928
6944
|
}
|
package/src/main.less
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
@import "./phoneNumberInput/PhoneNumberInput.less";
|
|
62
62
|
@import "./popover/Popover.less";
|
|
63
63
|
@import "./promoCard/PromoCard.less";
|
|
64
|
+
@import "./radioGroup/RadioGroup.less";
|
|
64
65
|
@import "./section/Section.less";
|
|
65
66
|
@import "./slidingPanel/SlidingPanel.less";
|
|
66
67
|
@import "./snackbar/Snackbar.less";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import RadioGroup, { RadioGroupProps } from './RadioGroup';
|
|
2
|
+
import { Field } from '../field/Field';
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
4
|
+
import { fn } from 'storybook/test';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
component: RadioGroup,
|
|
8
|
+
title: 'Forms/RadioGroup/tests',
|
|
9
|
+
tags: ['!autodocs'],
|
|
10
|
+
args: {
|
|
11
|
+
selectedValue: 'radio-2',
|
|
12
|
+
name: 'radio-group',
|
|
13
|
+
radios: [
|
|
14
|
+
{
|
|
15
|
+
value: 'radio-1',
|
|
16
|
+
label: 'Radio1',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: 'radio-2',
|
|
20
|
+
label: 'Radio2',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
onChange: fn(),
|
|
24
|
+
},
|
|
25
|
+
} satisfies Meta<typeof RadioGroup>;
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story<T extends string | number = string> = StoryObj<RadioGroupProps<T>>;
|
|
29
|
+
|
|
30
|
+
export const FieldsWithPrompt = {
|
|
31
|
+
render: function Render(args) {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{['i1', 'i2'].map((id) => (
|
|
35
|
+
<Field
|
|
36
|
+
key={id}
|
|
37
|
+
label="Field label"
|
|
38
|
+
description="Field description"
|
|
39
|
+
message="Something went great"
|
|
40
|
+
sentiment="positive"
|
|
41
|
+
>
|
|
42
|
+
<RadioGroup {...args} selectedValue="radio-2" />
|
|
43
|
+
</Field>
|
|
44
|
+
))}
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
} satisfies Story;
|
|
49
|
+
|
|
50
|
+
export const FieldsWithoutPrompt = {
|
|
51
|
+
render: function Render(args) {
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{['i1', 'i2'].map((id) => (
|
|
55
|
+
<Field key={id} label="Field label" sentiment="positive">
|
|
56
|
+
<RadioGroup {...args} selectedValue="radio-2" />
|
|
57
|
+
</Field>
|
|
58
|
+
))}
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
} satisfies Story;
|
|
@@ -29,7 +29,12 @@ export default function RadioGroup<T extends string | number = never>({
|
|
|
29
29
|
const [uncontrolledValue, setUncontrolledValue] = useState(controlledValue);
|
|
30
30
|
|
|
31
31
|
return radios.length > 0 ? (
|
|
32
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
role="radiogroup"
|
|
34
|
+
{...inputAttributes}
|
|
35
|
+
{...UNSAFE_inputAttributes}
|
|
36
|
+
className="wds-radio-group"
|
|
37
|
+
>
|
|
33
38
|
{radios.map(({ value = '' as T, ...restProps }, index) => (
|
|
34
39
|
<Radio
|
|
35
40
|
// eslint-disable-next-line react/no-array-index-key
|
|
@@ -122,6 +122,10 @@
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
&--prompt {
|
|
126
|
+
margin-top: var(--size-4);
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
// SIZES
|
|
126
130
|
&-sm {
|
|
127
131
|
&.typeahead--multiple {
|
|
@@ -207,7 +211,7 @@
|
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
.np-theme-personal & {
|
|
210
|
-
.input-group:not(.disabled
|
|
214
|
+
.input-group:not(.disabled, :disabled, .input-group--has-error):focus-within .tw-icon-search {
|
|
211
215
|
color: var(--color-interactive-primary);
|
|
212
216
|
}
|
|
213
217
|
.tw-icon-search {
|
|
@@ -102,7 +102,7 @@ describe('Typeahead', () => {
|
|
|
102
102
|
expect(onChange).toHaveBeenCalledWith([]);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it('shows
|
|
105
|
+
it('shows InlinePrompt when alert prop is provided', () => {
|
|
106
106
|
render(
|
|
107
107
|
<Typeahead
|
|
108
108
|
id="test"
|