@transferwise/components 0.0.0-experimental-c20c070 → 0.0.0-experimental-b2b287d

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 (126) hide show
  1. package/README.md +1 -1
  2. package/build/Link/Link.js.map +1 -1
  3. package/build/Link/Link.mjs.map +1 -1
  4. package/build/Loader/Loader.js.map +1 -1
  5. package/build/Loader/Loader.mjs.map +1 -1
  6. package/build/Section/Section.js.map +1 -1
  7. package/build/Section/Section.mjs.map +1 -1
  8. package/build/Snackbar/Snackbar.js +178 -102
  9. package/build/Snackbar/Snackbar.js.map +1 -1
  10. package/build/Snackbar/Snackbar.messages.js +14 -0
  11. package/build/Snackbar/Snackbar.messages.js.map +1 -0
  12. package/build/Snackbar/Snackbar.messages.mjs +10 -0
  13. package/build/Snackbar/Snackbar.messages.mjs.map +1 -0
  14. package/build/Snackbar/Snackbar.mjs +180 -101
  15. package/build/Snackbar/Snackbar.mjs.map +1 -1
  16. package/build/Snackbar/SnackbarContext.js.map +1 -1
  17. package/build/Snackbar/SnackbarContext.mjs.map +1 -1
  18. package/build/Snackbar/SnackbarPortal.js +12 -0
  19. package/build/Snackbar/SnackbarPortal.js.map +1 -0
  20. package/build/Snackbar/SnackbarPortal.mjs +8 -0
  21. package/build/Snackbar/SnackbarPortal.mjs.map +1 -0
  22. package/build/Snackbar/SnackbarProvider.js +24 -28
  23. package/build/Snackbar/SnackbarProvider.js.map +1 -1
  24. package/build/Snackbar/SnackbarProvider.mjs +25 -29
  25. package/build/Snackbar/SnackbarProvider.mjs.map +1 -1
  26. package/build/Snackbar/constants.js +17 -0
  27. package/build/Snackbar/constants.js.map +1 -0
  28. package/build/Snackbar/constants.mjs +11 -0
  29. package/build/Snackbar/constants.mjs.map +1 -0
  30. package/build/Snackbar/useSnackbarAutoDismiss.js +66 -0
  31. package/build/Snackbar/useSnackbarAutoDismiss.js.map +1 -0
  32. package/build/Snackbar/useSnackbarAutoDismiss.mjs +62 -0
  33. package/build/Snackbar/useSnackbarAutoDismiss.mjs.map +1 -0
  34. package/build/Snackbar/useSnackbarManager.js +153 -0
  35. package/build/Snackbar/useSnackbarManager.js.map +1 -0
  36. package/build/Snackbar/useSnackbarManager.mjs +149 -0
  37. package/build/Snackbar/useSnackbarManager.mjs.map +1 -0
  38. package/build/Snackbar/useSnackbarSwipeDismiss.js +135 -0
  39. package/build/Snackbar/useSnackbarSwipeDismiss.js.map +1 -0
  40. package/build/Snackbar/useSnackbarSwipeDismiss.mjs +131 -0
  41. package/build/Snackbar/useSnackbarSwipeDismiss.mjs.map +1 -0
  42. package/build/_deprecated/Select/Select.js +1 -1
  43. package/build/_deprecated/Select/Select.js.map +1 -1
  44. package/build/_deprecated/Select/Select.mjs +1 -1
  45. package/build/_deprecated/Select/Select.mjs.map +1 -1
  46. package/build/common/BottomSheet/BottomSheet.js +3 -1
  47. package/build/common/BottomSheet/BottomSheet.js.map +1 -1
  48. package/build/common/BottomSheet/BottomSheet.mjs +3 -1
  49. package/build/common/BottomSheet/BottomSheet.mjs.map +1 -1
  50. package/build/i18n/en.json +1 -0
  51. package/build/i18n/en.json.js +1 -0
  52. package/build/i18n/en.json.js.map +1 -1
  53. package/build/i18n/en.json.mjs +1 -0
  54. package/build/i18n/en.json.mjs.map +1 -1
  55. package/build/main.css +116 -53
  56. package/build/styles/Snackbar/Snackbar.css +117 -54
  57. package/build/styles/main.css +116 -53
  58. package/build/types/Link/Link.d.ts +1 -1
  59. package/build/types/Loader/Loader.d.ts +1 -1
  60. package/build/types/Section/Section.d.ts +1 -1
  61. package/build/types/Snackbar/Snackbar.d.ts +55 -9
  62. package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
  63. package/build/types/Snackbar/Snackbar.messages.d.ts +9 -0
  64. package/build/types/Snackbar/Snackbar.messages.d.ts.map +1 -0
  65. package/build/types/Snackbar/Snackbar.types.d.ts +12 -0
  66. package/build/types/Snackbar/Snackbar.types.d.ts.map +1 -0
  67. package/build/types/Snackbar/SnackbarContext.d.ts +2 -2
  68. package/build/types/Snackbar/SnackbarContext.d.ts.map +1 -1
  69. package/build/types/Snackbar/SnackbarPortal.d.ts +4 -0
  70. package/build/types/Snackbar/SnackbarPortal.d.ts.map +1 -0
  71. package/build/types/Snackbar/SnackbarProvider.d.ts +7 -0
  72. package/build/types/Snackbar/SnackbarProvider.d.ts.map +1 -1
  73. package/build/types/Snackbar/constants.d.ts +9 -0
  74. package/build/types/Snackbar/constants.d.ts.map +1 -0
  75. package/build/types/Snackbar/index.d.ts +1 -1
  76. package/build/types/Snackbar/index.d.ts.map +1 -1
  77. package/build/types/Snackbar/useSnackbar.d.ts +1 -1
  78. package/build/types/Snackbar/useSnackbar.d.ts.map +1 -1
  79. package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts +13 -0
  80. package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts.map +1 -0
  81. package/build/types/Snackbar/useSnackbarManager.d.ts +20 -0
  82. package/build/types/Snackbar/useSnackbarManager.d.ts.map +1 -0
  83. package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts +22 -0
  84. package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts.map +1 -0
  85. package/build/types/_deprecated/Select/Select.d.ts +1 -1
  86. package/build/types/common/BottomSheet/BottomSheet.d.ts +3 -1
  87. package/build/types/common/BottomSheet/BottomSheet.d.ts.map +1 -1
  88. package/build/types/index.d.ts +1 -1
  89. package/build/types/index.d.ts.map +1 -1
  90. package/build/types/test-utils/index.d.ts +2 -0
  91. package/build/types/test-utils/index.d.ts.map +1 -1
  92. package/package.json +3 -3
  93. package/src/FlowNavigation/_stories/FlowNavigation.CriticalBanner.docs.mdx +66 -0
  94. package/src/Link/Link.tsx +1 -1
  95. package/src/ListItem/AdditionalInfo/_stories/ListItemAdditionalInfo.story.tsx +1 -1
  96. package/src/ListItem/AvatarView/_stories/ListItemAvatarView.story.tsx +3 -3
  97. package/src/ListItem/_stories/ListItem.disabled.story.tsx +1 -1
  98. package/src/Loader/Loader.tsx +1 -1
  99. package/src/Prompt/CriticalBanner/_stories/CriticalBanner.story.tsx +2 -0
  100. package/src/Section/Section.tsx +1 -1
  101. package/src/Snackbar/Snackbar.css +117 -54
  102. package/src/Snackbar/Snackbar.less +139 -60
  103. package/src/Snackbar/Snackbar.messages.ts +9 -0
  104. package/src/Snackbar/Snackbar.test.tsx +989 -119
  105. package/src/Snackbar/Snackbar.tsx +208 -93
  106. package/src/Snackbar/Snackbar.types.ts +17 -0
  107. package/src/Snackbar/SnackbarContext.ts +2 -2
  108. package/src/Snackbar/SnackbarPortal.tsx +8 -0
  109. package/src/Snackbar/SnackbarProvider.test.tsx +783 -0
  110. package/src/Snackbar/SnackbarProvider.tsx +27 -19
  111. package/src/Snackbar/_stories/Snackbar.accessibility.docs.mdx +58 -0
  112. package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +16 -8
  113. package/src/Snackbar/_stories/Snackbar.story.tsx +388 -61
  114. package/src/Snackbar/_stories/Snackbar.test.story.tsx +171 -47
  115. package/src/Snackbar/constants.ts +10 -0
  116. package/src/Snackbar/index.ts +1 -1
  117. package/src/Snackbar/useSnackbarAutoDismiss.ts +74 -0
  118. package/src/Snackbar/useSnackbarManager.ts +169 -0
  119. package/src/Snackbar/useSnackbarSwipeDismiss.ts +169 -0
  120. package/src/_deprecated/Select/Select.tsx +1 -1
  121. package/src/_deprecated/Select/_stories/Select.story.tsx +1 -1
  122. package/src/common/BottomSheet/BottomSheet.tsx +3 -1
  123. package/src/i18n/en.json +1 -0
  124. package/src/index.ts +1 -1
  125. package/src/main.css +116 -53
  126. package/src/Prompt/CriticalBanner/_stories/CriticalBanner.docs.mdx +0 -122
