@transferwise/components 46.154.0 → 46.155.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 (184) hide show
  1. package/build/AvatarLayout/AvatarLayout.js.map +1 -1
  2. package/build/AvatarLayout/AvatarLayout.mjs.map +1 -1
  3. package/build/AvatarView/AvatarView.js.map +1 -1
  4. package/build/AvatarView/AvatarView.mjs.map +1 -1
  5. package/build/AvatarView/Dot/Dot.js.map +1 -1
  6. package/build/AvatarView/Dot/Dot.mjs.map +1 -1
  7. package/build/Body/Body.js.map +1 -1
  8. package/build/Body/Body.mjs.map +1 -1
  9. package/build/Chips/Chip/Chip.js.map +1 -1
  10. package/build/Chips/Chip/Chip.mjs.map +1 -1
  11. package/build/DefinitionList/DefinitionList.js +2 -1
  12. package/build/DefinitionList/DefinitionList.js.map +1 -1
  13. package/build/DefinitionList/DefinitionList.mjs +2 -1
  14. package/build/DefinitionList/DefinitionList.mjs.map +1 -1
  15. package/build/Display/Display.js.map +1 -1
  16. package/build/Display/Display.mjs.map +1 -1
  17. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js +2 -2
  18. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js.map +1 -1
  19. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs +1 -1
  20. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs.map +1 -1
  21. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js +4 -4
  22. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js.map +1 -1
  23. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs +1 -1
  24. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs.map +1 -1
  25. package/build/ExpressiveMoneyInput/Chevron/Chevron.js +2 -2
  26. package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
  27. package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +1 -1
  28. package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
  29. package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js.map +1 -1
  30. package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs.map +1 -1
  31. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js +3 -3
  32. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
  33. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs +1 -1
  34. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
  35. package/build/ExpressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
  36. package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
  37. package/build/IconButton/IconButton.js.map +1 -1
  38. package/build/IconButton/IconButton.mjs.map +1 -1
  39. package/build/Link/Link.js.map +1 -1
  40. package/build/Link/Link.mjs.map +1 -1
  41. package/build/ListItem/Navigation/ListItemNavigation.js +4 -2
  42. package/build/ListItem/Navigation/ListItemNavigation.js.map +1 -1
  43. package/build/ListItem/Navigation/ListItemNavigation.mjs +4 -2
  44. package/build/ListItem/Navigation/ListItemNavigation.mjs.map +1 -1
  45. package/build/Nudge/Nudge.js +11 -2
  46. package/build/Nudge/Nudge.js.map +1 -1
  47. package/build/Nudge/Nudge.mjs +12 -3
  48. package/build/Nudge/Nudge.mjs.map +1 -1
  49. package/build/Sticky/Sticky.js +1 -0
  50. package/build/Sticky/Sticky.js.map +1 -1
  51. package/build/Sticky/Sticky.mjs +1 -0
  52. package/build/Sticky/Sticky.mjs.map +1 -1
  53. package/build/Title/Title.js +1 -0
  54. package/build/Title/Title.js.map +1 -1
  55. package/build/Title/Title.mjs +1 -0
  56. package/build/Title/Title.mjs.map +1 -1
  57. package/build/_deprecated/Select/Option/Option.js.map +1 -1
  58. package/build/_deprecated/Select/Option/Option.mjs.map +1 -1
  59. package/build/_deprecated/Select/SearchBox/SearchBox.js.map +1 -1
  60. package/build/_deprecated/Select/SearchBox/SearchBox.mjs.map +1 -1
  61. package/build/common/Circle/Circle.js.map +1 -1
  62. package/build/common/Circle/Circle.mjs.map +1 -1
  63. package/build/main.css +36 -1
  64. package/build/styles/DefinitionList/DefinitionList.css +30 -0
  65. package/build/styles/Nudge/Nudge.css +1 -1
  66. package/build/styles/Sticky/Sticky.css +4 -0
  67. package/build/styles/main.css +36 -1
  68. package/build/types/AvatarLayout/AvatarLayout.d.ts +2 -2
  69. package/build/types/AvatarLayout/AvatarLayout.d.ts.map +1 -1
  70. package/build/types/AvatarLayout/index.d.ts +1 -1
  71. package/build/types/AvatarLayout/index.d.ts.map +1 -1
  72. package/build/types/AvatarView/AvatarView.d.ts +2 -2
  73. package/build/types/AvatarView/AvatarView.d.ts.map +1 -1
  74. package/build/types/AvatarView/Dot/Dot.d.ts +1 -1
  75. package/build/types/AvatarView/Dot/Dot.d.ts.map +1 -1
  76. package/build/types/AvatarView/index.d.ts +1 -1
  77. package/build/types/AvatarView/index.d.ts.map +1 -1
  78. package/build/types/Body/Body.d.ts +11 -0
  79. package/build/types/Body/Body.d.ts.map +1 -1
  80. package/build/types/Body/index.d.ts +1 -0
  81. package/build/types/Body/index.d.ts.map +1 -1
  82. package/build/types/Chips/Chip/Chip.d.ts +2 -2
  83. package/build/types/Chips/Chip/Chip.d.ts.map +1 -1
  84. package/build/types/Chips/index.d.ts +1 -1
  85. package/build/types/Chips/index.d.ts.map +1 -1
  86. package/build/types/DefinitionList/DefinitionList.d.ts.map +1 -1
  87. package/build/types/Display/Display.d.ts +2 -2
  88. package/build/types/Display/Display.d.ts.map +1 -1
  89. package/build/types/Display/index.d.ts +1 -0
  90. package/build/types/Display/index.d.ts.map +1 -1
  91. package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts +3 -3
  92. package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts.map +1 -1
  93. package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts +3 -3
  94. package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts.map +1 -1
  95. package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts +4 -4
  96. package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
  97. package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts +1 -1
  98. package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
  99. package/build/types/ExpressiveMoneyInput/index.d.ts +1 -1
  100. package/build/types/ExpressiveMoneyInput/index.d.ts.map +1 -1
  101. package/build/types/IconButton/IconButton.d.ts +1 -1
  102. package/build/types/IconButton/IconButton.d.ts.map +1 -1
  103. package/build/types/IconButton/index.d.ts +1 -1
  104. package/build/types/IconButton/index.d.ts.map +1 -1
  105. package/build/types/Link/Link.d.ts +2 -2
  106. package/build/types/Link/Link.d.ts.map +1 -1
  107. package/build/types/Link/index.d.ts +1 -1
  108. package/build/types/Link/index.d.ts.map +1 -1
  109. package/build/types/ListItem/ListItem.d.ts +1 -1
  110. package/build/types/ListItem/Navigation/ListItemNavigation.d.ts +12 -2
  111. package/build/types/ListItem/Navigation/ListItemNavigation.d.ts.map +1 -1
  112. package/build/types/Nudge/Nudge.d.ts +28 -5
  113. package/build/types/Nudge/Nudge.d.ts.map +1 -1
  114. package/build/types/Nudge/index.d.ts +1 -1
  115. package/build/types/Nudge/index.d.ts.map +1 -1
  116. package/build/types/Sticky/Sticky.d.ts.map +1 -1
  117. package/build/types/Title/Title.d.ts +12 -2
  118. package/build/types/Title/Title.d.ts.map +1 -1
  119. package/build/types/Title/index.d.ts +1 -0
  120. package/build/types/Title/index.d.ts.map +1 -1
  121. package/build/types/_deprecated/Select/Option/Option.d.ts +2 -2
  122. package/build/types/_deprecated/Select/Option/Option.d.ts.map +1 -1
  123. package/build/types/common/Circle/Circle.d.ts +1 -1
  124. package/build/types/common/Circle/Circle.d.ts.map +1 -1
  125. package/build/types/common/Circle/index.d.ts +1 -1
  126. package/build/types/common/Circle/index.d.ts.map +1 -1
  127. package/build/types/index.d.ts +4 -0
  128. package/build/types/index.d.ts.map +1 -1
  129. package/package.json +5 -5
  130. package/src/AvatarLayout/AvatarLayout.tsx +3 -3
  131. package/src/AvatarLayout/index.ts +1 -1
  132. package/src/AvatarView/AvatarView.tsx +4 -4
  133. package/src/AvatarView/Dot/Dot.tsx +1 -1
  134. package/src/AvatarView/index.ts +1 -1
  135. package/src/Body/Body.tsx +2 -2
  136. package/src/Body/index.ts +1 -0
  137. package/src/Chips/Chip/Chip.tsx +2 -2
  138. package/src/Chips/index.ts +1 -1
  139. package/src/DefinitionList/DefinitionList.css +30 -0
  140. package/src/DefinitionList/DefinitionList.less +24 -0
  141. package/src/DefinitionList/DefinitionList.tsx +1 -0
  142. package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +168 -0
  143. package/src/Display/Display.tsx +2 -2
  144. package/src/Display/index.ts +1 -0
  145. package/src/Drawer/Drawer.test.tsx +2 -3
  146. package/src/ExpressiveMoneyInput/AmountInput/AmountInput.tsx +4 -4
  147. package/src/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.tsx +1 -1
  148. package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +1 -1
  149. package/src/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.tsx +3 -3
  150. package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.test.tsx +1 -1
  151. package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.tsx +5 -8
  152. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.story.tsx +1 -1
  153. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.test.story.tsx +1 -1
  154. package/src/ExpressiveMoneyInput/hooks/useInputStyle.ts +1 -1
  155. package/src/ExpressiveMoneyInput/index.ts +1 -1
  156. package/src/IconButton/IconButton.tsx +2 -2
  157. package/src/IconButton/_stories/IconButton.story.tsx +8 -8
  158. package/src/IconButton/index.ts +1 -1
  159. package/src/Link/Link.tsx +2 -2
  160. package/src/Link/index.ts +1 -1
  161. package/src/ListItem/Navigation/ListItemNavigation.test.tsx +23 -0
  162. package/src/ListItem/Navigation/ListItemNavigation.tsx +17 -3
  163. package/src/ListItem/Navigation/_stories/ListItemNavigation.story.tsx +33 -0
  164. package/src/Nudge/Nudge.css +1 -1
  165. package/src/Nudge/Nudge.less +1 -1
  166. package/src/Nudge/Nudge.test.tsx +43 -0
  167. package/src/Nudge/Nudge.tsx +43 -8
  168. package/src/Nudge/index.ts +1 -1
  169. package/src/Sticky/Sticky.css +4 -0
  170. package/src/Sticky/Sticky.less +6 -0
  171. package/src/Sticky/Sticky.test.tsx +1 -0
  172. package/src/Sticky/Sticky.tsx +1 -0
  173. package/src/Sticky/_stories/Sticky.test.story.tsx +177 -0
  174. package/src/Title/Title.tsx +25 -20
  175. package/src/Title/index.ts +1 -0
  176. package/src/_deprecated/LegacyListItem/_stories/LegacyListItem.story.tsx +2 -3
  177. package/src/_deprecated/Select/Option/Option.test.tsx +1 -1
  178. package/src/_deprecated/Select/Option/Option.tsx +2 -2
  179. package/src/_deprecated/Select/SearchBox/SearchBox.tsx +2 -2
  180. package/src/common/Circle/Circle.tsx +2 -2
  181. package/src/common/Circle/index.ts +1 -1
  182. package/src/index.ts +4 -0
  183. package/src/main.css +36 -1
  184. package/src/main.less +1 -0
