@transferwise/components 46.136.1 → 46.137.1

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.
Files changed (264) hide show
  1. package/build/common/hooks/useContainerSize.js +30 -0
  2. package/build/common/hooks/useContainerSize.js.map +1 -0
  3. package/build/common/hooks/useContainerSize.mjs +28 -0
  4. package/build/common/hooks/useContainerSize.mjs.map +1 -0
  5. package/build/common/hooks/useResizeObserver.js +3 -3
  6. package/build/common/hooks/useResizeObserver.js.map +1 -1
  7. package/build/common/hooks/useResizeObserver.mjs +3 -3
  8. package/build/common/hooks/useResizeObserver.mjs.map +1 -1
  9. package/build/criticalBanner/CriticalCommsBanner.js +3 -0
  10. package/build/criticalBanner/CriticalCommsBanner.js.map +1 -1
  11. package/build/criticalBanner/CriticalCommsBanner.mjs +3 -0
  12. package/build/criticalBanner/CriticalCommsBanner.mjs.map +1 -1
  13. package/build/field/Field.js +3 -2
  14. package/build/field/Field.js.map +1 -1
  15. package/build/field/Field.mjs +3 -2
  16. package/build/field/Field.mjs.map +1 -1
  17. package/build/i18n/cs.json +2 -0
  18. package/build/i18n/cs.json.js +2 -0
  19. package/build/i18n/cs.json.js.map +1 -1
  20. package/build/i18n/cs.json.mjs +2 -0
  21. package/build/i18n/cs.json.mjs.map +1 -1
  22. package/build/i18n/de.json +2 -0
  23. package/build/i18n/de.json.js +2 -0
  24. package/build/i18n/de.json.js.map +1 -1
  25. package/build/i18n/de.json.mjs +2 -0
  26. package/build/i18n/de.json.mjs.map +1 -1
  27. package/build/i18n/en.json +2 -0
  28. package/build/i18n/en.json.js +2 -0
  29. package/build/i18n/en.json.js.map +1 -1
  30. package/build/i18n/en.json.mjs +2 -0
  31. package/build/i18n/en.json.mjs.map +1 -1
  32. package/build/i18n/es.json +2 -0
  33. package/build/i18n/es.json.js +2 -0
  34. package/build/i18n/es.json.js.map +1 -1
  35. package/build/i18n/es.json.mjs +2 -0
  36. package/build/i18n/es.json.mjs.map +1 -1
  37. package/build/i18n/fr.json +2 -0
  38. package/build/i18n/fr.json.js +2 -0
  39. package/build/i18n/fr.json.js.map +1 -1
  40. package/build/i18n/fr.json.mjs +2 -0
  41. package/build/i18n/fr.json.mjs.map +1 -1
  42. package/build/i18n/hu.json +2 -0
  43. package/build/i18n/hu.json.js +2 -0
  44. package/build/i18n/hu.json.js.map +1 -1
  45. package/build/i18n/hu.json.mjs +2 -0
  46. package/build/i18n/hu.json.mjs.map +1 -1
  47. package/build/i18n/id.json +2 -0
  48. package/build/i18n/id.json.js +2 -0
  49. package/build/i18n/id.json.js.map +1 -1
  50. package/build/i18n/id.json.mjs +2 -0
  51. package/build/i18n/id.json.mjs.map +1 -1
  52. package/build/i18n/it.json +2 -0
  53. package/build/i18n/it.json.js +2 -0
  54. package/build/i18n/it.json.js.map +1 -1
  55. package/build/i18n/it.json.mjs +2 -0
  56. package/build/i18n/it.json.mjs.map +1 -1
  57. package/build/i18n/ja.json +2 -0
  58. package/build/i18n/ja.json.js +2 -0
  59. package/build/i18n/ja.json.js.map +1 -1
  60. package/build/i18n/ja.json.mjs +2 -0
  61. package/build/i18n/ja.json.mjs.map +1 -1
  62. package/build/i18n/nl.json +2 -0
  63. package/build/i18n/nl.json.js +2 -0
  64. package/build/i18n/nl.json.js.map +1 -1
  65. package/build/i18n/nl.json.mjs +2 -0
  66. package/build/i18n/nl.json.mjs.map +1 -1
  67. package/build/i18n/pl.json +2 -0
  68. package/build/i18n/pl.json.js +2 -0
  69. package/build/i18n/pl.json.js.map +1 -1
  70. package/build/i18n/pl.json.mjs +2 -0
  71. package/build/i18n/pl.json.mjs.map +1 -1
  72. package/build/i18n/pt.json +2 -0
  73. package/build/i18n/pt.json.js +2 -0
  74. package/build/i18n/pt.json.js.map +1 -1
  75. package/build/i18n/pt.json.mjs +2 -0
  76. package/build/i18n/pt.json.mjs.map +1 -1
  77. package/build/i18n/ro.json +2 -0
  78. package/build/i18n/ro.json.js +2 -0
  79. package/build/i18n/ro.json.js.map +1 -1
  80. package/build/i18n/ro.json.mjs +2 -0
  81. package/build/i18n/ro.json.mjs.map +1 -1
  82. package/build/i18n/ru.json +2 -0
  83. package/build/i18n/ru.json.js +2 -0
  84. package/build/i18n/ru.json.js.map +1 -1
  85. package/build/i18n/ru.json.mjs +2 -0
  86. package/build/i18n/ru.json.mjs.map +1 -1
  87. package/build/i18n/th.json +2 -0
  88. package/build/i18n/th.json.js +2 -0
  89. package/build/i18n/th.json.js.map +1 -1
  90. package/build/i18n/th.json.mjs +2 -0
  91. package/build/i18n/th.json.mjs.map +1 -1
  92. package/build/i18n/tr.json +2 -0
  93. package/build/i18n/tr.json.js +2 -0
  94. package/build/i18n/tr.json.js.map +1 -1
  95. package/build/i18n/tr.json.mjs +2 -0
  96. package/build/i18n/tr.json.mjs.map +1 -1
  97. package/build/i18n/zh-CN.json +2 -0
  98. package/build/i18n/zh-CN.json.js +2 -0
  99. package/build/i18n/zh-CN.json.js.map +1 -1
  100. package/build/i18n/zh-CN.json.mjs +2 -0
  101. package/build/i18n/zh-CN.json.mjs.map +1 -1
  102. package/build/i18n/zh-HK.json +2 -0
  103. package/build/i18n/zh-HK.json.js +2 -0
  104. package/build/i18n/zh-HK.json.js.map +1 -1
  105. package/build/i18n/zh-HK.json.mjs +2 -0
  106. package/build/i18n/zh-HK.json.mjs.map +1 -1
  107. package/build/index.js +2 -0
  108. package/build/index.js.map +1 -1
  109. package/build/index.mjs +1 -0
  110. package/build/index.mjs.map +1 -1
  111. package/build/listItem/Prompt/ListItemPrompt.js +3 -2
  112. package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
  113. package/build/listItem/Prompt/ListItemPrompt.mjs +3 -2
  114. package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
  115. package/build/logo/Logo.js +77 -25
  116. package/build/logo/Logo.js.map +1 -1
  117. package/build/logo/Logo.mjs +79 -27
  118. package/build/logo/Logo.mjs.map +1 -1
  119. package/build/logo/logo-assets.js +68 -97
  120. package/build/logo/logo-assets.js.map +1 -1
  121. package/build/logo/logo-assets.mjs +62 -90
  122. package/build/logo/logo-assets.mjs.map +1 -1
  123. package/build/main.css +225 -59
  124. package/build/prompt/ActionPrompt/ActionPrompt.js +8 -40
  125. package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
  126. package/build/prompt/ActionPrompt/ActionPrompt.mjs +8 -40
  127. package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
  128. package/build/prompt/CriticalBanner/CriticalBanner.js +143 -0
  129. package/build/prompt/CriticalBanner/CriticalBanner.js.map +1 -0
  130. package/build/prompt/CriticalBanner/CriticalBanner.mjs +141 -0
  131. package/build/prompt/CriticalBanner/CriticalBanner.mjs.map +1 -0
  132. package/build/prompt/CriticalBanner/helpers.js +29 -0
  133. package/build/prompt/CriticalBanner/helpers.js.map +1 -0
  134. package/build/prompt/CriticalBanner/helpers.mjs +26 -0
  135. package/build/prompt/CriticalBanner/helpers.mjs.map +1 -0
  136. package/build/prompt/InfoPrompt/InfoPrompt.js +3 -2
  137. package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
  138. package/build/prompt/InfoPrompt/InfoPrompt.mjs +3 -2
  139. package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
  140. package/build/prompt/PrimitivePrompt/PrimitivePrompt.js +11 -4
  141. package/build/prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
  142. package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs +11 -4
  143. package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
  144. package/build/prompt/common/Expander/Expander.js +35 -0
  145. package/build/prompt/common/Expander/Expander.js.map +1 -0
  146. package/build/prompt/common/Expander/Expander.messages.js +17 -0
  147. package/build/prompt/common/Expander/Expander.messages.js.map +1 -0
  148. package/build/prompt/common/Expander/Expander.messages.mjs +13 -0
  149. package/build/prompt/common/Expander/Expander.messages.mjs.map +1 -0
  150. package/build/prompt/common/Expander/Expander.mjs +33 -0
  151. package/build/prompt/common/Expander/Expander.mjs.map +1 -0
  152. package/build/prompt/helpers/promptMedia.js +52 -0
  153. package/build/prompt/helpers/promptMedia.js.map +1 -0
  154. package/build/prompt/helpers/promptMedia.mjs +50 -0
  155. package/build/prompt/helpers/promptMedia.mjs.map +1 -0
  156. package/build/styles/css/neptune.css +0 -1
  157. package/build/styles/logo/Logo.css +3 -23
  158. package/build/styles/main.css +225 -59
  159. package/build/styles/prompt/CriticalBanner/CriticalBanner.css +134 -0
  160. package/build/styles/prompt/CriticalBanner/CriticalBanner.vars.css +0 -0
  161. package/build/styles/prompt/InfoPrompt/InfoPrompt.css +24 -0
  162. package/build/styles/prompt/common/Expander/Expander.css +8 -0
  163. package/build/styles/styles/less/neptune.css +0 -1
  164. package/build/typeahead/Typeahead.js +3 -2
  165. package/build/typeahead/Typeahead.js.map +1 -1
  166. package/build/typeahead/Typeahead.mjs +3 -2
  167. package/build/typeahead/Typeahead.mjs.map +1 -1
  168. package/build/types/common/hooks/useContainerSize.d.ts +14 -0
  169. package/build/types/common/hooks/useContainerSize.d.ts.map +1 -0
  170. package/build/types/common/hooks/useResizeObserver.d.ts +1 -1
  171. package/build/types/common/hooks/useResizeObserver.d.ts.map +1 -1
  172. package/build/types/criticalBanner/CriticalCommsBanner.d.ts +3 -0
  173. package/build/types/criticalBanner/CriticalCommsBanner.d.ts.map +1 -1
  174. package/build/types/index.d.ts +2 -2
  175. package/build/types/index.d.ts.map +1 -1
  176. package/build/types/logo/Logo.d.ts +33 -1
  177. package/build/types/logo/Logo.d.ts.map +1 -1
  178. package/build/types/logo/logo-assets.d.ts +33 -9
  179. package/build/types/logo/logo-assets.d.ts.map +1 -1
  180. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +2 -11
  181. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
  182. package/build/types/prompt/CriticalBanner/CriticalBanner.d.ts +39 -0
  183. package/build/types/prompt/CriticalBanner/CriticalBanner.d.ts.map +1 -0
  184. package/build/types/prompt/CriticalBanner/helpers.d.ts +18 -0
  185. package/build/types/prompt/CriticalBanner/helpers.d.ts.map +1 -0
  186. package/build/types/prompt/CriticalBanner/index.d.ts +3 -0
  187. package/build/types/prompt/CriticalBanner/index.d.ts.map +1 -0
  188. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
  189. package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts +35 -3
  190. package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
  191. package/build/types/prompt/common/Expander/Expander.d.ts +20 -0
  192. package/build/types/prompt/common/Expander/Expander.d.ts.map +1 -0
  193. package/build/types/prompt/common/Expander/Expander.messages.d.ts +14 -0
  194. package/build/types/prompt/common/Expander/Expander.messages.d.ts.map +1 -0
  195. package/build/types/prompt/helpers/promptMedia.d.ts +22 -0
  196. package/build/types/prompt/helpers/promptMedia.d.ts.map +1 -0
  197. package/build/types/prompt/index.d.ts +2 -0
  198. package/build/types/prompt/index.d.ts.map +1 -1
  199. package/build/types/test-utils/index.d.ts +4 -0
  200. package/build/types/test-utils/index.d.ts.map +1 -1
  201. package/package.json +22 -18
  202. package/src/alert/Alert.story.tsx +30 -1
  203. package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
  204. package/src/button/_stories/Button.story.tsx +11 -0
  205. package/src/common/hooks/useContainerSize.test.tsx +125 -0
  206. package/src/common/hooks/useContainerSize.ts +32 -0
  207. package/src/common/hooks/useResizeObserver.ts +3 -2
  208. package/src/criticalBanner/CriticalCommsBanner.story.tsx +4 -0
  209. package/src/criticalBanner/CriticalCommsBanner.test.story.tsx +1 -1
  210. package/src/criticalBanner/CriticalCommsBanner.tsx +3 -0
  211. package/src/i18n/cs.json +2 -0
  212. package/src/i18n/de.json +2 -0
  213. package/src/i18n/en.json +2 -0
  214. package/src/i18n/es.json +2 -0
  215. package/src/i18n/fr.json +2 -0
  216. package/src/i18n/hu.json +2 -0
  217. package/src/i18n/id.json +2 -0
  218. package/src/i18n/it.json +2 -0
  219. package/src/i18n/ja.json +2 -0
  220. package/src/i18n/nl.json +2 -0
  221. package/src/i18n/pl.json +2 -0
  222. package/src/i18n/pt.json +2 -0
  223. package/src/i18n/ro.json +2 -0
  224. package/src/i18n/ru.json +2 -0
  225. package/src/i18n/th.json +2 -0
  226. package/src/i18n/tr.json +2 -0
  227. package/src/i18n/zh-CN.json +2 -0
  228. package/src/i18n/zh-HK.json +2 -0
  229. package/src/index.ts +2 -2
  230. package/src/logo/Logo.css +3 -23
  231. package/src/logo/Logo.less +3 -29
  232. package/src/logo/Logo.story.tsx +117 -89
  233. package/src/logo/Logo.test.story.tsx +15 -24
  234. package/src/logo/Logo.tsx +90 -28
  235. package/src/logo/logo-assets.tsx +36 -92
  236. package/src/main.css +225 -59
  237. package/src/main.less +3 -1
  238. package/src/prompt/ActionPrompt/ActionPrompt.tsx +9 -62
  239. package/src/prompt/CriticalBanner/CriticalBanner.accessibility.docs.mdx +113 -0
  240. package/src/prompt/CriticalBanner/CriticalBanner.css +134 -0
  241. package/src/prompt/CriticalBanner/CriticalBanner.less +155 -0
  242. package/src/prompt/CriticalBanner/CriticalBanner.story.tsx +635 -0
  243. package/src/prompt/CriticalBanner/CriticalBanner.test.story.tsx +422 -0
  244. package/src/prompt/CriticalBanner/CriticalBanner.tsx +179 -0
  245. package/src/prompt/CriticalBanner/CriticalBanner.vars.css +0 -0
  246. package/src/prompt/CriticalBanner/CriticalBanner.vars.less +6 -0
  247. package/src/prompt/CriticalBanner/helpers.ts +39 -0
  248. package/src/prompt/CriticalBanner/index.ts +2 -0
  249. package/src/prompt/InfoPrompt/InfoPrompt.css +24 -0
  250. package/src/prompt/InfoPrompt/InfoPrompt.less +23 -0
  251. package/src/prompt/InfoPrompt/InfoPrompt.tsx +5 -1
  252. package/src/prompt/PrimitivePrompt/PrimitivePrompt.tsx +56 -40
  253. package/src/prompt/common/Expander/Expander.css +8 -0
  254. package/src/prompt/common/Expander/Expander.less +9 -0
  255. package/src/prompt/common/Expander/Expander.messages.ts +14 -0
  256. package/src/prompt/common/Expander/Expander.test.tsx +167 -0
  257. package/src/prompt/common/Expander/Expander.tsx +83 -0
  258. package/src/prompt/helpers/promptMedia.tsx +79 -0
  259. package/src/prompt/index.ts +4 -0
  260. package/src/radio/Radio.story.tsx +1 -1
  261. package/src/section/Section.story.tsx +2 -8
  262. package/src/sentimentSurface/SentimentSurface.story.tsx +43 -17
  263. package/src/statusIcon/StatusIcon.test.tsx +0 -2
  264. package/src/styles/less/neptune.css +0 -1
