@transferwise/components 45.14.2 → 45.15.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 (88) hide show
  1. package/build/index.esm.js +680 -50
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +683 -50
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -1
  6. package/build/styles/common/closeButton/CloseButton.css +1 -1
  7. package/build/styles/inputs/Input.css +1 -1
  8. package/build/styles/inputs/InputGroup.css +1 -1
  9. package/build/styles/inputs/SelectInput.css +1 -0
  10. package/build/styles/inputs/TextArea.css +1 -1
  11. package/build/styles/main.css +1 -1
  12. package/build/styles/promoCard/PromoCard.css +1 -1
  13. package/build/styles/stepper/Stepper.css +1 -1
  14. package/build/types/common/hooks/useMedia.d.ts +2 -0
  15. package/build/types/common/hooks/useMedia.d.ts.map +1 -0
  16. package/build/types/common/hooks/useScreenSize.d.ts +3 -0
  17. package/build/types/common/hooks/useScreenSize.d.ts.map +1 -0
  18. package/build/types/common/preventScroll/PreventScroll.d.ts +2 -0
  19. package/build/types/common/preventScroll/PreventScroll.d.ts.map +1 -0
  20. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts +7 -7
  21. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts.map +1 -1
  22. package/build/types/index.d.ts +4 -0
  23. package/build/types/index.d.ts.map +1 -1
  24. package/build/types/inputs/Input.d.ts +1 -0
  25. package/build/types/inputs/Input.d.ts.map +1 -1
  26. package/build/types/inputs/SearchInput.d.ts +10 -0
  27. package/build/types/inputs/SearchInput.d.ts.map +1 -0
  28. package/build/types/inputs/SelectInput.d.ts +41 -0
  29. package/build/types/inputs/SelectInput.d.ts.map +1 -0
  30. package/build/types/inputs/_BottomSheet.d.ts +17 -0
  31. package/build/types/inputs/_BottomSheet.d.ts.map +1 -0
  32. package/build/types/inputs/_ButtonInput.d.ts +6 -0
  33. package/build/types/inputs/_ButtonInput.d.ts.map +1 -0
  34. package/build/types/inputs/_Popover.d.ts +18 -0
  35. package/build/types/inputs/_Popover.d.ts.map +1 -0
  36. package/build/types/inputs/_common.d.ts.map +1 -1
  37. package/build/types/logo/Logo.d.ts.map +1 -1
  38. package/build/types/stepper/Stepper.d.ts.map +1 -1
  39. package/build/types/tile/Tile.d.ts.map +1 -1
  40. package/build/types/utilities/wrapInFragment.d.ts +3 -0
  41. package/build/types/utilities/wrapInFragment.d.ts.map +1 -0
  42. package/package.json +26 -20
  43. package/src/common/closeButton/CloseButton.css +1 -1
  44. package/src/common/hooks/useMedia.spec.ts +39 -0
  45. package/src/common/hooks/useMedia.ts +15 -0
  46. package/src/common/hooks/useScreenSize.ts +7 -0
  47. package/src/common/preventScroll/PreventScroll.tsx +6 -0
  48. package/src/decision/Decision.story.js +11 -11
  49. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +12 -12
  50. package/src/i18n/en.json +1 -0
  51. package/src/index.ts +8 -0
  52. package/src/inputs/Input.css +1 -1
  53. package/src/inputs/Input.less +14 -0
  54. package/src/inputs/Input.tsx +6 -2
  55. package/src/inputs/InputGroup.css +1 -1
  56. package/src/inputs/InputGroup.less +5 -0
  57. package/src/inputs/SearchInput.story.tsx +40 -0
  58. package/src/inputs/SearchInput.tsx +35 -0
  59. package/src/inputs/SelectInput.css +1 -0
  60. package/src/inputs/SelectInput.less +183 -0
  61. package/src/inputs/SelectInput.spec.tsx +120 -0
  62. package/src/inputs/SelectInput.story.tsx +259 -0
  63. package/src/inputs/SelectInput.tsx +565 -0
  64. package/src/inputs/TextArea.css +1 -1
  65. package/src/inputs/TextArea.less +5 -0
  66. package/src/inputs/_BottomSheet.less +107 -0
  67. package/src/inputs/_BottomSheet.tsx +128 -0
  68. package/src/inputs/_ButtonInput.less +7 -0
  69. package/src/inputs/_ButtonInput.tsx +27 -0
  70. package/src/inputs/_Popover.less +38 -0
  71. package/src/inputs/_Popover.tsx +118 -0
  72. package/src/inputs/_common.less +0 -4
  73. package/src/inputs/_common.ts +0 -1
  74. package/src/logo/Logo.js +3 -21
  75. package/src/logo/__snapshots__/Logo.spec.js.snap +78 -30
  76. package/src/main.css +1 -1
  77. package/src/main.less +4 -0
  78. package/src/promoCard/PromoCard.css +1 -1
  79. package/src/select/searchBox/__snapshots__/SearchBox.spec.js.snap +1 -1
  80. package/src/ssr.spec.js +7 -0
  81. package/src/stepper/Stepper.css +1 -1
  82. package/src/stepper/Stepper.less +1 -9
  83. package/src/stepper/Stepper.spec.js +4 -4
  84. package/src/stepper/Stepper.tsx +2 -5
  85. package/src/tile/Tile.js +5 -11
  86. package/src/tile/__snapshots__/Tile.spec.js.snap +7 -9
  87. package/src/utilities/wrapInFragment.tsx +3 -0
  88. /package/src/dateLookup/dateTrigger/{DateTrigger.messages.js → DateTrigger.messages.ts} +0 -0
