@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
@@ -0,0 +1,168 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { expect } from 'storybook/test';
3
+
4
+ import { Layout } from '../../common';
5
+
6
+ import DefinitionList, { type DefinitionListDefinition } from '..';
7
+
8
+ const longValues = {
9
+ amount: '1,234,567.89 GBP from your Wise Business account ending in 4821',
10
+ arrival: 'Tuesday, 14 July 2026 at 18:30 Eastern European Summer Time',
11
+ method: 'Business account ending in 4821 · Manual bank transfer',
12
+ recipients: 'Thirty-two recipients across twelve destination currencies',
13
+ };
14
+
15
+ const longTitles = {
16
+ amount: "Total amount we'll convert before sending this batch payment",
17
+ arrival: 'Estimated arrival time for all recipients in this batch',
18
+ method: 'Payment method used to fund this batch transfer',
19
+ recipients: 'Number of recipients included in this batch payment',
20
+ };
21
+
22
+ const longTitlesLongValues: DefinitionListDefinition[] = [
23
+ { key: 'amount', title: longTitles.amount, value: longValues.amount },
24
+ { key: 'arrival', title: longTitles.arrival, value: longValues.arrival },
25
+ { key: 'method', title: longTitles.method, value: longValues.method },
26
+ { key: 'recipients', title: longTitles.recipients, value: longValues.recipients },
27
+ ];
28
+
29
+ const longTitlesShortValues: DefinitionListDefinition[] = [
30
+ { key: 'amount', title: longTitles.amount, value: '1,234.56 GBP' },
31
+ { key: 'arrival', title: longTitles.arrival, value: 'Today' },
32
+ { key: 'method', title: longTitles.method, value: 'Bank transfer' },
33
+ { key: 'recipients', title: longTitles.recipients, value: '32' },
34
+ ];
35
+
36
+ const shortTitlesLongValues: DefinitionListDefinition[] = [
37
+ { key: 'amount', title: 'Amount', value: longValues.amount },
38
+ { key: 'arrival', title: 'Arrival', value: longValues.arrival },
39
+ { key: 'method', title: 'Method', value: longValues.method },
40
+ { key: 'recipients', title: 'Recipients', value: longValues.recipients },
41
+ ];
42
+
43
+ const mixedContentLengths: DefinitionListDefinition[] = [
44
+ { key: 'ticket-regression', title: "Total amount we'll convert", value: '1,234.56 GBP' },
45
+ { key: 'short-short', title: 'Fees', value: '12.34 GBP' },
46
+ { key: 'long-short', title: longTitles.arrival, value: 'Today' },
47
+ { key: 'short-long', title: 'Method', value: longValues.method },
48
+ { key: 'long-long', title: longTitles.recipients, value: longValues.recipients },
49
+ {
50
+ key: 'styled-content',
51
+ title: <strong>Current available balance before this payment</strong>,
52
+ value: <strong>98,765.43 GBP</strong>,
53
+ },
54
+ ];
55
+
56
+ type TestCaseProps = {
57
+ definitions: DefinitionListDefinition[];
58
+ label: string;
59
+ width?: number;
60
+ };
61
+
62
+ const TestCase = ({ definitions, label, width = 640 }: TestCaseProps) => (
63
+ <section style={{ maxWidth: '100%', width }}>
64
+ <h2 style={{ fontSize: 16, margin: '0 0 var(--size-16)' }}>{label}</h2>
65
+ <DefinitionList definitions={definitions} layout={Layout.HORIZONTAL_RIGHT_ALIGNED} />
66
+ </section>
67
+ );
68
+
69
+ const meta = {
70
+ component: DefinitionList,
71
+ parameters: { controls: { disable: true } },
72
+ tags: ['!autodocs', '!manifest'],
73
+ title: 'Typography/DefinitionList/Tests',
74
+ } satisfies Meta<typeof DefinitionList>;
75
+
76
+ export default meta;
77
+
78
+ type Story = StoryObj<typeof meta>;
79
+
80
+ const assertSharedColumnBoundary = async (canvasElement: HTMLElement) => {
81
+ const lists = [
82
+ ...canvasElement.querySelectorAll<HTMLElement>('.tw-definition-list--horizontal-right-aligned'),
83
+ ];
84
+
85
+ await expect(lists.length).toBeGreaterThan(0);
86
+
87
+ for (const list of lists) {
88
+ const rows = [...list.querySelectorAll<HTMLElement>('.tw-definition-list__item')];
89
+ const columns = rows.map((row) => ({
90
+ row,
91
+ title: row.querySelector<HTMLElement>('dt'),
92
+ value: row.querySelector<HTMLElement>('dd'),
93
+ }));
94
+
95
+ await expect(columns.every(({ title, value }) => title && value)).toBe(true);
96
+
97
+ const horizontalColumns = columns.filter(({ row }) =>
98
+ ['flex', 'grid'].includes(getComputedStyle(row).display),
99
+ );
100
+
101
+ if (horizontalColumns.length > 0) {
102
+ const titleRects = horizontalColumns.map(({ title }) => title?.getBoundingClientRect());
103
+ const valueRects = horizontalColumns.map(({ value }) => value?.getBoundingClientRect());
104
+ const isRtl = getComputedStyle(list).direction === 'rtl';
105
+
106
+ if (isRtl) {
107
+ const valueColumnEnd = Math.max(...valueRects.map((rect) => rect?.right ?? 0));
108
+ const titleColumnStart = Math.min(
109
+ ...titleRects.map((rect) => rect?.left ?? Number.POSITIVE_INFINITY),
110
+ );
111
+
112
+ await expect(valueColumnEnd).toBeLessThanOrEqual(titleColumnStart + 1);
113
+ await expect(titleColumnStart - valueColumnEnd).toBeGreaterThanOrEqual(51);
114
+ } else {
115
+ const titleColumnEnd = Math.max(...titleRects.map((rect) => rect?.right ?? 0));
116
+ const valueColumnStart = Math.min(
117
+ ...valueRects.map((rect) => rect?.left ?? Number.POSITIVE_INFINITY),
118
+ );
119
+
120
+ await expect(titleColumnEnd).toBeLessThanOrEqual(valueColumnStart + 1);
121
+ await expect(valueColumnStart - titleColumnEnd).toBeGreaterThanOrEqual(51);
122
+ }
123
+ } else {
124
+ for (const { title, value } of columns) {
125
+ if (title && value) {
126
+ const titleEnd = title.getBoundingClientRect().bottom;
127
+ const valueStart = value.getBoundingClientRect().top;
128
+
129
+ await expect(titleEnd).toBeLessThanOrEqual(valueStart + 1);
130
+ }
131
+ }
132
+ }
133
+
134
+ for (const { title, value } of columns) {
135
+ if (title && value) {
136
+ await expect(title.scrollWidth).toBeLessThanOrEqual(title.clientWidth);
137
+ await expect(value.scrollWidth).toBeLessThanOrEqual(value.clientWidth);
138
+ }
139
+ }
140
+ }
141
+ };
142
+
143
+ const verifySharedColumnBoundary: NonNullable<Story['play']> = async ({ canvasElement, step }) => {
144
+ await step('titles and values stay inside separate shared columns', async () => {
145
+ await assertSharedColumnBoundary(canvasElement);
146
+ });
147
+ };
148
+
149
+ export const HORIZONTAL_RIGHT_ALIGNED: Story = {
150
+ name: 'HORIZONTAL_RIGHT_ALIGNED',
151
+ render: () => (
152
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--size-48)' }}>
153
+ <TestCase
154
+ definitions={longTitlesLongValues}
155
+ label="Long titles with long values"
156
+ width={720}
157
+ />
158
+ <TestCase definitions={shortTitlesLongValues} label="All short titles with long values" />
159
+ <TestCase definitions={longTitlesShortValues} label="All long titles with short values" />
160
+ <TestCase
161
+ definitions={mixedContentLengths}
162
+ label="Mixed title and value lengths"
163
+ width={520}
164
+ />
165
+ </div>
166
+ ),
167
+ play: verifySharedColumnBoundary,
168
+ };
@@ -3,7 +3,7 @@ import { PropsWithChildren } from 'react';
3
3
 