@@ -33,5 +33,28 @@
33
33
 
34
34
  .wds-prompt__media-wrapper {
35
35
  padding: 0;
36
+
37
+ @media (--screen-400-zoom) {
38
+ padding-top: var(--size-4);
39
+ }
40
+
41
+ &:has(.wds-info-prompt__media > .tw-icon) {
42
+ @media (--screen-400-zoom) {
43
+ padding-top: var(--size-8);
44
+ }
45
+ }
46
+
47
+ // More padding for when there is a title
48
+ &:has(+ .wds-info-prompt__content .wds-info-prompt__title:first-child) {
49
+ @media (--screen-400-zoom) {
50
+ padding-top: var(--size-8);
51
+ }
52
+
53
+ &:has(.wds-info-prompt__media > .tw-icon) {
54
+ @media (--screen-400-zoom) {
55
+ padding-top: var(--size-12);
56
+ }
57
+ }
58
+ }
36
59
  }
37
60
  }
@@ -117,7 +117,11 @@ export const InfoPrompt = ({
117
117
  }
118
118
 
119
119
  if (sentiment === 'proposition') {
120
- return <GiftBox size={24} />;
120
+ return (
121
+ <span className="wds-info-prompt__media">
122
+ <GiftBox />
123
+ </span>
124
+ );
121
125
  }
122
126
 
123
127
  return <StatusIcon size={24} sentiment={statusIconSentiment} />;
@@ -1,17 +1,22 @@
1
1
  import { Cross } from '@transferwise/icons';
2
2
  import { clsx } from 'clsx';
3
- import SentimentSurface, { Sentiment } from '../../sentimentSurface';
3
+ import SentimentSurface, { Emphasis, Sentiment } from '../../sentimentSurface';
4
4
  import IconButton from '../../iconButton';
5
5
  import { useIntl } from 'react-intl';
6
6
  import closeBtnMessages from '../../common/closeButton/CloseButton.messages';
7
- import { HTMLAttributes, ReactNode } from 'react';
7
+ import { forwardRef, HTMLAttributes, ReactNode } from 'react';
8
8
 
9
9
  export type PrimitivePromptProps = HTMLAttributes<HTMLDivElement> & {
10
10
  /**
11
- * The sentiment determines the colour scheme
11
+ * The sentiment determines the colour scheme.
12
12
  * @default success
13
13
  */
14
14
  sentiment?: Sentiment;
15
+ /**
16
+ * The emphasis level affecting background and text contrast.
17
+ * @default 'base'
18
+ */
19
+ emphasis?: Emphasis;
15
20
  /**
16
21
  * Media to be displayed on the prompt (icon/image/etc).
17
22
  */
@@ -33,42 +38,53 @@ export type PrimitivePromptProps = HTMLAttributes<HTMLDivElement> & {
33
38
  /**
34
39
  * PrimitivePrompt is a low-level component that provides the structure, sentiment support and styling for various prompts.
35
40
  * Uses several css variables to handle styling from within the consuming component, e.g. --Prompt-padding. */
36
- export const PrimitivePrompt = ({
37
- sentiment = 'success',
38
- media,
39
- actions,
40
- onDismiss,
41
- className,
42
- children,
43
- ...restProps
44
- }: PrimitivePromptProps) => {
45
- const intl = useIntl();
41
+ export const PrimitivePrompt = forwardRef<HTMLDivElement, PrimitivePromptProps>(
42
+ (
43
+ {
44
+ sentiment = 'success',
45
+ emphasis = 'base',
46
+ media,
47
+ actions,
48
+ onDismiss,
49
+ className,
50
+ children,
51
+ ...restProps
52
+ },
53
+ ref,
54
+ ) => {
55
+ const intl = useIntl();
46
56
 
47
- return (
48
- <SentimentSurface
49
- sentiment={sentiment}
50
- className={clsx('wds-prompt', `wds-prompt--${sentiment}`, className)}
51
- {...restProps}
52
- >
53
- <div
54
- className={clsx('wds-prompt__content-wrapper', {
55
- 'wds-prompt__content-wrapper--with-dismiss': !!onDismiss,
56
- })}
57
+ return (
58
+ <SentimentSurface
59
+ // @ts-expect-error - SentimentSurface forwardRef types don't expose ref in props
60
+ ref={ref}
61
+ sentiment={sentiment}
62
+ emphasis={emphasis}
63
+ className={clsx('wds-prompt', `wds-prompt--${sentiment}`, className)}
64
+ {...restProps}
57
65
  >
58
- <div className={clsx('wds-prompt__media-wrapper')}>{media}</div>
59
- {children}
60
- {onDismiss && (
61
- <IconButton
62
- size={24}
63
- priority="secondary"
64
- aria-label={intl.formatMessage(closeBtnMessages.ariaLabel)}
65
- onClick={onDismiss}
66
- >
67
- <Cross />
68
- </IconButton>
69
- )}
70
- {actions && <div className="wds-prompt__actions-wrapper">{actions}</div>}
71
- </div>
72
- </SentimentSurface>
73
- );
74
- };
66
+ <div
67
+ className={clsx('wds-prompt__content-wrapper', {
68
+ 'wds-prompt__content-wrapper--with-dismiss': !!onDismiss,
69
+ })}
70
+ >
71
+ <div className={clsx('wds-prompt__media-wrapper')}>{media}</div>
72
+ {children}
73
+ {onDismiss && (
74
+ <IconButton
75
+ size={24}
76
+ priority="secondary"
77
+ aria-label={intl.formatMessage(closeBtnMessages.ariaLabel)}
78
+ onClick={onDismiss}
79
+ >
80
+ <Cross />
81
+ </IconButton>
82
+ )}
83
+ {actions && <div className="wds-prompt__actions-wrapper">{actions}</div>}
84
+ </div>
85
+ </SentimentSurface>
86
+ );
87
+ },
88
+ );
89
+
90
+ PrimitivePrompt.displayName = 'PrimitivePrompt';
@@ -0,0 +1,8 @@
1
+ .wds-expander-toggle {
2
+ align-self: flex-start;
3
+ flex-shrink: 0;
4
+ transition: transform 0.2s ease-in-out;
5
+ }
6
+ .wds-expander-toggle--collapsed {
7
+ transform: rotate(180deg);
8
+ }
@@ -0,0 +1,9 @@
1
+ .wds-expander-toggle {
2
+ align-self: flex-start;
3
+ flex-shrink: 0;
4
+ transition: transform 0.2s ease-in-out;
5
+
6
+ &--collapsed {
7
+ transform: rotate(180deg);
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ expandAriaLabel: {
5
+ id: 'neptune.Expander.expandAriaLabel',
6
+ defaultMessage: 'Expand',
7
+ description: 'Expand button for expander',
8
+ },
9
+ collapseAriaLabel: {
10
+ id: 'neptune.Expander.collapseAriaLabel',
11
+ defaultMessage: 'Collapse',
12
+ description: 'Collapse button for expander',
13
+ },
14
+ });
@@ -0,0 +1,167 @@
1
+ import { renderHook, act, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+
4
+ import { useExpanderState, shouldShowWhenExpanded, ExpanderToggle } from './Expander';
5
+ import { mockMatchMedia, render } from '../../../test-utils';
6
+
7
+ mockMatchMedia();
8
+
9
+ describe('useExpanderState', () => {
10
+ it('initializes with default expanded state (true)', () => {
11
+ const { result } = renderHook(() => useExpanderState());
12
+
13
+ expect(result.current.expanded).toBe(true);
14
+ });
15
+
16
+ it('initializes with custom expanded state', () => {
17
+ const { result } = renderHook(() => useExpanderState(false));
18
+
19
+ expect(result.current.expanded).toBe(false);
20
+ });
21
+
22
+ it('toggles expanded state', () => {
23
+ const { result } = renderHook(() => useExpanderState(true));
24
+
25
+ expect(result.current.expanded).toBe(true);
26
+
27
+ act(() => {
28
+ result.current.toggle();
29
+ });
30
+
31
+ expect(result.current.expanded).toBe(false);
32
+
33
+ act(() => {
34
+ result.current.toggle();
35
+ });
36
+
37
+ expect(result.current.expanded).toBe(true);
38
+ });
39
+
40
+ it('sets expanded state programmatically', () => {
41
+ const { result } = renderHook(() => useExpanderState(true));
42
+
43
+ act(() => {
44
+ result.current.setExpanded(false);
45
+ });
46
+
47
+ expect(result.current.expanded).toBe(false);
48
+
49
+ act(() => {
50
+ result.current.setExpanded(true);
51
+ });
52
+
53
+ expect(result.current.expanded).toBe(true);
54
+ });
55
+
56
+ it('memoizes toggle callback', () => {
57
+ const { result, rerender } = renderHook(() => useExpanderState(true));
58
+
59
+ const firstToggle = result.current.toggle;
60
+
61
+ rerender();
62
+
63
+ expect(result.current.toggle).toBe(firstToggle);
64
+ });
65
+ });
66
+
67
+ describe('shouldShowWhenExpanded', () => {
68
+ it('returns false when hasContent is false', () => {
69
+ expect(shouldShowWhenExpanded({ expanded: true, hasContent: false })).toBe(false);
70
+ expect(shouldShowWhenExpanded({ expanded: false, hasContent: false })).toBe(false);
71
+ });
72
+
73
+ it('returns true when expanded and hasContent are true', () => {
74
+ expect(shouldShowWhenExpanded({ expanded: true, hasContent: true })).toBe(true);
75
+ });
76
+
77
+ it('returns false when collapsed but hasContent is true', () => {
78
+ expect(shouldShowWhenExpanded({ expanded: false, hasContent: true })).toBe(false);
79
+ });
80
+
81
+ it('returns true when alwaysShow is true and hasContent is true, regardless of expanded', () => {
82
+ expect(shouldShowWhenExpanded({ expanded: true, hasContent: true, alwaysShow: true })).toBe(
83
+ true,
84
+ );
85
+ expect(shouldShowWhenExpanded({ expanded: false, hasContent: true, alwaysShow: true })).toBe(
86
+ true,
87
+ );
88
+ });
89
+
90
+ it('returns false when alwaysShow is true but hasContent is false', () => {
91
+ expect(shouldShowWhenExpanded({ expanded: true, hasContent: false, alwaysShow: true })).toBe(
92
+ false,
93
+ );
94
+ expect(shouldShowWhenExpanded({ expanded: false, hasContent: false, alwaysShow: true })).toBe(
95
+ false,
96
+ );
97
+ });
98
+ });
99
+
100
+ describe('ExpanderToggle', () => {
101
+ it('renders with correct aria-label when expanded', () => {
102
+ render(<ExpanderToggle expanded onToggle={jest.fn()} />);
103
+
104
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Collapse');
105
+ });
106
+
107
+ it('renders with correct aria-label when collapsed', () => {
108
+ render(<ExpanderToggle expanded={false} onToggle={jest.fn()} />);
109
+
110
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Expand');
111
+ });
112
+
113
+ it('renders with custom labels', () => {
114
+ const { rerender } = render(<ExpanderToggle expanded onToggle={jest.fn()} />);
115
+
116
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Collapse');
117
+
118
+ rerender(<ExpanderToggle expanded={false} onToggle={jest.fn()} />);
119
+
120
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Expand');
121
+ });
122
+
123
+ it('sets aria-expanded attribute correctly', () => {
124
+ const { rerender } = render(<ExpanderToggle expanded onToggle={jest.fn()} />);
125
+
126
+ expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true');
127
+
128
+ rerender(<ExpanderToggle expanded={false} onToggle={jest.fn()} />);
129
+
130
+ expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false');
131
+ });
132
+
133
+ it('calls onToggle when clicked', async () => {
134
+ const user = userEvent.setup();
135
+ const onToggle = jest.fn();
136
+
137
+ render(<ExpanderToggle expanded onToggle={onToggle} />);
138
+
139
+ await user.click(screen.getByRole('button'));
140
+
141
+ expect(onToggle).toHaveBeenCalledTimes(1);
142
+ });
143
+
144
+ it('applies custom className', () => {
145
+ render(<ExpanderToggle expanded className="custom-class" onToggle={jest.fn()} />);
146
+
147
+ expect(screen.getByRole('button')).toHaveClass('custom-class');
148
+ });
149
+
150
+ it('applies base class and collapsed class correctly', () => {
151
+ const { rerender } = render(<ExpanderToggle expanded onToggle={jest.fn()} />);
152
+
153
+ expect(screen.getByRole('button')).toHaveClass('wds-expander-toggle');
154
+ expect(screen.getByRole('button')).not.toHaveClass('wds-expander-toggle--collapsed');
155
+
156
+ rerender(<ExpanderToggle expanded={false} onToggle={jest.fn()} />);
157
+
158
+ expect(screen.getByRole('button')).toHaveClass('wds-expander-toggle');
159
+ expect(screen.getByRole('button')).toHaveClass('wds-expander-toggle--collapsed');
160
+ });
161
+
162
+ it('applies data-testid attribute', () => {
163
+ render(<ExpanderToggle expanded data-testid="expander-toggle" onToggle={jest.fn()} />);
164
+
165
+ expect(screen.getByTestId('expander-toggle')).toBeInTheDocument();
166
+ });
167
+ });
@@ -0,0 +1,83 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import { ChevronUp } from '@transferwise/icons';
4
+ import IconButton from '../../../iconButton';
5
+ import { useIntl } from 'react-intl';
6
+ import messages from './Expander.messages';
7
+
8
+ export type ExpanderState = {
9
+ expanded: boolean;
10
+ toggle: () => void;
11
+ setExpanded: (expanded: boolean) => void;
12
+ };
13
+
14
+ // Hook for managing expander state
15
+ export function useExpanderState(initialExpanded = true): ExpanderState {
16
+ const [expanded, setExpanded] = useState(initialExpanded);
17
+
18
+ const toggle = useCallback(() => {
19
+ setExpanded((prev) => !prev);
20
+ }, []);
21
+
22
+ return {
23
+ expanded,
24
+ toggle,
25
+ setExpanded,
26
+ };
27
+ }
28
+
29
+ // Helper for conditional rendering based on expanded state
30
+ export function shouldShowWhenExpanded({
31
+ expanded,
32
+ hasContent,
33
+ alwaysShow = false,
34
+ }: {
35
+ expanded: boolean;
36
+ hasContent: boolean;
37
+ alwaysShow?: boolean;
38
+ }): boolean {
39
+ if (alwaysShow) {
40
+ return hasContent;
41
+ }
42
+
43
+ return expanded && hasContent;
44
+ }
45
+
46
+ export type ExpanderToggleProps = {
47
+ expanded: boolean;
48
+ onToggle?: () => void;
49
+ size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
50
+ className?: string;
51
+ 'data-testid'?: string;
52
+ };
53
+
54
+ // Reusable toggle button component
55
+ export const ExpanderToggle = ({
56
+ expanded,
57
+ onToggle,
58
+ size = 24,
59
+ className,
60
+ 'data-testid': testId,
61
+ }: ExpanderToggleProps) => {
62
+ const intl = useIntl();
63
+
64
+ return (
65
+ <IconButton
66
+ size={size}
67
+ priority="secondary"
68
+ aria-label={intl.formatMessage(
69
+ expanded ? messages.collapseAriaLabel : messages.expandAriaLabel,
70
+ )}
71
+ aria-expanded={expanded}
72
+ className={clsx(
73
+ 'wds-expander-toggle',
74
+ { 'wds-expander-toggle--collapsed': !expanded },
75
+ className,
76
+ )}
77
+ data-testid={testId}
78
+ onClick={onToggle}
79
+ >
80
+ <ChevronUp />
81
+ </IconButton>
82
+ );
83
+ };
@@ -0,0 +1,79 @@
1
+ import { ReactNode } from 'react';
2
+ import { GiftBox } from '@transferwise/icons';
3
+
4
+ import AvatarView, { AvatarViewProps } from '../../avatarView';
5
+ import Image from '../../image';
6
+ import StatusIcon from '../../statusIcon';
7
+ import { BadgeAssetsProps } from '../../badge';
8
+ import { PrimitivePromptProps } from '../PrimitivePrompt';
9
+
10
+ export type PromptMedia = {
11
+ imgSrc?: string;
12
+ avatar?: Pick<AvatarViewProps, 'imgSrc' | 'profileName' | 'profileType'> & {
13
+ asset?: AvatarViewProps['children'];
14
+ badge?: Pick<BadgeAssetsProps, 'flagCode'>;
15
+ };
16
+ 'aria-label'?: string;
17
+ 'aria-hidden'?: boolean;
18
+ };
19
+
20
+ type RenderPromptMediaOptions = {
21
+ media: PromptMedia;
22
+ sentiment: PrimitivePromptProps['sentiment'];
23
+ mediaId: string;
24
+ imgClassName: string;
25
+ };
26
+
27
+ export function renderPromptMedia({
28
+ media,
29
+ sentiment,
30
+ mediaId,
31
+ imgClassName,
32
+ }: RenderPromptMediaOptions): ReactNode {
33
+ if (media?.imgSrc) {
34
+ return (
35
+ <Image
36
+ id={mediaId}
37
+ src={media.imgSrc}
38
+ className={imgClassName}
39
+ alt={media['aria-label'] ?? ''}
40
+ />
41
+ );
42
+ }
43
+ if (media?.avatar) {
44
+ const badge = media.avatar.badge
45
+ ? media.avatar.badge
46
+ : sentiment === 'proposition'
47
+ ? {}
48
+ : { status: sentiment };
49
+ return (
50
+ <AvatarView
51
+ {...media.avatar}
52
+ badge={badge}
53
+ aria-label={media['aria-label']}
54
+ aria-hidden={media['aria-hidden']}
55
+ id={mediaId}
56
+ size={48}
57
+ >
58
+ {media.avatar.asset}
59
+ </AvatarView>
60
+ );
61
+ }
62
+ return sentiment === 'proposition' ? (
63
+ <AvatarView
64
+ id={mediaId}
65
+ size={48}
66
+ aria-label={media['aria-label']}
67
+ aria-hidden={media['aria-hidden']}
68
+ >
69
+ <GiftBox />
70
+ </AvatarView>
71
+ ) : (
72
+ <StatusIcon
73
+ id={mediaId}
74
+ size={48}
75
+ sentiment={sentiment}
76
+ iconLabel={media['aria-hidden'] ? null : media['aria-label']}
77
+ />
78
+ );
79
+ }
@@ -12,3 +12,7 @@ export { ActionPrompt } from './ActionPrompt';
12
12
  // InfoPrompt
13
13
  export type { InfoPromptProps, InfoPromptAction, InfoPromptMedia } from './InfoPrompt';
14
14
  export { InfoPrompt } from './InfoPrompt';
15
+
16
+ // CriticalBanner
17
+ export type { CriticalBannerProps } from './CriticalBanner';
18
+ export { CriticalBanner } from './CriticalBanner';
@@ -52,7 +52,7 @@ export const Variants: StoryObj<RadioProps> = {
52
52
  id="radioGroup2"
53
53
  avatar={
54
54
  <AvatarView>
55
- <Flag code="IMP" />
55
+ <Flag code="IM" />
56
56
  </AvatarView>
57
57
  }
58
58
  value="option2"
@@ -108,14 +108,8 @@ export const WithListItems = () => {
108
108
  return (
109
109
  <Section>
110
110
  <Header title="Section with list items" />
111
- <ListItem
112
- title="Item 1"
113
- control={<ListItem.Navigation onClick={() => action('Item 1')} />}
114
- />
115
- <ListItem
116
- title="Item 2"
117
- control={<ListItem.Navigation onClick={() => action('Item 2')} />}
118
- />
111
+ <ListItem title="Item 1" control={<ListItem.Navigation onClick={() => action('Item 1')} />} />
112
+ <ListItem title="Item 2" control={<ListItem.Navigation onClick={() => action('Item 2')} />} />
119
113
  </Section>
120
114
  );
121
115
  };
@@ -202,18 +202,26 @@ const TokenSwatch = ({ token }: { token: string }) => (
202
202
  export const EmphasisLevels: Story = {
203
203
  render: (args) => (
204
204
  <>
205
- <SentimentSurface {...args} sentiment="negative" emphasis="base">
206
- <div className="p-a-2">Negative - Base emphasis</div>
207
- </SentimentSurface>
208
- <SentimentSurface {...args} sentiment="negative" emphasis="elevated">
209
- <div className="p-a-2">Negative - Elevated emphasis</div>
210
- </SentimentSurface>
211
- <SentimentSurface {...args} sentiment="success" emphasis="base">
212
- <div className="p-a-2">Success - Base emphasis</div>
213
- </SentimentSurface>
214
- <SentimentSurface {...args} sentiment="success" emphasis="elevated">
215
- <div className="p-a-2">Success - Elevated emphasis</div>
216
- </SentimentSurface>
205
+ {sentiments.map((sentiment) => (
206
+ <div key={sentiment}>
207
+ <SentimentSurface
208
+ key={`${sentiment}-base`}
209
+ {...args}
210
+ sentiment={sentiment}
211
+ emphasis="base"
212
+ >
213
+ <div className="p-a-2 text-capitalize">{sentiment} - Base emphasis</div>
214
+ </SentimentSurface>
215
+ <SentimentSurface
216
+ key={`${sentiment}-elevated`}
217
+ {...args}
218
+ sentiment={sentiment}
219
+ emphasis="elevated"
220
+ >
221
+ <div className="p-a-2 text-capitalize">{sentiment} - Elevated emphasis</div>
222
+ </SentimentSurface>
223
+ </div>
224
+ ))}
217
225
  </>
218
226
  ),
219
227
  parameters: {
@@ -369,8 +377,8 @@ export const SentimentAwareComponents: Story = {
369
377
  {STATUS_NEGATIVE}
370
378
  </div>
371
379
 
372
- <Header level="group" title="Success sentiment surface" />
373
- <SentimentSurface sentiment="success">
380
+ <Header level="group" title="Success sentiment surface - Base emphasis" />
381
+ <SentimentSurface sentiment="success" emphasis="base">
374
382
  <div className="p-a-2 m-b-2 sentimentAwareComponent">
375
383
  {BODY}
376
384
  {AVATAR_SUCCESS}
@@ -378,8 +386,26 @@ export const SentimentAwareComponents: Story = {
378
386
  </div>
379
387
  </SentimentSurface>
380
388
 
381
- <Header level="group" title="Negative sentiment surface" />
382
- <SentimentSurface sentiment="negative">
389
+ <Header level="group" title="Success sentiment surface - Elevated emphasis" />
390
+ <SentimentSurface sentiment="success" emphasis="elevated">
391
+ <div className="p-a-2 m-b-2 sentimentAwareComponent">
392
+ {BODY}
393
+ {AVATAR_SUCCESS}
394
+ {STATUS_SUCCESS}
395
+ </div>
396
+ </SentimentSurface>
397
+
398
+ <Header level="group" title="Negative sentiment surface - Base emphasis" />
399
+ <SentimentSurface sentiment="negative" emphasis="base">
400
+ <div className="p-a-2 m-b-2 sentimentAwareComponent">
401
+ {BODY}
402
+ {AVATAR_NEGATIVE}
403
+ {STATUS_NEGATIVE}
404
+ </div>
405
+ </SentimentSurface>
406
+
407
+ <Header level="group" title="Negative sentiment surface - Elevated emphasis" />
408
+ <SentimentSurface sentiment="negative" emphasis="elevated">
383
409
  <div className="p-a-2 m-b-2 sentimentAwareComponent">
384
410
  {BODY}
385
411
  {AVATAR_NEGATIVE}
@@ -427,7 +453,7 @@ function CustomCard({ children, className }: { className?: string; children: Rea
427
453
 
428
454
  export const CustomComponents: Story = {
429
455
  tags: ['!autodocs', '!dev'],
430
- render: (args) => (
456
+ render: () => (
431
457
  <>
432
458
  <style>{`
433
459
  .custom-card {
@@ -42,7 +42,6 @@ describe('StatusIcon', () => {
42
42
  },
43
43
  );
44
44
 
45
-
46
45
  it.each([
47
46
  [Sentiment.WARNING, 'alert-icon'],
48
47
  [Sentiment.PENDING, 'clock-borderless-icon'],
@@ -61,7 +60,6 @@ describe('StatusIcon', () => {
61
60
  },
62
61
  );
63
62
 
64
-
65
63
  describe('accessible name', () => {
66
64
  it.each([
67
65
  ['Error', Sentiment.NEGATIVE],
@@ -4456,7 +4456,6 @@ kbd kbd {
4456
4456
 
4457
4457
  .text-underline {
4458
4458
  text-decoration: underline;
4459
- -webkit-text-decoration: underline !important;
4460
4459
  text-decoration: underline !important;
4461
4460
  text-underline-offset: 0.3em;
4462
4461
  }