@@ -25,7 +25,7 @@ const getLocalStorage = (): string[] => {
25
25
  return storage.map((item) => String(item));
26
26
  }
27
27
  }
28
- } catch (error) {}
28
+ } catch {}
29
29
 
30
30
  return [];
31
31
  };
@@ -47,18 +47,41 @@ type MediaNameType =
47
47
  | `${Assets.DOCUMENTS}`;
48
48
 
49
49
  type BaseProps = {
50
- /** @deprecated Use `mediaName` property instead. */
50
+ /**
51
+ * @deprecated Use `mediaName` instead.
52
+ * Legacy custom media content is no longer used by `Nudge`.
53
+ */
51
54
  media?: ReactNode;
52
- /** Media name */
55
+ /**
56
+ * Illustration asset name shown at the start of the nudge.
57
+ */
53
58
  mediaName?: MediaNameType;
59
+ /** Main nudge message content. */
54
60
  title: ReactNode;
61
+ /**
62
+ * Legacy CTA label content.
63
+ * Rendered only when `action` is not provided.
64
+ */
55
65
  link?: ReactNode;
66
+ /**
67
+ * Legacy CTA destination for `link`.
68
+ * Ignored when `action` is provided.
69
+ */
56
70
  href?: string;
71
+ /**
72
+ * Legacy click handler for `link`.
73
+ * Ignored when `action` is provided.
74
+ */
57
75
  onClick?: (event?: MouseEvent<HTMLSpanElement>) => void;
58
76
  /** Fired when the user clicks on close button */
59
77
  onDismiss?: () => void;
60
- /** An optional call to action to sit under the main body of the nudge. If your label is short, use aria-label to provide more context */
78
+ /**
79
+ * Preferred CTA configuration rendered under the nudge body.
80
+ * Single CTA rule: when `action` is provided, legacy `link`/`href` is not rendered.
81
+ * If your label is short, use `aria-label` to provide more context.
82
+ */
61
83
  action?: AlertAction;
84
+ /** Optional class name applied to the root nudge container. */
62
85
  className?: string;
63
86
  };
