@transferwise/components 0.0.0-experimental-8b6ee2a → 0.0.0-experimental-335b83d

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 (71) hide show
  1. package/build/index.esm.js +669 -13
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +672 -13
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -1
  6. package/build/styles/inputs/Input.css +1 -1
  7. package/build/styles/inputs/InputGroup.css +1 -1
  8. package/build/styles/inputs/SelectInput.css +1 -0
  9. package/build/styles/inputs/TextArea.css +1 -1
  10. package/build/styles/main.css +1 -1
  11. package/build/types/common/hooks/useMedia.d.ts +2 -0
  12. package/build/types/common/hooks/useMedia.d.ts.map +1 -0
  13. package/build/types/common/hooks/useScreenSize.d.ts +3 -0
  14. package/build/types/common/hooks/useScreenSize.d.ts.map +1 -0
  15. package/build/types/common/preventScroll/PreventScroll.d.ts +2 -0
  16. package/build/types/common/preventScroll/PreventScroll.d.ts.map +1 -0
  17. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts +7 -7
  18. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts.map +1 -1
  19. package/build/types/index.d.ts +4 -0
  20. package/build/types/index.d.ts.map +1 -1
  21. package/build/types/inputs/Input.d.ts +1 -0
  22. package/build/types/inputs/Input.d.ts.map +1 -1
  23. package/build/types/inputs/SearchInput.d.ts +10 -0
  24. package/build/types/inputs/SearchInput.d.ts.map +1 -0
  25. package/build/types/inputs/SelectInput.d.ts +41 -0
  26. package/build/types/inputs/SelectInput.d.ts.map +1 -0
  27. package/build/types/inputs/_BottomSheet.d.ts +17 -0
  28. package/build/types/inputs/_BottomSheet.d.ts.map +1 -0
  29. package/build/types/inputs/_ButtonInput.d.ts +6 -0
  30. package/build/types/inputs/_ButtonInput.d.ts.map +1 -0
  31. package/build/types/inputs/_Popover.d.ts +18 -0
  32. package/build/types/inputs/_Popover.d.ts.map +1 -0
  33. package/build/types/inputs/_common.d.ts.map +1 -1
  34. package/build/types/utilities/wrapInFragment.d.ts +3 -0
  35. package/build/types/utilities/wrapInFragment.d.ts.map +1 -0
  36. package/package.json +27 -21
  37. package/src/avatar/Avatar.story.tsx +14 -16
  38. package/src/common/hooks/useMedia.ts +15 -0
  39. package/src/common/hooks/useScreenSize.ts +7 -0
  40. package/src/common/preventScroll/PreventScroll.tsx +6 -0
  41. package/src/index.ts +8 -0
  42. package/src/inputs/Input.css +1 -1
  43. package/src/inputs/Input.less +14 -0
  44. package/src/inputs/Input.tsx +6 -2
  45. package/src/inputs/InputGroup.css +1 -1
  46. package/src/inputs/InputGroup.less +6 -1
  47. package/src/inputs/SearchInput.story.tsx +40 -0
  48. package/src/inputs/SearchInput.tsx +35 -0
  49. package/src/inputs/SelectInput.css +1 -0
  50. package/src/inputs/SelectInput.less +183 -0
  51. package/src/inputs/SelectInput.story.tsx +259 -0
  52. package/src/inputs/SelectInput.tsx +565 -0
  53. package/src/inputs/TextArea.css +1 -1
  54. package/src/inputs/TextArea.less +5 -0
  55. package/src/inputs/_BottomSheet.less +107 -0
  56. package/src/inputs/_BottomSheet.tsx +128 -0
  57. package/src/inputs/_ButtonInput.less +7 -0
  58. package/src/inputs/_ButtonInput.tsx +27 -0
  59. package/src/inputs/_Popover.less +38 -0
  60. package/src/inputs/_Popover.tsx +118 -0
  61. package/src/inputs/_common.less +0 -4
  62. package/src/inputs/_common.ts +0 -1
  63. package/src/main.css +1 -1
  64. package/src/main.less +4 -0
  65. package/src/navigationOption/NavigationOption.story.js +5 -3
  66. package/src/radio/Radio.story.js +2 -3
  67. package/src/radioGroup/RadioGroup.story.js +1 -2
  68. package/src/select/searchBox/__snapshots__/SearchBox.spec.js.snap +1 -1
  69. package/src/ssr.spec.js +7 -0
  70. package/src/utilities/wrapInFragment.tsx +3 -0
  71. /package/src/dateLookup/dateTrigger/{DateTrigger.messages.js → DateTrigger.messages.ts} +0 -0
