@transferwise/components 45.17.1 → 45.19.0

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 (59) hide show
  1. package/build/index.esm.js +425 -368
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +425 -366
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -1
  6. package/build/styles/inputs/Input.css +1 -1
  7. package/build/styles/inputs/SelectInput.css +1 -1
  8. package/build/styles/inputs/TextArea.css +1 -1
  9. package/build/styles/instructionsList/InstructionsList.css +1 -1
  10. package/build/styles/main.css +1 -1
  11. package/build/styles/stepper/Stepper.css +1 -1
  12. package/build/styles/tooltip/Tooltip.css +1 -1
  13. package/build/types/button/Button.d.ts.map +1 -1
  14. package/build/types/common/polymorphicWithOverrides/PolymorphicWithOverrides.d.ts +13 -0
  15. package/build/types/common/polymorphicWithOverrides/PolymorphicWithOverrides.d.ts.map +1 -0
  16. package/build/types/index.d.ts +2 -2
  17. package/build/types/index.d.ts.map +1 -1
  18. package/build/types/inputs/SelectInput.d.ts +18 -6
  19. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  20. package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
  21. package/build/types/inputs/_Popover.d.ts.map +1 -1
  22. package/build/types/instructionsList/InstructionsList.d.ts +10 -3
  23. package/build/types/instructionsList/InstructionsList.d.ts.map +1 -1
  24. package/build/types/processIndicator/ProcessIndicator.d.ts +1 -1
  25. package/build/types/tooltip/Tooltip.d.ts +2 -1
  26. package/build/types/tooltip/Tooltip.d.ts.map +1 -1
  27. package/package.json +4 -3
  28. package/src/button/Button.story.tsx +6 -0
  29. package/src/button/Button.tsx +6 -1
  30. package/src/common/polymorphicWithOverrides/PolymorphicWithOverrides.tsx +19 -0
  31. package/src/index.ts +9 -1
  32. package/src/inputs/Input.css +1 -1
  33. package/src/inputs/SelectInput.css +1 -1
  34. package/src/inputs/SelectInput.less +20 -5
  35. package/src/inputs/SelectInput.story.tsx +85 -35
  36. package/src/inputs/SelectInput.tsx +176 -106
  37. package/src/inputs/TextArea.css +1 -1
  38. package/src/inputs/_BottomSheet.tsx +47 -37
  39. package/src/inputs/_Popover.less +1 -1
  40. package/src/inputs/_Popover.tsx +30 -27
  41. package/src/inputs/_common.less +6 -0
  42. package/src/inputs/_common.ts +4 -4
  43. package/src/instructionsList/InstructionList.story.tsx +39 -0
  44. package/src/instructionsList/InstructionsList.css +1 -1
  45. package/src/instructionsList/InstructionsList.less +3 -15
  46. package/src/instructionsList/InstructionsList.spec.tsx +35 -0
  47. package/src/instructionsList/InstructionsList.tsx +40 -25
  48. package/src/main.css +1 -1
  49. package/src/processIndicator/ProcessIndicator.js +2 -2
  50. package/src/ssr.spec.js +1 -0
  51. package/src/stepper/Stepper.css +1 -1
  52. package/src/stepper/Stepper.less +1 -1
  53. package/src/tooltip/Tooltip.css +1 -1
  54. package/src/tooltip/Tooltip.less +13 -0
  55. package/src/tooltip/Tooltip.spec.tsx +97 -29
  56. package/src/tooltip/Tooltip.tsx +24 -31
  57. package/src/tooltip/__snapshots__/Tooltip.spec.tsx.snap +31 -0
  58. package/src/instructionsList/InstructionList.story.js +0 -27
  59. package/src/instructionsList/InstructionsList.spec.js +0 -29
@@ -12,6 +12,7 @@ import classNames from 'classnames';
12
12
  import { useState } from 'react';
13
13
 
14
14
  import { CloseButton } from '../common/closeButton';
15
+ import FocusBoundary from '../common/focusBoundary/FocusBoundary';
15
16
  import { PreventScroll } from '../common/preventScroll/PreventScroll';
16
17
  import { Size } from '../common/propsValues/size';
17
18
 