@@ -79,31 +79,31 @@ exports[`FlowNavigation on mobile renders as expected 1`] = `
79
79
  class="tw-stepper-steps p-t-1 m-b-0"
80
80
  >
81
81
  <li
82
- class="hidden-xs tw-stepper__step"
82
+ class="hidden-xs tw-stepper__step np-text-body-default"
83
83
  style="left: 0%;"
84
84
  >
85
85
  <span
86
- class="np-text-body-default tw-stepper__step-label small"
86
+ class="tw-stepper__step-label"
87
87
  >
88
88
  label-0
89
89
  </span>
90
90
  </li>
91
91
  <li
92
- class="hidden-xs tw-stepper__step tw-stepper__step--active"
92
+ class="hidden-xs tw-stepper__step np-text-body-default-bold tw-stepper__step--active"
93
93
  style="left: 50%;"
94
94
  >
95
95
  <span
96
- class="np-text-body-default tw-stepper__step-label small"
96
+ class="tw-stepper__step-label"
97
97
  >
98
98
  label-1
99
99
  </span>
100
100
  </li>
101
101
  <li
102
- class="hidden-xs tw-stepper__step"
102
+ class="hidden-xs tw-stepper__step np-text-body-default"
103
103
  style="left: 100%;"
104
104
  >
105
105
  <span
106
- class="np-text-body-default tw-stepper__step-label small"
106
+ class="tw-stepper__step-label"
107
107
  >
108
108
  label-2
109
109
  </span>
@@ -192,31 +192,31 @@ exports[`FlowNavigation renders as expected 1`] = `
192
192
  class="tw-stepper-steps p-t-1 m-b-0"
193
193
  >
194
194
  <li
195
- class="hidden-xs tw-stepper__step tw-stepper__step--active"
195
+ class="hidden-xs tw-stepper__step np-text-body-default-bold tw-stepper__step--active"
196
196
  style="left: 0%;"
197
197
  >
198
198
  <span
199
- class="np-text-body-default tw-stepper__step-label small"
199
+ class="tw-stepper__step-label"
200
200
  >
201
201
  label-0
202
202
  </span>
203
203
  </li>
204
204
  <li