64
87
 
@@ -81,7 +104,7 @@ export interface RequiredPersistProps extends BaseProps {
81
104
  isPreviouslyDismissed?: (value: boolean) => void;
82
105
  }
83
106
 
84
- export type Props = OptionalId | RequiredPersistProps;
107
+ export type NudgeProps = OptionalId | RequiredPersistProps;
85
108
 
86
109
  const Nudge = ({
87
110
  mediaName,
@@ -95,8 +118,21 @@ const Nudge = ({
95
118
  id,
96
119
  className,
97
120
  action,
98
- }: Props) => {
121
+ }: NudgeProps) => {
99
122
  const intl = useIntl();
123
+ const hasLegacyLink = Boolean(link);
124
+ const hasAction = action != null;
125
+ const shouldRenderLegacyLink = hasLegacyLink && !hasAction;
126
+
127
+ useEffect(() => {
128
+ if (process.env.NODE_ENV !== 'production' && hasAction && (hasLegacyLink || href != null)) {
129
+ // eslint-disable-next-line no-console -- Intentional developer warning for conflicting API usage.
130
+ console.warn(
131
+ 'Nudge received both action and link/href props. Rendering action only. Remove link/href to silence this warning.',
132
+ );
133
+ }
134
+ }, [hasAction, hasLegacyLink, href]);
135
+
100
136
  const getIsDismissed = useCallback(
101
137
  () => (persistDismissal && id ? !!getLocalStorage()?.find((item) => item === id) : false),
102
138
  [persistDismissal, id],
@@ -166,8 +202,7 @@ const Nudge = ({
166
202
  <Body type={Typography.BODY_LARGE} className={clsx('wds-nudge-body')}>
167
203
  {title}
168
204
  </Body>
169
- {/* Merge these two Link instances into one */}
170
- {link && (
205
+ {shouldRenderLegacyLink && (
171
206
  <Link
172
207
  href={href}
173
208
  type={Typography.LINK_LARGE}
@@ -1,7 +1,7 @@
1
1
  export { default } from './Nudge';
2
2
 
3
3
  export type {
4
- Props as NudgeProps,
4
+ NudgeProps,
5
5
  OptionalId as NudgeOptionalId,
6
6
  RequiredPersistProps as NudgeRequiredPersistProps,
7
7
  } from './Nudge';
@@ -0,0 +1,4 @@
1
+ .wds-sticky.sliding-panel--open-bottom {
2
+ padding-bottom: env(safe-area-inset-bottom, 0px);
3
+ padding-bottom: var(--wds-sticky-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
4
+ }
@@ -0,0 +1,6 @@
1
+ .wds-sticky.sliding-panel--open-bottom {
2
+ padding-bottom: var(
3
+ --wds-sticky-safe-area-inset-bottom,
4
+ env(safe-area-inset-bottom, 0px)
5
+ );
6
+ }
@@ -21,6 +21,7 @@ describe('Sticky', () => {
21
21
 
22
22
  test('renders with default props', () => {
23
23
  expect(screen.getByTestId('test-sticky-panel')).toBeVisible();
24
+ expect(screen.getByTestId('test-sticky-panel')).toHaveClass('wds-sticky');
24
25
  expect(screen.getByTestId('test-sticky-panel')).toHaveClass('sliding-panel--open-bottom');
25
26
  expect(screen.getByText('Test Content')).toBeInTheDocument();
26
27
  });
@@ -46,6 +46,7 @@ const Sticky: FunctionComponent<StickyProps> = ({
46
46
  }: StickyProps) => {
47
47
  return (
48
48
  <SlidingPanel
49
+ className="wds-sticky"
49
50
  testId={testId}
50
51
  open={open}
51
52
  position={position}
@@ -0,0 +1,177 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { Card, Home, Payments, Recipients } from '@transferwise/icons';
3
+ import type { CSSProperties } from 'react';
4
+ import { useState } from 'react';
5
+ import { expect, userEvent, within } from 'storybook/test';
6
+
7
+ import { withVariantConfig } from '../../../.storybook/helpers';
8
+ import Button from '../../Button';
9
+ import { wait } from '../../test-utils/wait';
10
+ import Sticky from '../Sticky';
11
+
12
+ const SAFE_AREA_INSET = 64;
13
+ const DEMO_HEIGHT = 400;
14
+ const DEMO_WIDTH = 390;
15
+ const SAFE_AREA_VIEWPORT = 'safe-area-demo';
16
+
17
+ type SafeAreaDemoStyle = CSSProperties & {
18
+ '--wds-sticky-safe-area-inset-bottom': string;
19
+ };
20
+
21
+ const safeAreaDemoStyle: SafeAreaDemoStyle = {
22
+ '--wds-sticky-safe-area-inset-bottom': `${SAFE_AREA_INSET}px`,
23
+ background: 'var(--color-background-screen)',
24
+ height: DEMO_HEIGHT,
25
+ overflow: 'hidden',
26
+ };
27
+
28
+ const navigationItems = [
29
+ { Icon: Home, label: 'Home' },
30
+ { Icon: Card, label: 'Card' },
31
+ { Icon: Recipients, label: 'Recipients' },
32
+ { Icon: Payments, label: 'Payments' },
33
+ ] as const;
34
+
35
+ function SafeAreaBottomBar() {
36
+ return (
37
+ <nav
38
+ aria-label="Safe area bottom bar"
39
+ data-testid="safe-area-bottom-bar"
40
+ style={{
41
+ alignItems: 'center',
42
+ background: 'var(--color-background-elevated)',
43
+ borderTop: '1px solid var(--color-border-neutral)',
44
+ bottom: 0,
45
+ display: 'flex',
46
+ height: SAFE_AREA_INSET,
47
+ justifyContent: 'space-around',
48
+ left: 0,
49
+ padding: '0 8px',
50
+ position: 'fixed',
51
+ right: 0,
52
+ zIndex: 1031,
53
+ }}
54
+ >
55
+ {navigationItems.map(({ Icon, label }) => (
56
+ <span
57
+ key={label}
58
+ style={{
59
+ alignItems: 'center',
60
+ color: 'var(--color-content-primary)',
61
+ display: 'flex',
62
+ flexDirection: 'column',
63
+ fontSize: 11,
64
+ gap: 2,
65
+ }}
66
+ >
67
+ <Icon size={24} />
68
+ {label}
69
+ </span>
70
+ ))}
71
+ </nav>
72
+ );
73
+ }
74
+
75
+ function SafeAreaDemo() {
76
+ const [open, setOpen] = useState(false);
77
+
78
+ return (
79
+ <main style={safeAreaDemoStyle}>
80
+ <section style={{ maxWidth: 520, padding: 32 }}>
81
+ <h1 style={{ fontSize: 32, lineHeight: 1.15, margin: '0 0 16px' }}>
82
+ Sticky respects safe area at bottom of screen
83
+ </h1>
84
+ <Button v2 onClick={() => setOpen((currentOpen) => !currentOpen)}>
85
+ {open ? 'Hide Sticky' : 'Show Sticky'}
86
+ </Button>
87
+ </section>
88
+
89
+ <SafeAreaBottomBar />
90
+
91
+ <Sticky open={open} position="bottom" testId="safe-area-sticky">
92
+ <div className="p-a-2" data-testid="sticky-content">
93
+ Sticky
94
+ </div>
95
+ </Sticky>
96
+ </main>
97
+ );
98
+ }
99
+
100
+ const meta = {
101
+ component: Sticky,
102
+ parameters: {
103
+ controls: { disable: true },
104
+ layout: 'fullscreen',
105
+ },
106
+ tags: ['!autodocs', '!manifest'],
107
+ title: 'Dialogs/Sticky/Tests',
108
+ } satisfies Meta<typeof Sticky>;
109
+
110
+ export default meta;
111
+
112
+ type Story = StoryObj<typeof Sticky>;
113
+
114
+ const SafeAreaRegression: Story = {
115
+ parameters: {
116
+ chromatic: { delay: 500 },
117
+ viewport: {
118
+ options: {
119
+ [SAFE_AREA_VIEWPORT]: {
120
+ name: 'Safe area demo',
121
+ styles: { height: `${DEMO_HEIGHT}px`, width: `${DEMO_WIDTH}px` },
122
+ type: 'mobile',
123
+ },
124
+ },
125
+ },
126
+ },
127
+ globals: {
128
+ viewport: SAFE_AREA_VIEWPORT,
129
+ },
130
+ play: async ({ canvasElement, step }) => {
131
+ const canvas = within(canvasElement);
132
+
133
+ const expectStickyAboveSafeArea = async () => {
134
+ await wait(400);
135
+
136
+ const sticky = canvas.getByTestId('safe-area-sticky');
137
+ const stickyContent = canvas.getByTestId('sticky-content');
138
+ const safeArea = canvas.getByTestId('safe-area-bottom-bar');
139
+
140
+ await expect(sticky).toBeVisible();
141
+ await expect(window.getComputedStyle(sticky).paddingBottom).toBe(`${SAFE_AREA_INSET}px`);
142
+ await expect(Math.round(stickyContent.getBoundingClientRect().bottom)).toBe(
143
+ Math.round(safeArea.getBoundingClientRect().top),
144
+ );
145
+ await expect(stickyContent.getBoundingClientRect().bottom).toBeLessThanOrEqual(
146
+ safeArea.getBoundingClientRect().top,
147
+ );
148
+ };
149
+
150
+ await step('Open Sticky from the top button', async () => {
151
+ await userEvent.click(canvas.getByRole('button', { name: /^show sticky$/i }));
152
+ });
153
+
154
+ await step('Keep Sticky above the bottom safe area', expectStickyAboveSafeArea);
155
+
156
+ await step('Toggle Sticky closed and open again', async () => {
157
+ const hideStickyButton = canvas.getByRole('button', { name: /^hide sticky$/i });
158
+
159
+ await userEvent.click(hideStickyButton);
160
+ await wait(400);
161
+ await expect(canvas.queryByTestId('safe-area-sticky')).not.toBeInTheDocument();
162
+
163
+ await userEvent.click(canvas.getByRole('button', { name: /^show sticky$/i }));
164
+ await expectStickyAboveSafeArea();
165
+ });
166
+ },
167
+ render: () => <SafeAreaDemo />,
168
+ };
169
+
170
+ /**
171
+ * Reproduces the MobileNav replacement from DS-7193 with a fixed bottom safe area.
172
+ * It verifies that Sticky clears the inset.
173
+ */
174
+ export const MobileNavSafeArea: Story = {
175
+ ...SafeAreaRegression,
176
+ ...withVariantConfig(['mobile'], SafeAreaRegression),
177
+ };
@@ -1,5 +1,5 @@
1
1
  import { clsx } from 'clsx';
2
- import React, { forwardRef, LabelHTMLAttributes, LiHTMLAttributes } from 'react';
2
+ import React, { LabelHTMLAttributes, LiHTMLAttributes } from 'react';
3
3
 
4
4
  import { TitleTypes, Typography, Heading } from '../common';
5
5
 
@@ -13,7 +13,9 @@ const titleTypeMapping = {
13
13
  [Typography.TITLE_GROUP]: 'h5',
14
14
  } as const;
15
15
 
16
- type Props = LabelHTMLAttributes<HTMLHeadingElement | HTMLSpanElement | HTMLLabelElement> &
16
+ export type TitleProps = LabelHTMLAttributes<
17
+ HTMLHeadingElement | HTMLSpanElement | HTMLLabelElement
18
+ > &
17
19
  LiHTMLAttributes<HTMLLIElement> & {
18
20
  /**
19
21
  * Default value will based one `type` prop
@@ -25,29 +27,32 @@ type Props = LabelHTMLAttributes<HTMLHeadingElement | HTMLSpanElement | HTMLLabe
25
27
  type?: TitleTypes;
26
28
  };
27
29
 
28
- const Title = React.forwardRef<HTMLHeadingElement | HTMLSpanElement | HTMLLabelElement, Props>(
29
- ({ as, type = DEFAULT_TYPE, className, ...props }, ref) => {
30
- const mapping = titleTypeMapping[type as keyof typeof titleTypeMapping];
31
- const isTypeSupported = mapping !== undefined;
32
- if (isTypeSupported) {
33
- const HeaderTag = as ?? mapping;
34
- return (
35
- <HeaderTag
36
- ref={ref as React.Ref<any>}
37
- {...props}
38
- className={clsx(`np-text-${type}`, className)}
39
- />
40
- );
41
- }
42
- const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
30
+ const Title = React.forwardRef<
31
+ HTMLHeadingElement | HTMLSpanElement | HTMLLabelElement | HTMLLIElement | HTMLLegendElement,
32
+ TitleProps
33
+ >(({ as, type = DEFAULT_TYPE, className, ...props }, ref) => {
34
+ const mapping = titleTypeMapping[type as keyof typeof titleTypeMapping];
35
+ const isTypeSupported = mapping !== undefined;
36
+ if (isTypeSupported) {
37
+ const HeaderTag = as ?? mapping;
43
38
  return (
44
39
  <HeaderTag
45
40
  ref={ref as React.Ref<any>}
46
41
  {...props}
47
- className={clsx(`np-text-${DEFAULT_TYPE}`, className)}
42
+ className={clsx(`np-text-${type}`, className)}
48
43
  />
49
44
  );
50
- },
51
- );
45
+ }
46
+ const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
47
+ return (
48
+ <HeaderTag
49
+ ref={ref as React.Ref<any>}
50
+ {...props}
51
+ className={clsx(`np-text-${DEFAULT_TYPE}`, className)}
52
+ />
53
+ );
54
+ });
55
+
56
+ Title.displayName = 'Title';
52
57
 
53
58
  export default Title;
@@ -1 +1,2 @@
1
1
  export { default } from './Title';
2
+ export type { TitleProps } from './Title';
@@ -1,12 +1,11 @@
1
1
  import { action } from 'storybook/actions';
2
2
  import { Documents, FastFlag } from '@transferwise/icons';
3
- import { ComponentProps } from 'react';
4
3
 
5
4
  import { Button, IconButton, List } from '../../..';
6
5
  import AvatarView from '../../../AvatarView';
7
6
  import Info from '../../../Info';
8
7
  import Title from '../../../Title/Title';
9
- import LegacyListItem from '..';
8
+ import LegacyListItem, { type LegacyListItemProps } from '..';
10
9
 
11
10
  /**
12
11
  * > ⚠️ This component is **deprecated** and superseded by the [new ListItem component](?path=/docs/content-listitem--docs)
@@ -22,7 +21,7 @@ const Template = ({
22
21
  title = 'Wise',
23
22
  value = 'We’re building the world’s most international account',
24
23
  ...props
25
- }: Partial<ComponentProps<typeof LegacyListItem>>) => {
24
+ }: Partial<LegacyListItemProps>) => {
26
25
  return <LegacyListItem title={title} value={value} {...props} />;
27
26
  };
28
27
 
@@ -1,7 +1,7 @@
1
1
  import { render, screen } from '@testing-library/react';
2
2
  import { Person } from '@transferwise/icons';
3
3
 
4
- import Option, { Props as OptionProps } from './Option';
4
+ import Option, { type OptionProps } from './Option';
5
5
 
6
6
  describe('Option', () => {
7
7
  const defaultProps: OptionProps<string> = {
@@ -6,7 +6,7 @@ import { cloneElement, ReactElement, ReactNode } from 'react';
6
6
  import Body from '../../../Body';
7
7
  import { Typography } from '../../../common';
8
8
 
9
- export type Props<T> = {
9
+ export type OptionProps<T> = {
10
10
  label: ReactNode;
11
11
  value: T;
12
12
  currency?: string;
@@ -27,7 +27,7 @@ function Option<T>({
27
27
  icon,
28
28
  selected = false,
29
29
  testId,
30
- }: Props<T>) {
30
+ }: OptionProps<T>) {
31
31
  const iconElement = icon
32
32
  ? cloneElement(icon as ReactElement<IdIconProps>, {
33
33
  size: 24,
@@ -4,13 +4,13 @@ import { forwardRef, InputHTMLAttributes } from 'react';
4
4
 
5
5
  import { Input } from '../../../Inputs/Input/Input';
6
6
 
7
- type Props = {
7
+ type SearchBoxProps = {
8
8
  id?: string;
9
9
  classNames?: Record<string, string>;
10
10
  focusedOptionId?: string;
11
11
  } & Pick<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onClick' | 'placeholder' | 'value'>;
12
12
 
13
- const SearchBox = forwardRef<HTMLInputElement, Props>(
13
+ const SearchBox = forwardRef<HTMLInputElement, SearchBoxProps>(
14
14
  (
15
15
  {
16
16
  id,
@@ -5,7 +5,7 @@ import { useMedia } from '../hooks/useMedia';
5
5
 
6
6
  export type ShapeSize = 16 | 24 | 32 | 40 | 48 | 56 | 72 | 88 | 96;
7
7
 
8
- export type Props = {
8
+ export type CircleProps = {
9
9
  /**
10
10
  * Modify underlying element, `div` by default
11
11
  */
@@ -64,7 +64,7 @@ const Circle = forwardRef(function Circle(
64
64
  className,
65
65
  style,
66
66
  ...props
67
- }: Props,
67
+ }: CircleProps,
68
68
  ref,
69
69
  ) {
70
70
  const isTinyViewport = useMedia(`(max-width: ${Breakpoint.EXTRA_SMALL}px)`);
@@ -1,2 +1,2 @@
1
1
  export { default } from './Circle';
2
- export { type Props as CircleProps } from './Circle';
2
+ export { type CircleProps } from './Circle';
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export type {
25
25
  ButtonProps as LegacyButtonProps,
26
26
  AnchorElementProps,
27
27
  } from './Button';
28
+ export type { BodyProps } from './Body';
28
29
  export type { CardProps } from './_deprecated/Card';
29
30
  export type {
30
31
  CarouselProps,
@@ -63,6 +64,7 @@ export type {
63
64
  DefinitionListLayout,
64
65
  } from './DefinitionList';
65
66
  export type { DimmerProps } from './Dimmer';
67
+ export type { DisplayProps } from './Display';
66
68
  export type { DrawerProps } from './Drawer';
67
69
  export type { DividerProps } from './Divider';
68
70
  export type { HeaderProps } from './Header';
@@ -89,6 +91,7 @@ export type { TextAreaProps } from './Inputs/TextArea/TextArea';
89
91
  export type { InstructionsListProps } from './InstructionsList';
90
92
  export type { LabelProps, LabelOptionalProps, LabelDescriptionProps } from './Label';
91
93
  export type { LegacyListItemProps } from './_deprecated/LegacyListItem';
94
+ export type { LinkProps } from './Link';
92
95
  export type { LoaderProps } from './Loader';
93
96
  export type { MarkdownProps } from './Markdown';
94
97
  export type { ModalProps } from './Modal';
@@ -145,6 +148,7 @@ export type { SwitchOptionProps } from './_deprecated/SwitchOption';
145
148
  export type { TabItem, TabsProps, TabProps, TabListProps, TabPanelProps } from './Tabs';
146
149
  export type { TextareaWithDisplayFormatProps } from './TextareaWithDisplayFormat';
147
150
  export type { TileProps } from './Tile';
151
+ export type { TitleProps } from './Title';
148
152
  export type { TooltipProps } from './Tooltip';
149
153
  export type { TypeaheadOption, TypeaheadProps } from './Typeahead';
150
154
  export type { UploadProps } from './Upload';
package/src/main.css CHANGED
@@ -28611,6 +28611,36 @@ button.np-option {
28611
28611
  margin-bottom: 8px;
28612
28612
  margin-bottom: var(--size-8);
28613
28613
  }
28614
+ .tw-definition-list.tw-definition-list--horizontal-right-aligned {
28615
+ display: grid;
28616
+ grid-template-columns: minmax(0, max-content) minmax(0, max-content);
28617
+ grid-column-gap: 52px;
28618
+ grid-column-gap: var(--size-52);
28619
+ -moz-column-gap: 52px;
28620
+ column-gap: 52px;
28621
+ -moz-column-gap: var(--size-52);
28622
+ column-gap: var(--size-52);
28623
+ justify-content: space-between;
28624
+ }
28625
+ .tw-definition-list.tw-definition-list--horizontal-right-aligned .tw-definition-list__item {
28626
+ display: grid;
28627
+ grid-template-columns: subgrid;
28628
+ grid-column: 1 / -1;
28629
+ }
28630
+ .tw-definition-list.tw-definition-list--horizontal-right-aligned .tw-definition-list__item dt {
28631
+ max-width: none;
28632
+ min-width: 0;
28633
+ word-wrap: anywhere;
28634
+ margin-right: 0;
28635
+ }
28636
+ [dir="rtl"] .tw-definition-list.tw-definition-list--horizontal-right-aligned .tw-definition-list__item dt {
28637
+ margin-left: 0;
28638
+ margin-right: 0;
28639
+ margin-right: initial;
28640
+ }
28641
+ .tw-definition-list.tw-definition-list--horizontal-right-aligned .tw-definition-list__item dd {
28642
+ min-width: 0;
28643
+ }
28614
28644
  }
28615
28645
 
28616
28646
  .np-theme-personal .tw-definition-list dt {
@@ -31453,7 +31483,7 @@ html:not([dir="rtl"]) .np-navigation-option {
31453
31483
 
31454
31484
  .wds-nudge-media-documents {
31455
31485
  margin-left: -8px;
31456
- margin-top: 22px;
31486
+ margin-top: 8px;
31457
31487
  position: absolute;
31458
31488
  width: 120px;
31459
31489
  }
@@ -32682,6 +32712,11 @@ html:not([dir="rtl"]) .np-navigation-option {
32682
32712
  will-change: width;
32683
32713
  }
32684
32714
 
32715
+ .wds-sticky.sliding-panel--open-bottom {
32716
+ padding-bottom: env(safe-area-inset-bottom, 0px);
32717
+ padding-bottom: var(--wds-sticky-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
32718
+ }
32719
+
32685
32720
  .np-select .np-dropdown-toggle {
32686
32721
  background-color: #ffffff;
32687
32722
  background-color: var(--color-background-screen);
package/src/main.less CHANGED
@@ -78,6 +78,7 @@
78
78
  @import "./Snackbar/Snackbar.less";
79
79
  @import "./StatusIcon/StatusIcon.less";
80
80
  @import "./Stepper/Stepper.less";
81
+ @import "./Sticky/Sticky.less";
81
82
  @import "./_deprecated/Select/Select.less";
82
83
  @import "./SegmentedControl/SegmentedControl.less";
83
84
  @import "./_deprecated/Summary/Summary.less";