@transferwise/components 45.21.3 → 45.23.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 +103 -77
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +103 -77
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -1
  6. package/build/styles/flowNavigation/FlowNavigation.css +1 -1
  7. package/build/styles/flowNavigation/animatedLabel/AnimatedLabel.css +1 -1
  8. package/build/styles/header/Header.css +1 -1
  9. package/build/styles/inputs/SelectInput.css +1 -1
  10. package/build/styles/main.css +1 -1
  11. package/build/styles/overlayHeader/OverlayHeader.css +1 -1
  12. package/build/types/common/flowHeader/FlowHeader.d.ts.map +1 -1
  13. package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
  14. package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts +1 -2
  15. package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts.map +1 -1
  16. package/build/types/flowNavigation/backButton/BackButton.d.ts +10 -11
  17. package/build/types/flowNavigation/backButton/BackButton.d.ts.map +1 -1
  18. package/build/types/header/Header.d.ts +1 -1
  19. package/build/types/header/Header.d.ts.map +1 -1
  20. package/build/types/inputs/SelectInput.d.ts +5 -1
  21. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  22. package/build/types/overlayHeader/OverlayHeader.d.ts.map +1 -1
  23. package/build/types/title/Title.d.ts +1 -1
  24. package/build/types/title/Title.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/common/flowHeader/FlowHeader.tsx +4 -22
  27. package/src/common/flowHeader/__snapshots__/FlowHeader.spec.js.snap +6 -22
  28. package/src/flowNavigation/FlowNavigation.css +1 -1
  29. package/src/flowNavigation/FlowNavigation.less +0 -9
  30. package/src/flowNavigation/FlowNavigation.spec.js +3 -3
  31. package/src/flowNavigation/FlowNavigation.story.js +22 -189
  32. package/src/flowNavigation/FlowNavigation.tsx +22 -27
  33. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +18 -26
  34. package/src/flowNavigation/animatedLabel/AnimatedLabel.css +1 -1
  35. package/src/flowNavigation/animatedLabel/AnimatedLabel.less +0 -6
  36. package/src/flowNavigation/animatedLabel/AnimatedLabel.spec.js +7 -21
  37. package/src/flowNavigation/animatedLabel/AnimatedLabel.tsx +8 -17
  38. package/src/flowNavigation/animatedLabel/__snapshots__/AnimatedLabel.spec.js.snap +4 -4
  39. package/src/flowNavigation/backButton/BackButton.js +14 -9
  40. package/src/flowNavigation/backButton/BackButton.spec.js +3 -2
  41. package/src/flowNavigation/backButton/__snapshots__/BackButton.spec.js.snap +28 -21
  42. package/src/header/Header.css +1 -1
  43. package/src/header/Header.less +5 -0
  44. package/src/header/Header.tsx +21 -2
  45. package/src/inputs/SelectInput.css +1 -1
  46. package/src/inputs/SelectInput.less +7 -10
  47. package/src/inputs/SelectInput.spec.tsx +40 -0
  48. package/src/inputs/SelectInput.story.tsx +18 -0
  49. package/src/inputs/SelectInput.tsx +46 -12
  50. package/src/inputs/_BottomSheet.less +1 -1
  51. package/src/inputs/_Popover.less +1 -0
  52. package/src/main.css +1 -1
  53. package/src/overlayHeader/OverlayHeader.css +1 -1
  54. package/src/overlayHeader/OverlayHeader.js +6 -4
  55. package/src/overlayHeader/OverlayHeader.less +0 -8
  56. package/src/overlayHeader/OverlayHeader.spec.js +1 -1
  57. package/src/overlayHeader/__snapshots__/OverlayHeader.spec.js.snap +10 -14
  58. package/src/title/Title.tsx +1 -1
  59. package/src/flowNavigation/animatedLabel/AnimatedLabel.story.js +0 -22
@@ -6,23 +6,19 @@ exports[`FlowNavigation on mobile renders as expected 1`] = `
6
6
  class="np-flow-navigation d-flex align-items-center justify-content-center p-y-3 np-flow-navigation--border-bottom"
7
7
  >
8
8
  <div
9
- class="np-flow-header d-flex flex-wrap align-items-center np-flow-navigation__content p-x-3 np-flow-navigation--xs-max"
9
+ class="np-flow-header d-flex flex-wrap align-items-center justify-content-between flex__item--12 np-flow-navigation__content p-x-3 np-flow-navigation--xs-max"
10
10
  >
11
+ <div>
12
+ BackButton
13
+ </div>
11
14
  <div
12
- class="np-flow-header__left flex__item--8"
15
+ class="m-x-1"
16
+ data-testid="activeLabel-1"
13
17
  >
14
- <div>
15
- BackButton
16
- <div
17
- class="m-x-1"
18
- data-testid="activeLabel-0"
19
- >
20
- AnimatedLabel
21
- </div>
22
- </div>
18
+ AnimatedLabel
23
19
  </div>
24
20
  <div
25
- class="align-items-center d-flex np-flow-header__right justify-content-end flex__item--4 "
21
+ class="d-flex align-items-center"
26
22
  >
27
23
  <div
28
24
  class="tw-avatar tw-avatar--48 tw-avatar--initials np-text-title-body"
@@ -34,7 +30,7 @@ exports[`FlowNavigation on mobile renders as expected 1`] = `
34
30
  </div>