205
- class="hidden-xs tw-stepper__step"
205
+ class="hidden-xs tw-stepper__step np-text-body-default"
206
206
  style="left: 50%;"
207
207
  >
208
208
  <span
209
- class="np-text-body-default tw-stepper__step-label small"
209
+ class="tw-stepper__step-label"
210
210
  >
211
211
  label-1
212
212
  </span>
213
213
  </li>
214
214
  <li
215
- class="hidden-xs tw-stepper__step"
215
+ class="hidden-xs tw-stepper__step np-text-body-default"
216
216
  style="left: 100%;"
217
217
  >
218
218
  <span
219
- class="np-text-body-default tw-stepper__step-label small"
219
+ class="tw-stepper__step-label"
220
220
  >
221
221
  label-2
222
222
  </span>
package/src/i18n/en.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "neptune.DateInput.day.label": "Day",
6
6
  "neptune.DateInput.month.label": "Month",
7
7
  "neptune.DateInput.year.label": "Year",
8
+ "neptune.Link.opensInNewTab": "(opens in new tab)",
8
9
  "neptune.MoneyInput.Select.placeholder": "Select an option...",
9
10
  "neptune.Select.searchPlaceholder": "Search...",
10
11
  "neptune.Summary.statusDone": "Item done",
package/src/index.ts CHANGED
@@ -3,6 +3,12 @@
3
3
  */
4
4
  export type { InputProps } from './inputs/Input';
5
5
  export type { InputGroupProps } from './inputs/InputGroup';
6
+ export type { SearchInputProps } from './inputs/SearchInput';
7
+ export type {
8
+ SelectInputItem,
9
+ SelectInputOptionContentProps,
10
+ SelectInputProps,
11
+ } from './inputs/SelectInput';
6
12
  export type { TextAreaProps } from './inputs/TextArea';
7
13
  export type { UploadedFile, UploadError, UploadResponse } from './uploadInput/types';
8
14
  export type { ModalProps } from './modal';
@@ -54,6 +60,8 @@ export { default as Info } from './info';
54
60
  export { default as InlineAlert } from './inlineAlert';
55
61
  export { Input } from './inputs/Input';
56
62
  export { InputGroup } from './inputs/InputGroup';
63
+ export { SearchInput } from './inputs/SearchInput';
64
+ export { SelectInput, SelectInputOptionContent } from './inputs/SelectInput';
57
65
  export { TextArea } from './inputs/TextArea';
58
66
  export { default as InputWithDisplayFormat } from './inputWithDisplayFormat';
59
67
  export { default as InstructionsList } from './instructionsList';