@@ -0,0 +1,169 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+
3
+ import { SNACKBAR_SWIPE_DISMISS_THRESHOLD } from './constants';
4
+
5
+ const DISMISS_CLASS = 'wds-Snackbar--swipe-dismiss';
6
+ const SETTLE_CLASS = 'wds-Snackbar--swipe-settle';
7
+
8
+ type SwipePointer = {
9
+ id: number;
10
+ target: Element;
11
+ startX: number;
12
+ startY: number;
13
+ threshold: number;
14
+ };
15
+
16
+ /**
17
+ * Captures one primary pointer and follows downward, mostly vertical movement without re-rendering.
18
+ * Release dismisses at half the surface height; shorter/cancelled gestures settle back into place.
19
+ * The outer wrapper moves independently of entry/exit motion, and a committed swipe consumes its
20
+ * follow-up click so dragging from the action cannot activate it.
21
+ */
22
+ const useSnackbarSwipeDismiss = ({
23
+ onDismissRequest,
24
+ onSwipeChange,
25
+ visible,
26
+ }: {
27
+ onDismissRequest: () => void;
28
+ /** Reports when a gesture starts and ends so auto-dismiss can pause beneath the pointer. */
29
+ onSwipeChange: (swiping: boolean) => void;
30
+ visible: boolean;
31
+ }) => {
32
+ const containerRef = useRef<HTMLDivElement>(null);
33
+ const pointerRef = useRef<SwipePointer | null>(null);
34
+ const suppressClick = useRef(false);
35
+
36
+ const clearSwipePosition = useCallback(() => {
37
+ const pointer = pointerRef.current;
38
+ pointerRef.current = null;
39
+ if (pointer) {
40
+ if (pointer.target.hasPointerCapture?.(pointer.id)) {
41
+ pointer.target.releasePointerCapture(pointer.id);
42
+ }
43
+ onSwipeChange(false);
44
+ }
45
+ suppressClick.current = false;
46
+ containerRef.current?.classList.remove(DISMISS_CLASS, SETTLE_CLASS);
47
+ containerRef.current?.style.removeProperty('transform');
48
+ }, [onSwipeChange]);
49
+
50
+ const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
51
+ if (event.button !== 0 || !event.isPrimary || pointerRef.current) {
52
+ return;
53
+ }
54
+
55
+ clearSwipePosition();
56
+ const target = event.target as Element;
57
+ target.setPointerCapture?.(event.pointerId);
58
+ pointerRef.current = {
59
+ id: event.pointerId,
60
+ target,
61
+ startX: event.clientX,
62
+ startY: event.clientY,
63
+ threshold: event.currentTarget.offsetHeight * SNACKBAR_SWIPE_DISMISS_THRESHOLD,
64
+ };
65
+ onSwipeChange(true);
66
+ };
67
+
68
+ const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
69
+ const pointer = pointerRef.current;
70
+ if (pointer?.id !== event.pointerId) {
71
+ return;
72
+ }
73
+
74
+ const offsetY = event.clientY - pointer.startY;
75
+ const offsetX = event.clientX - pointer.startX;
76
+ if (offsetY > 0 && offsetY > Math.abs(offsetX)) {
77
+ containerRef.current?.style.setProperty('transform', `translateY(${offsetY}px)`);
78
+ } else {
79
+ containerRef.current?.style.removeProperty('transform');
80
+ }
81
+ };
82
+
83
+ const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
84
+ const pointer = pointerRef.current;
85
+ if (pointer?.id !== event.pointerId) {
86
+ return;
87
+ }
88
+
89
+ const offsetY = event.clientY - pointer.startY;
90
+ const offsetX = event.clientX - pointer.startX;
91
+ const container = containerRef.current;
92
+ const bounds = pointer.target.closest('a, button')?.getBoundingClientRect();
93
+ clearSwipePosition();
94
+
95
+ if (offsetY >= pointer.threshold && offsetY > Math.abs(offsetX)) {
96
+ if (container) {
97
+ // Include wrapper padding and the safe area, not only the surface, to clear the viewport.
98
+ container.classList.add(DISMISS_CLASS);
99
+ container.style.setProperty(
100
+ 'transform',
101
+ `translateY(${Math.max(offsetY, container.offsetHeight)}px)`,
102
+ );
103
+ }
104
+ suppressClick.current = true;
105
+ onDismissRequest();
106
+ } else {
107
+ // Capture retargets clicks to the pressed control even when released outside it.
108
+ suppressClick.current =
109
+ !!bounds &&
110
+ (offsetX !== 0 || offsetY !== 0) &&
111
+ (event.clientX < bounds.left ||
112
+ event.clientX > bounds.right ||
113
+ event.clientY < bounds.top ||
114
+ event.clientY > bounds.bottom);
115
+ container?.classList.add(SETTLE_CLASS);
116
+ }
117
+ };
118
+
119
+ const handlePointerCancel = useCallback(
120
+ (event: Pick<PointerEvent, 'pointerId'>) => {
121
+ if (pointerRef.current?.id !== event.pointerId) {
122
+ return;
123
+ }
124
+
125
+ clearSwipePosition();
126
+ containerRef.current?.classList.add(SETTLE_CLASS);
127
+ },
128
+ [clearSwipePosition],
129
+ );
130
+
131
+ useEffect(() => {
132
+ if (visible) {
133
+ clearSwipePosition();
134
+ } else if (pointerRef.current) {
135
+ // An exit starting mid-gesture (timeout, Escape, replacement) ends it without dismissing again.
136
+ handlePointerCancel({ pointerId: pointerRef.current.id });
137
+ }
138
+ }, [visible, clearSwipePosition, handlePointerCancel]);
139
+
140
+ useEffect(() => {
141
+ // Removed descendants report capture loss on their document, not the Snackbar surface.
142
+ const ownerDocument = containerRef.current?.ownerDocument;
143
+ ownerDocument?.addEventListener('lostpointercapture', handlePointerCancel);
144
+ return () => {
145
+ ownerDocument?.removeEventListener('lostpointercapture', handlePointerCancel);
146
+ clearSwipePosition();
147
+ };
148
+ }, [clearSwipePosition, handlePointerCancel]);
149
+
150
+ const handleClickCapture = (event: React.MouseEvent<HTMLDivElement>) => {
151
+ if (suppressClick.current && event.detail > 0) {
152
+ suppressClick.current = false;
153
+ event.preventDefault();
154
+ event.stopPropagation();
155
+ }
156
+ };
157
+
158
+ return {
159
+ clearSwipePosition,
160
+ containerRef,
161
+ handleClickCapture,
162
+ handlePointerCancel,
163
+ handlePointerDown,
164
+ handlePointerMove,
165
+ handlePointerUp,
166
+ };
167
+ };
168
+
169
+ export default useSnackbarSwipeDismiss;
@@ -141,7 +141,7 @@ export interface SelectProps {
141
141
  const defaultClassNames = {};
142
142
 
143
143
  /**
144
- * @deprecated Use `SelectInput` instead (https://storybook.wise.design/?path=/docs/forms-selectinput--docs)
144
+ * @deprecated Use `SelectInput` instead (https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput)
145
145
  */
146
146
  export default function Select({
147
147
  placeholder,
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  import Select, { SelectItem, SelectOptionItem } from '..';
5
5
 
6
6
  /**
7
- * @deprecated Use **`SelectInput`** instead <br/> [https://storybook.wise.design/?path=/docs/forms-selectinput--docs](https://storybook.wise.design/?path=/docs/forms-selectinput--docs)
7
+ * @deprecated Use **`SelectInput`** instead <br/> [https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput](https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput)
8
8
  */
9
9
  const meta: Meta<typeof Select> = {
10
10
  component: Select,
@@ -36,7 +36,9 @@ export type BottomSheetProps = PropsWithChildren<
36
36
  >;
37
37
 
38
38
  /**
39
- * Design guidance: https://docs.wise.design/components/bottom-sheet/
39
+ * Neptune: https://transferwise.github.io/neptune/components/bottom-sheet/
40
+ *
41
+ * Neptune Web: https://transferwise.github.io/neptune-web/components/overlays/BottomSheet
40
42
  *
41
43
  */
42
44
  const BottomSheet = ({ role = 'dialog', ...props }: BottomSheetProps) => {
package/src/i18n/en.json CHANGED
@@ -38,6 +38,7 @@
38
38
  "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefix",
39
39
  "neptune.Select.searchPlaceholder": "Search...",
40
40
  "neptune.SelectInput.noResultsFound": "No results found",
41
+ "neptune.Snackbar.escapeDismissHint": "Press Escape to dismiss.",
41
42
  "neptune.StatusIcon.iconLabel.error": "Error:",
42
43
  "neptune.StatusIcon.iconLabel.information": "Information:",
43
44
  "neptune.StatusIcon.iconLabel.pending": "Pending:",
package/src/index.ts CHANGED
@@ -135,7 +135,7 @@ export type {
135
135
  SelectProps,
136
136
  } from './_deprecated/Select';
137
137
  export type { SlidingPanelProps } from './SlidingPanel';
138
- export type { SnackbarProps, SnackbarProviderProps } from './Snackbar';
138
+ export type { SnackbarCreationProps, SnackbarProviderProps } from './Snackbar';
139
139
  export type { SnackbarContextType } from './Snackbar/SnackbarContext';
140
140
  export type { StatusIconProps, StatusIconSentiment } from './StatusIcon';
141
141
  export type { StickyProps } from './Sticky';
package/src/main.css CHANGED
@@ -31322,75 +31322,138 @@ button.np-link {
31322
31322
  [dir="rtl"] .np-theme-personal .sliding-panel.sliding-panel--border-left {
31323
31323
  box-shadow: inset 1px 0 0 0 var(--color-border-neutral);
31324
31324
  }
31325
- .snackbar {
31326
- text-align: center;
31325
+ .wds-Snackbar {
31326
+ display: flex;
31327
+ justify-content: center;
31328
+ box-sizing: border-box;
31329
+ padding-inline: var(--size-16);
31330
+ padding-block-end: calc(var(--size-16) + env(safe-area-inset-bottom));
31327
31331
  position: fixed;
31328
31332
  inset-block-end: 0;
31329
- inset-inline-start: 0;
31330
- inset-inline-end: 0;
31333
+ inset-inline: 0;
31331
31334
  z-index: 1060;
31332
31335
  }
31333
- .snackbar__text {
31334
- background: #37517e;
31335
- color: #ffffff;
31336
- display: inline-flex;
31337
- border-radius: 2px;
31338
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
31339
- align-items: center;
31340
- justify-content: space-between;
31336
+ @media (min-width: 600px) {
31337
+ .wds-Snackbar {
31338
+ padding-block-end: var(--size-24);
31339
+ }
31340
+ }
31341
+ .wds-Snackbar--swipe-dismiss {
31342
+ transition: transform var(--Snackbar-exit-duration) cubic-bezier(1, 0, 1, 1);
31343
+ }
31344
+ .wds-Snackbar--swipe-settle {
31345
+ transition: transform var(--Snackbar-exit-duration) cubic-bezier(0, 0, 0, 1);
31346
+ }
31347
+ .wds-Snackbar-container {
31348
+ --Prompt-border-radius: var(--radius-medium);
31349
+ --Prompt-gap: var(--size-8);
31350
+ --Prompt-padding-x: var(--size-12);
31351
+ --Prompt-padding-y: var(--size-12);
31352
+ box-sizing: border-box;
31353
+ max-inline-size: 480px;
31341
31354
  text-align: start;
31342
- font-size: var(--wds-font-size-body);
31343
- margin-block: 0;
31344
- margin-inline: var(--size-16);
31345
- padding-block: var(--size-8);
31346
- padding-inline: var(--size-16);
31347
- inline-size: calc(100% - var(--size-32));
31348
- transition: 400ms cubic-bezier(0.215, 0.61, 0.355, 1);
31355
+ cursor: grab;
31356
+ touch-action: pan-x pinch-zoom;
31349
31357
  }
31350
- .np-theme-personal .snackbar__text {
31351
- box-shadow: 0 2px 4px 0 rgba(69, 71, 69, 0.35);
31352
- position: relative;
31353
- background: var(--color-contrast-theme);
31354
- border-radius: var(--radius-small);
31355
- color: var(--color-contrast-overlay);
31358
+ .wds-Snackbar-container .wds-prompt__content-wrapper--with-dismiss {
31359
+ grid-template-columns: auto minmax(0, 1fr);
31356
31360
  }
31357
- .np-theme-personal .snackbar__text::after {
31358
- display: block;
31359
- border-radius: inherit;
31360
- block-size: 100%;
31361
- inline-size: 100%;
31362
- content: "";
31363
- position: absolute;
31364
- inset-inline-start: 0;
31365
- inset-block-start: 0;
31366
- z-index: -1;
31367
- background-color: var(--color-background-neutral);
31361
+ @media (min-width: 600px) {
31362
+ .wds-Snackbar-container .wds-prompt__content-wrapper--with-dismiss {
31363
+ grid-template-columns: auto minmax(0, 1fr) auto;
31364
+ }
31368
31365
  }
31369
- .np-theme-personal--dark .snackbar__text,
31370
- .np-theme-personal--forest-green .snackbar__text {
31371
- box-shadow: none;
31366
+ .wds-Snackbar-container .wds-prompt__media-wrapper {
31367
+ display: flex;
31368
+ align-items: center;
31369
+ min-block-size: max(var(--size-32), 1lh);
31370
+ padding-block-start: 0;
31371
+ color: var(--color-sentiment-content-primary);
31372
+ }
31373
+ .wds-Snackbar-container .wds-prompt__media-wrapper:has(+ .wds-Snackbar__content .wds-Snackbar__action) {
31374
+ min-block-size: max(var(--size-32), calc(1lh + var(--size-5) * 2));
31375
+ }
31376
+ .wds-Snackbar__media-asset {
31377
+ display: flex;
31378
+ }
31379
+ .wds-Snackbar__media-asset svg {
31380
+ inline-size: var(--size-24);
31381
+ block-size: var(--size-24);
31382
+ }
31383
+ .wds-Snackbar__process-indicator .process-circle {
31384
+ stroke: currentColor;
31385
+ }
31386
+ .wds-Snackbar-container .wds-prompt__dismiss {
31387
+ display: none;
31388
+ align-self: flex-start;
31389
+ margin-block-start: calc(var(--size-4) - 1px);
31372
31390
  }
31373
31391
  @media (min-width: 600px) {
31374
- .snackbar__text {
31375
- inline-size: auto;
31376
- max-inline-size: 600px;
31392
+ .wds-Snackbar-container .wds-prompt__dismiss {
31393
+ display: flex;
31377
31394
  }
31378
31395
  }
31379
- .snackbar__text__action {
31396
+ .wds-Snackbar__action {
31380
31397
  flex-shrink: 0;
31381
- margin-inline-start: var(--size-16);
31382
31398
  }
31383
- .snackbar__text-container-enter,
31384
- .snackbar__text-container-exit {
31385
- transform: translateY(calc(100% + 10px));
31399
+ .wds-Snackbar__content {
31400
+ display: flex;
31401
+ align-items: flex-start;
31402
+ gap: var(--size-8);
31403
+ flex: 1 0 0;
31404
+ min-block-size: var(--size-32);
31405
+ min-inline-size: 0;
31386
31406
  }
31387
- .snackbar__text-container-enter-active {
31388
- transform: translateY(-16px);
31389
- transform: translateY(calc(-16px - env(safe-area-inset-bottom)));
31407
+ .wds-Snackbar__message {
31408
+ display: flex;
31409
+ flex-direction: column;
31410
+ justify-content: center;
31411
+ inline-size: auto;
31412
+ flex: 1 0 0;
31413
+ min-inline-size: 0;
31414
+ align-self: stretch;
31390
31415
  }
31391
- @media (min-width: 600px) {
31392
- .snackbar__text-container-enter-active {
31393
- transform: translateY(-24px);
31416
+ .wds-Snackbar__body {
31417
+ display: block;
31418
+ }
31419
+ .wds-Snackbar-container-appear,
31420
+ .wds-Snackbar-container-enter {
31421
+ opacity: 0;
31422
+ transform: translateY(80px) scale(0.5);
31423
+ }
31424
+ .wds-Snackbar-container-appear-active,
31425
+ .wds-Snackbar-container-enter-active {
31426
+ opacity: 1;
31427
+ transform: translateY(0) scale(1);
31428
+ transition: opacity 150ms linear, transform var(--Snackbar-enter-duration) cubic-bezier(0, 0, 0, 1);
31429
+ }
31430
+ .wds-Snackbar-container-exit {
31431
+ opacity: 1;
31432
+ transform: translateY(0) scale(1);
31433
+ }
31434
+ .wds-Snackbar-container-exit-active {
31435
+ opacity: 0;
31436
+ transform: translateY(80px) scale(0.5);
31437
+ transition: opacity 50ms linear 50ms, transform var(--Snackbar-exit-duration) cubic-bezier(1, 0, 1, 1);
31438
+ }
31439
+ @media (prefers-reduced-motion: reduce) {
31440
+ .wds-Snackbar--swipe-dismiss,
31441
+ .wds-Snackbar--swipe-settle {
31442
+ transition: none;
31443
+ }
31444
+ .wds-Snackbar-container-appear,
31445
+ .wds-Snackbar-container-appear-active,
31446
+ .wds-Snackbar-container-enter,
31447
+ .wds-Snackbar-container-enter-active,
31448
+ .wds-Snackbar-container-exit {
31449
+ opacity: 1;
31450
+ transform: none;
31451
+ transition: none;
31452
+ }
31453
+ .wds-Snackbar-container-exit-active {
31454
+ opacity: 0;
31455
+ transform: none;
31456
+ transition: none;
31394
31457
  }
31395
31458
  }
31396
31459
  .status-circle.negative,
@@ -1,122 +0,0 @@
1
- import { Meta, Source } from '@storybook/addon-docs/blocks';
2
-
3
- <Meta title="Prompts/CriticalBanner/Developer Guide" tags={['new']} />
4
-
5
- # Developer Guide
6
-
7
- **Note:** Any usages of `Layout`, `Flow` or `HashRouterFlow` rely on the `global-critical-banner` feature flag being enabled. Whilst this flag is currently disabled in production, it's recommended to test with this flag enabled in staging, to ensure the critical banner behaves as expected for your users.
8
-
9
- Typically `CriticalBanner` should be used in one of three ways. Pick the one that matches the page or flow you're building and avoid using CriticalBanner as a standalone component.
10
-
11
- - `Layout` via `@transferwise/navigation-ui` - Fetched automatically from navigation-service (for non-flow layouts)
12
- - `Flow` / `HashRouterFlow` via `@transferwise/flow` - Fetched automatically from navigation-service (for flows)
13
- - `FlowNavigation` via `@transferwise/components` - Passed in by you through the `banner` prop (for custom solutions)
14
-
15
- If your page uses `Layout`, `Flow` or `HashRouterFlow`, you don't need to render `CriticalBanner` yourself. These components fetch the banner, cache it and render it for you automatically.
16
-
17
- ## Layout from @transferwise/navigation-ui
18
-
19
- `Layout` has no banner prop. It fetches the critical banner from navigation-service on the client and renders it above the page content.
20
-
21
- Requirements:
22
-
23
- - `@transferwise/navigation-ui` `4.46.0` or later
24
- - The page must use the composable layout, and `global-critical-banner` feature flags in navigation-service control this. The legacy layout doesn't render the banner.
25
-
26
- <Source
27
- code={`import { Layout, fetchNavigationData, type NavigationData } from '@transferwise/navigation-ui';
28
-
29
- // Optional: fetch initial navigation data server side, so the banner cache is scoped correctly.
30
- export const getServerSideProps = async () => {
31
- const navigationData = await fetchNavigationData({
32
- httpClient,
33
- navigationServiceUrl: process.env.NAVIGATION_SERVICE_URL,
34
- });
35
-
36
- return { props: { navigationData } };
37
- };
38
-
39
- const Page = ({ navigationData }: { navigationData: NavigationData }) => (
40
- <Layout navigationData={navigationData} onMixpanelEvent={trackEvent}>
41
- { /* Page content */ }
42
- </Layout>
43
- );
44
- `}
45
- language="typescript"
46
- dark
47
- />
48
-
49
- `Layout` also handles:
50
-
51
- - saving the collapsed or expanded state when the user toggles the banner
52
- - caching the banner for the session, so the entry animation only plays for new or changed banners
53
- - sending Mixpanel events for toggles and action clicks through `onMixpanelEvent`
54
-
55
-
56
- ## Flow / HashRouterFlow from @transferwise/flow
57
-
58
- `Flow` and `HashRouterFlow`, from `@transferwise/flow` render `FlowNavigationWrapper` from `@transferwise/navigation-ui`. It fetches, caches and renders the critical banner the same way `Layout` does. You don't need to pass a banner, but do require `@transferwise/navigation-ui` as a dependency.
59
-
60
- Requirements:
61
-
62
- - `@transferwise/flow` `13.0.0` or later
63
- - `@transferwise/navigation-ui` `4.51.0` or later, which is a required peer dependency
64
-
65
- You can pass two optional properties in the `navigation` options:
66
-
67
- - `navigationData`: initial navigation data, usually fetched server side, so the banner cache is scoped correctly. If you leave it out, the banner is fetched on the client.
68
- - `onMixpanelEvent`: called when the user toggles the banner or clicks its action.
69
-
70
- <Source
71
- code={`import { HashRouterFlow } from '@transferwise/flow';
72
- import type { NavigationData } from '@transferwise/navigation-ui';
73
-
74
- const SendFlow = ({ navigationData }: { navigationData?: NavigationData }) => (
75
- <HashRouterFlow
76
- steps={steps}
77
- trackEvent={trackEvent}
78
- navigation={{
79
- profile,
80
- onClose,
81
- navigationData,
82
- onMixpanelEvent: trackEvent,
83
- }}
84
- />
85
- );
86
- `}
87
- language="typescript"
88
- dark
89
- />
90
-
91
- ## FlowNavigation from @transferwise/components
92
-
93
- `FlowNavigation` from `@transferwise/components` renders a critical banner above the navigation when you pass a `banner` prop. Use this if you build your flow with `FlowNavigation` directly. You're responsible for getting the banner data.
94
-
95
- `banner` accepts either:
96
-
97
- - `CriticalBannerProps`, the same props `CriticalBanner` takes
98
- - the legacy `CriticalBannerData` shape returned by navigation-service. `FlowNavigation` maps it to `CriticalBannerProps` for you.
99
-
100
- <Source
101
- code={`import { FlowNavigation, type CriticalBannerProps } from '@transferwise/components';
102
-
103
- const banner: CriticalBannerProps = {
104
- sentiment: 'negative',
105
- title: 'Action required',
106
- description: 'Please verify your identity to continue with this transfer.',
107
- action: { label: 'Verify now', onClick: handleVerify },
108
- };
109
-
110
- <FlowNavigation
111
- banner={banner}
112
- steps={steps}
113
- activeStep={activeStep}
114
- avatar={avatar}
115
- onClose={onClose}
116
- />;
117
- `}
118
- language="typescript"
119
- dark
120
- />
121
-
122
- Requires `@transferwise/components` `46.162.0` or later. Support for the legacy `CriticalBannerData` shape was added in `46.164.0`.