35
31
  </div>
36
32
  <span
37
- class="separator"
33
+ class="m-x-1"
38
34
  />
39
35
  <button
40
36
  aria-label="Close"
@@ -122,20 +118,16 @@ exports[`FlowNavigation renders as expected 1`] = `
122
118
  class="np-flow-navigation d-flex align-items-center justify-content-center p-y-3 np-flow-navigation--border-bottom"
123
119
  >
124
120
  <div
125
- class="np-flow-header d-flex flex-wrap align-items-center np-flow-navigation__content p-x-3 np-flow-navigation--sm np-flow-navigation--lg"
121
+ class="np-flow-header d-flex flex-wrap align-items-center justify-content-between flex__item--12 np-flow-navigation__content p-x-3 np-flow-navigation--sm np-flow-navigation--lg"
126
122
  >
123
+ <img
124
+ alt="logo"
125
+ height="24"
126
+ src="logo.svg"
127
+ width="138"
128
+ />
127
129
  <div
128
- class="np-flow-header__left"
129
- >
130
- <img
131
- alt="logo"
132
- height="24"
133
- src="logo.svg"
134
- width="138"
135
- />
136
- </div>
137
- <div
138
- class="align-items-center d-flex np-flow-header__right justify-content-end order-2"
130
+ class="d-flex align-items-center order-2"
139
131
  >
140
132
  <div
141
133
  class="tw-avatar tw-avatar--48 tw-avatar--initials np-text-title-body"
@@ -147,7 +139,7 @@ exports[`FlowNavigation renders as expected 1`] = `
147
139
  </div>
148
140
  </div>
149
141
  <span
150
- class="separator"
142
+ class="m-x-1"
151
143
  />
152
144
  <button
153
145
  aria-label="Close"
@@ -1 +1 @@
1
- .np-animated-label{height:24px;overflow:hidden;padding-top:1px;position:relative}.np-animated-label>*{height:0;opacity:0;position:absolute;transform:translateX(-8px);transition:all .3s ease-in}.np-animated-label--in{height:auto;opacity:1;position:relative;top:auto;transform:translateX(0);transition:all .3s ease-in .3s}.np-animated-label--out{position:relative;top:-24px;transform:translateX(8px)}
1
+ .np-animated-label{height:24px;overflow:hidden;padding-top:1px;position:relative}.np-animated-label>*{height:0;opacity:0;position:absolute;transform:translateX(-8px);transition:all .3s ease-in}.np-animated-label--in{height:auto;opacity:1;position:relative;top:auto;transform:translateX(0);transition:all .3s ease-in .3s}
@@ -28,10 +28,4 @@
28
28
  transform: translateX(0);
29
29
  transition: all @transition-duration ease-in @transition-duration;
30
30
  }
31
-
32
- &--out {
33
- position: relative;
34
- top: -@label-height;
35
- transform: translateX(@slide-length);
36
- }
37
31
  }
@@ -14,12 +14,6 @@ describe('AnimatedLabel', () => {
14
14
  expect(container).toMatchSnapshot();
15
15
  });
16
16
 