@@ -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,259 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Calendar } from '@transferwise/icons';
3
+ import { Flag } from '@wise/art';
4
+ import { useState } from 'react';
5
+
6
+ import { getMonthNames } from '../common/dateUtils';
7
+
8
+ import { SelectInput, type SelectInputItem, SelectInputOptionContent } from './SelectInput';
9
+
10
+ export default {
11
+ component: SelectInput,
12
+ } satisfies Meta<typeof SelectInput>;
13
+
14
+ interface TestMonth {
15
+ id: number;
16
+ name: string;
17
+ unavailable: boolean;
18
+ }
19
+
20
+ const testMonths: TestMonth[] = getMonthNames('en-US').map((name, index) => ({
21
+ id: index + 1,
22
+ name,
23
+ unavailable: index % 6 === 2,
24
+ }));
25
+
26
+ const testQuarters = [
27
+ testMonths.slice(0, 3),
28
+ testMonths.slice(3, 6),
29
+ testMonths.slice(6, 9),
30
+ testMonths.slice(9, 12),
31
+ ] as const;
32
+
33
+ export const Basic: StoryObj<{
34
+ filterable: boolean;
35
+ filterPlaceholder: string;
36
+ clearable: boolean;
37
+ invalid: boolean;
38
+ disabled: boolean;
39
+ onChange: (value: TestMonth | null) => void;
40
+ onClear: () => void;
41
+ }> = {
42
+ render: function Story({
43
+ filterable,
44
+ filterPlaceholder,
45
+ clearable,
46
+ disabled,
47
+ onChange,
48
+ onClear,
49
+ }) {
50
+ const [selectedMonth, setSelectedMonth] = useState<TestMonth | null>(null);
51
+
52
+ return (
53
+ <div className="d-flex flex-column">
54
+ {/* TODO:
55
+ <Field
56
+ label="Label"
57
+ hint="Information message."
58
+ error={invalid ? "Error message." : undefined}
59
+ >
60
+ */}
61
+ <SelectInput
62
+ placeholder="Month"
63
+ items={testQuarters
64
+ .flatMap<SelectInputItem<TestMonth>>((quarterMonths, index) => [
65
+ {
66
+ type: 'group',
67
+ label: `Quarter #${index + 1}`,
68
+ options: quarterMonths.map((month) => ({
69
+ type: 'option',
70
+ value: month,
71
+ filterMatchers: [month.name],
72
+ disabled: month.unavailable,
73
+ })),
74
+ },
75
+ { type: 'separator' },
76
+ ])
77
+ .slice(0, -1)}
78
+ value={selectedMonth}
79
+ renderValue={(month, compact) => (
80
+ <SelectInputOptionContent
81
+ title={month.name}
82
+ note="Note"
83
+ description={compact ? undefined : `Month #${month.id}`}
84
+ icon={<Calendar size={24} />}
85
+ />
86
+ )}
87
+ filterable={filterable}
88
+ filterPlaceholder={filterPlaceholder}
89
+ disabled={disabled}
90
+ onChange={(month) => {
91
+ setSelectedMonth(month);
92
+ onChange(month);
93
+ }}
94
+ onClear={
95
+ clearable
96
+ ? () => {
97
+ setSelectedMonth(null);
98
+ onClear();
99
+ }
100
+ : undefined
101
+ }
102
+ />
103
+ </div>
104
+ );
105
+ },
106
+ args: {
107
+ filterable: true,
108
+ filterPlaceholder: 'Type a month’s name',
109
+ clearable: true,
110
+ invalid: false,
111
+ disabled: false,
112
+ },
113
+ argTypes: {
114
+ onChange: {
115
+ action: 'changed',
116
+ },
117
+ onClear: {
118
+ action: 'cleared',
119
+ },
120
+ },
121
+ };
122
+
123
+ interface Month {
124
+ id: number;
125
+ name: string;
126
+ }
127
+
128
+ const months: Month[] = getMonthNames('en-US').map((name, index) => ({
129
+ id: index + 1,
130
+ name,
131
+ }));
132
+
133
+ export const Months: StoryObj<{
134
+ onChange: (value: Month | null) => void;
135
+ onClear: () => void;
136
+ }> = {
137
+ render: function Story({ onChange, onClear }) {
138
+ const [selectedMonth, setSelectedMonth] = useState<Month | null>(null);
139
+
140
+ return (
141
+ <div className="d-flex flex-column">
142
+ <SelectInput
143
+ placeholder="Month"
144
+ items={months.map((month) => ({ type: 'option', value: month }))}
145
+ value={selectedMonth}
146
+ renderValue={(month) => <SelectInputOptionContent title={month.name} />}
147
+ onChange={(month) => {
148
+ setSelectedMonth(month);
149
+ onChange(month);
150
+ }}
151
+ onClear={() => {
152
+ setSelectedMonth(null);
153
+ onClear();
154
+ }}
155
+ />
156
+ </div>
157
+ );
158
+ },
159
+ argTypes: {
160
+ onChange: {
161
+ action: 'changed',
162
+ },
163
+ onClear: {
164
+ action: 'cleared',
165
+ },
166
+ },
167
+ };
168
+
169
+ interface Currency {
170
+ code: string;
171
+ name: string;
172
+ countries?: string[];
173
+ }
174
+
175
+ const popularCurrencies: Currency[] = [
176
+ {
177
+ code: 'USD',
178
+ name: 'United States dollar',
179
+ countries: ['Hong Kong', 'Saudi Arabia'],
180
+ },
181
+ {
182
+ code: 'EUR',
183
+ name: 'Euro',
184
+ countries: ['Spain', 'Germany', 'France', 'Austria', 'Estonia'],
185
+ },
186
+ {
187
+ code: 'GBP',
188
+ name: 'British pound',
189
+ countries: ['England', 'Scotland', 'Wales'],
190
+ },
191
+ ];
192
+
193
+ const otherCurrencies: Currency[] = [
194
+ {
195
+ code: 'CAD',
196
+ name: 'Canadian dollar',
197
+ countries: ['Canada'],
198
+ },
199
+ {
200
+ code: 'AUD',
201
+ name: 'Australian dollar',
202
+ },
203
+ ];
204
+
205
+ const allCurrencies: Currency[] = [...popularCurrencies, ...otherCurrencies].sort((a, b) =>
206
+ a.code.localeCompare(b.code),
207
+ );
208
+
209
+ export const Currencies: StoryObj<{
210
+ onChange: (value: Currency) => void;
211
+ }> = {
212
+ render: function Story({ onChange }) {
213
+ const [selectedCurrency, setSelectedCurrency] = useState<Currency>(popularCurrencies[0]);
214
+
215
+ return (
216
+ <div className="d-flex flex-column">
217
+ <SelectInput
218
+ items={[
219
+ {
220
+ type: 'group',
221
+ label: 'Popular currencies',
222
+ options: popularCurrencies.map((currency) => ({
223
+ type: 'option',
224
+ value: currency,
225
+ })),
226
+ },
227
+ {
228
+ type: 'group',
229
+ label: 'All currencies',
230
+ options: allCurrencies.map((currency) => ({
231
+ type: 'option',
232
+ value: currency,
233
+ })),
234
+ },
235
+ ]}
236
+ value={selectedCurrency}
237
+ renderValue={(currency) => (
238
+ <SelectInputOptionContent
239
+ title={currency.code}
240
+ note={currency.name}
241
+ icon={<Flag code={currency.code} intrinsicSize={24} />}
242
+ />
243
+ )}
244
+ filterable
245
+ filterPlaceholder="Type a currency / country"
246
+ onChange={(currency) => {
247
+ setSelectedCurrency(currency);
248
+ onChange(currency);
249
+ }}
250
+ />
251
+ </div>
252
+ );
253
+ },
254
+ argTypes: {
255
+ onChange: {
256
+ action: 'changed',
257
+ },
258
+ },
259
+ };