@@ -1 +1 @@
1
- .np-form-control{--ring-width:1px;--ring-color:var(--color-interactive-secondary);background-color:transparent;border:none;box-shadow:inset 0 0 0 var(--ring-width) var(--ring-color);color:#37517e;color:var(--color-content-primary);min-height:0;padding-left:16px;padding-left:var(--size-16);padding-right:16px;padding-right:var(--size-16);transition-duration:.3s;transition-property:color,opacity,box-shadow;transition-timing-function:ease-in-out}.np-form-control:focus-visible{outline:none}.np-form-control[aria-invalid=true]{--ring-width:2px;--ring-color:var(--color-sentiment-negative)!important}.np-form-control:hover:enabled{--ring-width:2px;--ring-color:var(--color-interactive-secondary-hover)}.np-form-control:focus:enabled{--ring-width:3px;--ring-color:var(--color-interactive-primary)}.np-form-control--size-auto{padding-bottom:12px;padding-bottom:var(--size-12);padding-top:12px;padding-top:var(--size-12)}.np-form-control--size-lg,.np-form-control--size-md,.np-form-control--size-sm{padding-bottom:0!important;padding-top:0!important}.np-form-control--size-sm{height:32px!important;height:var(--size-32)!important}.np-form-control--size-md{height:48px!important;height:var(--size-48)!important}.np-form-control--size-lg{height:72px!important;height:var(--size-72)!important}.np-form-control--shape-rectangle{border-radius:10px;border-radius:var(--radius-small)}
1
+ .np-form-control{--ring-width:1px;--ring-color:var(--color-interactive-secondary);background-color:transparent;border:none;box-shadow:inset 0 0 0 var(--ring-width) var(--ring-color);color:#37517e;color:var(--color-content-primary);min-height:0;padding-left:16px;padding-left:var(--size-16);padding-right:16px;padding-right:var(--size-16);transition-duration:.3s;transition-property:color,opacity,box-shadow;transition-timing-function:ease-in-out}.np-form-control:focus-visible{outline:none}.np-form-control[aria-invalid=true]{--ring-width:2px;--ring-color:var(--color-sentiment-negative)!important}.np-form-control:hover:enabled{--ring-width:2px;--ring-color:var(--color-interactive-secondary-hover)}.np-form-control:focus:enabled{--ring-width:3px;--ring-color:var(--color-interactive-primary)}.np-form-control--size-auto{padding-bottom:12px;padding-bottom:var(--size-12);padding-top:12px;padding-top:var(--size-12)}.np-form-control--size-lg,.np-form-control--size-md,.np-form-control--size-sm{padding-bottom:0!important;padding-top:0!important}.np-form-control--size-sm{height:32px!important;height:var(--size-32)!important}.np-form-control--size-md{height:48px!important;height:var(--size-48)!important}.np-form-control--size-lg{height:72px!important;height:var(--size-72)!important}.np-input::-moz-placeholder{color:#768e9c;color:var(--color-content-tertiary)}.np-input::placeholder{color:#768e9c;color:var(--color-content-tertiary)}.np-input--shape-rectangle{border-radius:10px!important;border-radius:var(--radius-small)!important}.np-input--shape-pill{border-radius:9999px!important;border-radius:var(--radius-full)!important}
@@ -1 +1,15 @@
1
1
  @import "./_common.less";
2
+
3
+ .np-input {
4
+ &::placeholder {
5
+ color: var(--color-content-tertiary);
6
+ }
7
+
8
+ &--shape-rectangle {
9
+ border-radius: var(--radius-small) !important;
10
+ }
11
+
12
+ &--shape-pill {
13
+ border-radius: var(--radius-full) !important;
14
+ }
15
+ }
@@ -12,12 +12,13 @@ export interface InputProps
12
12
  React.ComponentPropsWithRef<'input'>,
13
13
  {
14
14
  size?: 'auto' | SizeSmall | SizeMedium | SizeLarge;
15
+ shape?: 'rectangle' | 'pill';
15
16
  'aria-invalid'?: boolean;
16
17
  }
17
18
  > {}
18
19
 
19
20
  export const Input = forwardRef(function Input(
20
- { size = 'auto', className, ...restProps }: InputProps,
21
+ { size = 'auto', shape = 'rectangle', className, ...restProps }: InputProps,
21
22
  reference: React.ForwardedRef<HTMLInputElement>,
22
23
  ) {
23
24
  const inputPaddings = useInputPaddings();
@@ -25,7 +26,10 @@ export const Input = forwardRef(function Input(
25
26
  return (
26
27
  <input
27
28
  ref={reference}
28
- className={classNames(className, formControlClassNameBase({ size }))}
29
+ className={classNames(className, formControlClassNameBase({ size }), 'np-input', {
30
+ 'np-input--shape-rectangle': shape === 'rectangle',
31
+ 'np-input--shape-pill': shape === 'pill',
32
+ })}
29
33
  // eslint-disable-next-line react/forbid-dom-props
30
34
  style={inputPaddings}
31
35
  {...restProps}
@@ -1 +1 @@
1
- .np-input-group{border-radius:50%;border-radius:var(--radius-full);display:inline-grid;grid-auto-columns:minmax(0,1fr)}.np-input-group>*{grid-column-start:1;grid-row-start:1}.np-input-addon{align-items:center;color:#c9cbce;color:var(--color-interactive-secondary);display:inline-flex;pointer-events:none;transition-duration:.15s;transition-property:color,opacity;transition-timing-function:ease-out;z-index:10}.np-input-group:has(>:is(input,button,select):focus-visible) .np-input-addon{color:var(--color-interactive-primary)}.np-input-group:has(>:is(input,button,select):hover) .np-input-addon{color:#b5b7ba;color:var(--color-interactive-secondary-hover)}.np-input-addon--placement-start{justify-self:start}.np-input-addon--placement-end{justify-self:end}.np-input-addon--interactive>*{pointer-events:auto}.np-input-addon--padding-sm{padding-left:8px;padding-left:var(--size-8);padding-right:8px;padding-right:var(--size-8)}.np-input-addon--padding-md{padding-left:16px;padding-left:var(--size-16);padding-right:16px;padding-right:var(--size-16)}.np-input-addon--padding-md.np-input-addon--placement-start{padding-inline-end:8px;padding-inline-end:var(--size-8)}.np-input-addon--padding-md.np-input-addon--placement-end{padding-inline-start:8px;padding-inline-start:var(--size-8)}
1
+ .np-input-group{border-radius:9999px;border-radius:var(--radius-full);display:inline-grid;grid-auto-columns:minmax(0,1fr)}.np-input-group>*{grid-column-start:1;grid-row-start:1}.np-input-addon{align-items:center;color:#c9cbce;color:var(--color-interactive-secondary);display:inline-flex;pointer-events:none;transition-duration:.15s;transition-property:color,opacity;transition-timing-function:ease-out;z-index:10}.np-input-group:disabled .np-input-addon:not(.np-input-addon--interactive){mix-blend-mode:luminosity;opacity:.45}.np-input-group:has(>:is(input,button,select):focus-visible) .np-input-addon{color:var(--color-interactive-primary)}.np-input-group:has(>:is(input,button,select):hover) .np-input-addon{color:#b5b7ba;color:var(--color-interactive-secondary-hover)}.np-input-addon--placement-start{justify-self:start}.np-input-addon--placement-end{justify-self:end}.np-input-addon--interactive>*{pointer-events:auto}.np-input-addon--padding-sm{padding-left:8px;padding-left:var(--size-8);padding-right:8px;padding-right:var(--size-8)}.np-input-addon--padding-md{padding-left:16px;padding-left:var(--size-16);padding-right:16px;padding-right:var(--size-16)}.np-input-addon--padding-md.np-input-addon--placement-start{padding-inline-end:8px;padding-inline-end:var(--size-8)}.np-input-addon--padding-md.np-input-addon--placement-end{padding-inline-start:8px;padding-inline-start:var(--size-8)}
@@ -21,6 +21,11 @@
21
21
  transition-timing-function: ease-out;
22
22
  transition-duration: 150ms;
23
23
 
24
+ .np-input-group:disabled &:not(&--interactive) {
25
+ opacity: 0.45;
26
+ mix-blend-mode: luminosity;
27
+ }
28
+
24
29
  .np-input-group:has(>:is(input,button,select):focus-visible) & {
25
30
  color: var(--color-interactive-primary);
26
31
  }
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+
4
+ import { SearchInput } from './SearchInput';
5
+
6
+ export default {
7
+ component: SearchInput,
8
+ title: 'Forms/SearchInput',
9
+ } satisfies Meta<typeof SearchInput>;
10
+
11
+ type Story = StoryObj<typeof SearchInput>;
12
+
13
+ export const Basic: Story = {
14
+ render: (args) => <SearchInputBasic {...args} />,
15
+ args: {
16
+ size: 'md',
17
+ shape: 'pill',
18
+ disabled: false,
19
+ },
20
+ argTypes: {
21
+ onChange: {
22
+ action: 'changed',
23
+ },
24
+ },
25
+ };
26
+
27
+ function SearchInputBasic({ onChange, ...restProps }: NonNullable<Story['args']>) {
28
+ const [value, setValue] = useState('Text value');
29
+
30
+ return (
31
+ <SearchInput
32
+ {...restProps}
33
+ value={value}
34
+ onChange={(event) => {
35
+ setValue(event.currentTarget.value);
36
+ onChange?.(event);
37
+ }}
38
+ />
39
+ );
40
+ }
@@ -0,0 +1,35 @@
1
+ import { Search } from '@transferwise/icons';
2
+ import { forwardRef } from 'react';
3
+
4
+ import { Merge } from '../utils';
5
+
6
+ import { Input } from './Input';
7
+ import { InputGroup } from './InputGroup';
8
+
9
+ export interface SearchInputProps
10
+ extends Merge<
11
+ React.ComponentPropsWithRef<'input'>,
12
+ {
13
+ size?: 'sm' | 'md';
14
+ shape?: 'rectangle' | 'pill';
15
+ 'aria-invalid'?: boolean;
16
+ }
17
+ > {}
18
+
19
+ export const SearchInput = forwardRef(function SearchInput(
20
+ { shape = 'pill', disabled, className, ...restProps }: SearchInputProps,
21
+ ref: React.ForwardedRef<HTMLInputElement>,
22
+ ) {
23
+ return (
24
+ <InputGroup
25
+ addonStart={{
26
+ content: <Search size={24} />,
27
+ initialContentWidth: 24,
28
+ }}
29
+ disabled={disabled}
30
+ className={className}
31
+ >
32
+ <Input ref={ref} role="searchbox" inputMode="search" shape={shape} {...restProps} />
33
+ </InputGroup>
34
+ );
35
+ });
@@ -0,0 +1 @@
1
+ .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-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:#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;padding:12px 16px;padding:var(--size-12) var(--size-16)}.np-select-input-option-container--active{background-color:var(--color-background-screen-hover)}.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-compact{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-secondary{color:#5d7079;color:var(--color-content-secondary)}.np-select-input-option-content-text-compact{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{align-items:center;background:none;border-radius:.125rem;border-width:0;display:inline-flex;height:32px;height:var(--size-32);justify-content:center;width:32px;width:var(--size-32)}.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-width:1px;height:24px;height:var(--size-24)}
@@ -0,0 +1,183 @@
1
+ @import (reference) "../../node_modules/@transferwise/neptune-css/src/less/ring.less";
2
+
3
+ .np-select-input-placeholder {
4
+ overflow: hidden;
5
+ text-overflow: ellipsis;
6
+ white-space: nowrap;
7
+ color: var(--color-content-tertiary);
8
+ }
9
+
10
+ .np-select-input-options-container {
11
+ display: flex;
12
+ height: 100%;
13
+ flex-direction: column;
14
+
15
+ &:focus {
16
+ outline: none;
17
+ }
18
+
19
+ @media (--screen-sm) {
20
+ & {
21
+ max-height: 28rem /* 448px */;
22
+ }
23
+ }
24
+ }
25
+
26
+ .np-select-input-query-container {
27
+ display: flex;
28
+ flex-direction: column;
29
+ padding: var(--size-8);
30
+ padding-top: 0px;
31
+
32
+ @media (--screen-sm) {
33
+ & {
34
+ padding-top: var(--size-8);
35
+ }
36
+ }
37
+ }
38
+
39
+ .np-select-input-listbox-container {
40
+ position: relative;
41
+ height: var(--initial-height);
42
+ scroll-padding-top: var(--size-8);
43
+ scroll-padding-bottom: var(--size-8);
44
+ overflow-y: auto;
45
+
46
+ @media (--screen-sm) {
47
+ & {
48
+ height: auto;
49
+ }
50
+ }
51
+
52
+ &--has-group {
53
+ scroll-padding-top: var(--size-32);
54
+ }
55
+ }
56
+
57
+ .np-select-input-listbox {
58
+ padding: var(--size-8);
59
+
60
+ &:focus {
61
+ outline: none;
62
+ }
63
+ }
64
+
65
+ .np-select-input-separator-item {
66
+ margin: var(--size-8);
67
+ border-top-width: 1px;
68
+ }
69
+
70
+ .np-select-input-group-item {
71
+ &--without-needle:first-child {
72
+ margin-top: calc(-1 * var(--size-8));
73
+ }
74
+ }
75
+
76
+ .np-select-input-group-item-header {
77
+ position: sticky;
78
+ top: 0px;
79
+ z-index: 10;
80
+ background-color: var(--color-background-elevated);
81
+ padding: var(--size-8) var(--size-16) var(--size-4);
82
+ color: var(--color-content-secondary);
83
+ }
84
+
85
+ .np-select-input-option-container {
86
+ display: flex;
87
+ align-items: center;
88
+ column-gap: var(--size-8);
89
+ border-radius: var(--radius-small);
90
+ padding: var(--size-12) var(--size-16);
91
+ color: var(--color-content-primary);
92
+
93
+ &--active {
94
+ background-color: var(--color-background-screen-hover);
95
+ }
96
+
97
+ &--disabled {
98
+ opacity: 0.45;
99
+ }
100
+ }
101
+
102
+ .np-select-input-option-check {
103
+ &--not-selected {
104
+ visibility: hidden;
105
+ }
106
+ }
107
+
108
+ .np-select-input-option {
109
+ flex: 1;
110
+ }
111
+
112
+ .np-select-input-option-content-container {
113
+ display: flex;
114
+ align-items: center;
115
+ column-gap: var(--size-8);
116
+ color: var(--color-content-primary);
117
+ }
118
+
119
+ .np-select-input-option-content-icon {
120
+ display: flex;
121
+
122
+ &--not-compact {
123
+ align-self: flex-start;
124
+ }
125
+ }
126
+
127
+ .np-select-input-option-content-text {
128
+ display: flex;
129
+ flex: 1;
130
+ flex-direction: column;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .np-select-input-option-content-text-secondary {
135
+ color: var(--color-content-secondary);
136
+ }
137
+
138
+ .np-select-input-option-content-text-compact {
139
+ overflow: hidden;
140
+ text-overflow: ellipsis;
141
+ white-space: nowrap;
142
+ }
143
+
144
+ .np-select-input-option-content-text-line-1 {
145
+ > :not([hidden]) ~ :not([hidden]) {
146
+ margin-right: var(--size-8);
147
+ margin-left: var(--size-8);
148
+ }
149
+ }
150
+
151
+ .np-select-input-addon-container {
152
+ pointer-events: none;
153
+ display: inline-flex;
154
+ align-items: center;
155
+ }
156
+
157
+ .np-select-input-addon {
158
+ border-width: 0;
159
+ background: none;
160
+
161
+ display: inline-flex;
162
+ height: var(--size-32);
163
+ width: var(--size-32);
164
+ align-items: center;
165
+ justify-content: center;
166
+ border-radius: 0.125rem /* 2px */; /* TODO: Tokenize */
167
+
168
+ &--interactive {
169
+ pointer-events: auto;
170
+ color: var(--color-interactive-secondary);
171
+
172
+ &:hover {
173
+ color: var(--color-interactive-secondary-hover);
174
+ }
175
+
176
+ .focus-ring();
177
+ }
178
+ }
179
+
180
+ .np-select-input-addon-separator {
181
+ height: var(--size-24);
182
+ border-inline-start-width: 1px;
183
+ }
@@ -0,0 +1,120 @@
1
+ import { act, screen, within } from '@testing-library/react';
2
+ import userEvent, { specialChars } from '@testing-library/user-event';
3
+
4
+ import { render } from '../test-utils';
5
+
6
+ import { SelectInput } from './SelectInput';
7
+
8
+ Object.defineProperty(window, 'matchMedia', {
9
+ writable: true,
10
+ value: jest.fn((query: string) => {
11
+ const matches = /^\(min-width: ([0-9]+)px\)$/.exec(query);
12
+ const minWidth = matches != null ? Number(matches[1]) : undefined;
13
+ return {
14
+ matches: minWidth != null ? window.innerWidth >= minWidth : false,
15
+ media: query,
16
+ onchange: null,
17
+ addListener: jest.fn(), // deprecated
18
+ removeListener: jest.fn(), // deprecated
19
+ addEventListener: jest.fn(),
20
+ removeEventListener: jest.fn(),
21
+ dispatchEvent: jest.fn(),
22
+ };
23
+ }),
24
+ });
25
+
26
+ Object.defineProperty(window, 'ResizeObserver', {
27
+ writable: true,
28
+ value: jest.fn(() => ({
29
+ observe: jest.fn(),
30
+ unobserve: jest.fn(),
31
+ disconnect: jest.fn(),
32
+ })),
33
+ });
34
+
35
+ describe('SelectInput', () => {
36
+ it('renders placeholder', () => {
37
+ render(
38
+ <SelectInput
39
+ placeholder="Currency"
40
+ items={[
41
+ { type: 'option', value: 'USD' },
42
+ { type: 'option', value: 'EUR' },
43
+ ]}
44
+ />,
45
+ );
46
+
47
+ expect(screen.getByText('Currency')).toBeInTheDocument();
48
+ });
49
+
50
+ it('shows item selected via mouse', async () => {
51
+ render(
52
+ <SelectInput
53
+ items={[
54
+ { type: 'option', value: 'USD' },
55
+ { type: 'option', value: 'EUR' },
56
+ ]}
57
+ />,
58
+ );
59
+
60
+ expect(screen.queryByText('EUR')).not.toBeInTheDocument();
61
+
62
+ const trigger = screen.getAllByRole('button')[0];
63
+ // eslint-disable-next-line @typescript-eslint/require-await
64
+ await act(async () => {
65
+ userEvent.click(trigger);
66
+ });
67
+
68
+ const listbox = screen.getByRole('listbox');
69
+ const option = within(listbox).getByRole('option', { name: 'EUR' });
70
+ userEvent.click(option);
71
+
72
+ expect(trigger).toHaveTextContent('EUR');
73
+ });
74
+
75
+ it('filters items via keyboard', async () => {
76
+ render(
77
+ <SelectInput
78
+ items={[
79
+ {
80
+ type: 'group',
81
+ label: 'Popular currencies',
82
+ options: [
83
+ { type: 'option', value: 'USD' },
84
+ { type: 'option', value: 'EUR' },
85
+ { type: 'option', value: 'GBP' },
86
+ ],
87
+ },
88
+ ]}
89
+ filterable
90
+ />,
91
+ );
92
+
93
+ const trigger = screen.getAllByRole('button')[0];
94
+ // eslint-disable-next-line @typescript-eslint/require-await
95
+ await act(async () => {
96
+ userEvent.tab();
97
+ userEvent.keyboard(specialChars.enter);
98
+ });
99
+
100
+ const listbox = screen.getByRole('listbox');
101
+ expect(within(listbox).getAllByRole('option')).toHaveLength(3);
102
+
103
+ userEvent.keyboard('u');
104
+ expect(within(listbox).getAllByRole('option')).toHaveLength(2);
105
+
106
+ userEvent.keyboard('r');
107
+ expect(within(listbox).getByRole('option')).toBeInTheDocument();
108
+
109
+ userEvent.keyboard('x');
110
+ expect(within(listbox).queryByRole('option')).not.toBeInTheDocument();
111
+
112
+ userEvent.keyboard(specialChars.backspace);
113
+ expect(within(listbox).getByRole('option')).toBeInTheDocument();
114
+
115
+ const option = within(listbox).getAllByRole('option')[0];
116
+ userEvent.click(option);
117
+
118
+ expect(trigger).toHaveTextContent('EUR');
119
+ });
120
+ });