17
- it('renders aria-label if provided', () => {
18
- render(<AnimatedLabel {...props} aria-label="hello" />);
19
- const checkbox = screen.getByLabelText('hello');
20
- expect(checkbox).toBeInTheDocument();
21
- });
22
-
23
17
  it('renders only one label with class in', () => {
24
18
  const { container } = render(<AnimatedLabel {...props} />);
25
19
  expect(screen.getByText(props.labels[0])).toHaveClass('np-animated-label--in');
@@ -28,41 +22,33 @@ describe('AnimatedLabel', () => {
28
22
 
29
23
  it('renders only one label with class out', () => {
30
24
  const { container } = render(<AnimatedLabel {...props} />);
31
- expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--out');
32
- expect(container.querySelectorAll('.np-animated-label--out')).toHaveLength(1);
25
+ expect(screen.getByText(props.labels[1])).not.toHaveClass('np-animated-label--in');
26
+ expect(container.querySelectorAll('.np-animated-label--in')).toHaveLength(1);
33
27
  });
34
28
 
35
29
  it('when activeLabel increase it switches class accordingly', () => {
36
30
  const { rerender } = render(<AnimatedLabel {...props} />);
37
31
  expect(screen.getByText(props.labels[0])).toHaveClass('np-animated-label--in');
38
- expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--out');
39
-
40
- expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--out');
32
+ expect(screen.getByText(props.labels[1])).not.toHaveClass('np-animated-label--in');
41
33
  expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--in');
42
34
 
43
35
  rerender(<AnimatedLabel {...props} activeLabel={1} />);
44
36
 
45
- expect(screen.getByText(props.labels[0])).not.toHaveClass('np-animated-label--out');
46
37
  expect(screen.getByText(props.labels[0])).not.toHaveClass('np-animated-label--in');
47
-
48
38
  expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--in');
49
- expect(screen.getByText(props.labels[2])).toHaveClass('np-animated-label--out');
39
+ expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--in');
50
40
  });
51
41
 
52
42
  it('when activeLabel decrease it switches class accordingly', () => {
53
43
  const { rerender } = render(<AnimatedLabel {...props} activeLabel={1} />);
54
- expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--in');
55
- expect(screen.getByText(props.labels[2])).toHaveClass('np-animated-label--out');
56
-
57
- expect(screen.getByText(props.labels[0])).not.toHaveClass('np-animated-label--out');
58
44
  expect(screen.getByText(props.labels[0])).not.toHaveClass('np-animated-label--in');
45
+ expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--in');
46
+ expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--in');
59
47
 
60
48
  rerender(<AnimatedLabel {...props} activeLabel={0} />);
61
49
 
62
50
  expect(screen.getByText(props.labels[0])).toHaveClass('np-animated-label--in');
63
- expect(screen.getByText(props.labels[1])).toHaveClass('np-animated-label--out');
64
-
65
- expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--out');
51
+ expect(screen.getByText(props.labels[1])).not.toHaveClass('np-animated-label--in');
66
52
  expect(screen.getByText(props.labels[2])).not.toHaveClass('np-animated-label--in');
67
53
  });
68
54
  });
@@ -1,41 +1,32 @@
1
1
  import classNames from 'classnames';
2
2
  import React from 'react';
3
3
 
4
+ import Body from '../../body';
5
+ import { Typography } from '../../common';
6
+
4
7
  export interface AnimatedLabelProps {
5
8
  activeLabel: number;
6
9
  className?: string;
7
10
  labels: React.ReactNode[];
8
- 'aria-label': string;
9
11
  }
10
12
 
11
- const AnimatedLabel = ({
12
- activeLabel,
13
- className,
14
- labels,
15
- 'aria-label': ariaLabel,
16
- }: AnimatedLabelProps) => {
17
- const numberLabels = labels.length - 1;
18
-
13
+ const AnimatedLabel = ({ activeLabel, className, labels }: AnimatedLabelProps) => {
19
14
  return (
20
- <div
21
- aria-label={ariaLabel}
22
- className={classNames('np-animated-label', 'np-text-body-large-bold', className)}
23
- >
15
+ <Body type={Typography.BODY_LARGE_BOLD} className={classNames('np-animated-label', className)}>
24
16
  {labels.map((label, index) => {
25
17
  const nextLabel = index - 1;
26
18
  return (
27
19
  <div
28
20
  key={nextLabel}
29
- className={classNames('text-xs-left', {
30
- 'np-animated-label--in': index === activeLabel,
31
- 'np-animated-label--out': nextLabel === activeLabel && nextLabel !== numberLabels,
21
+ className={classNames('text-xs-center', {
22
+ 'np-animated-label--in text-ellipsis': index === activeLabel,
32
23
  })}
33
24
  >
34
25
  {label}
35
26
  </div>
36
27
  );
37
28
  })}
38
- </div>
29
+ </Body>
39
30
  );
40
31
  };
41
32
 
@@ -3,20 +3,20 @@
3
3
  exports[`AnimatedLabel renders all labels 1`] = `
4
4
  <div>
5
5
  <div
6
- class="np-animated-label np-text-body-large-bold"
6
+ class="np-text-body-large-bold np-animated-label"
7
7
  >
8
8
  <div
9
- class="text-xs-left np-animated-label--in"
9
+ class="text-xs-center np-animated-label--in text-ellipsis"
10
10
  >
11
11
  label1
12
12
  </div>
13
13
  <div
14
- class="text-xs-left np-animated-label--out"
14
+ class="text-xs-center"
15
15
  >
16
16
  label2
17
17
  </div>
18
18
  <div
19
- class="text-xs-left"
19
+ class="text-xs-center"
20
20
  >
21
21
  label3
22
22
  </div>
@@ -2,18 +2,23 @@ import { ArrowLeft as ArrowLeftIcon } from '@transferwise/icons';
2
2
  import classNames from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
- const BackButton = ({ label, className, onClick }) => (
6
- <button
7
- type="button"
8
- className={classNames('np-back-button', 'align-items-center', 'btn-unstyled', className)}
9
- onClick={onClick}
10
- >
11
- <ArrowLeftIcon size={24} />
12
- {label}
13
- </button>
5
+ import Avatar, { AvatarType } from '../../avatar';
6
+
7
+ const BackButton = ({ className, onClick, 'aria-label': ariaLabel }) => (
8
+ <Avatar type={AvatarType.ICON} size={40}>
9
+ <button
10
+ type="button"
11
+ aria-label={ariaLabel}
12
+ className={classNames('np-back-button', 'btn-unstyled', className)}
13
+ onClick={onClick}
14
+ >
15
+ <ArrowLeftIcon size={24} />
16
+ </button>
17
+ </Avatar>
14
18
  );
15
19
 
16
20
  BackButton.propTypes = {
21
+ 'aria-label': PropTypes.string.isRequired,
17
22
  className: PropTypes.string,
18
23
  label: PropTypes.element,
19
24
  onClick: PropTypes.func,
@@ -1,5 +1,5 @@
1
- import '@testing-library/jest-dom';
2
- import { render } from '@testing-library/react';
1
+ import { render } from '../../test-utils';
2
+ import messages from '../FlowNavigation.messages';
3
3
 
4
4
  import BackButton from '.';
5
5
 
@@ -7,6 +7,7 @@ const props = {
7
7
  label: <>label</>,
8
8
  className: 'className',
9
9
  onClick: jest.fn(),
10
+ 'aria-label': messages.back.defaultMessage,
10
11
  };
11
12
  describe('BackButton', () => {
12
13
  it(`renders as expected`, () => {
@@ -2,29 +2,36 @@
2
2
 
3
3
  exports[`BackButton renders as expected 1`] = `
4
4
  <div>
5
- <button
6
- class="np-back-button align-items-center btn-unstyled className"
7
- type="button"
5
+ <div
6
+ class="tw-avatar tw-avatar--40 tw-avatar--icon"
8
7
  >
9
- <span
10
- aria-hidden="true"
11
- class="tw-icon tw-icon-arrow-left "
12
- data-testid="arrow-left-icon"
13
- role="presentation"
8
+ <div
9
+ class="tw-avatar__content"
14
10
  >
15
- <svg
16
- fill="currentColor"
17
- focusable="false"
18
- height="24"
19
- viewBox="0 0 24 24"
20
- width="24"
11
+ <button
12
+ class="np-back-button btn-unstyled className"
13
+ type="button"
21
14
  >
22
- <path
23
- d="M22.286 11.316H4.629l7.114-7.114-1.2-1.2-8.572 8.571a.829.829 0 0 0 0 1.2l8.572 8.572 1.2-1.2-7.114-7.114h17.657v-1.715Z"
24
- />
25
- </svg>
26
- </span>
27
- label
28
- </button>
15
+ <span
16
+ aria-hidden="true"
17
+ class="tw-icon tw-icon-arrow-left "
18
+ data-testid="arrow-left-icon"
19
+ role="presentation"
20
+ >
21
+ <svg
22
+ fill="currentColor"
23
+ focusable="false"
24
+ height="24"
25
+ viewBox="0 0 24 24"
26
+ width="24"
27
+ >
28
+ <path
29
+ d="M22.286 11.316H4.629l7.114-7.114-1.2-1.2-8.572 8.571a.829.829 0 0 0 0 1.2l8.572 8.572 1.2-1.2-7.114-7.114h17.657v-1.715Z"
30
+ />
31
+ </svg>
32
+ </span>
33
+ </button>
34
+ </div>
35
+ </div>
29
36
  </div>
30
37
  `;
@@ -1 +1 @@
1
- .np-header{align-items:flex-end;border-bottom:1px solid #0000001a;border-bottom:1px solid var(--color-border-neutral);display:flex;justify-content:space-between;margin-bottom:8px;margin-bottom:var(--size-8);max-width:100%;padding:8px 0;padding:var(--size-8) 0}.np-header__title{color:#5d7079;color:var(--color-content-secondary)}.np-theme-personal .np-header{-moz-column-gap:24px;column-gap:24px;-moz-column-gap:var(--size-24);column-gap:var(--size-24)}.np-theme-personal .np-header__button{margin-inline:-16px;margin-inline:calc(var(--size-16)*-1);margin-bottom:-4px;margin-bottom:calc(var(--size-4)*-1)}
1
+ .np-header{align-items:flex-end;border-bottom:1px solid #0000001a;border-bottom:1px solid var(--color-border-neutral);display:flex;justify-content:space-between;margin-bottom:8px;margin-bottom:var(--size-8);max-width:100%;padding:8px 0;padding:var(--size-8) 0}.np-header__title{color:#5d7079;color:var(--color-content-secondary)}.np-theme-personal .np-header{-moz-column-gap:24px;column-gap:24px;-moz-column-gap:var(--size-24);column-gap:var(--size-24)}.np-theme-personal .np-header__button{margin-inline:-16px;margin-inline:calc(var(--size-16)*-1);margin-bottom:-4px;margin-bottom:calc(var(--size-4)*-1)}.np-header legend{border-bottom:none;padding:0}
@@ -19,4 +19,9 @@
19
19
  margin-bottom: calc(var(--size-4) * -1);
20
20
  }
21
21
  }
22
+
23
+ legend {
24
+ padding: 0;
25
+ border-bottom: none;
26
+ }
22
27
  }
@@ -25,7 +25,7 @@ export type HeaderProps = CommonProps & {
25
25
  *
26
26
  * @default "h5"
27
27
  */
28
- as?: Heading;
28
+ as?: Heading | 'legend';
29
29
  title: string;
30
30
  };
31
31
 
@@ -66,12 +66,31 @@ const HeaderAction = ({ action }: { action: ButtonActionProps | LinkActionProps
66
66
  *
67
67
  */
68
68
  export const Header = ({ action, as = 'h5', title, className }: HeaderProps) => {
69
+ if (!action) {
70
+ return (
71
+ <Title
72
+ as={as}
73
+ type={Typography.TITLE_GROUP}
74
+ className={classNames('np-header', 'np-header__title', className)}
75
+ >
76
+ {title}
77
+ </Title>
78
+ );
79
+ }
80
+
81
+ if (as === 'legend') {
82
+ // eslint-disable-next-line no-console
83
+ console.warn(
84
+ 'Legends should be the first child in a fieldset, and this is not possible when including an action',
85
+ );
86
+ }
87
+
69
88
  return (
70
89
  <div className={classNames('np-header', className)}>
71
90
  <Title as={as} type={Typography.TITLE_GROUP} className="np-header__title">
72
91
  {title}
73
92
  </Title>
74
- {action && <HeaderAction action={action} />}
93
+ <HeaderAction action={action} />
75
94
  </div>
76
95
  );
77
96
  };
@@ -1 +1 @@
1
- .np-bottom-sheet-v2-container{position:relative;z-index:1060}.np-bottom-sheet-v2-backdrop-container--enter,.np-bottom-sheet-v2-backdrop-container--leave{transition-duration:.15s;transition-property:opacity;transition-timing-function:ease-out}.np-bottom-sheet-v2-backdrop-container--enter-from,.np-bottom-sheet-v2-backdrop-container--leave-to{opacity:0}.np-bottom-sheet-v2-backdrop{background-color:#37517e;background-color:var(--color-content-primary);inset:0;opacity:.4;position:fixed}.np-bottom-sheet-v2{display:flex;flex-direction:column;inset:0;justify-content:flex-end;padding-left:8px;padding-left:var(--size-8);padding-right:8px;padding-right:var(--size-8);padding-top:64px;padding-top:var(--size-64);position:fixed}.np-bottom-sheet-v2-content{max-height:100%}.np-bottom-sheet-v2-content--enter,.np-bottom-sheet-v2-content--leave{transition-duration:.3s;transition-property:transform;transition-timing-function:ease-out}@media (prefers-reduced-motion:reduce){.np-bottom-sheet-v2-content--enter,.np-bottom-sheet-v2-content--leave{transition-property:opacity}}@media (prefers-reduced-motion:no-preference){.np-bottom-sheet-v2-content--enter-from,.np-bottom-sheet-v2-content--leave-to{transform:translateY(100%)}}@media (prefers-reduced-motion:reduce){.np-bottom-sheet-v2-content--enter-from,.np-bottom-sheet-v2-content--leave-to{opacity:0}}.np-bottom-sheet-v2-content-inner-container{background-color:#fff;background-color:var(--color-background-elevated);border-top-left-radius:32px;border-top-right-radius:32px;box-shadow:0 0 40px rgba(69,71,69,.2);display:flex;flex-direction:column;height:100%}.np-bottom-sheet-v2-content-inner-container:focus{outline:none}.np-bottom-sheet-v2-header{align-self:flex-end;padding:16px;padding:var(--size-16)}.np-bottom-sheet-v2-content-inner{display:grid;grid-template-rows:repeat(1,minmax(0,1fr));overflow-y:auto;padding-top:0;row-gap:8px;row-gap:var(--size-8)}.np-bottom-sheet-v2-content-inner--has-title{grid-template-rows:auto 1fr}.np-bottom-sheet-v2-content-inner--padding-md{padding:16px;padding:var(--size-16)}.np-bottom-sheet-v2-title{color:#37517e;color:var(--color-content-primary)}.np-bottom-sheet-v2-body{color:#5d7079;color:var(--color-content-secondary)}.np-button-input{align-content:center;border-radius:10px;border-radius:var(--size-10);display:inline-grid;grid-auto-columns:minmax(0,1fr);text-align:start}.np-popover-v2-container{background-color:#fff;background-color:var(--color-background-elevated);border-radius:10px;border-radius:var(--radius-small);box-shadow:0 0 40px rgba(69,71,69,.2);display:flex;flex-direction:column;max-height:var(--max-height);min-width:20rem;width:var(--width);z-index:1060}.np-popover-v2-container:focus{outline:none}.np-popover-v2{display:grid;grid-template-rows:repeat(1,minmax(0,1fr));overflow-y:auto;row-gap:8px;row-gap:var(--size-8)}.np-popover-v2--has-title{grid-template-rows:auto 1fr}.np-popover-v2--padding-md{padding:16px;padding:var(--size-16)}.np-popover-v2-title{color:#37517e;color:var(--color-content-primary)}.np-popover-v2-content{color:#5d7079;color:var(--color-content-secondary)}.np-select-input-placeholder{color:#768e9c;color:var(--color-content-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.np-select-input-options-container{display:flex;flex-direction:column;height:100%}.np-select-input-options-container:focus{outline:none}@media (min-width:576px){.np-select-input-options-container{max-height:28rem}}.np-select-input-options-status{align-items:center;-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);display:flex;padding:8px 24px 0;padding:var(--size-8) var(--size-24) 0}.np-select-input-options-status-icon{color:#768e9c;color:var(--color-content-tertiary);padding:0 4px;padding:0 var(--size-4)}.np-select-input-query-container{display:flex;flex-direction:column;padding:8px;padding:var(--size-8);padding-top:0}@media (min-width:576px){.np-select-input-query-container{padding-top:8px;padding-top:var(--size-8)}}.np-select-input-listbox-container{height:var(--initial-height);overflow-y:auto;position:relative;scroll-padding-bottom:8px;scroll-padding-bottom:var(--size-8);scroll-padding-top:8px;scroll-padding-top:var(--size-8)}@media (min-width:576px){.np-select-input-listbox-container{height:auto}}.np-select-input-listbox-container--has-group{scroll-padding-top:32px;scroll-padding-top:var(--size-32)}.np-select-input-listbox{padding:8px;padding:var(--size-8)}.np-select-input-listbox:focus{outline:none}.np-select-input-separator-item{border-top-width:1px;margin:8px;margin:var(--size-8)}.np-select-input-group-item--without-needle:first-child{margin-top:-8px;margin-top:calc(var(--size-8)*-1)}.np-select-input-group-item-header{background-color:#fff;background-color:var(--color-background-elevated);color:#5d7079;color:var(--color-content-secondary);padding:8px 16px 4px;padding:var(--size-8) var(--size-16) var(--size-4);position:sticky;top:0;z-index:10}.np-select-input-option-container{align-items:center;border-radius:10px;border-radius:var(--radius-small);color:var(--color-interactive-primary);-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);cursor:default;display:flex;padding:12px 16px;padding:var(--size-12) var(--size-16);-webkit-user-select:none;-moz-user-select:none;user-select:none}.np-select-input-option-container--active{box-shadow:inset 0 0 0 1px #c9cbce;box-shadow:inset 0 0 0 1px var(--color-interactive-secondary)}.np-select-input-option-container--disabled{opacity:.45}.np-select-input-option-check--not-selected{visibility:hidden}.np-select-input-option{flex:1}.np-select-input-option-content-container{align-items:center;color:#37517e;color:var(--color-content-primary);-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);display:flex}.np-select-input-option-content-icon{display:flex}.np-select-input-option-content-icon--not-within-trigger{align-self:flex-start}.np-select-input-option-content-text{display:flex;flex:1;flex-direction:column;overflow:hidden}.np-select-input-option-content-text-primary{font:inherit}.np-select-input-option-content-text-secondary{color:#5d7079;color:var(--color-content-secondary)}.np-select-input-option-content-text-within-trigger{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.np-select-input-option-content-text-line-1>:not([hidden])~:not([hidden]){margin-left:8px;margin-left:var(--size-8);margin-right:8px;margin-right:var(--size-8)}.np-select-input-addon-container{align-items:center;display:inline-flex;pointer-events:none}.np-select-input-addon-container,.np-select-input-addon-container>:not([hidden])~:not([hidden]){margin-inline-start:4px;margin-inline-start:var(--size-4)}.np-select-input-addon{align-items:center;background:none;border-radius:.125rem;border-width:0;display:inline-flex;height:24px;height:var(--size-24);justify-content:center;width:24px;width:var(--size-24)}.np-select-input-addon--interactive{color:#c9cbce;color:var(--color-interactive-secondary);pointer-events:auto}.np-select-input-addon--interactive:hover{color:#b5b7ba;color:var(--color-interactive-secondary-hover)}.np-select-input-addon--interactive:focus{outline:none}.np-select-input-addon--interactive:focus-visible{outline:var(--ring-outline-color) solid var(--ring-outline-width);outline-offset:var(--ring-outline-offset)}.np-select-input-addon-separator{border-inline-start:1px solid #0000001a;border-inline-start:1px solid var(--color-border-neutral);height:24px;height:var(--size-24)}
1
+ .np-bottom-sheet-v2-container{position:relative;z-index:1060}.np-bottom-sheet-v2-backdrop-container--enter,.np-bottom-sheet-v2-backdrop-container--leave{transition-duration:.3s;transition-property:opacity;transition-timing-function:ease-out}.np-bottom-sheet-v2-backdrop-container--enter-from,.np-bottom-sheet-v2-backdrop-container--leave-to{opacity:0}.np-bottom-sheet-v2-backdrop{background-color:#37517e;background-color:var(--color-content-primary);inset:0;opacity:.4;position:fixed}.np-bottom-sheet-v2{display:flex;flex-direction:column;inset:0;justify-content:flex-end;padding-left:8px;padding-left:var(--size-8);padding-right:8px;padding-right:var(--size-8);padding-top:64px;padding-top:var(--size-64);position:fixed}.np-bottom-sheet-v2-content{max-height:100%}.np-bottom-sheet-v2-content--enter,.np-bottom-sheet-v2-content--leave{transition-duration:.3s;transition-property:transform;transition-timing-function:ease-out}@media (prefers-reduced-motion:reduce){.np-bottom-sheet-v2-content--enter,.np-bottom-sheet-v2-content--leave{transition-property:opacity}}@media (prefers-reduced-motion:no-preference){.np-bottom-sheet-v2-content--enter-from,.np-bottom-sheet-v2-content--leave-to{transform:translateY(100%)}}@media (prefers-reduced-motion:reduce){.np-bottom-sheet-v2-content--enter-from,.np-bottom-sheet-v2-content--leave-to{opacity:0}}.np-bottom-sheet-v2-content-inner-container{background-color:#fff;background-color:var(--color-background-elevated);border-top-left-radius:32px;border-top-right-radius:32px;box-shadow:0 0 40px rgba(69,71,69,.2);display:flex;flex-direction:column;height:100%}.np-bottom-sheet-v2-content-inner-container:focus{outline:none}.np-bottom-sheet-v2-header{align-self:flex-end;padding:16px;padding:var(--size-16)}.np-bottom-sheet-v2-content-inner{display:grid;grid-template-rows:repeat(1,minmax(0,1fr));overflow-y:auto;padding-top:0;row-gap:8px;row-gap:var(--size-8)}.np-bottom-sheet-v2-content-inner--has-title{grid-template-rows:auto 1fr}.np-bottom-sheet-v2-content-inner--padding-md{padding:16px;padding:var(--size-16)}.np-bottom-sheet-v2-title{color:#37517e;color:var(--color-content-primary)}.np-bottom-sheet-v2-body{color:#5d7079;color:var(--color-content-secondary)}.np-button-input{align-content:center;border-radius:10px;border-radius:var(--size-10);display:inline-grid;grid-auto-columns:minmax(0,1fr);text-align:start}.np-popover-v2-container{background-color:#fff;background-color:var(--color-background-elevated);border-radius:10px;border-radius:var(--radius-small);box-shadow:0 0 40px rgba(69,71,69,.2);display:flex;flex-direction:column;max-height:var(--max-height);min-width:20rem;overflow:hidden;width:var(--width);z-index:1060}.np-popover-v2-container:focus{outline:none}.np-popover-v2{display:grid;grid-template-rows:repeat(1,minmax(0,1fr));overflow-y:auto;row-gap:8px;row-gap:var(--size-8)}.np-popover-v2--has-title{grid-template-rows:auto 1fr}.np-popover-v2--padding-md{padding:16px;padding:var(--size-16)}.np-popover-v2-title{color:#37517e;color:var(--color-content-primary)}.np-popover-v2-content{color:#5d7079;color:var(--color-content-secondary)}.np-select-input-placeholder{color:#768e9c;color:var(--color-content-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.np-select-input-options-container{display:flex;flex-direction:column;height:100%}.np-select-input-options-container:focus{outline:none}@media (min-width:576px){.np-select-input-options-container{max-height:28rem}}.np-select-input-options-status{align-items:center;-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);display:flex;padding:8px 24px 0;padding:var(--size-8) var(--size-24) 0}.np-select-input-options-status-icon{color:#768e9c;color:var(--color-content-tertiary);padding:0 4px;padding:0 var(--size-4)}.np-select-input-query-container{display:flex;flex-direction:column;padding:8px;padding:var(--size-8)}.np-select-input-listbox-container{height:var(--initial-height);overflow-y:auto;position:relative;scroll-padding-bottom:8px;scroll-padding-bottom:var(--size-8);scroll-padding-top:8px;scroll-padding-top:var(--size-8)}@media (min-width:576px){.np-select-input-listbox-container{height:auto}}.np-select-input-listbox-container--has-group{scroll-padding-top:32px;scroll-padding-top:var(--size-32)}.np-select-input-listbox{--ring-outline-offset:calc(var(--ring-outline-width)*-1);border-radius:10px;border-radius:var(--radius-small);padding:8px;padding:var(--size-8)}.np-select-input-listbox:focus{outline:none}.np-select-input-listbox:focus-visible{outline:var(--ring-outline-color) solid var(--ring-outline-width);outline-offset:var(--ring-outline-offset)}.np-select-input-separator-item{border-top-width:1px;margin:8px;margin:var(--size-8)}.np-select-input-group-item--without-needle:first-child{margin-top:-8px;margin-top:calc(var(--size-8)*-1)}.np-select-input-group-item-header{background-color:#fff;background-color:var(--color-background-elevated);color:#5d7079;color:var(--color-content-secondary);padding:8px 16px 4px;padding:var(--size-8) var(--size-16) var(--size-4);position:sticky;top:0;z-index:10}.np-select-input-option-container{align-items:center;border-radius:10px;border-radius:var(--radius-small);color:var(--color-interactive-primary);-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);cursor:default;display:flex;padding:12px 16px;padding:var(--size-12) var(--size-16);-webkit-user-select:none;-moz-user-select:none;user-select:none}.np-select-input-option-container--active{box-shadow:inset 0 0 0 1px #c9cbce;box-shadow:inset 0 0 0 1px var(--color-interactive-secondary)}.np-select-input-option-container--disabled{opacity:.45}.np-select-input-option-check--not-selected{visibility:hidden}.np-select-input-option{flex:1}.np-select-input-option-content-container{align-items:center;color:#37517e;color:var(--color-content-primary);-moz-column-gap:8px;column-gap:8px;-moz-column-gap:var(--size-8);column-gap:var(--size-8);display:flex}.np-select-input-option-content-icon{display:flex}.np-select-input-option-content-icon--not-within-trigger{align-self:flex-start}.np-select-input-option-content-text{display:flex;flex:1;flex-direction:column;overflow:hidden}.np-select-input-option-content-text-primary{font:inherit}.np-select-input-option-content-text-secondary{color:#5d7079;color:var(--color-content-secondary)}.np-select-input-option-content-text-within-trigger{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.np-select-input-option-content-text-line-1>:not([hidden])~:not([hidden]){margin-left:8px;margin-left:var(--size-8);margin-right:8px;margin-right:var(--size-8)}.np-select-input-footer{padding:4px 24px 16px;padding:var(--size-4) var(--size-24) var(--size-16)}.np-select-input-addon-container{align-items:center;display:inline-flex;pointer-events:none}.np-select-input-addon-container,.np-select-input-addon-container>:not([hidden])~:not([hidden]){margin-inline-start:4px;margin-inline-start:var(--size-4)}.np-select-input-addon{align-items:center;background:none;border-radius:.125rem;border-width:0;display:inline-flex;height:24px;height:var(--size-24);justify-content:center;width:24px;width:var(--size-24)}.np-select-input-addon--interactive{color:#c9cbce;color:var(--color-interactive-secondary);pointer-events:auto}.np-select-input-addon--interactive:hover{color:#b5b7ba;color:var(--color-interactive-secondary-hover)}.np-select-input-addon--interactive:focus{outline:none}.np-select-input-addon--interactive:focus-visible{outline:var(--ring-outline-color) solid var(--ring-outline-width);outline-offset:var(--ring-outline-offset)}.np-select-input-addon-separator{border-inline-start:1px solid #0000001a;border-inline-start:1px solid var(--color-border-neutral);height:24px;height:var(--size-24)}
@@ -42,13 +42,6 @@
42
42
  display: flex;
43
43
  flex-direction: column;
44
44
  padding: var(--size-8);
45
- padding-top: 0px;
46
-
47
- @media (--screen-sm) {
48
- & {
49
- padding-top: var(--size-8);
50
- }
51
- }
52
45
  }
53
46
 
54
47
  .np-select-input-listbox-container {
@@ -70,11 +63,11 @@
70
63
  }
71
64
 
72
65
  .np-select-input-listbox {
66
+ border-radius: var(--radius-small);
73
67
  padding: var(--size-8);
74
68
 
75
- &:focus {
76
- outline: none;
77
- }
69
+ .focus-ring();
70
+ --ring-outline-offset: calc(-1 * var(--ring-outline-width));
78
71
  }
79
72
 
80
73
  .np-select-input-separator-item {
@@ -169,6 +162,10 @@
169
162
  }
170
163
  }
171
164
 
165
+ .np-select-input-footer {
166
+ padding: var(--size-4) var(--size-24) var(--size-16);
167
+ }
168
+
172
169
  .np-select-input-addon-container {
173
170
  pointer-events: none;
174
171
  margin-inline-start: var(--size-4);
@@ -47,6 +47,46 @@ describe('SelectInput', () => {
47
47
  expect(screen.getByText('Currency')).toBeInTheDocument();
48
48
  });
49
49
 
50
+ it('renders footer', async () => {
51
+ render(
52
+ <SelectInput
53
+ items={[
54
+ { type: 'option', value: 'USD' },
55
+ { type: 'option', value: 'EUR' },
56
+ ]}
57
+ renderFooter={({ normalizedQuery }) =>
58
+ normalizedQuery != null ? (
59
+ <>Showing results for ‘{normalizedQuery}’</>
60
+ ) : (
61
+ <>All items shown</>
62
+ )
63
+ }
64
+ filterable
65
+ />,
66
+ );
67
+
68
+ // eslint-disable-next-line @typescript-eslint/require-await
69
+ await act(async () => {
70
+ userEvent.tab();
71
+ userEvent.keyboard(specialChars.enter);
72
+ });
73
+
74
+ const footer = screen.getByText('All items shown');
75
+ expect(footer).toBeInTheDocument();
76
+
77
+ userEvent.keyboard('u');
78
+ expect(footer).toHaveTextContent(/‘u’$/);
79
+
80
+ userEvent.keyboard('r');
81
+ expect(footer).toHaveTextContent(/‘ur’$/);
82
+
83
+ userEvent.keyboard('x');
84
+ expect(footer).toHaveTextContent(/‘urx’$/);
85
+
86
+ userEvent.keyboard(specialChars.backspace);
87
+ expect(footer).toHaveTextContent(/‘ur’$/);
88
+ });
89
+
50
90
  it('shows item selected via mouse', async () => {
51
91
  render(
52
92
  <SelectInput
@@ -266,6 +266,24 @@ export const Currencies: StoryObj<{
266
266
  icon={<Flag code={currency.code} intrinsicSize={24} />}
267
267
  />
268
268
  )}
269
+ renderFooter={({ resultsEmpty, normalizedQuery }) =>
270
+ resultsEmpty && normalizedQuery != null && /^[a-z]{3}$/u.test(normalizedQuery) ? (
271
+ <>
272
+ It’s not possible use {normalizedQuery.toUpperCase()} yet.{' '}
273
+ <a href="#_" className="np-text-link-default">
274
+ Email me when it’s available.
275
+ </a>
276
+ </>
277
+ ) : (
278
+ <>
279
+ Can’t find it?{' '}
280
+ <a href="#_" className="np-text-link-default">
281
+ Request the currency you need,
282
+ </a>{' '}
283
+ and we’ll notify you once it’s available.
284
+ </>
285
+ )
286
+ }
269
287
  filterable
270
288
  filterPlaceholder="Type a currency / country"
271
289
  size="lg"