@transferwise/components 45.21.3 → 45.22.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.
- package/build/index.esm.js +90 -76
- package/build/index.esm.js.map +1 -1
- package/build/index.js +90 -76
- package/build/index.js.map +1 -1
- package/build/main.css +1 -1
- package/build/styles/flowNavigation/FlowNavigation.css +1 -1
- package/build/styles/flowNavigation/animatedLabel/AnimatedLabel.css +1 -1
- package/build/styles/inputs/SelectInput.css +1 -1
- package/build/styles/main.css +1 -1
- package/build/styles/overlayHeader/OverlayHeader.css +1 -1
- package/build/types/common/flowHeader/FlowHeader.d.ts.map +1 -1
- package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts +1 -2
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts.map +1 -1
- package/build/types/flowNavigation/backButton/BackButton.d.ts +10 -11
- package/build/types/flowNavigation/backButton/BackButton.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +5 -1
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/overlayHeader/OverlayHeader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/flowHeader/FlowHeader.tsx +4 -22
- package/src/common/flowHeader/__snapshots__/FlowHeader.spec.js.snap +6 -22
- package/src/flowNavigation/FlowNavigation.css +1 -1
- package/src/flowNavigation/FlowNavigation.less +0 -9
- package/src/flowNavigation/FlowNavigation.spec.js +3 -3
- package/src/flowNavigation/FlowNavigation.story.js +22 -189
- package/src/flowNavigation/FlowNavigation.tsx +22 -27
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +18 -26
- package/src/flowNavigation/animatedLabel/AnimatedLabel.css +1 -1
- package/src/flowNavigation/animatedLabel/AnimatedLabel.less +0 -6
- package/src/flowNavigation/animatedLabel/AnimatedLabel.spec.js +7 -21
- package/src/flowNavigation/animatedLabel/AnimatedLabel.tsx +8 -17
- package/src/flowNavigation/animatedLabel/__snapshots__/AnimatedLabel.spec.js.snap +4 -4
- package/src/flowNavigation/backButton/BackButton.js +14 -9
- package/src/flowNavigation/backButton/BackButton.spec.js +3 -2
- package/src/flowNavigation/backButton/__snapshots__/BackButton.spec.js.snap +28 -21
- package/src/inputs/SelectInput.css +1 -1
- package/src/inputs/SelectInput.less +7 -10
- package/src/inputs/SelectInput.spec.tsx +40 -0
- package/src/inputs/SelectInput.story.tsx +18 -0
- package/src/inputs/SelectInput.tsx +46 -12
- package/src/inputs/_BottomSheet.less +1 -1
- package/src/inputs/_Popover.less +1 -0
- package/src/main.css +1 -1
- package/src/overlayHeader/OverlayHeader.css +1 -1
- package/src/overlayHeader/OverlayHeader.js +6 -4
- package/src/overlayHeader/OverlayHeader.less +0 -8
- package/src/overlayHeader/OverlayHeader.spec.js +1 -1
- package/src/overlayHeader/__snapshots__/OverlayHeader.spec.js.snap +10 -14
- 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="
|
|
15
|
+
class="m-x-1"
|
|
16
|
+
data-testid="activeLabel-1"
|
|
13
17
|
>
|
|
14
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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}
|
|
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}
|
|
@@ -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--
|
|
32
|
-
expect(container.querySelectorAll('.np-animated-label--
|
|
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--
|
|
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--
|
|
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--
|
|
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
|
-
<
|
|
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-
|
|
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
|
-
</
|
|
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-
|
|
6
|
+
class="np-text-body-large-bold np-animated-label"
|
|
7
7
|
>
|
|
8
8
|
<div
|
|
9
|
-
class="text-xs-
|
|
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-
|
|
14
|
+
class="text-xs-center"
|
|
15
15
|
>
|
|
16
16
|
label2
|
|
17
17
|
</div>
|
|
18
18
|
<div
|
|
19
|
-
class="text-xs-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 '
|
|
2
|
-
import
|
|
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
|
-
<
|
|
6
|
-
class="
|
|
7
|
-
type="button"
|
|
5
|
+
<div
|
|
6
|
+
class="tw-avatar tw-avatar--40 tw-avatar--icon"
|
|
8
7
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
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
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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-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:.
|
|
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
|
-
|
|
76
|
-
|
|
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"
|
|
@@ -135,6 +135,10 @@ export interface SelectInputProps<T = string> {
|
|
|
135
135
|
| (keyof NonNullable<T> & string)
|
|
136
136
|
| ((a: T | undefined, b: T | undefined) => boolean);
|
|
137
137
|
renderValue?: (value: NonNullable<T>, withinTrigger: boolean) => React.ReactNode;
|
|
138
|
+
renderFooter?: (args: {
|
|
139
|
+
resultsEmpty: boolean;
|
|
140
|
+
normalizedQuery: string | null | undefined;
|
|
141
|
+
}) => React.ReactNode;
|
|
138
142
|
renderTrigger?: (args: {
|
|
139
143
|
content: React.ReactNode;
|
|
140
144
|
placeholderShown: boolean;
|
|
@@ -211,6 +215,7 @@ export function SelectInput<T = string>({
|
|
|
211
215
|
value: controlledValue,
|
|
212
216
|
compareValues,
|
|
213
217
|
renderValue = wrapInFragment,
|
|
218
|
+
renderFooter,
|
|
214
219
|
renderTrigger = defaultRenderTrigger,
|
|
215
220
|
filterable,
|
|
216
221
|
filterPlaceholder,
|
|
@@ -296,6 +301,7 @@ export function SelectInput<T = string>({
|
|
|
296
301
|
<SelectInputOptions
|
|
297
302
|
items={items}
|
|
298
303
|
renderValue={renderValue}
|
|
304
|
+
renderFooter={renderFooter}
|
|
299
305
|
filterable={filterable}
|
|
300
306
|
filterPlaceholder={filterPlaceholder}
|
|
301
307
|
searchInputRef={searchInputRef}
|
|
@@ -351,17 +357,22 @@ const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContai
|
|
|
351
357
|
}, [ariaActiveDescendant, handleAriaActiveDescendantChange]);
|
|
352
358
|
|
|
353
359
|
return (
|
|
354
|
-
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
355
360
|
<div
|
|
356
361
|
ref={ref}
|
|
362
|
+
role="none"
|
|
357
363
|
onKeyDown={(event) => {
|
|
358
|
-
// Prevent
|
|
359
|
-
if (event.key === '
|
|
364
|
+
// Prevent confirmation close without an active item
|
|
365
|
+
if (event.key === 'Enter' && ariaActiveDescendant == null) {
|
|
360
366
|
return;
|
|
361
367
|
}
|
|
362
368
|
|
|
363
|
-
// Prevent
|
|
364
|
-
if (event.key === '
|
|
369
|
+
// Prevent absorbing actions early
|
|
370
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
371
|
+
onKeyDown?.({
|
|
372
|
+
...event,
|
|
373
|
+
preventDefault: () => {},
|
|
374
|
+
stopPropagation: () => {},
|
|
375
|
+
});
|
|
365
376
|
return;
|
|
366
377
|
}
|
|
367
378
|
|
|
@@ -373,7 +384,10 @@ const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContai
|
|
|
373
384
|
});
|
|
374
385
|
|
|
375
386
|
interface SelectInputOptionsProps<T = string>
|
|
376
|
-
extends Pick<
|
|
387
|
+
extends Pick<
|
|
388
|
+
SelectInputProps<T>,
|
|
389
|
+
'items' | 'renderValue' | 'renderFooter' | 'filterable' | 'filterPlaceholder'
|
|
390
|
+
> {
|
|
377
391
|
searchInputRef: React.RefObject<HTMLInputElement>;
|
|
378
392
|
listboxRef: React.RefObject<HTMLDivElement>;
|
|
379
393
|
}
|
|
@@ -381,6 +395,7 @@ interface SelectInputOptionsProps<T = string>
|
|
|
381
395
|
function SelectInputOptions<T = string>({
|
|
382
396
|
items,
|
|
383
397
|
renderValue = wrapInFragment,
|
|
398
|
+
renderFooter,
|
|
384
399
|
filterable = false,
|
|
385
400
|
filterPlaceholder,
|
|
386
401
|
searchInputRef,
|
|
@@ -397,7 +412,7 @@ function SelectInputOptions<T = string>({
|
|
|
397
412
|
}
|
|
398
413
|
return undefined;
|
|
399
414
|
}, [filterable, query]);
|
|
400
|
-
const
|
|
415
|
+
const resultsEmpty = needle != null && filterSelectInputItems(items, needle).length === 0;
|
|
401
416
|
|
|
402
417
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
403
418
|
useEffect(() => {
|
|
@@ -409,7 +424,7 @@ function SelectInputOptions<T = string>({
|
|
|
409
424
|
}
|
|
410
425
|
}, []);
|
|
411
426
|
|
|
412
|
-
const showStatus =
|
|
427
|
+
const showStatus = resultsEmpty;
|
|
413
428
|
const statusId = useId();
|
|
414
429
|
const listboxId = useId();
|
|
415
430
|
|
|
@@ -451,7 +466,7 @@ function SelectInputOptions<T = string>({
|
|
|
451
466
|
</div>
|
|
452
467
|
) : null}
|
|
453
468
|
|
|
454
|
-
<
|
|
469
|
+
<section
|
|
455
470
|
ref={listboxContainerRef}
|
|
456
471
|
className={classNames(
|
|
457
472
|
'np-select-input-listbox-container',
|
|
@@ -459,7 +474,7 @@ function SelectInputOptions<T = string>({
|
|
|
459
474
|
'np-select-input-listbox-container--has-group',
|
|
460
475
|
)}
|
|
461
476
|
>
|
|
462
|
-
{
|
|
477
|
+
{resultsEmpty ? (
|
|
463
478
|
<div id={statusId} className="np-select-input-options-status">
|
|
464
479
|
<CrossCircle size={16} className="np-select-input-options-status-icon" />
|
|
465
480
|
{intl.formatMessage(messages.noResultsFound)}
|
|
@@ -484,7 +499,26 @@ function SelectInputOptions<T = string>({
|
|
|
484
499
|
/>
|
|
485
500
|
))}
|
|
486
501
|
</div>
|
|
487
|
-
|
|
502
|
+
|
|
503
|
+
{renderFooter != null ? (
|
|
504
|
+
<footer className="np-select-input-footer">
|
|
505
|
+
<div
|
|
506
|
+
role="none"
|
|
507
|
+
onKeyDown={(event) => {
|
|
508
|
+
// Prevent interfering with Headless UI
|
|
509
|
+
if (event.key !== 'Escape') {
|
|
510
|
+
event.stopPropagation();
|
|
511
|
+
}
|
|
512
|
+
}}
|
|
513
|
+
>
|
|
514
|
+
{renderFooter({
|
|
515
|
+
resultsEmpty,
|
|
516
|
+
normalizedQuery: needle,
|
|
517
|
+
})}
|
|
518
|
+
</div>
|
|
519
|
+
</footer>
|
|
520
|
+
) : null}
|
|
521
|
+
</section>
|
|
488
522
|
</ListboxBase.Options>
|
|
489
523
|
);
|
|
490
524
|
}
|
|
@@ -547,7 +581,7 @@ function SelectInputGroupItemView<T = string>({
|
|
|
547
581
|
{needle == null ? (
|
|
548
582
|
<header
|
|
549
583
|
id={headerId}
|
|
550
|
-
role="
|
|
584
|
+
role="none"
|
|
551
585
|
className="np-select-input-group-item-header np-text-title-group"
|
|
552
586
|
>
|
|
553
587
|
{item.label}
|