@@ -84,47 +85,56 @@ export function BottomSheet({
84
85
  <div className="np-bottom-sheet-v2-backdrop" />
85
86
  </Transition.Child>
86
87
 
87
- <FloatingFocusManager context={context} initialFocus={initialFocusRef}>
88
- <div className="np-bottom-sheet-v2">
89
- <Transition.Child
90
- className="np-bottom-sheet-v2-content"
91
- enter="np-bottom-sheet-v2-content--enter"
92
- enterFrom="np-bottom-sheet-v2-content--enter-from"
93
- leave="np-bottom-sheet-v2-content--leave"
94
- leaveTo="np-bottom-sheet-v2-content--leave-to"
95
- >
96
- <div
97
- key={floatingKey} // Force inner state invalidation on open
98
- ref={refs.setFloating}
99
- className="np-bottom-sheet-v2-content-inner-container"
100
- {...getFloatingProps()}
88
+ <FocusBoundary>
89
+ <FloatingFocusManager
90
+ context={context}
91
+ initialFocus={initialFocusRef}
92
+ guards={false}
93
+ modal={false}
94
+ >
95
+ <div className="np-bottom-sheet-v2">
96
+ <Transition.Child
97
+ className="np-bottom-sheet-v2-content"
98
+ enter="np-bottom-sheet-v2-content--enter"
99
+ enterFrom="np-bottom-sheet-v2-content--enter-from"
100
+ leave="np-bottom-sheet-v2-content--leave"
101
+ leaveTo="np-bottom-sheet-v2-content--leave-to"
101
102
  >
102
- <div className="np-bottom-sheet-v2-header">
103
- <CloseButton
104
- size={Size.SMALL}
105
- onClick={() => {
106
- onClose?.();
107
- }}
108
- />
109
- </div>
110
103
  <div
111
- className={classNames(
112
- 'np-bottom-sheet-v2-content-inner',
113
- title && 'np-bottom-sheet-v2-content-inner--has-title',
114
- {
115
- 'np-bottom-sheet-v2-content-inner--padding-md': padding === 'md',
116
- },
117
- )}
104
+ key={floatingKey} // Force inner state invalidation on open
105
+ ref={refs.setFloating}
106
+ className="np-bottom-sheet-v2-content-inner-container"
107
+ {...getFloatingProps()}
118
108
  >
119
- {title ? (
120
- <h2 className="np-bottom-sheet-v2-title np-text-title-body">{title}</h2>
121
- ) : null}
122
- <div className="np-bottom-sheet-v2-body np-text-body-default">{children}</div>
109
+ <div className="np-bottom-sheet-v2-header">
110
+ <CloseButton
111
+ size={Size.SMALL}
112
+ onClick={() => {
113
+ onClose?.();
114
+ }}
115
+ />
116
+ </div>
117
+ <div
118
+ className={classNames(
119
+ 'np-bottom-sheet-v2-content-inner',
120
+ title && 'np-bottom-sheet-v2-content-inner--has-title',
121
+ {
122
+ 'np-bottom-sheet-v2-content-inner--padding-md': padding === 'md',
123
+ },
124
+ )}
125
+ >
126
+ {title ? (
127
+ <h2 className="np-bottom-sheet-v2-title np-text-title-body">{title}</h2>
128
+ ) : null}
129
+ <div className="np-bottom-sheet-v2-body np-text-body-default">
130
+ {children}
131
+ </div>
132
+ </div>
123
133
  </div>
124
- </div>
125
- </Transition.Child>
126
- </div>
127
- </FloatingFocusManager>
134
+ </Transition.Child>
135
+ </div>
136
+ </FloatingFocusManager>
137
+ </FocusBoundary>
128
138
  </Transition>
129
139
  </ThemeProvider>
130
140
  </FloatingPortal>
@@ -3,7 +3,7 @@
3
3
  display: flex;
4
4
  max-height: var(--max-height);
5
5
  width: var(--width);
6
- min-width: min-content;
6
+ min-width: 20rem;
7
7
  flex-direction: column;
8
8
  border-radius: var(--radius-small);
9
9
  background-color: var(--color-background-elevated);
@@ -17,6 +17,7 @@ import { ThemeProvider, useTheme } from '@wise/components-theming';
17
17
  import classNames from 'classnames';
18
18
  import { useState } from 'react';
19
19
 
20
+ import FocusBoundary from '../common/focusBoundary/FocusBoundary';
20
21
  import { PreventScroll } from '../common/preventScroll/PreventScroll';
21
22
 
22
23
  export interface PopoverProps {
@@ -88,36 +89,38 @@ export function Popover({
88
89
 
89
90
  <FloatingPortal>
90
91
  <ThemeProvider theme={theme} screenMode={screenMode} isNotRootProvider>
91
- <FloatingFocusManager context={context}>
92
- <Transition
93
- show={open}
94
- leave="transition-opacity"
95
- leaveTo="opacity-0"
96
- beforeEnter={() => {
97
- setFloatingKey((prev) => prev + 1);
98
- }}
99
- >
100
- <div
101
- key={floatingKey} // Force inner state invalidation on open
102
- ref={refs.setFloating}
103
- className="np-popover-v2-container"
104
- // eslint-disable-next-line react/forbid-dom-props
105
- style={floatingStyles}
106
- {...getFloatingProps()}
107
- >
92
+ <Transition
93
+ show={open}
94
+ leave="transition-opacity"
95
+ leaveTo="opacity-0"
96
+ beforeEnter={() => {
97
+ setFloatingKey((prev) => prev + 1);
98
+ }}
99
+ >
100
+ <FocusBoundary>
101
+ <FloatingFocusManager context={context} guards={false} modal={false}>
108
102
  <div
109
- className={classNames('np-popover-v2', title && 'np-popover-v2--has-title', {
110
- 'np-popover-v2--padding-md': padding === 'md',
111
- })}
103
+ key={floatingKey} // Force inner state invalidation on open
104
+ ref={refs.setFloating}
105
+ className="np-popover-v2-container"
106
+ // eslint-disable-next-line react/forbid-dom-props
107
+ style={floatingStyles}
108
+ {...getFloatingProps()}
112
109
  >
113
- {title ? (
114
- <h2 className="np-popover-v2-title np-text-title-body">{title}</h2>
115
- ) : null}
116
- <div className="np-popover-v2-content np-text-body-default">{children}</div>
110
+ <div
111
+ className={classNames('np-popover-v2', title && 'np-popover-v2--has-title', {
112
+ 'np-popover-v2--padding-md': padding === 'md',
113
+ })}
114
+ >
115
+ {title ? (
116
+ <h2 className="np-popover-v2-title np-text-title-body">{title}</h2>
117
+ ) : null}
118
+ <div className="np-popover-v2-content np-text-body-default">{children}</div>
119
+ </div>
117
120
  </div>
118
- </div>
119
- </Transition>
120
- </FloatingFocusManager>
121
+ </FloatingFocusManager>
122
+ </FocusBoundary>
123
+ </Transition>
121
124
  </ThemeProvider>
122
125
  </FloatingPortal>
123
126
  </>
@@ -1,3 +1,5 @@
1
+ @import (reference) '../../node_modules/@transferwise/neptune-css/src/less/core/_typography.less';
2
+
1
3
  .np-form-control {
2
4
  --ring-width: 1px;
3
5
  --ring-color: var(--color-interactive-secondary);
@@ -33,6 +35,7 @@
33
35
  }
34
36
 
35
37
  &--size-auto {
38
+ .np-text-body-large;
36
39
  padding-top: var(--size-12);
37
40
  padding-bottom: var(--size-12);
38
41
  }
@@ -46,14 +49,17 @@
46
49
  }
47
50
 
48
51
  &--size-sm {
52
+ .np-text-body-default;
49
53
  height: var(--size-32) !important;
50
54
  }
51
55
 
52
56
  &--size-md {
57
+ .np-text-body-large;
53
58
  height: var(--size-48) !important;
54
59
  }
55
60
 
56
61
  &--size-lg {
62
+ .np-text-title-subsection;
57
63
  height: var(--size-72) !important;
58
64
  }
59
65
  }
@@ -11,10 +11,10 @@ export function formControlClassNameBase({ size = 'auto' }: FormControlPropsBase
11
11
  'form-control', // TODO: Deprecate
12
12
  'np-form-control',
13
13
  {
14
- 'np-form-control--size-auto np-text-body-large': size === 'auto',
15
- 'np-form-control--size-sm np-text-body-default': size === 'sm',
16
- 'np-form-control--size-md np-text-body-large': size === 'md',
17
- 'np-form-control--size-lg np-text-title-subsection': size === 'lg',
14
+ 'np-form-control--size-auto': size === 'auto',
15
+ 'np-form-control--size-sm': size === 'sm',
16
+ 'np-form-control--size-md': size === 'md',
17
+ 'np-form-control--size-lg': size === 'lg',
18
18
  },
19
19
  );
20
20
  }
@@ -0,0 +1,39 @@
1
+ import Link from '../link';
2
+
3
+ import InstructionsList from './InstructionsList';
4
+
5
+ export default {
6
+ component: InstructionsList,
7
+ title: 'Typography/InstructionsList',
8
+ };
9
+
10
+ export const Basic = () => {
11
+ return (
12
+ <>
13
+ <InstructionsList
14
+ dos={[
15
+ 'Do an initial money transfer',
16
+ 'Invite at least 3 friends',
17
+ <span key="12">
18
+ Paying extra{' '}
19
+ <Link href="" type="link-large">
20
+ hidden fees
21
+ </Link>{' '}
22
+ for transfers
23
+ </span>,
24
+ ]}
25
+ donts={['Paying extra hidden fees for transfers', 'Use bad exchange rate']}
26
+ />
27
+ <InstructionsList
28
+ dos={[
29
+ { content: 'Multiple currencies', 'aria-label': 'Supports multiple currencies' },
30
+ { content: 'Existing recipients', 'aria-label': 'Supports existing recipients' },
31
+ ]}
32
+ donts={[
33
+ { content: 'Create recipients', 'aria-label': "Doesn't support creating recipients" },
34
+ { content: 'Edit recipients', 'aria-label': "Doesn't support editing recipients" },
35
+ ]}
36
+ />
37
+ </>
38
+ );
39
+ };
@@ -1 +1 @@
1
- .tw-instructions{display:flex;flex-direction:column}.tw-instructions .instruction{display:flex;margin-top:8px;margin-top:var(--padding-x-small)}.tw-instructions .instruction .do,.tw-instructions .instruction .dont{flex-shrink:0;margin-bottom:0;margin-right:16px;margin-right:var(--padding-small)}.np-theme-personal .tw-instructions .instruction .do,.np-theme-personal .tw-instructions .instruction .dont{margin-right:8px;margin-right:var(--padding-x-small)}.tw-instructions .instruction .do{color:#008026;color:var(--color-content-positive)}.np-theme-personal .tw-instructions .instruction .do{color:var(--color-sentiment-positive)}.tw-instructions .instruction .dont{color:#cf2929;color:var(--color-content-negative)}.np-theme-personal .tw-instructions .instruction .dont{color:var(--color-sentiment-negative)}
1
+ .tw-instructions{display:flex;flex-direction:column}.tw-instructions .instruction{display:flex;margin-top:8px;margin-top:var(--padding-x-small)}.tw-instructions .instruction .do,.tw-instructions .instruction .dont{flex-shrink:0;margin-bottom:0;margin-right:8px;margin-right:var(--padding-x-small)}.tw-instructions .instruction .do{color:var(--color-sentiment-positive)}.tw-instructions .instruction .dont{color:var(--color-sentiment-negative)}
@@ -9,28 +9,16 @@
9
9
  .do,
10
10
  .dont {
11
11
  flex-shrink: 0;
12
- margin-right: var(--padding-small);
12
+ margin-right: var(--padding-x-small);
13
13
  margin-bottom: 0;
14
-
15
- .np-theme-personal & {
16
- margin-right: var(--padding-x-small);
17
- }
18
14
  }
19
15
 
20
16
  .do {
21
- color: var(--color-content-positive);
22
-
23
- .np-theme-personal & {
24
- color: var(--color-sentiment-positive);
25
- }
17
+ color: var(--color-sentiment-positive);
26
18
  }
27
19
 
28
20
  .dont {
29
- color: var(--color-content-negative);
30
-
31
- .np-theme-personal & {
32
- color: var(--color-sentiment-negative);
33
- }
21
+ color: var(--color-sentiment-negative);
34
22
  }
35
23
  }
36
24
  }
@@ -0,0 +1,35 @@
1
+ import { render, screen } from '../test-utils';
2
+
3
+ import InstructionsList from '.';
4
+
5
+ describe('InstructionsList', () => {
6
+ it('should render dos list only', () => {
7
+ const dos = ['Test this component', 'With multiple dos'];
8
+ render(<InstructionsList dos={dos} />);
9
+
10
+ expect(screen.getByText(dos[0])).toBeInTheDocument();
11
+ expect(screen.getByText(dos[1])).toBeInTheDocument();
12
+ });
13
+
14
+ it('should render donts list only', () => {
15
+ const donts = ['Card validation', 'ID verification'];
16
+
17
+ render(<InstructionsList donts={donts} />);
18
+ expect(screen.getByText(donts[0])).toBeInTheDocument();
19
+ expect(screen.getByText(donts[1])).toBeInTheDocument();
20
+ });
21
+
22
+ it('should render do/dont lists with description for icons', () => {
23
+ const dos = [{ content: 'Card validation', 'aria-label': 'Provides card validation' }];
24
+ const donts = [{ content: 'ID verification', 'aria-label': 'No ID verification' }];
25
+
26
+ const { container } = render(<InstructionsList dos={dos} donts={donts} />);
27
+ expect(screen.getByText(dos[0].content)).toBeInTheDocument();
28
+ expect(screen.getByText(donts[0].content)).toBeInTheDocument();
29
+
30
+ const instructions = container.querySelectorAll('.instruction');
31
+ expect(instructions).toHaveLength(2);
32
+ expect(instructions[0]).toHaveAttribute('aria-label', dos[0]['aria-label']);
33
+ expect(instructions[1]).toHaveAttribute('aria-label', donts[0]['aria-label']);
34
+ });
35
+ });
@@ -1,46 +1,61 @@
1
- import { CrossCircle, CrossCircleFill, CheckCircle, CheckCircleFill } from '@transferwise/icons';
2
- import { useTheme } from '@wise/components-theming';
3
- import { ReactNode } from 'react';
1
+ import { CrossCircleFill as DontIcon, CheckCircleFill as DoIcon } from '@transferwise/icons';
2
+ import { ReactNode, isValidElement } from 'react';
4
3
 
5
4
  import Body from '../body/Body';
6
- import { CommonProps } from '../common';
7
- import { Typography } from '../common/propsValues/typography';
5
+ import { Typography, CommonProps } from '../common';
8
6
 
9
- type Props = CommonProps & {
10
- dos?: ReactNode[];
11
- donts?: ReactNode[];
7
+ type InstructionNode = {
8
+ content: ReactNode;
9
+ ['aria-label']: string;
12
10
  };
13
11
 
14
- const InstructionsList = (props: Props) => {
15
- const { isModern } = useTheme();
16
- const { dos, donts } = props;
17
- const DontIcon = isModern ? CrossCircleFill : CrossCircle;
18
- const DoIcon = isModern ? CheckCircleFill : CheckCircle;
12
+ type Props = CommonProps &
13
+ (
14
+ | {
15
+ dos?: ReactNode[];
16
+ donts?: ReactNode[];
17
+ }
18
+ | {
19
+ dos?: InstructionNode[];
20
+ donts?: InstructionNode[];
21
+ }
22
+ );
19
23
 
24
+ const InstructionsList = ({ dos, donts }: Props) => {
20
25
  return (
21
26
  <div className="tw-instructions">
22
27
  {dos &&
23
28
  dos.map((doThis, index) => (
24
29
  // eslint-disable-next-line react/no-array-index-key
25
- <div key={index} className="instruction">
26
- <DoIcon size={24} className="do" />
27
- <Body className="text-primary" type={Typography.BODY_LARGE}>
28
- {doThis}
29
- </Body>
30
- </div>
30
+ <Instruction key={index} item={doThis} type="do" />
31
31
  ))}
32
32
  {donts &&
33
33
  donts.map((dont, index) => (
34
34
  // eslint-disable-next-line react/no-array-index-key
35
- <div key={index} className="instruction">
36
- <DontIcon size={24} className="dont" />
37
- <Body className="text-primary" type={Typography.BODY_LARGE}>
38
- {dont}
39
- </Body>
40
- </div>
35
+ <Instruction key={index} item={dont} type="dont" />
41
36
  ))}
42
37
  </div>
43
38
  );
44
39
  };
45
40
 
41
+ function Instruction({ item, type }: { item: ReactNode | InstructionNode; type: 'do' | 'dont' }) {
42
+ const isInstructionNode =
43
+ typeof item === 'object' && item !== null && 'content' in item && 'aria-label' in item;
44
+ return (
45
+ <div
46
+ className="instruction"
47
+ aria-label={isInstructionNode ? (item['aria-label'] as string) : undefined}
48
+ >
49
+ {type === 'do' ? (
50
+ <DoIcon size={24} className={type} />
51
+ ) : (
52
+ <DontIcon size={24} className={type} />
53
+ )}
54
+ <Body className="text-primary" type={Typography.BODY_LARGE}>
55
+ {isInstructionNode ? item.content : item}
56
+ </Body>
57
+ </div>
58
+ );
59
+ }
60
+
46
61
  export default InstructionsList;