4
4
  import { DisplayTypes, Typography } from '../common';
5
5
 
6
- type Props = PropsWithChildren<{
6
+ export type DisplayProps = PropsWithChildren<{
7
7
  as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
8
8
  id?: string;
9
9
  type?: DisplayTypes;
@@ -16,7 +16,7 @@ function Display({
16
16
  children,
17
17
  className,
18
18
  id,
19
- }: Props) {
19
+ }: DisplayProps) {
20
20
  return (
21
21
  <Heading id={id} className={clsx('np-display', `np-text-${type}`, 'text-primary', className)}>
22
22
  {children}
@@ -1 +1,2 @@
1
1
  export { default } from './Display';
2
+ export type { DisplayProps } from './Display';
@@ -1,14 +1,13 @@
1
1
  import { render, screen, userEvent, mockMatchMedia } from '../test-utils';
2
2
  import { Position } from '../common';
3
3
 
4
- import Drawer from './Drawer';
4
+ import Drawer, { type DrawerProps } from './Drawer';
5
5
 
6
6
  jest.mock('../common/DOMOperations');
7
7
  mockMatchMedia();
8
8
 
9
9
  describe('Drawer', () => {
10
- const renderedComponent = (props: React.ComponentProps<typeof Drawer>) =>
11
- render(<Drawer {...props} />);
10
+ const renderedComponent = (props: DrawerProps) => render(<Drawer {...props} />);
12
11
 
13
12
  const props = {
14
13
  open: true,
@@ -1,10 +1,10 @@
1
1
  import { formatAmount } from '@transferwise/formatting';
2
2
  import { clsx } from 'clsx';
3
- import { AnimatePresence } from 'framer-motion';
3
+ import { AnimatePresence } from 'motion/react';
4
4
  import { type ChangeEvent, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';
5
5
  import { useIntl } from 'react-intl';
6
6
 
7
- import { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
7
+ import type { ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
8
8
  import { AnimatedNumber } from '../AnimatedNumber/AnimatedNumber';
9
9
  import { useFocus } from '../hooks/useFocus';
10
10
  import { useInputStyle } from '../hooks/useInputStyle';
@@ -19,7 +19,7 @@ import {
19
19
  isInputPossiblyOverflowing,
20
20
  } from './utils';
21
21
 
22
- type Props = {
22
+ type AmountInputProps = {
23
23
  id: string;
24
24
  describedById?: string;
25
25
  amount?: number | null;
@@ -38,7 +38,7 @@ export const AmountInput = ({
38
38
  onChange,
39
39
  onFocusChange,
40
40
  loading,
41
- }: Props) => {
41
+ }: AmountInputProps) => {
42
42
  const intl = useIntl();
43
43
  const { focus, setFocus, visualFocus, setVisualFocus } = useFocus();
44
44
 
@@ -1,4 +1,4 @@
1
- import { motion, useReducedMotion } from 'framer-motion';
1
+ import { motion, useReducedMotion } from 'motion/react';
2
2
  import type { ReactNode } from 'react';
3
3
 
4
4
  interface Props {
@@ -1,6 +1,6 @@
1
1
  import { ChevronLeft } from '@transferwise/icons';
2
2
  import { clsx } from 'clsx';
3
- import { motion } from 'framer-motion';
3
+ import { motion } from 'motion/react';
4
4
 
5
5
  interface Props {
6
6
  shouldShow: boolean;
@@ -5,7 +5,7 @@ import {
5
5
  SelectInputOptionContent,
6
6
  SelectInputTriggerButton,
7
7
  } from '../../Inputs/SelectInput';
8
- import { CurrencyType, Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
8
+ import type { CurrencyType, ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
9
9
  import { ChevronDown } from '@transferwise/icons';
10
10
  import { Flag } from '@wise/art';
11
11
  import {
@@ -34,7 +34,7 @@ export interface CurrencySection {
34
34
 
35
35
  export type CurrencyOptions = CurrencySection[];
36
36
 
37
- export type Props = {
37
+ export type CurrencySelectorProps = {
38
38
  id: string;
39
39
  labelId: string;
40
40
  options?: CurrencyOptions;
@@ -53,7 +53,7 @@ export const CurrencySelector = ({
53
53
  addons,
54
54
  onOpen,
55
55
  onSearchChange,
56
- }: Props) => {
56
+ }: CurrencySelectorProps) => {
57
57
  const intl = useIntl();
58
58
 
59
59
  const allCurrencyOptions = useMemo(() => getUniqueCurrencies(options), [options]);
@@ -2,7 +2,7 @@ import { screen, render, mockMatchMedia, mockResizeObserver, waitFor } from '../
2
2
  import { Sentiment } from '../common';
3
3
 
4
4
  import ExpressiveMoneyInput from './ExpressiveMoneyInput';
5
- import type { Props as ExpressiveMoneyInputProps } from './ExpressiveMoneyInput';
5
+ import type { ExpressiveMoneyInputProps } from './ExpressiveMoneyInput';
6
6
  import { Confetti } from '@transferwise/icons';
7
7
 
8
8
  const defaultProps: ExpressiveMoneyInputProps = {
@@ -1,12 +1,9 @@
1
1
  import Body from '../Body';
2
2
  import { clsx } from 'clsx';
3
- import { AnimatePresence, motion } from 'framer-motion';
3
+ import { AnimatePresence, motion } from 'motion/react';
4
4
  import { useId, type ReactNode } from 'react';
5
5
 
6
- import {
7
- type Props as CurrencySelectorProps,
8
- CurrencySelector,
9
- } from './CurrencySelector/CurrencySelector';
6
+ import { CurrencySelector, type CurrencySelectorProps } from './CurrencySelector/CurrencySelector';
10
7
  import { CommonProps } from '../common';
11
8
  import { AmountInput } from './AmountInput/AmountInput';
12
9
  import { Chevron } from './Chevron/Chevron';
@@ -26,7 +23,7 @@ type CustomCurrencySelectorInstanceType = {
26
23
  type CurrencySelectorType = DefaultCurrencySelectorInstanceType &
27
24
  CustomCurrencySelectorInstanceType;
28
25
 
29
- export type Props = {
26
+ export type ExpressiveMoneyInputProps = {
30
27
  label?: ReactNode;
31
28
  currencySelector?: CurrencySelectorType;
32
29
  amount?: AmountType;
@@ -65,7 +62,7 @@ export type Props = {
65
62
  * direct contributions by consuming product teams are highly encouraged.
66
63
  *
67
64
  * > ⚠️ **Prerequisite:** <br />The component depends on the
68
- * [framer-motion](https://www.npmjs.com/package/framer-motion) package, which has been
65
+ * [motion](https://www.npmjs.com/package/motion) package, which has been
69
66
  * deliberately excluded from the Design System bundle and kept as an opt-in only.
70
67
  * **Make sure to add it to your project dependencies.**
71
68
  */
@@ -81,7 +78,7 @@ export default function ExpressiveMoneyInput({
81
78
  autoFocus,
82
79
  loading,
83
80
  onFocusChange,
84
- }: Props) {
81
+ }: ExpressiveMoneyInputProps) {
85
82
  const inputId = useId();
86
83
  const labelId = useId();
87
84
  const customAlertId = useId();
@@ -19,7 +19,7 @@ import Modal from '../../Modal';
19
19
  import ListItem from '../../ListItem';
20
20
  import List from '../../List';
21
21
  import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '..';
22
- import type { Props as CurrencySelectorProps } from '../CurrencySelector/CurrencySelector';
22
+ import type { CurrencySelectorProps } from '../CurrencySelector/CurrencySelector';
23
23
 
24
24
  const getCurrencySelectorProps = ({
25
25
  logEvents = false,
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
  import { fn } from 'storybook/test';
3
- import ExpressiveMoneyInput, { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
3
+ import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
4
4
 
5
5
  const meta: Meta<typeof ExpressiveMoneyInput> = {
6
6
  component: ExpressiveMoneyInput,
@@ -6,7 +6,7 @@ import {
6
6
  useLayoutEffect,
7
7
  useState,
8
8
  } from 'react';
9
- import { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
9
+ import type { ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
10
10
 
11
11
  type InputStyleObject = {
12
12
  value: string;
@@ -1,2 +1,2 @@
1
- export type { Props as ExpressiveMoneyInputProps, CurrencyType } from './ExpressiveMoneyInput';
1
+ export type { ExpressiveMoneyInputProps, CurrencyType } from './ExpressiveMoneyInput';
2
2
  export { default } from './ExpressiveMoneyInput';
@@ -3,7 +3,7 @@ import { PrimitiveAnchor, PrimitiveButton } from '../Primitives';
3
3
  import Circle from '../common/Circle';
4
4
  import { clsx } from 'clsx';
5
5
 
6
- export type Props = {
6
+ export type IconButtonProps = {
7
7
  /** @default 48 */
8
8
  size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
9
9
  /** @default 'primary' */
@@ -27,7 +27,7 @@ const IconButton = forwardRef(function IconButton(
27
27
  priority = 'primary',
28
28
  type = 'default',
29
29
  ...props
30
- }: Props,
30
+ }: IconButtonProps,
31
31
  ref,
32
32
  ) {
33
33
  const isLink = href !== '#';
@@ -18,7 +18,7 @@ import {
18
18
  createSandboxStory,
19
19
  globalScope,
20
20
  } from '../../../.storybook/components/sandbox/SandboxEditor';
21
- import IconButton, { Props } from '../IconButton';
21
+ import IconButton, { type IconButtonProps } from '../IconButton';
22
22
  import SentimentSurface from '../../SentimentSurface';
23
23
 
24
24
  /**
@@ -46,7 +46,7 @@ export default {
46
46
 
47
47
  type Story = StoryObj<typeof IconButton>;
48
48
 
49
- const sizes: Props['size'][] = [16, 24, 32, 40, 48, 56, 72];
49
+ const sizes: IconButtonProps['size'][] = [16, 24, 32, 40, 48, 56, 72];
50
50
 
51
51
  /**
52
52
  * Explore size, priority, type, and disabled state via the controls panel.
@@ -107,7 +107,7 @@ export const Priority: Story = {
107
107
  </div>
108
108
  ),
109
109
  ],
110
- render: (args: Props) => (
110
+ render: (args: IconButtonProps) => (
111
111
  <>
112
112
  <IconButton priority="primary" type={args.type} aria-label="Primary" onClick={fn()}>
113
113
  <Plus />
@@ -152,7 +152,7 @@ export const Type: Story = {
152
152
  </div>
153
153
  ),
154
154
  ],
155
- render: (args: Props) => (
155
+ render: (args: IconButtonProps) => (
156
156
  <>
157
157
  <IconButton priority={args.priority} type="default" aria-label="Default" onClick={fn()}>
158
158
  <Plus />
@@ -195,7 +195,7 @@ export const Size: Story = {
195
195
  </div>
196
196
  ),
197
197
  ],
198
- render: (args: Props) => (
198
+ render: (args: IconButtonProps) => (
199
199
  <>
200
200
  {sizes.map((size) => (
201
201
  <div
@@ -254,7 +254,7 @@ export const Disabled: Story = {
254
254
  target: { table: { disable: true } },
255
255
  onClick: { table: { disable: true } },
256
256
  },
257
- render: (args: Props) => (
257
+ render: (args: IconButtonProps) => (
258
258
  <IconButton disabled priority={args.priority} type={args.type} aria-label="Disabled">
259
259
  <Plus />
260
260
  </IconButton>
@@ -284,7 +284,7 @@ export const AsAnchor: Story = {
284
284
  disabled: { table: { disable: true } },
285
285
  onClick: { table: { disable: true } },
286
286
  },
287
- render: (args: Props) => (
287
+ render: (args: IconButtonProps) => (
288
288
  <IconButton {...args} aria-label="External link">
289
289
  <ArrowLeft />
290
290
  </IconButton>
@@ -305,7 +305,7 @@ export const SentimentAwareness: Story = {
305
305
  options: [16, 24, 32, 40, 48, 56, 72],
306
306
  },
307
307
  },
308
- render: (args: Props) => {
308
+ render: (args: IconButtonProps) => {
309
309
  const priorities = [
310
310
  { key: 'primary', label: 'primary' },
311
311
  { key: 'secondary', label: 'secondary' },
@@ -1,2 +1,2 @@
1
- export type { Props as IconButtonProps } from './IconButton';
1
+ export type { IconButtonProps } from './IconButton';
2
2
  export { default } from './IconButton';
package/src/Link/Link.tsx CHANGED
@@ -8,7 +8,7 @@ import { LinkLarge, LinkDefault, Typography } from '../common';
8
8
 
9
9
  import messages from './Link.messages';
10
10
 
11
- export type Props = PrimitiveAnchorProps &
11
+ export type LinkProps = PrimitiveAnchorProps &
12
12
  Pick<PrimitiveButtonProps, 'onClick'> & { type?: LinkLarge | LinkDefault };
13
13
 
14
14
  /**
@@ -26,7 +26,7 @@ const Link = ({
26
26
  onClick,
27
27
  disabled,
28
28
  ...props
29
- }: Props) => {
29
+ }: LinkProps) => {
30
30
  const { formatMessage } = useIntl();
31
31
  const isButton = Boolean(onClick) && href === undefined;
32
32
  const isBlank = target === '_blank';
package/src/Link/index.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { default } from './Link';
2
- export type { Props as LinkProps } from './Link';
2
+ export type { LinkProps } from './Link';
@@ -30,6 +30,29 @@ describe('ListItem.Navigation', () => {
30
30
  });
31
31
  });
32
32
 
33
+ describe('hideIndicator', () => {
34
+ it('renders the chevron by default for a button', () => {
35
+ renderWith({ control: <ListItem.Navigation onClick={() => {}} /> });
36
+ expect(screen.getByTestId('NavigationIndicator')).toBeInTheDocument();
37
+ });
38
+
39
+ it('renders the chevron by default for a link', () => {
40
+ renderWith({ control: <ListItem.Navigation href="/test-path" /> });
41
+ expect(screen.getByTestId('NavigationIndicator')).toBeInTheDocument();
42
+ });
43
+
44
+ it('hides the chevron when hideIndicator is true on a button', () => {
45
+ renderWith({ control: <ListItem.Navigation hideIndicator onClick={() => {}} /> });
46
+ expect(screen.queryByTestId('NavigationIndicator')).not.toBeInTheDocument();
47
+ });
48
+
49
+ it('hides the chevron when hideIndicator is true on a link', () => {
50
+ renderWith({ control: <ListItem.Navigation hideIndicator href="/test-path" /> });
51
+ expect(screen.getByRole('link')).toBeInTheDocument();
52
+ expect(screen.queryByTestId('NavigationIndicator')).not.toBeInTheDocument();
53
+ });
54
+ });
55
+
33
56
  describe('as link', () => {
34
57
  it('renders a link if href is set', () => {
35
58
  renderWith({ control: <ListItem.Navigation href="/test-path" /> });
@@ -5,7 +5,17 @@ import { PrimitiveButton } from '../../Primitives';
5
5
  import { useContext } from 'react';
6
6
  import { ListItemContext } from '../ListItemContext';
7
7
 
8
- export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'target'>;
8
+ export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
9
+ /**
10
+ * When `true`, the chevron indicator is hidden. Use when surrounding context makes the
11
+ * interactive affordance clear without a directional cue (e.g. a widget row with trailing
12
+ * value content).<br />
13
+ * **NB:** This feature should be used with great care as it can result in the item looking
14
+ * as non-interactive, significantly degrading accessibility, UX, and conversion,
15
+ * especially if used on busy screens with multiple Lists.
16
+ */
17
+ hideIndicator?: boolean;
18
+ };
9
19
 
10
20
  /**
11
21
  * This component allows for rendering a control functionally synonymous with HTML `anchor` or a
@@ -15,10 +25,14 @@ export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'ta
15
25
  * <br />
16
26
  * Please refer to the [Design documentation](https://docs.wise.design/components/list-item---navigation) for details.
17
27
  */
18
- export const Navigation = function Navigation({ href, ...props }: ListItemNavigationProps) {
28
+ export const Navigation = function Navigation({
29
+ href,
30
+ hideIndicator,
31
+ ...props
32
+ }: ListItemNavigationProps) {
19
33
  const { baseItemProps } = useListItemControl('navigation', { href, ...props });
20
34
  const { ids, describedByIds } = useContext(ListItemContext);
21
- const icon = <ChevronRight size={16} />;
35
+ const icon = hideIndicator ? null : <ChevronRight size={16} data-testid="NavigationIndicator" />;
22
36
 
23
37
  return href ? (
24
38
  <>{icon}</>
@@ -50,6 +50,39 @@ export const Playground: Story = {
50
50
  },
51
51
  };
52
52
 
53
+ /**
54
+ * Use `hideIndicator` when surrounding context (e.g. trailing value content) makes the
55
+ * interactive affordance clear without a chevron. <br />
56
+ * **NB:** This feature should be used with great care as it can result in the item looking
57
+ * as non-interactive, significantly degrading accessibility, UX, and conversion,
58
+ * especially if used on busy screens with multiple Lists.
59
+ */
60
+ export const WithoutIndicator: Story = {
61
+ parameters: {
62
+ controls: { disable: true },
63
+ },
64
+ render: (args) => {
65
+ return (
66
+ <List>
67
+ <ListItem
68
+ control={<ListItem.Navigation href="#target" hideIndicator onClick={args.onClick} />}
69
+ title="Navigation without chevron"
70
+ subtitle="Interactive row — no directional indicator"
71
+ valueTitle="£1,234.56"
72
+ media={MEDIA.avatarSingle}
73
+ />
74
+
75
+ <ListItem
76
+ control={<ListItem.Navigation hideIndicator onClick={args.onClick} />}
77
+ title="Navigation without chevron (button)"
78
+ subtitle="Triggers a click handler"
79
+ media={MEDIA.avatarSingle}
80
+ />
81
+ </List>
82
+ );
83
+ },
84
+ };
85
+
53
86
  /**
54
87
  * Navigation control can be rendered as HTML anchor or a button
55
88
  */
@@ -171,7 +171,7 @@
171
171
  }
172
172
  .wds-nudge-media-documents {
173
173
  margin-left: -8px;
174
- margin-top: 22px;
174
+ margin-top: 8px;
175
175
  position: absolute;
176
176
  width: 120px;
177
177
  }
@@ -82,7 +82,7 @@
82
82
  }
83
83
 
84
84
  &-documents {
85
- .media-position(120px, -8px, 22px);
85
+ .media-position(120px, -8px, 8px);
86
86
  }
87
87
  }
88
88
 
@@ -42,6 +42,49 @@ describe('Nudge', () => {
42
42
  expect(defaultProps.onDismiss).toHaveBeenCalledTimes(1);
43
43
  });
44
44
 
45
+ it('renders action only when both action and link/href are supplied', () => {
46
+ const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
47
+
48
+ render(
49
+ <Nudge
50
+ {...defaultProps}
51
+ action={{
52
+ text: 'Learn more',
53
+ href: '#action-link',
54
+ 'aria-label': 'Learn more about rewards',
55
+ }}
56
+ />,
57
+ );
58
+
59
+ expect(screen.queryByText('CTA')).not.toBeInTheDocument();
60
+
61
+ const actionLink = screen.getByText('Learn more');
62
+ expect(actionLink).toBeInTheDocument();
63
+ expect(actionLink).toHaveAttribute('href', '#action-link');
64
+
65
+ consoleWarn.mockRestore();
66
+ });
67
+ it('shows a warning in development when both action and link/href are supplied', () => {
68
+ const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
69
+
70
+ render(
71
+ <Nudge
72
+ {...defaultProps}
73
+ action={{
74
+ text: 'Learn more',
75
+ href: '#action-link',
76
+ 'aria-label': 'Learn more about rewards',
77
+ }}
78
+ />,
79
+ );
80
+
81
+ expect(consoleWarn).toHaveBeenCalledWith(
82
+ 'Nudge received both action and link/href props. Rendering action only. Remove link/href to silence this warning.',
83
+ );
84
+
85
+ consoleWarn.mockRestore();
86
+ });
87
+
45
88
  describe('Nudge persistance', () => {
46
89
  beforeEach(() => {
47
90
  jest.resetAllMocks();