@transferwise/components 0.0.0-experimental-58e9ef8 → 0.0.0-experimental-a88d24d
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/avatarView/AvatarView.js.map +1 -1
- package/build/avatarView/AvatarView.mjs.map +1 -1
- package/build/common/preventScroll/PreventScroll.js +1 -8
- package/build/common/preventScroll/PreventScroll.js.map +1 -1
- package/build/common/preventScroll/PreventScroll.mjs +1 -8
- package/build/common/preventScroll/PreventScroll.mjs.map +1 -1
- package/build/inputs/_BottomSheet.js +1 -29
- package/build/inputs/_BottomSheet.js.map +1 -1
- package/build/inputs/_BottomSheet.mjs +2 -30
- package/build/inputs/_BottomSheet.mjs.map +1 -1
- package/build/main.css +9 -13
- package/build/prompt/ActionPrompt/ActionPrompt.js +27 -4
- package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.mjs +27 -4
- package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
- package/build/slidingPanel/SlidingPanel.js +20 -23
- package/build/slidingPanel/SlidingPanel.js.map +1 -1
- package/build/slidingPanel/SlidingPanel.mjs +21 -24
- package/build/slidingPanel/SlidingPanel.mjs.map +1 -1
- package/build/statusIcon/StatusIcon.js +2 -0
- package/build/statusIcon/StatusIcon.js.map +1 -1
- package/build/statusIcon/StatusIcon.mjs +2 -0
- package/build/statusIcon/StatusIcon.mjs.map +1 -1
- package/build/styles/dimmer/Dimmer.css +0 -1
- package/build/styles/inputs/SelectInput.css +9 -12
- package/build/styles/main.css +9 -13
- package/build/types/avatarView/AvatarView.d.ts +1 -1
- package/build/types/avatarView/AvatarView.d.ts.map +1 -1
- package/build/types/common/preventScroll/PreventScroll.d.ts +1 -1
- package/build/types/common/preventScroll/PreventScroll.d.ts.map +1 -1
- package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +4 -2
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
- package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.d.ts +2 -1
- package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/avatarView/AvatarView.tsx +1 -1
- package/src/common/bottomSheet/__snapshots__/BottomSheet.test.tsx.snap +0 -6
- package/src/common/preventScroll/PreventScroll.tsx +1 -11
- package/src/dimmer/Dimmer.css +0 -1
- package/src/dimmer/Dimmer.less +0 -1
- package/src/inputs/SelectInput.css +9 -12
- package/src/inputs/_BottomSheet.less +6 -12
- package/src/inputs/_BottomSheet.tsx +5 -19
- package/src/main.css +9 -13
- package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +65 -0
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +4 -1
- package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +147 -0
- package/src/prompt/ActionPrompt/ActionPrompt.tsx +48 -7
- package/src/slidingPanel/SlidingPanel.tsx +24 -29
- package/src/statusIcon/StatusIcon.tsx +8 -1
- package/src/common/bottomSheet/BottomSheet.test.story.tsx +0 -94
- package/src/inputs/SelectInput.test.story.tsx +0 -83
- package/src/moneyInput/MoneyInput.test.story.tsx +0 -101
|
@@ -5,6 +5,7 @@ import { SizeSmall, SizeMedium, SizeLarge, Sentiment, Status } from '../common';
|
|
|
5
5
|
type LegacySizes = SizeSmall | SizeMedium | SizeLarge;
|
|
6
6
|
export type StatusIconSentiment = Sentiment | Status.PENDING;
|
|
7
7
|
export type StatusIconProps = {
|
|
8
|
+
id?: string;
|
|
8
9
|
sentiment?: `${StatusIconSentiment}`;
|
|
9
10
|
size?: LegacySizes | 16 | 24 | 32 | 40 | 48 | 56 | 72;
|
|
10
11
|
/**
|
|
@@ -14,6 +15,6 @@ export type StatusIconProps = {
|
|
|
14
15
|
* */
|
|
15
16
|
iconLabel?: string | null;
|
|
16
17
|
};
|
|
17
|
-
declare const StatusIcon: ({ sentiment, size: sizeProp, iconLabel }: StatusIconProps) => import("react").JSX.Element;
|
|
18
|
+
declare const StatusIcon: ({ id, sentiment, size: sizeProp, iconLabel, }: StatusIconProps) => import("react").JSX.Element;
|
|
18
19
|
export default StatusIcon;
|
|
19
20
|
//# sourceMappingURL=StatusIcon.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusIcon.d.ts","sourceRoot":"","sources":["../../../src/statusIcon/StatusIcon.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAoB,MAAM,EAAE,MAAM,WAAW,CAAC;AAMlG;;GAEG;AACH,KAAK,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACrC,IAAI,CAAC,EAAE,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACtD;;;;SAIK;IACL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAQF,QAAA,MAAM,UAAU,GAAI,
|
|
1
|
+
{"version":3,"file":"StatusIcon.d.ts","sourceRoot":"","sources":["../../../src/statusIcon/StatusIcon.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAoB,MAAM,EAAE,MAAM,WAAW,CAAC;AAMlG;;GAEG;AACH,KAAK,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACrC,IAAI,CAAC,EAAE,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACtD;;;;SAIK;IACL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAQF,QAAA,MAAM,UAAU,GAAI,+CAKjB,eAAe,gCA0EjB,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
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-a88d24d",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -106,8 +106,8 @@
|
|
|
106
106
|
"@floating-ui/react": "^0.27.16",
|
|
107
107
|
"@headlessui/react": "^2.2.9",
|
|
108
108
|
"@popperjs/core": "^2.11.8",
|
|
109
|
-
"@react-aria/focus": "^3.21.
|
|
110
|
-
"@react-aria/overlays": "^3.31.
|
|
109
|
+
"@react-aria/focus": "^3.21.3",
|
|
110
|
+
"@react-aria/overlays": "^3.31.0",
|
|
111
111
|
"@transferwise/formatting": "^2.13.4",
|
|
112
112
|
"@transferwise/neptune-validation": "^3.3.1",
|
|
113
113
|
"clsx": "^2.1.1",
|
|
@@ -32,7 +32,7 @@ export type Props = {
|
|
|
32
32
|
style?: Pick<React.CSSProperties, 'border' | 'backgroundColor' | 'color'>;
|
|
33
33
|
} & Pick<
|
|
34
34
|
HTMLAttributes<HTMLDivElement>,
|
|
35
|
-
'className' | 'children' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'
|
|
35
|
+
'id' | 'className' | 'children' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'
|
|
36
36
|
>;
|
|
37
37
|
|
|
38
38
|
function AvatarView({
|
|
@@ -20,12 +20,6 @@ exports[`BottomSheet renders content when open 1`] = `
|
|
|
20
20
|
<div
|
|
21
21
|
class="dimmer-content-positioner"
|
|
22
22
|
>
|
|
23
|
-
<style>
|
|
24
|
-
html, body {
|
|
25
|
-
height: 100dvh;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
}
|
|
28
|
-
</style>
|
|
29
23
|
<div
|
|
30
24
|
class="sliding-panel sliding-panel--open-bottom np-bottom-sheet sliding-panel-appear sliding-panel-appear-active"
|
|
31
25
|
>
|
|
@@ -2,15 +2,5 @@ import { usePreventScroll } from '@react-aria/overlays';
|
|
|
2
2
|
|
|
3
3
|
export function PreventScroll() {
|
|
4
4
|
usePreventScroll();
|
|
5
|
-
|
|
6
|
-
// ios18
|
|
7
|
-
return (
|
|
8
|
-
<style>{`html, body {
|
|
9
|
-
height: 100dvh;
|
|
10
|
-
overflow: hidden;
|
|
11
|
-
}`}</style>
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
// ios26
|
|
15
|
-
return <style>{`html, body { height: 100dvh; overflow: hidden; }`}</style>;
|
|
5
|
+
return null;
|
|
16
6
|
}
|
package/src/dimmer/Dimmer.css
CHANGED
package/src/dimmer/Dimmer.less
CHANGED
|
@@ -11,38 +11,35 @@
|
|
|
11
11
|
transition-property: opacity;
|
|
12
12
|
transition-timing-function: ease-out;
|
|
13
13
|
transition-duration: 300ms;
|
|
14
|
-
height: 100vh;
|
|
15
|
-
min-height: -webkit-fill-available;
|
|
16
14
|
}
|
|
17
15
|
.np-bottom-sheet-v2-backdrop--closed {
|
|
18
16
|
opacity: 0;
|
|
19
17
|
}
|
|
20
18
|
.np-bottom-sheet-v2 {
|
|
21
19
|
position: fixed;
|
|
22
|
-
inset:
|
|
20
|
+
inset: 0px;
|
|
23
21
|
bottom: env(keyboard-inset-height, 0px);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
margin-left: 8px;
|
|
23
|
+
margin-left: var(--size-8);
|
|
24
|
+
margin-right: 8px;
|
|
25
|
+
margin-right: var(--size-8);
|
|
26
|
+
margin-top: 64px;
|
|
27
|
+
margin-top: var(--size-64);
|
|
28
28
|
display: flex;
|
|
29
29
|
flex-direction: column;
|
|
30
30
|
justify-content: flex-end;
|
|
31
|
-
height: 100%;
|
|
32
|
-
min-height: -webkit-fill-available;
|
|
33
31
|
}
|
|
34
32
|
.np-bottom-sheet-v2-content {
|
|
35
33
|
display: flex;
|
|
36
34
|
flex-direction: column;
|
|
37
35
|
overflow: auto;
|
|
38
36
|
border-top-left-radius: 32px;
|
|
39
|
-
|
|
37
|
+
/* TODO: Tokenize */
|
|
40
38
|
border-top-right-radius: 32px;
|
|
41
|
-
|
|
39
|
+
/* TODO: Tokenize */
|
|
42
40
|
background-color: #ffffff;
|
|
43
41
|
background-color: var(--color-background-elevated);
|
|
44
42
|
box-shadow: 0 0 40px rgba(69, 71, 69, 0.2);
|
|
45
|
-
will-change: transform;
|
|
46
43
|
}
|
|
47
44
|
.np-bottom-sheet-v2-content:focus {
|
|
48
45
|
outline: none;
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
transition-property: opacity;
|
|
12
12
|
transition-timing-function: ease-out;
|
|
13
13
|
transition-duration: 300ms;
|
|
14
|
-
height: 100vh;
|
|
15
|
-
min-height: -webkit-fill-available;
|
|
16
14
|
|
|
17
15
|
&--closed {
|
|
18
16
|
opacity: 0;
|
|
@@ -21,28 +19,24 @@
|
|
|
21
19
|
|
|
22
20
|
.np-bottom-sheet-v2 {
|
|
23
21
|
position: fixed;
|
|
24
|
-
inset:
|
|
22
|
+
inset: 0px;
|
|
25
23
|
bottom: env(keyboard-inset-height, 0px);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
padding-bottom: min(env(safe-area-inset-bottom, var(--size-16)));
|
|
24
|
+
margin-left: var(--size-8);
|
|
25
|
+
margin-right: var(--size-8);
|
|
26
|
+
margin-top: var(--size-64);
|
|
30
27
|
display: flex;
|
|
31
28
|
flex-direction: column;
|
|
32
29
|
justify-content: flex-end;
|
|
33
|
-
height: 100%;
|
|
34
|
-
min-height: -webkit-fill-available;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
.np-bottom-sheet-v2-content {
|
|
38
33
|
display: flex;
|
|
39
34
|
flex-direction: column;
|
|
40
35
|
overflow: auto;
|
|
41
|
-
border-top-left-radius:
|
|
42
|
-
border-top-right-radius:
|
|
36
|
+
border-top-left-radius: 32px; /* TODO: Tokenize */
|
|
37
|
+
border-top-right-radius: 32px; /* TODO: Tokenize */
|
|
43
38
|
background-color: var(--color-background-elevated);
|
|
44
39
|
box-shadow: 0 0 40px rgb(69 71 69 / 0.2);
|
|
45
|
-
will-change: transform;
|
|
46
40
|
|
|
47
41
|
&:focus {
|
|
48
42
|
outline: none;
|
|
@@ -10,11 +10,12 @@ import { Transition, TransitionChild } from '@headlessui/react';
|
|
|
10
10
|
import { FocusScope } from '@react-aria/focus';
|
|
11
11
|
import { ThemeProvider, useTheme } from '@wise/components-theming';
|
|
12
12
|
import { clsx } from 'clsx';
|
|
13
|
-
import { Fragment, useState
|
|
13
|
+
import { Fragment, useState } from 'react';
|
|
14
14
|
|
|
15
|
-
import { CloseButton
|
|
15
|
+
import { CloseButton } from '../common/closeButton';
|
|
16
16
|
import { useVirtualKeyboard } from '../common/hooks/useVirtualKeyboard';
|
|
17
17
|
import { PreventScroll } from '../common/preventScroll/PreventScroll';
|
|
18
|
+
import { Size } from '../common/propsValues/size';
|
|
18
19
|
|
|
19
20
|
export interface BottomSheetProps {
|
|
20
21
|
open: boolean;
|
|
@@ -32,19 +33,6 @@ export interface BottomSheetProps {
|
|
|
32
33
|
onCloseEnd?: () => void;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
/**
|
|
36
|
-
* App pages set scroll-behavior to 'smooth' which causes mobile Safari to
|
|
37
|
-
* slow-scroll and glitch. This function temporarily disables that behaviour
|
|
38
|
-
* while the BottomSheet is open.
|
|
39
|
-
*/
|
|
40
|
-
const freezeScroll = (shouldFreeze = true) => {
|
|
41
|
-
if (shouldFreeze) {
|
|
42
|
-
document.documentElement.style.scrollBehavior = 'unset';
|
|
43
|
-
} else {
|
|
44
|
-
document.documentElement.style.removeProperty('scroll-behavior');
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
36
|
export function BottomSheet({
|
|
49
37
|
open,
|
|
50
38
|
renderTrigger,
|
|
@@ -66,14 +54,12 @@ export function BottomSheet({
|
|
|
66
54
|
},
|
|
67
55
|
});
|
|
68
56
|
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
freezeScroll(open);
|
|
71
|
-
}, [open]);
|
|
72
|
-
|
|
73
57
|
const dismiss = useDismiss(context);
|
|
74
58
|
const role = useRole(context);
|
|
75
59
|
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, role]);
|
|
60
|
+
|
|
76
61
|
const [floatingKey, setFloatingKey] = useState(0);
|
|
62
|
+
|
|
77
63
|
const { theme, screenMode } = useTheme();
|
|
78
64
|
|
|
79
65
|
return (
|
package/src/main.css
CHANGED
|
@@ -2574,7 +2574,6 @@ button.np-option {
|
|
|
2574
2574
|
}
|
|
2575
2575
|
.no-scroll {
|
|
2576
2576
|
overflow: hidden;
|
|
2577
|
-
scroll-behavior: unset;
|
|
2578
2577
|
}
|
|
2579
2578
|
.dimmer {
|
|
2580
2579
|
position: fixed;
|
|
@@ -3840,38 +3839,35 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
3840
3839
|
transition-property: opacity;
|
|
3841
3840
|
transition-timing-function: ease-out;
|
|
3842
3841
|
transition-duration: 300ms;
|
|
3843
|
-
height: 100vh;
|
|
3844
|
-
min-height: -webkit-fill-available;
|
|
3845
3842
|
}
|
|
3846
3843
|
.np-bottom-sheet-v2-backdrop--closed {
|
|
3847
3844
|
opacity: 0;
|
|
3848
3845
|
}
|
|
3849
3846
|
.np-bottom-sheet-v2 {
|
|
3850
3847
|
position: fixed;
|
|
3851
|
-
inset:
|
|
3848
|
+
inset: 0px;
|
|
3852
3849
|
bottom: env(keyboard-inset-height, 0px);
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3850
|
+
margin-left: 8px;
|
|
3851
|
+
margin-left: var(--size-8);
|
|
3852
|
+
margin-right: 8px;
|
|
3853
|
+
margin-right: var(--size-8);
|
|
3854
|
+
margin-top: 64px;
|
|
3855
|
+
margin-top: var(--size-64);
|
|
3857
3856
|
display: flex;
|
|
3858
3857
|
flex-direction: column;
|
|
3859
3858
|
justify-content: flex-end;
|
|
3860
|
-
height: 100%;
|
|
3861
|
-
min-height: -webkit-fill-available;
|
|
3862
3859
|
}
|
|
3863
3860
|
.np-bottom-sheet-v2-content {
|
|
3864
3861
|
display: flex;
|
|
3865
3862
|
flex-direction: column;
|
|
3866
3863
|
overflow: auto;
|
|
3867
3864
|
border-top-left-radius: 32px;
|
|
3868
|
-
|
|
3865
|
+
/* TODO: Tokenize */
|
|
3869
3866
|
border-top-right-radius: 32px;
|
|
3870
|
-
|
|
3867
|
+
/* TODO: Tokenize */
|
|
3871
3868
|
background-color: #ffffff;
|
|
3872
3869
|
background-color: var(--color-background-elevated);
|
|
3873
3870
|
box-shadow: 0 0 40px rgba(69, 71, 69, 0.2);
|
|
3874
|
-
will-change: transform;
|
|
3875
3871
|
}
|
|
3876
3872
|
.np-bottom-sheet-v2-content:focus {
|
|
3877
3873
|
outline: none;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Meta, Canvas, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/ActionPrompt/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
Under the hood, `ActionPrompt` is marked as `role="region"` for a section of content that users might want to navigate to directly.
|
|
8
|
+
|
|
9
|
+
By default, it's labelled by the `media` asset and `title`, as well as described by the `description` if one is provided.
|
|
10
|
+
|
|
11
|
+
## Announcement Behaviour
|
|
12
|
+
|
|
13
|
+
`ActionPrompt` should be treated as a hint or suggestion as part of the product UX. Even the negative variant still serves as a non-critical message. Therefore, it shouldn't be announced via screen reader—neither assertively (`role="alert"`) nor politely (`role="status"`).
|
|
14
|
+
|
|
15
|
+
**Note:** For immediate user feedback, use [InfoPrompt](?path=/docs/prompts-infoprompt--docs) or [InlinePrompt](https://storybook.wise.design/?path=/docs/prompts-inlineprompt--docs) instead.
|
|
16
|
+
|
|
17
|
+
If you want to provide a custom label for screen readers, you can use the `aria-label` prop. Make sure to include all necessary information in it, because when provided, the default labelling will be removed.
|
|
18
|
+
|
|
19
|
+
<Source
|
|
20
|
+
dark
|
|
21
|
+
code={`
|
|
22
|
+
<ActionPrompt
|
|
23
|
+
aria-label="JFYI: Henry requested 30 USD for lunch"
|
|
24
|
+
media={{ avatar: { imgSrc: 'profile-photo.webp' } }}
|
|
25
|
+
title="Henry requested 30 USD"
|
|
26
|
+
description="Lunch date"
|
|
27
|
+
...
|
|
28
|
+
/>
|
|
29
|
+
`}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
## Media
|
|
33
|
+
|
|
34
|
+
You can use the `aria-hidden` attribute on media assets to hide them from screen readers if they're not providing any additional information. This is useful when the media is purely decorative or when its content is already conveyed through other means (e.g., text).
|
|
35
|
+
|
|
36
|
+
<Source
|
|
37
|
+
dark
|
|
38
|
+
code={`
|
|
39
|
+
<ActionPrompt
|
|
40
|
+
media={{
|
|
41
|
+
'aria-hidden': true,
|
|
42
|
+
avatar: { asset: <People /> },
|
|
43
|
+
}}
|
|
44
|
+
title="Sync contacts for a faster experience"
|
|
45
|
+
...
|
|
46
|
+
/>
|
|
47
|
+
`}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
You can also use `aria-label` on media assets to provide a custom label for screen readers.
|
|
51
|
+
|
|
52
|
+
<Source
|
|
53
|
+
dark
|
|
54
|
+
code={`
|
|
55
|
+
<ActionPrompt
|
|
56
|
+
media={{
|
|
57
|
+
'aria-label': 'Henry Adams photo',
|
|
58
|
+
avatar: { imgSrc: 'profile-photo.webp' },
|
|
59
|
+
}}
|
|
60
|
+
title="Henry requested 30 USD"
|
|
61
|
+
description="Lunch date"
|
|
62
|
+
...
|
|
63
|
+
/>
|
|
64
|
+
`}
|
|
65
|
+
/>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { ReactElement, useState } from 'react';
|
|
2
2
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
3
|
import { fn } from 'storybook/test';
|
|
4
|
-
import { Bank } from '@transferwise/icons';
|
|
4
|
+
import { Bank, Freeze, People } from '@transferwise/icons';
|
|
5
5
|
import { ActionPrompt } from './ActionPrompt';
|
|
6
6
|
import { lorem10 } from '../../test-utils';
|
|
7
|
+
import Body from '../../body';
|
|
8
|
+
import { action } from 'storybook/actions';
|
|
9
|
+
import Title from '../../title';
|
|
7
10
|
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
8
11
|
|
|
9
12
|
const meta: Meta<typeof ActionPrompt> = {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Freeze, People } from '@transferwise/icons';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import ActionPrompt from './ActionPrompt';
|
|
4
|
+
import { Body, Title } from '../..';
|
|
5
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Prompts/ActionPrompt/Tests',
|
|
9
|
+
component: ActionPrompt,
|
|
10
|
+
tags: ['!manifest', '!autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof ActionPrompt>;
|
|
14
|
+
|
|
15
|
+
export const VariousA11yFeatures: Story = {
|
|
16
|
+
name: 'Various a11y features',
|
|
17
|
+
render: () => {
|
|
18
|
+
return (
|
|
19
|
+
<Body>
|
|
20
|
+
<Title type="title-body">Neutral Prompt with Avatar Image and Custom label for media</Title>
|
|
21
|
+
<ActionPrompt
|
|
22
|
+
className="m-b-2"
|
|
23
|
+
sentiment="neutral"
|
|
24
|
+
media={{
|
|
25
|
+
'aria-label': 'Henry Profile Photo',
|
|
26
|
+
avatar: { imgSrc: '../../avatar-square-dude.webp' },
|
|
27
|
+
}}
|
|
28
|
+
title="Henry requested 30 USD"
|
|
29
|
+
description="Lunch date"
|
|
30
|
+
action={{
|
|
31
|
+
label: 'Sent',
|
|
32
|
+
onClick: () => {
|
|
33
|
+
action('send');
|
|
34
|
+
},
|
|
35
|
+
}}
|
|
36
|
+
actionSecondary={{
|
|
37
|
+
label: 'Decline',
|
|
38
|
+
onClick: () => {
|
|
39
|
+
action('decline');
|
|
40
|
+
},
|
|
41
|
+
}}
|
|
42
|
+
onDismiss={() => action('dismiss')}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<Title type="title-body">Warning Prompt</Title>
|
|
46
|
+
<ActionPrompt
|
|
47
|
+
className="m-b-2"
|
|
48
|
+
sentiment="warning"
|
|
49
|
+
media={{
|
|
50
|
+
'aria-label': 'Image of beautiful Business Wise Card',
|
|
51
|
+
imgSrc: '../../wise-card.svg',
|
|
52
|
+
}}
|
|
53
|
+
title="Your Wise Card expires soon"
|
|
54
|
+
description="Renew your card to keep spending"
|
|
55
|
+
action={{
|
|
56
|
+
label: 'Renew card',
|
|
57
|
+
onClick: () => {
|
|
58
|
+
action('renew');
|
|
59
|
+
},
|
|
60
|
+
}}
|
|
61
|
+
actionSecondary={{
|
|
62
|
+
label: 'Notify later',
|
|
63
|
+
onClick: () => {
|
|
64
|
+
action('notifyLater');
|
|
65
|
+
},
|
|
66
|
+
}}
|
|
67
|
+
onDismiss={() => action('dismiss')}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<Title type="title-body">Warning Prompt Avatar Icon</Title>
|
|
71
|
+
<ActionPrompt
|
|
72
|
+
className="m-b-2"
|
|
73
|
+
sentiment="warning"
|
|
74
|
+
media={{ avatar: { asset: <Freeze /> } }}
|
|
75
|
+
title="Your Wise Card expires soon"
|
|
76
|
+
description="Renew your card to keep spending"
|
|
77
|
+
action={{
|
|
78
|
+
label: 'Renew card',
|
|
79
|
+
onClick: () => {
|
|
80
|
+
action('renew');
|
|
81
|
+
},
|
|
82
|
+
}}
|
|
83
|
+
onDismiss={() => {
|
|
84
|
+
action('dismiss');
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<Title type="title-body">Proposition Prompt Avatar Icon</Title>
|
|
89
|
+
<ActionPrompt
|
|
90
|
+
className="m-b-2"
|
|
91
|
+
sentiment="proposition"
|
|
92
|
+
media={{ avatar: { asset: <People /> } }}
|
|
93
|
+
title="Sync contacts for a faster experience"
|
|
94
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
95
|
+
action={{
|
|
96
|
+
label: 'Sync contacts',
|
|
97
|
+
onClick: () => {
|
|
98
|
+
action('sync');
|
|
99
|
+
},
|
|
100
|
+
}}
|
|
101
|
+
onDismiss={() => {
|
|
102
|
+
action('dismiss');
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<Title type="title-body">Negative Prompt Avatar Icon + muted media</Title>
|
|
107
|
+
<ActionPrompt
|
|
108
|
+
className="m-b-2"
|
|
109
|
+
sentiment="negative"
|
|
110
|
+
media={{ avatar: { asset: <People /> }, 'aria-hidden': true }}
|
|
111
|
+
title="Sync contacts for a faster experience"
|
|
112
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
113
|
+
action={{
|
|
114
|
+
label: 'Sync contacts',
|
|
115
|
+
onClick: () => {
|
|
116
|
+
action('sync');
|
|
117
|
+
},
|
|
118
|
+
}}
|
|
119
|
+
onDismiss={() => {
|
|
120
|
+
action('dismiss');
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
<Title type="title-body">
|
|
125
|
+
Negative Prompt + override content with custom message via aria-label
|
|
126
|
+
</Title>
|
|
127
|
+
<ActionPrompt
|
|
128
|
+
className="m-b-2"
|
|
129
|
+
aria-label="hey customer, here is custom message"
|
|
130
|
+
sentiment="negative"
|
|
131
|
+
media={{ avatar: { asset: <People /> } }}
|
|
132
|
+
title="Sync contacts for a faster experience"
|
|
133
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
134
|
+
action={{
|
|
135
|
+
label: 'Sync contacts',
|
|
136
|
+
onClick: () => {
|
|
137
|
+
action('sync');
|
|
138
|
+
},
|
|
139
|
+
}}
|
|
140
|
+
onDismiss={() => {
|
|
141
|
+
action('dismiss');
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
</Body>
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
1
|
+
import { AriaAttributes, ReactNode, useId } from 'react';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
|
|
4
4
|
import StatusIcon from '../../statusIcon';
|
|
@@ -23,6 +23,7 @@ export type ActionPromptProps = {
|
|
|
23
23
|
badge?: Pick<BadgeAssetsProps, 'flagCode'>;
|
|
24
24
|
};
|
|
25
25
|
'aria-label'?: string;
|
|
26
|
+
'aria-hidden'?: boolean;
|
|
26
27
|
};
|
|
27
28
|
action: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
|
|
28
29
|
label: ButtonProps['children'];
|
|
@@ -30,6 +31,7 @@ export type ActionPromptProps = {
|
|
|
30
31
|
actionSecondary?: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
|
|
31
32
|
label: ButtonProps['children'];
|
|
32
33
|
};
|
|
34
|
+
'aria-label'?: AriaAttributes['aria-label'];
|
|
33
35
|
} & Pick<PrimitivePromptProps, 'id' | 'className' | 'data-testid' | 'sentiment' | 'onDismiss'>;
|
|
34
36
|
|
|
35
37
|
export const ActionPrompt = ({
|
|
@@ -37,19 +39,32 @@ export const ActionPrompt = ({
|
|
|
37
39
|
title,
|
|
38
40
|
description,
|
|
39
41
|
onDismiss,
|
|
40
|
-
media,
|
|
42
|
+
media = {},
|
|
41
43
|
action,
|
|
42
44
|
actionSecondary,
|
|
43
45
|
id,
|
|
44
46
|
className,
|
|
45
47
|
'data-testid': testId,
|
|
48
|
+
'aria-label': ariaLabel,
|
|
46
49
|
}: ActionPromptProps) => {
|
|
47
50
|
const isMobile = !useScreenSize(Breakpoint.MEDIUM);
|
|
48
51
|
|
|
52
|
+
const mediaId = useId();
|
|
53
|
+
const titleId = useId();
|
|
54
|
+
const descId = useId();
|
|
55
|
+
|
|
56
|
+
const ariaLabelledByIds = [
|
|
57
|
+
media['aria-hidden'] ? undefined : mediaId,
|
|
58
|
+
Boolean(ariaLabel) ? undefined : titleId,
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(' ');
|
|
62
|
+
|
|
49
63
|
const renderMedia = () => {
|
|
50
64
|
if (media?.imgSrc) {
|
|
51
65
|
return (
|
|
52
66
|
<Image
|
|
67
|
+
id={mediaId}
|
|
53
68
|
src={media.imgSrc}
|
|
54
69
|
className="wds-action-prompt--media-image"
|
|
55
70
|
alt={media['aria-label'] ?? ''}
|
|
@@ -63,17 +78,34 @@ export const ActionPrompt = ({
|
|
|
63
78
|
? {}
|
|
64
79
|
: { status: sentiment };
|
|
65
80
|
return (
|
|
66
|
-
<AvatarView
|
|
81
|
+
<AvatarView
|
|
82
|
+
{...media.avatar}
|
|
83
|
+
badge={badge}
|
|
84
|
+
aria-label={media['aria-label']}
|
|
85
|
+
aria-hidden={media['aria-hidden']}
|
|
86
|
+
id={mediaId}
|
|
87
|
+
size={48}
|
|
88
|
+
>
|
|
67
89
|
{media.avatar.asset}
|
|
68
90
|
</AvatarView>
|
|
69
91
|
);
|
|
70
92
|
}
|
|
71
93
|
return sentiment === 'proposition' ? (
|
|
72
|
-
<AvatarView
|
|
94
|
+
<AvatarView
|
|
95
|
+
id={mediaId}
|
|
96
|
+
size={48}
|
|
97
|
+
aria-label={media['aria-label']}
|
|
98
|
+
aria-hidden={media['aria-hidden']}
|
|
99
|
+
>
|
|
73
100
|
<GiftBox />
|
|
74
101
|
</AvatarView>
|
|
75
102
|
) : (
|
|
76
|
-
<StatusIcon
|
|
103
|
+
<StatusIcon
|
|
104
|
+
id={mediaId}
|
|
105
|
+
size={48}
|
|
106
|
+
sentiment={sentiment}
|
|
107
|
+
iconLabel={Boolean(media['aria-hidden']) ? null : media['aria-label']}
|
|
108
|
+
/>
|
|
77
109
|
);
|
|
78
110
|
};
|
|
79
111
|
|
|
@@ -117,10 +149,19 @@ export const ActionPrompt = ({
|
|
|
117
149
|
</>
|
|
118
150
|
}
|
|
119
151
|
onDismiss={onDismiss}
|
|
152
|
+
role="region"
|
|
153
|
+
{...(Boolean(ariaLabel)
|
|
154
|
+
? { 'aria-label': ariaLabel }
|
|
155
|
+
: {
|
|
156
|
+
'aria-labelledby': ariaLabelledByIds,
|
|
157
|
+
'aria-describedby': descId,
|
|
158
|
+
})}
|
|
120
159
|
>
|
|
121
160
|
<div className={clsx('d-flex', 'flex-column', 'justify-content-center')}>
|
|
122
|
-
<Body type={Typography.BODY_LARGE_BOLD}>
|
|
123
|
-
|
|
161
|
+
<Body id={titleId} type={Typography.BODY_LARGE_BOLD}>
|
|
162
|
+
{title}
|
|
163
|
+
</Body>
|
|
164
|
+
{description && <Body id={descId}>{description}</Body>}
|
|
124
165
|
</div>
|
|
125
166
|
</PrimitivePrompt>
|
|
126
167
|
);
|