@transferwise/components 0.0.0-experimental-e4f5e73 → 0.0.0-experimental-c718986
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.mjs +1 -1
- package/build/common/liveRegion/LiveRegion.js +42 -0
- package/build/common/liveRegion/LiveRegion.js.map +1 -0
- package/build/common/liveRegion/LiveRegion.mjs +40 -0
- package/build/common/liveRegion/LiveRegion.mjs.map +1 -0
- package/build/flowNavigation/FlowNavigation.js +1 -1
- package/build/flowNavigation/FlowNavigation.mjs +1 -1
- package/build/overlayHeader/OverlayHeader.js +1 -1
- package/build/overlayHeader/OverlayHeader.mjs +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.js +35 -29
- package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.mjs +35 -29
- package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
- package/build/types/common/index.d.ts +2 -0
- package/build/types/common/index.d.ts.map +1 -1
- package/build/types/common/liveRegion/LiveRegion.d.ts +23 -0
- package/build/types/common/liveRegion/LiveRegion.d.ts.map +1 -0
- package/build/types/common/liveRegion/index.d.ts +3 -0
- package/build/types/common/liveRegion/index.d.ts.map +1 -0
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +11 -2
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/index.ts +2 -0
- package/src/common/liveRegion/LiveRegion.test.tsx +56 -0
- package/src/common/liveRegion/LiveRegion.tsx +49 -0
- package/src/common/liveRegion/index.ts +2 -0
- package/src/listItem/ListItem.less +0 -2
- package/src/listItem/_stories/ListItem.story.tsx +2 -3
- package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +119 -0
- package/src/prompt/InfoPrompt/InfoPrompt.tsx +47 -34
- package/src/listItem/_stories/Breakpoints/ListItem.noMedia.test.story.tsx +0 -62
- package/src/listItem/_stories/Breakpoints/ListItem.sideMedia.test.story.tsx +0 -62
- package/src/listItem/_stories/Breakpoints/ListItem.stackedMedia.test.story.tsx +0 -62
package/build/alert/Alert.js
CHANGED
|
@@ -26,13 +26,13 @@ require('../common/propsValues/markdownNodeType.js');
|
|
|
26
26
|
require('../common/fileType.js');
|
|
27
27
|
var constants = require('../common/constants.js');
|
|
28
28
|
var CloseButton = require('../common/closeButton/CloseButton.js');
|
|
29
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
29
30
|
var StatusIcon = require('../statusIcon/StatusIcon.js');
|
|
30
31
|
var Title = require('../title/Title.js');
|
|
31
32
|
var logActionRequired = require('../utilities/logActionRequired.js');
|
|
32
33
|
var InlineMarkdown = require('./inlineMarkdown/InlineMarkdown.js');
|
|
33
34
|
var Button_resolver = require('../button/Button.resolver.js');
|
|
34
35
|
var Link = require('../link/Link.js');
|
|
35
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
36
36
|
|
|
37
37
|
exports.AlertArrowPosition = void 0;
|
|
38
38
|
(function (AlertArrowPosition) {
|
package/build/alert/Alert.mjs
CHANGED
|
@@ -22,13 +22,13 @@ import '../common/propsValues/markdownNodeType.mjs';
|
|
|
22
22
|
import '../common/fileType.mjs';
|
|
23
23
|
import { WDS_LIVE_REGION_DELAY_MS } from '../common/constants.mjs';
|
|
24
24
|
import { CloseButton } from '../common/closeButton/CloseButton.mjs';
|
|
25
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
25
26
|
import StatusIcon from '../statusIcon/StatusIcon.mjs';
|
|
26
27
|
import Title from '../title/Title.mjs';
|
|
27
28
|
import { logActionRequired } from '../utilities/logActionRequired.mjs';
|
|
28
29
|
import InlineMarkdown from './inlineMarkdown/InlineMarkdown.mjs';
|
|
29
30
|
import Button from '../button/Button.resolver.mjs';
|
|
30
31
|
import Link from '../link/Link.mjs';
|
|
31
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
32
32
|
|
|
33
33
|
var AlertArrowPosition;
|
|
34
34
|
(function (AlertArrowPosition) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
|
|
5
|
+
const ARIA_LIVE_ROLE_MAP = {
|
|
6
|
+
assertive: 'alert',
|
|
7
|
+
polite: 'status'
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Renders an ARIA live region with the correct implicit role.
|
|
11
|
+
*
|
|
12
|
+
* - `aria-live="polite"` → `role="status"`
|
|
13
|
+
* - `aria-live="assertive"` → `role="alert"`
|
|
14
|
+
* - `aria-live="off"` → no live region
|
|
15
|
+
*
|
|
16
|
+
* The `role` prop is intentionally excluded from the public API
|
|
17
|
+
* to prevent mismatches between `aria-live` and `role`.
|
|
18
|
+
*/
|
|
19
|
+
const LiveRegion = ({
|
|
20
|
+
'aria-live': ariaLive,
|
|
21
|
+
children,
|
|
22
|
+
...props
|
|
23
|
+
}) => {
|
|
24
|
+
if (ariaLive === 'off') {
|
|
25
|
+
return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
|
|
26
|
+
children: children
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
30
|
+
role: ARIA_LIVE_ROLE_MAP[ariaLive],
|
|
31
|
+
"aria-live": ariaLive,
|
|
32
|
+
"aria-atomic": "true",
|
|
33
|
+
style: {
|
|
34
|
+
display: 'contents'
|
|
35
|
+
},
|
|
36
|
+
...props,
|
|
37
|
+
children: children
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
exports.LiveRegion = LiveRegion;
|
|
42
|
+
//# sourceMappingURL=LiveRegion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveRegion.js","sources":["../../../src/common/liveRegion/LiveRegion.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react';\n\nconst ARIA_LIVE_ROLE_MAP = {\n assertive: 'alert',\n polite: 'status',\n} as const;\n\nexport type AriaLive = 'off' | 'polite' | 'assertive';\n\nexport interface LiveRegionProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'role' | 'aria-live' | 'aria-atomic'\n> {\n /**\n * Determines urgency: 'assertive' interrupts, 'polite' waits for idle, 'off' disables live region.\n */\n 'aria-live': AriaLive;\n /** Test ID for testing tools */\n 'data-testid'?: string;\n children?: ReactNode;\n}\n\n/**\n * Renders an ARIA live region with the correct implicit role.\n *\n * - `aria-live=\"polite\"` → `role=\"status\"`\n * - `aria-live=\"assertive\"` → `role=\"alert\"`\n * - `aria-live=\"off\"` → no live region\n *\n * The `role` prop is intentionally excluded from the public API\n * to prevent mismatches between `aria-live` and `role`.\n */\nexport const LiveRegion = ({ 'aria-live': ariaLive, children, ...props }: LiveRegionProps) => {\n if (ariaLive === 'off') {\n return <>{children}</>;\n }\n\n return (\n <div\n role={ARIA_LIVE_ROLE_MAP[ariaLive]}\n aria-live={ariaLive}\n aria-atomic=\"true\"\n style={{ display: 'contents' }}\n {...props}\n >\n {children}\n </div>\n );\n};\n"],"names":["ARIA_LIVE_ROLE_MAP","assertive","polite","LiveRegion","ariaLive","children","props","_jsx","_Fragment","role","style","display"],"mappings":";;;;AAEA,MAAMA,kBAAkB,GAAG;AACzBC,EAAAA,SAAS,EAAE,OAAO;AAClBC,EAAAA,MAAM,EAAE;CACA;AAiBV;;;;;;;;;AASG;AACI,MAAMC,UAAU,GAAGA,CAAC;AAAE,EAAA,WAAW,EAAEC,QAAQ;EAAEC,QAAQ;EAAE,GAAGC;AAAK,CAAmB,KAAI;EAC3F,IAAIF,QAAQ,KAAK,KAAK,EAAE;IACtB,oBAAOG,cAAA,CAAAC,mBAAA,EAAA;AAAAH,MAAAA,QAAA,EAAGA;AAAQ,MAAI;AACxB,EAAA;AAEA,EAAA,oBACEE,cAAA,CAAA,KAAA,EAAA;AACEE,IAAAA,IAAI,EAAET,kBAAkB,CAACI,QAAQ,CAAE;AACnC,IAAA,WAAA,EAAWA,QAAS;AACpB,IAAA,aAAA,EAAY,MAAM;AAClBM,IAAAA,KAAK,EAAE;AAAEC,MAAAA,OAAO,EAAE;KAAa;AAAA,IAAA,GAC3BL,KAAK;AAAAD,IAAAA,QAAA,EAERA;AAAQ,GACN,CAAC;AAEV;;;;"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
const ARIA_LIVE_ROLE_MAP = {
|
|
4
|
+
assertive: 'alert',
|
|
5
|
+
polite: 'status'
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Renders an ARIA live region with the correct implicit role.
|
|
9
|
+
*
|
|
10
|
+
* - `aria-live="polite"` → `role="status"`
|
|
11
|
+
* - `aria-live="assertive"` → `role="alert"`
|
|
12
|
+
* - `aria-live="off"` → no live region
|
|
13
|
+
*
|
|
14
|
+
* The `role` prop is intentionally excluded from the public API
|
|
15
|
+
* to prevent mismatches between `aria-live` and `role`.
|
|
16
|
+
*/
|
|
17
|
+
const LiveRegion = ({
|
|
18
|
+
'aria-live': ariaLive,
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}) => {
|
|
22
|
+
if (ariaLive === 'off') {
|
|
23
|
+
return /*#__PURE__*/jsx(Fragment, {
|
|
24
|
+
children: children
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return /*#__PURE__*/jsx("div", {
|
|
28
|
+
role: ARIA_LIVE_ROLE_MAP[ariaLive],
|
|
29
|
+
"aria-live": ariaLive,
|
|
30
|
+
"aria-atomic": "true",
|
|
31
|
+
style: {
|
|
32
|
+
display: 'contents'
|
|
33
|
+
},
|
|
34
|
+
...props,
|
|
35
|
+
children: children
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { LiveRegion };
|
|
40
|
+
//# sourceMappingURL=LiveRegion.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveRegion.mjs","sources":["../../../src/common/liveRegion/LiveRegion.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react';\n\nconst ARIA_LIVE_ROLE_MAP = {\n assertive: 'alert',\n polite: 'status',\n} as const;\n\nexport type AriaLive = 'off' | 'polite' | 'assertive';\n\nexport interface LiveRegionProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'role' | 'aria-live' | 'aria-atomic'\n> {\n /**\n * Determines urgency: 'assertive' interrupts, 'polite' waits for idle, 'off' disables live region.\n */\n 'aria-live': AriaLive;\n /** Test ID for testing tools */\n 'data-testid'?: string;\n children?: ReactNode;\n}\n\n/**\n * Renders an ARIA live region with the correct implicit role.\n *\n * - `aria-live=\"polite\"` → `role=\"status\"`\n * - `aria-live=\"assertive\"` → `role=\"alert\"`\n * - `aria-live=\"off\"` → no live region\n *\n * The `role` prop is intentionally excluded from the public API\n * to prevent mismatches between `aria-live` and `role`.\n */\nexport const LiveRegion = ({ 'aria-live': ariaLive, children, ...props }: LiveRegionProps) => {\n if (ariaLive === 'off') {\n return <>{children}</>;\n }\n\n return (\n <div\n role={ARIA_LIVE_ROLE_MAP[ariaLive]}\n aria-live={ariaLive}\n aria-atomic=\"true\"\n style={{ display: 'contents' }}\n {...props}\n >\n {children}\n </div>\n );\n};\n"],"names":["ARIA_LIVE_ROLE_MAP","assertive","polite","LiveRegion","ariaLive","children","props","_jsx","_Fragment","role","style","display"],"mappings":";;AAEA,MAAMA,kBAAkB,GAAG;AACzBC,EAAAA,SAAS,EAAE,OAAO;AAClBC,EAAAA,MAAM,EAAE;CACA;AAiBV;;;;;;;;;AASG;AACI,MAAMC,UAAU,GAAGA,CAAC;AAAE,EAAA,WAAW,EAAEC,QAAQ;EAAEC,QAAQ;EAAE,GAAGC;AAAK,CAAmB,KAAI;EAC3F,IAAIF,QAAQ,KAAK,KAAK,EAAE;IACtB,oBAAOG,GAAA,CAAAC,QAAA,EAAA;AAAAH,MAAAA,QAAA,EAAGA;AAAQ,MAAI;AACxB,EAAA;AAEA,EAAA,oBACEE,GAAA,CAAA,KAAA,EAAA;AACEE,IAAAA,IAAI,EAAET,kBAAkB,CAACI,QAAQ,CAAE;AACnC,IAAA,WAAA,EAAWA,QAAS;AACpB,IAAA,aAAA,EAAY,MAAM;AAClBM,IAAAA,KAAK,EAAE;AAAEC,MAAAA,OAAO,EAAE;KAAa;AAAA,IAAA,GAC3BL,KAAK;AAAAD,IAAAA,QAAA,EAERA;AAAQ,GACN,CAAC;AAEV;;;;"}
|
|
@@ -24,6 +24,7 @@ require('../common/propsValues/scroll.js');
|
|
|
24
24
|
require('../common/propsValues/markdownNodeType.js');
|
|
25
25
|
require('../common/fileType.js');
|
|
26
26
|
var CloseButton = require('../common/closeButton/CloseButton.js');
|
|
27
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
27
28
|
var FlowHeader = require('../common/flowHeader/FlowHeader.js');
|
|
28
29
|
var Logo = require('../logo/Logo.js');
|
|
29
30
|
var Stepper = require('../stepper/Stepper.js');
|
|
@@ -32,7 +33,6 @@ var FlowNavigation_messages = require('./FlowNavigation.messages.js');
|
|
|
32
33
|
var AnimatedLabel = require('./animatedLabel/AnimatedLabel.js');
|
|
33
34
|
var IconButton = require('../iconButton/IconButton.js');
|
|
34
35
|
var icons = require('@transferwise/icons');
|
|
35
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
36
36
|
|
|
37
37
|
const FlowNavigation = ({
|
|
38
38
|
activeStep = 0,
|
|
@@ -20,6 +20,7 @@ import '../common/propsValues/scroll.mjs';
|
|
|
20
20
|
import '../common/propsValues/markdownNodeType.mjs';
|
|
21
21
|
import '../common/fileType.mjs';
|
|
22
22
|
import { CloseButton } from '../common/closeButton/CloseButton.mjs';
|
|
23
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
23
24
|
import FlowHeader from '../common/flowHeader/FlowHeader.mjs';
|
|
24
25
|
import Logo from '../logo/Logo.mjs';
|
|
25
26
|
import Stepper from '../stepper/Stepper.mjs';
|
|
@@ -28,7 +29,6 @@ import messages from './FlowNavigation.messages.mjs';
|
|
|
28
29
|
import AnimatedLabel from './animatedLabel/AnimatedLabel.mjs';
|
|
29
30
|
import IconButton from '../iconButton/IconButton.mjs';
|
|
30
31
|
import { ArrowLeft } from '@transferwise/icons';
|
|
31
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
32
32
|
|
|
33
33
|
const FlowNavigation = ({
|
|
34
34
|
activeStep = 0,
|
|
@@ -23,9 +23,9 @@ require('../common/propsValues/scroll.js');
|
|
|
23
23
|
require('../common/propsValues/markdownNodeType.js');
|
|
24
24
|
require('../common/fileType.js');
|
|
25
25
|
var CloseButton = require('../common/closeButton/CloseButton.js');
|
|
26
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
26
27
|
var FlowHeader = require('../common/flowHeader/FlowHeader.js');
|
|
27
28
|
var Logo = require('../logo/Logo.js');
|
|
28
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
29
29
|
|
|
30
30
|
const defaultLogo = /*#__PURE__*/jsxRuntime.jsx(Logo.default, {});
|
|
31
31
|
function OverlayHeader({
|
|
@@ -19,9 +19,9 @@ import '../common/propsValues/scroll.mjs';
|
|
|
19
19
|
import '../common/propsValues/markdownNodeType.mjs';
|
|
20
20
|
import '../common/fileType.mjs';
|
|
21
21
|
import { CloseButton } from '../common/closeButton/CloseButton.mjs';
|
|
22
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
22
23
|
import FlowHeader from '../common/flowHeader/FlowHeader.mjs';
|
|
23
24
|
import Logo from '../logo/Logo.mjs';
|
|
24
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
25
25
|
|
|
26
26
|
const defaultLogo = /*#__PURE__*/jsx(Logo, {});
|
|
27
27
|
function OverlayHeader({
|
|
@@ -25,6 +25,7 @@ var clsx = require('clsx');
|
|
|
25
25
|
require('react-intl');
|
|
26
26
|
require('../../common/closeButton/CloseButton.messages.js');
|
|
27
27
|
var jsxRuntime = require('react/jsx-runtime');
|
|
28
|
+
var LiveRegion = require('../../common/liveRegion/LiveRegion.js');
|
|
28
29
|
var StatusIcon = require('../../statusIcon/StatusIcon.js');
|
|
29
30
|
var Body = require('../../body/Body.js');
|
|
30
31
|
var Link = require('../../link/Link.js');
|
|
@@ -38,6 +39,7 @@ const InfoPrompt = ({
|
|
|
38
39
|
title,
|
|
39
40
|
description,
|
|
40
41
|
className,
|
|
42
|
+
'aria-live': ariaLive = 'polite',
|
|
41
43
|
'data-testid': dataTestId,
|
|
42
44
|
...restProps
|
|
43
45
|
}) => {
|
|
@@ -76,35 +78,39 @@ const InfoPrompt = ({
|
|
|
76
78
|
sentiment: statusIconSentiment
|
|
77
79
|
});
|
|
78
80
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
// Render content directly in LiveRegion
|
|
82
|
+
return /*#__PURE__*/jsxRuntime.jsx(LiveRegion.LiveRegion, {
|
|
83
|
+
"aria-live": ariaLive,
|
|
84
|
+
children: /*#__PURE__*/jsxRuntime.jsx(PrimitivePrompt.PrimitivePrompt, {
|
|
85
|
+
sentiment: sentiment$1,
|
|
86
|
+
media: renderMedia(),
|
|
87
|
+
"data-testid": dataTestId,
|
|
88
|
+
className: clsx.clsx('wds-info-prompt', className),
|
|
89
|
+
...restProps,
|
|
90
|
+
onTouchStart: handleTouchStart,
|
|
91
|
+
onTouchEnd: handleTouchEnd,
|
|
92
|
+
onTouchMove: handleTouchMove,
|
|
93
|
+
onDismiss: onDismiss,
|
|
94
|
+
children: /*#__PURE__*/jsxRuntime.jsxs("div", {
|
|
95
|
+
className: "wds-info-prompt__content",
|
|
96
|
+
children: [title && /*#__PURE__*/jsxRuntime.jsx(Body.default, {
|
|
97
|
+
className: "wds-info-prompt__title",
|
|
98
|
+
type: typography.Typography.BODY_LARGE_BOLD,
|
|
99
|
+
as: "span",
|
|
100
|
+
children: title
|
|
101
|
+
}), /*#__PURE__*/jsxRuntime.jsx(Body.default, {
|
|
102
|
+
as: "span",
|
|
103
|
+
className: "wds-info-prompt__description",
|
|
104
|
+
children: description
|
|
105
|
+
}), action && /*#__PURE__*/jsxRuntime.jsx(Link.default, {
|
|
106
|
+
href: action.href,
|
|
107
|
+
target: action.target,
|
|
108
|
+
type: typography.Typography.LINK_DEFAULT,
|
|
109
|
+
className: "wds-info-prompt__action",
|
|
110
|
+
onClick: action.onClick,
|
|
111
|
+
children: action.label
|
|
112
|
+
})]
|
|
113
|
+
})
|
|
108
114
|
})
|
|
109
115
|
});
|
|
110
116
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfoPrompt.js","sources":["../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"sourcesContent":["import { HTMLAttributes, ReactNode, useState } from 'react';\nimport { Sentiment, Typography } from '../../common';\nimport { GiftBox } from '@transferwise/icons';\nimport type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';\nimport StatusIcon from '../../statusIcon';\nimport { clsx } from 'clsx';\nimport Body from '../../body';\nimport Link, { LinkProps } from '../../link';\nimport { PrimitivePrompt, PrimitivePromptProps } from '../PrimitivePrompt';\n\nexport type InfoPromptAction = Pick<LinkProps, 'href' | 'target' | 'onClick'> & {\n /**\n * The label text for the action link\n */\n label: string;\n};\n\nexport type InfoPromptMedia = {\n /**\n * The icon/image asset to display.\n * The asset should include its own accessibility attributes (e.g. title, aria-label) if it conveys meaning.\n */\n asset: ReactNode;\n};\n\nexport type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &\n Pick<PrimitivePromptProps, 'data-testid'> & {\n /**\n * The sentiment determines the colour scheme\n * @default 'neutral'\n */\n sentiment?: SurfaceSentiment;\n /**\n * Handler called when the close button is clicked.\n * If not provided, the close button is hidden.\n */\n onDismiss?: () => void;\n /**\n * Custom media to override the default status icon.\n * Success and proposition sentiments support 2 status variations: standard checkmark & confetti.\n */\n media?: InfoPromptMedia;\n /**\n * Action link to be displayed below the description\n */\n action?: InfoPromptAction;\n /**\n * Title content for the prompt\n */\n title?: string;\n /**\n * Description text for the prompt (required)\n */\n description: string;\n className?: string;\n };\n\n/**\n * InfoPrompt displays important contextual messages to users within a screen.\n * It provides a visually distinct way to communicate information, warnings, errors,\n * or positive feedback with optional actions and dismissal capabilities.\n *\n * Use this component to replace the deprecated Alert component.\n *\n * Guidance can be found in the [design system](https://wise.design/components/info-prompt).\n */\nexport const InfoPrompt = ({\n sentiment = 'neutral',\n onDismiss,\n media,\n action,\n title,\n description,\n className,\n 'data-testid': dataTestId,\n ...restProps\n}: InfoPromptProps) => {\n const [shouldFire, setShouldFire] = useState<boolean>();\n const statusIconSentiment =\n sentiment === 'success'\n ? Sentiment.POSITIVE\n : (sentiment as Exclude<SurfaceSentiment, 'proposition'>);\n\n const handleTouchStart = () => {\n setShouldFire(true);\n };\n\n const handleTouchEnd = () => {\n if (shouldFire && action?.href) {\n if (action.target === '_blank') {\n window.top?.open(action.href, '_blank', 'noopener, noreferrer');\n } else {\n window.top?.location.assign(action.href);\n }\n }\n setShouldFire(false);\n };\n\n const handleTouchMove = () => {\n setShouldFire(false);\n };\n\n const renderMedia = () => {\n if (media) {\n return <span className=\"wds-info-prompt__media\">{media.asset}</span>;\n }\n\n if (sentiment === 'proposition') {\n return <GiftBox size={24} />;\n }\n\n return <StatusIcon size={24} sentiment={statusIconSentiment} />;\n };\n\n return (\n <PrimitivePrompt\n
|
|
1
|
+
{"version":3,"file":"InfoPrompt.js","sources":["../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"sourcesContent":["import { HTMLAttributes, ReactNode, useState } from 'react';\nimport { LiveRegion, Sentiment, Typography } from '../../common';\nimport type { AriaLive } from '../../common';\nimport { GiftBox } from '@transferwise/icons';\nimport type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';\nimport StatusIcon from '../../statusIcon';\nimport { clsx } from 'clsx';\nimport Body from '../../body';\nimport Link, { LinkProps } from '../../link';\nimport { PrimitivePrompt, PrimitivePromptProps } from '../PrimitivePrompt';\n\nexport type InfoPromptAction = Pick<LinkProps, 'href' | 'target' | 'onClick'> & {\n /**\n * The label text for the action link\n */\n label: string;\n};\n\nexport type InfoPromptMedia = {\n /**\n * The icon/image asset to display.\n * The asset should include its own accessibility attributes (e.g. title, aria-label) if it conveys meaning.\n */\n asset: ReactNode;\n};\n\nexport type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'aria-live' | 'role'> &\n Pick<PrimitivePromptProps, 'data-testid'> & {\n /**\n * The sentiment determines the colour scheme\n * @default 'neutral'\n */\n sentiment?: SurfaceSentiment;\n /**\n * Handler called when the close button is clicked.\n * If not provided, the close button is hidden.\n */\n onDismiss?: () => void;\n /**\n * Custom media to override the default status icon.\n * Success and proposition sentiments support 2 status variations: standard checkmark & confetti.\n */\n media?: InfoPromptMedia;\n /**\n * Action link to be displayed below the description\n */\n action?: InfoPromptAction;\n /**\n * Title content for the prompt\n */\n title?: string;\n /**\n * Description text for the prompt (required)\n */\n description: string;\n className?: string;\n /**\n * Sets the ARIA live region politeness level.\n * - `'polite'` — announced after the current speech (default)\n * - `'assertive'` — interrupts the current speech immediately\n * - `'off'` — disables the live region entirely\n * @default 'polite'\n */\n 'aria-live'?: AriaLive;\n };\n\n/**\n * InfoPrompt displays important contextual messages to users within a screen.\n * It provides a visually distinct way to communicate information, warnings, errors,\n * or positive feedback with optional actions and dismissal capabilities.\n *\n * Use this component to replace the deprecated Alert component.\n *\n * Guidance can be found in the [design system](https://wise.design/components/info-prompt).\n */\nexport const InfoPrompt = ({\n sentiment = 'neutral',\n onDismiss,\n media,\n action,\n title,\n description,\n className,\n 'aria-live': ariaLive = 'polite',\n 'data-testid': dataTestId,\n ...restProps\n}: InfoPromptProps) => {\n const [shouldFire, setShouldFire] = useState<boolean>();\n const statusIconSentiment =\n sentiment === 'success'\n ? Sentiment.POSITIVE\n : (sentiment as Exclude<SurfaceSentiment, 'proposition'>);\n\n const handleTouchStart = () => {\n setShouldFire(true);\n };\n\n const handleTouchEnd = () => {\n if (shouldFire && action?.href) {\n if (action.target === '_blank') {\n window.top?.open(action.href, '_blank', 'noopener, noreferrer');\n } else {\n window.top?.location.assign(action.href);\n }\n }\n setShouldFire(false);\n };\n\n const handleTouchMove = () => {\n setShouldFire(false);\n };\n\n const renderMedia = () => {\n if (media) {\n return <span className=\"wds-info-prompt__media\">{media.asset}</span>;\n }\n\n if (sentiment === 'proposition') {\n return <GiftBox size={24} />;\n }\n\n return <StatusIcon size={24} sentiment={statusIconSentiment} />;\n };\n\n // Render content directly in LiveRegion\n return (\n <LiveRegion aria-live={ariaLive}>\n <PrimitivePrompt\n sentiment={sentiment}\n media={renderMedia()}\n data-testid={dataTestId}\n className={clsx('wds-info-prompt', className)}\n {...restProps}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n onTouchMove={handleTouchMove}\n onDismiss={onDismiss}\n >\n <div className=\"wds-info-prompt__content\">\n {title && (\n <Body className=\"wds-info-prompt__title\" type={Typography.BODY_LARGE_BOLD} as=\"span\">\n {title}\n </Body>\n )}\n <Body as=\"span\" className=\"wds-info-prompt__description\">\n {description}\n </Body>\n {action && (\n <Link\n href={action.href}\n target={action.target}\n type={Typography.LINK_DEFAULT}\n className=\"wds-info-prompt__action\"\n onClick={action.onClick}\n >\n {action.label}\n </Link>\n )}\n </div>\n </PrimitivePrompt>\n </LiveRegion>\n );\n};\n"],"names":["InfoPrompt","sentiment","onDismiss","media","action","title","description","className","ariaLive","dataTestId","restProps","shouldFire","setShouldFire","useState","statusIconSentiment","Sentiment","POSITIVE","handleTouchStart","handleTouchEnd","href","target","window","top","open","location","assign","handleTouchMove","renderMedia","_jsx","children","asset","GiftBox","size","StatusIcon","LiveRegion","PrimitivePrompt","clsx","onTouchStart","onTouchEnd","onTouchMove","_jsxs","Body","type","Typography","BODY_LARGE_BOLD","as","Link","LINK_DEFAULT","onClick","label"],"mappings":"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"}
|
|
@@ -23,6 +23,7 @@ import { clsx } from 'clsx';
|
|
|
23
23
|
import 'react-intl';
|
|
24
24
|
import '../../common/closeButton/CloseButton.messages.mjs';
|
|
25
25
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
26
|
+
import { LiveRegion } from '../../common/liveRegion/LiveRegion.mjs';
|
|
26
27
|
import StatusIcon from '../../statusIcon/StatusIcon.mjs';
|
|
27
28
|
import Body from '../../body/Body.mjs';
|
|
28
29
|
import Link from '../../link/Link.mjs';
|
|
@@ -36,6 +37,7 @@ const InfoPrompt = ({
|
|
|
36
37
|
title,
|
|
37
38
|
description,
|
|
38
39
|
className,
|
|
40
|
+
'aria-live': ariaLive = 'polite',
|
|
39
41
|
'data-testid': dataTestId,
|
|
40
42
|
...restProps
|
|
41
43
|
}) => {
|
|
@@ -74,35 +76,39 @@ const InfoPrompt = ({
|
|
|
74
76
|
sentiment: statusIconSentiment
|
|
75
77
|
});
|
|
76
78
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
79
|
+
// Render content directly in LiveRegion
|
|
80
|
+
return /*#__PURE__*/jsx(LiveRegion, {
|
|
81
|
+
"aria-live": ariaLive,
|
|
82
|
+
children: /*#__PURE__*/jsx(PrimitivePrompt, {
|
|
83
|
+
sentiment: sentiment,
|
|
84
|
+
media: renderMedia(),
|
|
85
|
+
"data-testid": dataTestId,
|
|
86
|
+
className: clsx('wds-info-prompt', className),
|
|
87
|
+
...restProps,
|
|
88
|
+
onTouchStart: handleTouchStart,
|
|
89
|
+
onTouchEnd: handleTouchEnd,
|
|
90
|
+
onTouchMove: handleTouchMove,
|
|
91
|
+
onDismiss: onDismiss,
|
|
92
|
+
children: /*#__PURE__*/jsxs("div", {
|
|
93
|
+
className: "wds-info-prompt__content",
|
|
94
|
+
children: [title && /*#__PURE__*/jsx(Body, {
|
|
95
|
+
className: "wds-info-prompt__title",
|
|
96
|
+
type: Typography.BODY_LARGE_BOLD,
|
|
97
|
+
as: "span",
|
|
98
|
+
children: title
|
|
99
|
+
}), /*#__PURE__*/jsx(Body, {
|
|
100
|
+
as: "span",
|
|
101
|
+
className: "wds-info-prompt__description",
|
|
102
|
+
children: description
|
|
103
|
+
}), action && /*#__PURE__*/jsx(Link, {
|
|
104
|
+
href: action.href,
|
|
105
|
+
target: action.target,
|
|
106
|
+
type: Typography.LINK_DEFAULT,
|
|
107
|
+
className: "wds-info-prompt__action",
|
|
108
|
+
onClick: action.onClick,
|
|
109
|
+
children: action.label
|
|
110
|
+
})]
|
|
111
|
+
})
|
|
106
112
|
})
|
|
107
113
|
});
|
|
108
114
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfoPrompt.mjs","sources":["../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"sourcesContent":["import { HTMLAttributes, ReactNode, useState } from 'react';\nimport { Sentiment, Typography } from '../../common';\nimport { GiftBox } from '@transferwise/icons';\nimport type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';\nimport StatusIcon from '../../statusIcon';\nimport { clsx } from 'clsx';\nimport Body from '../../body';\nimport Link, { LinkProps } from '../../link';\nimport { PrimitivePrompt, PrimitivePromptProps } from '../PrimitivePrompt';\n\nexport type InfoPromptAction = Pick<LinkProps, 'href' | 'target' | 'onClick'> & {\n /**\n * The label text for the action link\n */\n label: string;\n};\n\nexport type InfoPromptMedia = {\n /**\n * The icon/image asset to display.\n * The asset should include its own accessibility attributes (e.g. title, aria-label) if it conveys meaning.\n */\n asset: ReactNode;\n};\n\nexport type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &\n Pick<PrimitivePromptProps, 'data-testid'> & {\n /**\n * The sentiment determines the colour scheme\n * @default 'neutral'\n */\n sentiment?: SurfaceSentiment;\n /**\n * Handler called when the close button is clicked.\n * If not provided, the close button is hidden.\n */\n onDismiss?: () => void;\n /**\n * Custom media to override the default status icon.\n * Success and proposition sentiments support 2 status variations: standard checkmark & confetti.\n */\n media?: InfoPromptMedia;\n /**\n * Action link to be displayed below the description\n */\n action?: InfoPromptAction;\n /**\n * Title content for the prompt\n */\n title?: string;\n /**\n * Description text for the prompt (required)\n */\n description: string;\n className?: string;\n };\n\n/**\n * InfoPrompt displays important contextual messages to users within a screen.\n * It provides a visually distinct way to communicate information, warnings, errors,\n * or positive feedback with optional actions and dismissal capabilities.\n *\n * Use this component to replace the deprecated Alert component.\n *\n * Guidance can be found in the [design system](https://wise.design/components/info-prompt).\n */\nexport const InfoPrompt = ({\n sentiment = 'neutral',\n onDismiss,\n media,\n action,\n title,\n description,\n className,\n 'data-testid': dataTestId,\n ...restProps\n}: InfoPromptProps) => {\n const [shouldFire, setShouldFire] = useState<boolean>();\n const statusIconSentiment =\n sentiment === 'success'\n ? Sentiment.POSITIVE\n : (sentiment as Exclude<SurfaceSentiment, 'proposition'>);\n\n const handleTouchStart = () => {\n setShouldFire(true);\n };\n\n const handleTouchEnd = () => {\n if (shouldFire && action?.href) {\n if (action.target === '_blank') {\n window.top?.open(action.href, '_blank', 'noopener, noreferrer');\n } else {\n window.top?.location.assign(action.href);\n }\n }\n setShouldFire(false);\n };\n\n const handleTouchMove = () => {\n setShouldFire(false);\n };\n\n const renderMedia = () => {\n if (media) {\n return <span className=\"wds-info-prompt__media\">{media.asset}</span>;\n }\n\n if (sentiment === 'proposition') {\n return <GiftBox size={24} />;\n }\n\n return <StatusIcon size={24} sentiment={statusIconSentiment} />;\n };\n\n return (\n <PrimitivePrompt\n
|
|
1
|
+
{"version":3,"file":"InfoPrompt.mjs","sources":["../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"sourcesContent":["import { HTMLAttributes, ReactNode, useState } from 'react';\nimport { LiveRegion, Sentiment, Typography } from '../../common';\nimport type { AriaLive } from '../../common';\nimport { GiftBox } from '@transferwise/icons';\nimport type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';\nimport StatusIcon from '../../statusIcon';\nimport { clsx } from 'clsx';\nimport Body from '../../body';\nimport Link, { LinkProps } from '../../link';\nimport { PrimitivePrompt, PrimitivePromptProps } from '../PrimitivePrompt';\n\nexport type InfoPromptAction = Pick<LinkProps, 'href' | 'target' | 'onClick'> & {\n /**\n * The label text for the action link\n */\n label: string;\n};\n\nexport type InfoPromptMedia = {\n /**\n * The icon/image asset to display.\n * The asset should include its own accessibility attributes (e.g. title, aria-label) if it conveys meaning.\n */\n asset: ReactNode;\n};\n\nexport type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'aria-live' | 'role'> &\n Pick<PrimitivePromptProps, 'data-testid'> & {\n /**\n * The sentiment determines the colour scheme\n * @default 'neutral'\n */\n sentiment?: SurfaceSentiment;\n /**\n * Handler called when the close button is clicked.\n * If not provided, the close button is hidden.\n */\n onDismiss?: () => void;\n /**\n * Custom media to override the default status icon.\n * Success and proposition sentiments support 2 status variations: standard checkmark & confetti.\n */\n media?: InfoPromptMedia;\n /**\n * Action link to be displayed below the description\n */\n action?: InfoPromptAction;\n /**\n * Title content for the prompt\n */\n title?: string;\n /**\n * Description text for the prompt (required)\n */\n description: string;\n className?: string;\n /**\n * Sets the ARIA live region politeness level.\n * - `'polite'` — announced after the current speech (default)\n * - `'assertive'` — interrupts the current speech immediately\n * - `'off'` — disables the live region entirely\n * @default 'polite'\n */\n 'aria-live'?: AriaLive;\n };\n\n/**\n * InfoPrompt displays important contextual messages to users within a screen.\n * It provides a visually distinct way to communicate information, warnings, errors,\n * or positive feedback with optional actions and dismissal capabilities.\n *\n * Use this component to replace the deprecated Alert component.\n *\n * Guidance can be found in the [design system](https://wise.design/components/info-prompt).\n */\nexport const InfoPrompt = ({\n sentiment = 'neutral',\n onDismiss,\n media,\n action,\n title,\n description,\n className,\n 'aria-live': ariaLive = 'polite',\n 'data-testid': dataTestId,\n ...restProps\n}: InfoPromptProps) => {\n const [shouldFire, setShouldFire] = useState<boolean>();\n const statusIconSentiment =\n sentiment === 'success'\n ? Sentiment.POSITIVE\n : (sentiment as Exclude<SurfaceSentiment, 'proposition'>);\n\n const handleTouchStart = () => {\n setShouldFire(true);\n };\n\n const handleTouchEnd = () => {\n if (shouldFire && action?.href) {\n if (action.target === '_blank') {\n window.top?.open(action.href, '_blank', 'noopener, noreferrer');\n } else {\n window.top?.location.assign(action.href);\n }\n }\n setShouldFire(false);\n };\n\n const handleTouchMove = () => {\n setShouldFire(false);\n };\n\n const renderMedia = () => {\n if (media) {\n return <span className=\"wds-info-prompt__media\">{media.asset}</span>;\n }\n\n if (sentiment === 'proposition') {\n return <GiftBox size={24} />;\n }\n\n return <StatusIcon size={24} sentiment={statusIconSentiment} />;\n };\n\n // Render content directly in LiveRegion\n return (\n <LiveRegion aria-live={ariaLive}>\n <PrimitivePrompt\n sentiment={sentiment}\n media={renderMedia()}\n data-testid={dataTestId}\n className={clsx('wds-info-prompt', className)}\n {...restProps}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n onTouchMove={handleTouchMove}\n onDismiss={onDismiss}\n >\n <div className=\"wds-info-prompt__content\">\n {title && (\n <Body className=\"wds-info-prompt__title\" type={Typography.BODY_LARGE_BOLD} as=\"span\">\n {title}\n </Body>\n )}\n <Body as=\"span\" className=\"wds-info-prompt__description\">\n {description}\n </Body>\n {action && (\n <Link\n href={action.href}\n target={action.target}\n type={Typography.LINK_DEFAULT}\n className=\"wds-info-prompt__action\"\n onClick={action.onClick}\n >\n {action.label}\n </Link>\n )}\n </div>\n </PrimitivePrompt>\n </LiveRegion>\n );\n};\n"],"names":["InfoPrompt","sentiment","onDismiss","media","action","title","description","className","ariaLive","dataTestId","restProps","shouldFire","setShouldFire","useState","statusIconSentiment","Sentiment","POSITIVE","handleTouchStart","handleTouchEnd","href","target","window","top","open","location","assign","handleTouchMove","renderMedia","_jsx","children","asset","GiftBox","size","StatusIcon","LiveRegion","PrimitivePrompt","clsx","onTouchStart","onTouchEnd","onTouchMove","_jsxs","Body","type","Typography","BODY_LARGE_BOLD","as","Link","LINK_DEFAULT","onClick","label"],"mappings":"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"}
|
|
@@ -26,4 +26,6 @@ export * from './initials';
|
|
|
26
26
|
export * from './colors';
|
|
27
27
|
export * from './constants';
|
|
28
28
|
export { CloseButton } from './closeButton';
|
|
29
|
+
export { LiveRegion } from './liveRegion';
|
|
30
|
+
export type { AriaLive, LiveRegionProps } from './liveRegion';
|
|
29
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACxE,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAE5B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACxE,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAE5B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export type AriaLive = 'off' | 'polite' | 'assertive';
|
|
3
|
+
export interface LiveRegionProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role' | 'aria-live' | 'aria-atomic'> {
|
|
4
|
+
/**
|
|
5
|
+
* Determines urgency: 'assertive' interrupts, 'polite' waits for idle, 'off' disables live region.
|
|
6
|
+
*/
|
|
7
|
+
'aria-live': AriaLive;
|
|
8
|
+
/** Test ID for testing tools */
|
|
9
|
+
'data-testid'?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Renders an ARIA live region with the correct implicit role.
|
|
14
|
+
*
|
|
15
|
+
* - `aria-live="polite"` → `role="status"`
|
|
16
|
+
* - `aria-live="assertive"` → `role="alert"`
|
|
17
|
+
* - `aria-live="off"` → no live region
|
|
18
|
+
*
|
|
19
|
+
* The `role` prop is intentionally excluded from the public API
|
|
20
|
+
* to prevent mismatches between `aria-live` and `role`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const LiveRegion: ({ "aria-live": ariaLive, children, ...props }: LiveRegionProps) => import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=LiveRegion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveRegion.d.ts","sourceRoot":"","sources":["../../../../src/common/liveRegion/LiveRegion.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOvD,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEtD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,cAAc,CAAC,cAAc,CAAC,EAC9B,MAAM,GAAG,WAAW,GAAG,aAAa,CACrC;IACC;;OAEG;IACH,WAAW,EAAE,QAAQ,CAAC;IACtB,gCAAgC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,+CAA+C,eAAe,gCAgBxF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/common/liveRegion/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { AriaLive } from '../../common';
|
|
2
3
|
import type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';
|
|
3
4
|
import { LinkProps } from '../../link';
|
|
4
5
|
import { PrimitivePromptProps } from '../PrimitivePrompt';
|
|
@@ -15,7 +16,7 @@ export type InfoPromptMedia = {
|
|
|
15
16
|
*/
|
|
16
17
|
asset: ReactNode;
|
|
17
18
|
};
|
|
18
|
-
export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & Pick<PrimitivePromptProps, 'data-testid'> & {
|
|
19
|
+
export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'aria-live' | 'role'> & Pick<PrimitivePromptProps, 'data-testid'> & {
|
|
19
20
|
/**
|
|
20
21
|
* The sentiment determines the colour scheme
|
|
21
22
|
* @default 'neutral'
|
|
@@ -44,6 +45,14 @@ export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & Pi
|
|
|
44
45
|
*/
|
|
45
46
|
description: string;
|
|
46
47
|
className?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Sets the ARIA live region politeness level.
|
|
50
|
+
* - `'polite'` — announced after the current speech (default)
|
|
51
|
+
* - `'assertive'` — interrupts the current speech immediately
|
|
52
|
+
* - `'off'` — disables the live region entirely
|
|
53
|
+
* @default 'polite'
|
|
54
|
+
*/
|
|
55
|
+
'aria-live'?: AriaLive;
|
|
47
56
|
};
|
|
48
57
|
/**
|
|
49
58
|
* InfoPrompt displays important contextual messages to users within a screen.
|
|
@@ -54,5 +63,5 @@ export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & Pi
|
|
|
54
63
|
*
|
|
55
64
|
* Guidance can be found in the [design system](https://wise.design/components/info-prompt).
|
|
56
65
|
*/
|
|
57
|
-
export declare const InfoPrompt: ({ sentiment, onDismiss, media, action, title, description, className, "data-testid": dataTestId, ...restProps }: InfoPromptProps) => import("react").JSX.Element;
|
|
66
|
+
export declare const InfoPrompt: ({ sentiment, onDismiss, media, action, title, description, className, "aria-live": ariaLive, "data-testid": dataTestId, ...restProps }: InfoPromptProps) => import("react").JSX.Element;
|
|
58
67
|
//# sourceMappingURL=InfoPrompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfoPrompt.d.ts","sourceRoot":"","sources":["../../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"InfoPrompt.d.ts","sourceRoot":"","sources":["../../../../src/prompt/InfoPrompt/InfoPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,SAAS,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI5E,OAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAmB,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,GAAG;IAC9E;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC,GAChG,IAAI,CAAC,oBAAoB,EAAE,aAAa,CAAC,GAAG;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,QAAQ,CAAC;CACxB,CAAC;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GAAI,wIAWxB,eAAe,gCA4EjB,CAAC"}
|
package/package.json
CHANGED
package/src/common/index.ts
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { LiveRegion, LiveRegionProps } from './LiveRegion';
|
|
3
|
+
|
|
4
|
+
describe('LiveRegion', () => {
|
|
5
|
+
const renderLiveRegion = (props: Partial<LiveRegionProps> & Pick<LiveRegionProps, 'aria-live'>) =>
|
|
6
|
+
render(<LiveRegion {...props}>{props.children ?? 'Live content'}</LiveRegion>);
|
|
7
|
+
|
|
8
|
+
describe('when aria-live is "polite"', () => {
|
|
9
|
+
it('renders with role="status"', () => {
|
|
10
|
+
renderLiveRegion({ 'aria-live': 'polite' });
|
|
11
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('sets aria-live="polite"', () => {
|
|
15
|
+
renderLiveRegion({ 'aria-live': 'polite' });
|
|
16
|
+
expect(screen.getByRole('status')).toHaveAttribute('aria-live', 'polite');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('sets aria-atomic="true"', () => {
|
|
20
|
+
renderLiveRegion({ 'aria-live': 'polite' });
|
|
21
|
+
expect(screen.getByRole('status')).toHaveAttribute('aria-atomic', 'true');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('when aria-live is "assertive"', () => {
|
|
26
|
+
it('renders with role="alert"', () => {
|
|
27
|
+
renderLiveRegion({ 'aria-live': 'assertive' });
|
|
28
|
+
expect(screen.getByRole('alert')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('sets aria-live="assertive"', () => {
|
|
32
|
+
renderLiveRegion({ 'aria-live': 'assertive' });
|
|
33
|
+
expect(screen.getByRole('alert')).toHaveAttribute('aria-live', 'assertive');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('sets aria-atomic="true"', () => {
|
|
37
|
+
renderLiveRegion({ 'aria-live': 'assertive' });
|
|
38
|
+
expect(screen.getByRole('alert')).toHaveAttribute('aria-atomic', 'true');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders children', () => {
|
|
43
|
+
renderLiveRegion({ 'aria-live': 'polite', children: 'Transfer sent' });
|
|
44
|
+
expect(screen.getByText('Transfer sent')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('passes additional HTML attributes to the wrapper div', () => {
|
|
48
|
+
renderLiveRegion({ 'aria-live': 'polite', className: 'custom' });
|
|
49
|
+
expect(screen.getByRole('status')).toHaveClass('custom');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('supports data-testid prop', () => {
|
|
53
|
+
renderLiveRegion({ 'aria-live': 'polite', 'data-testid': 'live-region' });
|
|
54
|
+
expect(screen.getByTestId('live-region')).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const ARIA_LIVE_ROLE_MAP = {
|
|
4
|
+
assertive: 'alert',
|
|
5
|
+
polite: 'status',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type AriaLive = 'off' | 'polite' | 'assertive';
|
|
9
|
+
|
|
10
|
+
export interface LiveRegionProps extends Omit<
|
|
11
|
+
HTMLAttributes<HTMLDivElement>,
|
|
12
|
+
'role' | 'aria-live' | 'aria-atomic'
|
|
13
|
+
> {
|
|
14
|
+
/**
|
|
15
|
+
* Determines urgency: 'assertive' interrupts, 'polite' waits for idle, 'off' disables live region.
|
|
16
|
+
*/
|
|
17
|
+
'aria-live': AriaLive;
|
|
18
|
+
/** Test ID for testing tools */
|
|
19
|
+
'data-testid'?: string;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Renders an ARIA live region with the correct implicit role.
|
|
25
|
+
*
|
|
26
|
+
* - `aria-live="polite"` → `role="status"`
|
|
27
|
+
* - `aria-live="assertive"` → `role="alert"`
|
|
28
|
+
* - `aria-live="off"` → no live region
|
|
29
|
+
*
|
|
30
|
+
* The `role` prop is intentionally excluded from the public API
|
|
31
|
+
* to prevent mismatches between `aria-live` and `role`.
|
|
32
|
+
*/
|
|
33
|
+
export const LiveRegion = ({ 'aria-live': ariaLive, children, ...props }: LiveRegionProps) => {
|
|
34
|
+
if (ariaLive === 'off') {
|
|
35
|
+
return <>{children}</>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
role={ARIA_LIVE_ROLE_MAP[ariaLive]}
|
|
41
|
+
aria-live={ariaLive}
|
|
42
|
+
aria-atomic="true"
|
|
43
|
+
style={{ display: 'contents' }}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -428,9 +428,8 @@ export const Spotlight: StoryObj<PreviewStoryArgs> = {
|
|
|
428
428
|
|
|
429
429
|
return (
|
|
430
430
|
<List>
|
|
431
|
-
<ListItem {...props} {...previewProps}
|
|
432
|
-
<ListItem {...props} {...previewProps}
|
|
433
|
-
<ListItem {...props} {...previewProps} title="No Spotlgiht" />
|
|
431
|
+
<ListItem {...props} {...previewProps} spotlight="inactive" />
|
|
432
|
+
<ListItem {...props} {...previewProps} spotlight="active" />
|
|
434
433
|
</List>
|
|
435
434
|
);
|
|
436
435
|
},
|
|
@@ -297,3 +297,122 @@ export const TinyScreen: Story = {
|
|
|
297
297
|
),
|
|
298
298
|
...withVariantConfig(['400%']),
|
|
299
299
|
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Test that the default LiveRegion renders with role="status" and aria-live="polite".
|
|
303
|
+
*/
|
|
304
|
+
export const LiveRegionPoliteDefault: Story = {
|
|
305
|
+
play: async ({ canvasElement, step }) => {
|
|
306
|
+
const canvas = within(canvasElement);
|
|
307
|
+
|
|
308
|
+
await step('Verify live region with role="status" exists', async () => {
|
|
309
|
+
await waitFor(async () => {
|
|
310
|
+
const liveRegion = canvas.getByRole('status');
|
|
311
|
+
await expect(liveRegion).toBeInTheDocument();
|
|
312
|
+
await waitFor(async () => expect(liveRegion).toHaveAttribute('aria-live', 'polite'));
|
|
313
|
+
await expect(liveRegion).toHaveAttribute('aria-atomic', 'true');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await step('Verify prompt content is inside the live region', async () => {
|
|
318
|
+
const liveRegion = canvas.getByRole('status');
|
|
319
|
+
await expect(within(liveRegion).getByText('Polite announcement')).toBeInTheDocument();
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
args: {
|
|
323
|
+
description: 'Polite announcement',
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Test that aria-live="assertive" renders with role="alert".
|
|
329
|
+
*/
|
|
330
|
+
export const LiveRegionAssertive: Story = {
|
|
331
|
+
play: async ({ canvasElement, step }) => {
|
|
332
|
+
const canvas = within(canvasElement);
|
|
333
|
+
|
|
334
|
+
await step('Verify live region with role="alert" exists', async () => {
|
|
335
|
+
await waitFor(async () => {
|
|
336
|
+
const liveRegion = canvas.getByRole('alert');
|
|
337
|
+
await expect(liveRegion).toBeInTheDocument();
|
|
338
|
+
await waitFor(async () => expect(liveRegion).toHaveAttribute('aria-live', 'assertive'));
|
|
339
|
+
await expect(liveRegion).toHaveAttribute('aria-atomic', 'true');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await step('Verify prompt content is inside the live region', async () => {
|
|
344
|
+
const liveRegion = canvas.getByRole('alert');
|
|
345
|
+
await expect(within(liveRegion).getByText('Payment failed')).toBeInTheDocument();
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
args: {
|
|
349
|
+
sentiment: 'negative',
|
|
350
|
+
description: 'Payment failed',
|
|
351
|
+
'aria-live': 'assertive',
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Test that aria-live="off" renders without a live region wrapper.
|
|
357
|
+
*/
|
|
358
|
+
export const LiveRegionOff: Story = {
|
|
359
|
+
play: async ({ canvasElement, step }) => {
|
|
360
|
+
const canvas = within(canvasElement);
|
|
361
|
+
|
|
362
|
+
await step('Verify no live region wrapper exists', async () => {
|
|
363
|
+
await waitFor(async () => {
|
|
364
|
+
await expect(canvas.queryByRole('status')).not.toBeInTheDocument();
|
|
365
|
+
await expect(canvas.queryByRole('alert')).not.toBeInTheDocument();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await step('Verify prompt content still renders', async () => {
|
|
370
|
+
await expect(canvas.getByText('Static info')).toBeInTheDocument();
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
args: {
|
|
374
|
+
description: 'Static info',
|
|
375
|
+
'aria-live': 'off',
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Test that a dismissed prompt also removes the live region.
|
|
381
|
+
*/
|
|
382
|
+
export const LiveRegionDismiss: Story = {
|
|
383
|
+
play: async ({ canvasElement, step }) => {
|
|
384
|
+
const canvas = within(canvasElement);
|
|
385
|
+
|
|
386
|
+
await step('Verify live region exists before dismiss', async () => {
|
|
387
|
+
await waitFor(async () => {
|
|
388
|
+
await expect(canvas.getByRole('status')).toBeInTheDocument();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await step('Dismiss the prompt', async () => {
|
|
393
|
+
const dismissButton = canvas.getByRole('button', { name: /close/i });
|
|
394
|
+
await userEvent.click(dismissButton);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await step('Verify live region is removed', async () => {
|
|
398
|
+
await waitFor(async () => {
|
|
399
|
+
await expect(canvas.queryByRole('status')).not.toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
render: function Render(args: InfoPromptProps) {
|
|
404
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
405
|
+
|
|
406
|
+
if (!isVisible) {
|
|
407
|
+
return <div data-testid="dismissed">Prompt dismissed</div>;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<InfoPrompt
|
|
412
|
+
{...args}
|
|
413
|
+
description="Dismissable live region"
|
|
414
|
+
onDismiss={() => setIsVisible(false)}
|
|
415
|
+
/>
|
|
416
|
+
);
|
|
417
|
+
},
|
|
418
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode, useState } from 'react';
|
|
2
|
-
import { Sentiment, Typography } from '../../common';
|
|
2
|
+
import { LiveRegion, Sentiment, Typography } from '../../common';
|
|
3
|
+
import type { AriaLive } from '../../common';
|
|
3
4
|
import { GiftBox } from '@transferwise/icons';
|
|
4
5
|
import type { Sentiment as SurfaceSentiment } from '../../sentimentSurface';
|
|
5
6
|
import StatusIcon from '../../statusIcon';
|
|
@@ -23,7 +24,7 @@ export type InfoPromptMedia = {
|
|
|
23
24
|
asset: ReactNode;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &
|
|
27
|
+
export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'aria-live' | 'role'> &
|
|
27
28
|
Pick<PrimitivePromptProps, 'data-testid'> & {
|
|
28
29
|
/**
|
|
29
30
|
* The sentiment determines the colour scheme
|
|
@@ -53,6 +54,14 @@ export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &
|
|
|
53
54
|
*/
|
|
54
55
|
description: string;
|
|
55
56
|
className?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Sets the ARIA live region politeness level.
|
|
59
|
+
* - `'polite'` — announced after the current speech (default)
|
|
60
|
+
* - `'assertive'` — interrupts the current speech immediately
|
|
61
|
+
* - `'off'` — disables the live region entirely
|
|
62
|
+
* @default 'polite'
|
|
63
|
+
*/
|
|
64
|
+
'aria-live'?: AriaLive;
|
|
56
65
|
};
|
|
57
66
|
|
|
58
67
|
/**
|
|
@@ -72,6 +81,7 @@ export const InfoPrompt = ({
|
|
|
72
81
|
title,
|
|
73
82
|
description,
|
|
74
83
|
className,
|
|
84
|
+
'aria-live': ariaLive = 'polite',
|
|
75
85
|
'data-testid': dataTestId,
|
|
76
86
|
...restProps
|
|
77
87
|
}: InfoPromptProps) => {
|
|
@@ -112,39 +122,42 @@ export const InfoPrompt = ({
|
|
|
112
122
|
return <StatusIcon size={24} sentiment={statusIconSentiment} />;
|
|
113
123
|
};
|
|
114
124
|
|
|
125
|
+
// Render content directly in LiveRegion
|
|
115
126
|
return (
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{
|
|
127
|
+
<LiveRegion aria-live={ariaLive}>
|
|
128
|
+
<PrimitivePrompt
|
|
129
|
+
sentiment={sentiment}
|
|
130
|
+
media={renderMedia()}
|
|
131
|
+
data-testid={dataTestId}
|
|
132
|
+
className={clsx('wds-info-prompt', className)}
|
|
133
|
+
{...restProps}
|
|
134
|
+
onTouchStart={handleTouchStart}
|
|
135
|
+
onTouchEnd={handleTouchEnd}
|
|
136
|
+
onTouchMove={handleTouchMove}
|
|
137
|
+
onDismiss={onDismiss}
|
|
138
|
+
>
|
|
139
|
+
<div className="wds-info-prompt__content">
|
|
140
|
+
{title && (
|
|
141
|
+
<Body className="wds-info-prompt__title" type={Typography.BODY_LARGE_BOLD} as="span">
|
|
142
|
+
{title}
|
|
143
|
+
</Body>
|
|
144
|
+
)}
|
|
145
|
+
<Body as="span" className="wds-info-prompt__description">
|
|
146
|
+
{description}
|
|
131
147
|
</Body>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)}
|
|
147
|
-
</div>
|
|
148
|
-
</PrimitivePrompt>
|
|
148
|
+
{action && (
|
|
149
|
+
<Link
|
|
150
|
+
href={action.href}
|
|
151
|
+
target={action.target}
|
|
152
|
+
type={Typography.LINK_DEFAULT}
|
|
153
|
+
className="wds-info-prompt__action"
|
|
154
|
+
onClick={action.onClick}
|
|
155
|
+
>
|
|
156
|
+
{action.label}
|
|
157
|
+
</Link>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</PrimitivePrompt>
|
|
161
|
+
</LiveRegion>
|
|
149
162
|
);
|
|
150
163
|
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import List from '../../../list';
|
|
3
|
-
import { ListItem, type ListItemProps } from '../../ListItem';
|
|
4
|
-
import {
|
|
5
|
-
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
6
|
-
SB_LIST_ITEM_CONTROLS as CONTROLS,
|
|
7
|
-
SB_LIST_ITEM_PROMPTS as PROMPT,
|
|
8
|
-
SB_LIST_ITEM_TEXT as TEXT,
|
|
9
|
-
SB_LIST_ITEM_MEDIA as MEDIA,
|
|
10
|
-
} from '../subcomponents';
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
component: ListItem,
|
|
14
|
-
title: 'Content/ListItem/tests/Breakpoints/NoMedia',
|
|
15
|
-
tags: ['!autodocs', '!manifest'],
|
|
16
|
-
parameters: {
|
|
17
|
-
controls: { disable: true },
|
|
18
|
-
actions: { disable: true },
|
|
19
|
-
knobs: { disable: true },
|
|
20
|
-
},
|
|
21
|
-
} satisfies Meta<ListItemProps>;
|
|
22
|
-
|
|
23
|
-
type Story = StoryObj<ListItemProps>;
|
|
24
|
-
|
|
25
|
-
const widths = [240, 241];
|
|
26
|
-
|
|
27
|
-
const sharedProps: Partial<ListItemProps> = {
|
|
28
|
-
subtitle: TEXT.subtitle,
|
|
29
|
-
valueTitle: TEXT.valueTitle,
|
|
30
|
-
valueSubtitle: TEXT.valueSubtitle,
|
|
31
|
-
additionalInfo: INFO.nonInteractive,
|
|
32
|
-
control: CONTROLS.switch,
|
|
33
|
-
prompt: PROMPT.interactive,
|
|
34
|
-
media: MEDIA.avatarSingle,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const NoMedia: Story = {
|
|
38
|
-
render: () => (
|
|
39
|
-
<div
|
|
40
|
-
style={{
|
|
41
|
-
display: 'grid',
|
|
42
|
-
gridTemplateColumns: widths.map((w) => `${w}px`).join(' '),
|
|
43
|
-
gap: 16,
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
{widths.map((w) => (
|
|
47
|
-
<div key={w} style={{ textAlign: 'center', fontWeight: 'bold' }}>
|
|
48
|
-
{w}px
|
|
49
|
-
</div>
|
|
50
|
-
))}
|
|
51
|
-
{widths.map((w) => (
|
|
52
|
-
<div key={w} style={{ width: w }}>
|
|
53
|
-
<List>
|
|
54
|
-
<ListItem {...sharedProps} title="Inactive Spotlight" spotlight="inactive" />
|
|
55
|
-
<ListItem {...sharedProps} title="Active Spotlight" spotlight="active" />
|
|
56
|
-
<ListItem {...sharedProps} title="No Spotlight" />
|
|
57
|
-
</List>
|
|
58
|
-
</div>
|
|
59
|
-
))}
|
|
60
|
-
</div>
|
|
61
|
-
),
|
|
62
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import List from '../../../list';
|
|
3
|
-
import { ListItem, type ListItemProps } from '../../ListItem';
|
|
4
|
-
import {
|
|
5
|
-
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
6
|
-
SB_LIST_ITEM_CONTROLS as CONTROLS,
|
|
7
|
-
SB_LIST_ITEM_MEDIA as MEDIA,
|
|
8
|
-
SB_LIST_ITEM_PROMPTS as PROMPT,
|
|
9
|
-
SB_LIST_ITEM_TEXT as TEXT,
|
|
10
|
-
} from '../subcomponents';
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
component: ListItem,
|
|
14
|
-
title: 'Content/ListItem/tests/Breakpoints/SideMedia',
|
|
15
|
-
tags: ['!autodocs', '!manifest'],
|
|
16
|
-
parameters: {
|
|
17
|
-
controls: { disable: true },
|
|
18
|
-
actions: { disable: true },
|
|
19
|
-
knobs: { disable: true },
|
|
20
|
-
},
|
|
21
|
-
} satisfies Meta<ListItemProps>;
|
|
22
|
-
|
|
23
|
-
type Story = StoryObj<ListItemProps>;
|
|
24
|
-
|
|
25
|
-
const widths = [332, 333];
|
|
26
|
-
|
|
27
|
-
const sharedProps: Partial<ListItemProps> = {
|
|
28
|
-
subtitle: TEXT.subtitle,
|
|
29
|
-
valueTitle: TEXT.valueTitle,
|
|
30
|
-
valueSubtitle: TEXT.valueSubtitle,
|
|
31
|
-
additionalInfo: INFO.nonInteractive,
|
|
32
|
-
control: CONTROLS.switch,
|
|
33
|
-
prompt: PROMPT.interactive,
|
|
34
|
-
media: MEDIA.avatarSingle,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const SideMedia: Story = {
|
|
38
|
-
render: () => (
|
|
39
|
-
<div
|
|
40
|
-
style={{
|
|
41
|
-
display: 'grid',
|
|
42
|
-
gridTemplateColumns: widths.map((w) => `${w}px`).join(' '),
|
|
43
|
-
gap: 16,
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
{widths.map((w) => (
|
|
47
|
-
<div key={w} style={{ textAlign: 'center', fontWeight: 'bold' }}>
|
|
48
|
-
{w}px
|
|
49
|
-
</div>
|
|
50
|
-
))}
|
|
51
|
-
{widths.map((w) => (
|
|
52
|
-
<div key={w} style={{ width: w }}>
|
|
53
|
-
<List>
|
|
54
|
-
<ListItem {...sharedProps} title="Inactive Spotlight" spotlight="inactive" />
|
|
55
|
-
<ListItem {...sharedProps} title="Active Spotlight" spotlight="active" />
|
|
56
|
-
<ListItem {...sharedProps} title="No Spotlight" />
|
|
57
|
-
</List>
|
|
58
|
-
</div>
|
|
59
|
-
))}
|
|
60
|
-
</div>
|
|
61
|
-
),
|
|
62
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import List from '../../../list';
|
|
3
|
-
import { ListItem, type ListItemProps } from '../../ListItem';
|
|
4
|
-
import {
|
|
5
|
-
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
6
|
-
SB_LIST_ITEM_CONTROLS as CONTROLS,
|
|
7
|
-
SB_LIST_ITEM_MEDIA as MEDIA,
|
|
8
|
-
SB_LIST_ITEM_PROMPTS as PROMPT,
|
|
9
|
-
SB_LIST_ITEM_TEXT as TEXT,
|
|
10
|
-
} from '../subcomponents';
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
component: ListItem,
|
|
14
|
-
title: 'Content/ListItem/tests/Breakpoints/StackedMedia',
|
|
15
|
-
tags: ['!autodocs', '!manifest'],
|
|
16
|
-
parameters: {
|
|
17
|
-
controls: { disable: true },
|
|
18
|
-
actions: { disable: true },
|
|
19
|
-
knobs: { disable: true },
|
|
20
|
-
},
|
|
21
|
-
} satisfies Meta<ListItemProps>;
|
|
22
|
-
|
|
23
|
-
type Story = StoryObj<ListItemProps>;
|
|
24
|
-
|
|
25
|
-
const widths = [308, 309];
|
|
26
|
-
|
|
27
|
-
const sharedProps: Partial<ListItemProps> = {
|
|
28
|
-
subtitle: TEXT.subtitle,
|
|
29
|
-
valueTitle: TEXT.valueTitle,
|
|
30
|
-
valueSubtitle: TEXT.valueSubtitle,
|
|
31
|
-
additionalInfo: INFO.nonInteractive,
|
|
32
|
-
control: CONTROLS.switch,
|
|
33
|
-
prompt: PROMPT.interactive,
|
|
34
|
-
media: MEDIA.avatarSingle,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const StackedMedia: Story = {
|
|
38
|
-
render: () => (
|
|
39
|
-
<div
|
|
40
|
-
style={{
|
|
41
|
-
display: 'grid',
|
|
42
|
-
gridTemplateColumns: widths.map((w) => `${w}px`).join(' '),
|
|
43
|
-
gap: 16,
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
{widths.map((w) => (
|
|
47
|
-
<div key={w} style={{ textAlign: 'center', fontWeight: 'bold' }}>
|
|
48
|
-
{w}px
|
|
49
|
-
</div>
|
|
50
|
-
))}
|
|
51
|
-
{widths.map((w) => (
|
|
52
|
-
<div key={w} style={{ width: w }}>
|
|
53
|
-
<List>
|
|
54
|
-
<ListItem {...sharedProps} title="Inactive Spotlight" spotlight="inactive" />
|
|
55
|
-
<ListItem {...sharedProps} title="Active Spotlight" spotlight="active" />
|
|
56
|
-
<ListItem {...sharedProps} title="No Spotlight" />
|
|
57
|
-
</List>
|
|
58
|
-
</div>
|
|
59
|
-
))}
|
|
60
|
-
</div>
|
|
61
|
-
),
|
|
62
|
-
};
|