@transferwise/components 0.0.0-experimental-335b83d → 0.0.0-experimental-fd2ddc2
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 +65 -688
- package/build/index.esm.js.map +1 -1
- package/build/index.js +65 -691
- package/build/index.js.map +1 -1
- package/build/main.css +1 -1
- package/build/styles/inputs/Input.css +1 -1
- package/build/styles/inputs/InputGroup.css +1 -1
- package/build/styles/inputs/TextArea.css +1 -1
- package/build/styles/main.css +1 -1
- package/build/styles/promoCard/PromoCard.css +1 -1
- package/build/types/common/card/index.d.ts +1 -0
- package/build/types/common/card/index.d.ts.map +1 -1
- package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts +7 -7
- package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts.map +1 -1
- package/build/types/index.d.ts +0 -4
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/Input.d.ts +0 -1
- package/build/types/inputs/Input.d.ts.map +1 -1
- package/build/types/inputs/_common.d.ts.map +1 -1
- package/build/types/promoCard/PromoCard.d.ts +9 -3
- package/build/types/promoCard/PromoCard.d.ts.map +1 -1
- package/build/types/promoCard/PromoCardIndicator.d.ts +5 -3
- package/build/types/promoCard/PromoCardIndicator.d.ts.map +1 -1
- package/package.json +22 -28
- package/src/common/card/Card.tsx +1 -1
- package/src/common/card/index.ts +1 -0
- package/src/index.ts +0 -8
- package/src/inputs/Input.css +1 -1
- package/src/inputs/Input.less +0 -14
- package/src/inputs/Input.tsx +2 -6
- package/src/inputs/InputGroup.css +1 -1
- package/src/inputs/InputGroup.less +1 -6
- package/src/inputs/TextArea.css +1 -1
- package/src/inputs/TextArea.less +0 -5
- package/src/inputs/_common.less +4 -0
- package/src/inputs/_common.ts +1 -0
- package/src/main.css +1 -1
- package/src/main.less +0 -4
- package/src/promoCard/PromoCard.css +1 -1
- package/src/promoCard/PromoCard.less +9 -9
- package/src/promoCard/PromoCard.spec.tsx +1 -0
- package/src/promoCard/PromoCard.story.tsx +86 -29
- package/src/promoCard/PromoCard.tsx +69 -22
- package/src/promoCard/PromoCardIndicator.tsx +20 -8
- package/src/select/searchBox/__snapshots__/SearchBox.spec.js.snap +1 -1
- package/src/ssr.spec.js +0 -7
- package/build/styles/inputs/SelectInput.css +0 -1
- package/build/types/common/hooks/useMedia.d.ts +0 -2
- package/build/types/common/hooks/useMedia.d.ts.map +0 -1
- package/build/types/common/hooks/useScreenSize.d.ts +0 -3
- package/build/types/common/hooks/useScreenSize.d.ts.map +0 -1
- package/build/types/common/preventScroll/PreventScroll.d.ts +0 -2
- package/build/types/common/preventScroll/PreventScroll.d.ts.map +0 -1
- package/build/types/inputs/SearchInput.d.ts +0 -10
- package/build/types/inputs/SearchInput.d.ts.map +0 -1
- package/build/types/inputs/SelectInput.d.ts +0 -41
- package/build/types/inputs/SelectInput.d.ts.map +0 -1
- package/build/types/inputs/_BottomSheet.d.ts +0 -17
- package/build/types/inputs/_BottomSheet.d.ts.map +0 -1
- package/build/types/inputs/_ButtonInput.d.ts +0 -6
- package/build/types/inputs/_ButtonInput.d.ts.map +0 -1
- package/build/types/inputs/_Popover.d.ts +0 -18
- package/build/types/inputs/_Popover.d.ts.map +0 -1
- package/build/types/utilities/wrapInFragment.d.ts +0 -3
- package/build/types/utilities/wrapInFragment.d.ts.map +0 -1
- package/src/common/hooks/useMedia.ts +0 -15
- package/src/common/hooks/useScreenSize.ts +0 -7
- package/src/common/preventScroll/PreventScroll.tsx +0 -6
- package/src/inputs/SearchInput.story.tsx +0 -40
- package/src/inputs/SearchInput.tsx +0 -35
- package/src/inputs/SelectInput.css +0 -1
- package/src/inputs/SelectInput.less +0 -183
- package/src/inputs/SelectInput.story.tsx +0 -259
- package/src/inputs/SelectInput.tsx +0 -565
- package/src/inputs/_BottomSheet.less +0 -107
- package/src/inputs/_BottomSheet.tsx +0 -128
- package/src/inputs/_ButtonInput.less +0 -7
- package/src/inputs/_ButtonInput.tsx +0 -27
- package/src/inputs/_Popover.less +0 -38
- package/src/inputs/_Popover.tsx +0 -118
- package/src/utilities/wrapInFragment.tsx +0 -3
- /package/src/dateLookup/dateTrigger/{DateTrigger.messages.ts → DateTrigger.messages.js} +0 -0
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
import { Listbox as ListboxBase } from '@headlessui/react';
|
|
2
|
-
import { useId } from '@radix-ui/react-id';
|
|
3
|
-
import { Check, ChevronDown, Cross } from '@transferwise/icons';
|
|
4
|
-
import classNames from 'classnames';
|
|
5
|
-
import { createContext, useState, useRef, forwardRef, useEffect, useMemo, useContext } from 'react';
|
|
6
|
-
import { useIntl } from 'react-intl';
|
|
7
|
-
import mergeRefs from 'react-merge-refs';
|
|
8
|
-
|
|
9
|
-
import { useEffectEvent } from '../common/hooks/useEffectEvent';
|
|
10
|
-
import { useScreenSize } from '../common/hooks/useScreenSize';
|
|
11
|
-
import { Breakpoint } from '../common/propsValues/breakpoint';
|
|
12
|
-
import messages from '../dateLookup/dateTrigger/DateTrigger.messages';
|
|
13
|
-
import { wrapInFragment } from '../utilities/wrapInFragment';
|
|
14
|
-
|
|
15
|
-
import { InputGroup } from './InputGroup';
|
|
16
|
-
import { SearchInput } from './SearchInput';
|
|
17
|
-
import { BottomSheet } from './_BottomSheet';
|
|
18
|
-
import { ButtonInput, type ButtonInputProps } from './_ButtonInput';
|
|
19
|
-
import { Popover } from './_Popover';
|
|
20
|
-
|
|
21
|
-
function searchableString(value: string) {
|
|
22
|
-
return value.trim().replace(/\s+/gu, ' ').toLowerCase();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function inferSearchableStrings(value: unknown) {
|
|
26
|
-
if (typeof value === 'string') {
|
|
27
|
-
return [searchableString(value)];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof value === 'object' && value != null) {
|
|
31
|
-
return Object.values(value)
|
|
32
|
-
.filter((innerValue): innerValue is string => typeof innerValue === 'string')
|
|
33
|
-
.map((innerValue) => searchableString(innerValue));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const SelectInputHasValueContext = createContext(false);
|
|
40
|
-
|
|
41
|
-
const SelectInputOptionContentCompactContext = createContext(false);
|
|
42
|
-
|
|
43
|
-
interface SelectInputOptionItem<T = string> {
|
|
44
|
-
type: 'option';
|
|
45
|
-
value: T;
|
|
46
|
-
filterMatchers?: readonly string[];
|
|
47
|
-
disabled?: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface SelectInputGroupItem<T = string> {
|
|
51
|
-
type: 'group';
|
|
52
|
-
label: string;
|
|
53
|
-
options: readonly SelectInputOptionItem<T>[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface SelectInputSeparatorItem {
|
|
57
|
-
type: 'separator';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type SelectInputItem<T = string> =
|
|
61
|
-
| SelectInputOptionItem<T>
|
|
62
|
-
| SelectInputGroupItem<T>
|
|
63
|
-
| SelectInputSeparatorItem;
|
|
64
|
-
|
|
65
|
-
function dedupeSelectInputOptionItem<T>(
|
|
66
|
-
item: SelectInputOptionItem<T>,
|
|
67
|
-
existingValues: Set<T>,
|
|
68
|
-
): SelectInputOptionItem<T | undefined> {
|
|
69
|
-
if (existingValues.has(item.value)) {
|
|
70
|
-
return {
|
|
71
|
-
...item,
|
|
72
|
-
value: undefined,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
existingValues.add(item.value);
|
|
76
|
-
return item;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function dedupeSelectInputItems<T>(
|
|
80
|
-
items: readonly SelectInputItem<T>[],
|
|
81
|
-
): SelectInputItem<T | undefined>[] {
|
|
82
|
-
const existingValues = new Set<T>();
|
|
83
|
-
return items.map((item) => {
|
|
84
|
-
switch (item.type) {
|
|
85
|
-
case 'option': {
|
|
86
|
-
return dedupeSelectInputOptionItem(item, existingValues);
|
|
87
|
-
}
|
|
88
|
-
case 'group': {
|
|
89
|
-
return {
|
|
90
|
-
...item,
|
|
91
|
-
options: item.options.map((option) =>
|
|
92
|
-
dedupeSelectInputOptionItem(option, existingValues),
|
|
93
|
-
),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
default:
|
|
97
|
-
}
|
|
98
|
-
return item;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface SelectInputProps<T = string> {
|
|
103
|
-
name?: string;
|
|
104
|
-
placeholder?: string;
|
|
105
|
-
// TODO: multiple?: boolean;
|
|
106
|
-
items: readonly SelectInputItem<NonNullable<T>>[];
|
|
107
|
-
defaultValue?: T;
|
|
108
|
-
value?: T;
|
|
109
|
-
renderValue?: (value: NonNullable<T>, compact: boolean) => React.ReactNode;
|
|
110
|
-
compareValues?:
|
|
111
|
-
| (keyof NonNullable<T> & string)
|
|
112
|
-
| ((a: T | undefined, b: T | undefined) => boolean);
|
|
113
|
-
filterable?: boolean;
|
|
114
|
-
filterPlaceholder?: string;
|
|
115
|
-
disabled?: boolean;
|
|
116
|
-
className?: string;
|
|
117
|
-
onChange?: (value: T) => void;
|
|
118
|
-
onClear?: () => void;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function SelectInput<T>({
|
|
122
|
-
name,
|
|
123
|
-
placeholder,
|
|
124
|
-
items,
|
|
125
|
-
defaultValue,
|
|
126
|
-
value: controlledValue,
|
|
127
|
-
renderValue = wrapInFragment,
|
|
128
|
-
compareValues,
|
|
129
|
-
filterable,
|
|
130
|
-
filterPlaceholder,
|
|
131
|
-
disabled,
|
|
132
|
-
className,
|
|
133
|
-
onChange,
|
|
134
|
-
onClear,
|
|
135
|
-
}: SelectInputProps<T>) {
|
|
136
|
-
const intl = useIntl();
|
|
137
|
-
|
|
138
|
-
const [open, setOpen] = useState(false);
|
|
139
|
-
|
|
140
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
141
|
-
|
|
142
|
-
const screenSm = useScreenSize(Breakpoint.SMALL);
|
|
143
|
-
const OptionsOverlay = screenSm ? Popover : BottomSheet;
|
|
144
|
-
|
|
145
|
-
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
146
|
-
const listboxRef = useRef<HTMLDivElement>(null);
|
|
147
|
-
const controllerRef = filterable ? searchInputRef : listboxRef;
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<ListboxBase
|
|
151
|
-
name={name}
|
|
152
|
-
defaultValue={defaultValue}
|
|
153
|
-
value={controlledValue}
|
|
154
|
-
// TODO: Remove assertion when upgrading TypeScript to v5
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
156
|
-
by={compareValues as any}
|
|
157
|
-
disabled={disabled}
|
|
158
|
-
onChange={(value) => {
|
|
159
|
-
setOpen(false);
|
|
160
|
-
onChange?.(value);
|
|
161
|
-
}}
|
|
162
|
-
>
|
|
163
|
-
{({ disabled: uiDisabled, value }) => (
|
|
164
|
-
<SelectInputHasValueContext.Provider value={value != null}>
|
|
165
|
-
<InputGroup
|
|
166
|
-
addonEnd={{
|
|
167
|
-
content: (
|
|
168
|
-
<span
|
|
169
|
-
className={classNames(
|
|
170
|
-
'np-select-input-addon-container',
|
|
171
|
-
uiDisabled && 'disabled',
|
|
172
|
-
)}
|
|
173
|
-
>
|
|
174
|
-
{onClear != null && value != null ? (
|
|
175
|
-
<>
|
|
176
|
-
<button
|
|
177
|
-
type="button"
|
|
178
|
-
aria-label={intl.formatMessage(messages.ariaLabel)}
|
|
179
|
-
disabled={uiDisabled}
|
|
180
|
-
className="np-select-input-addon np-select-input-addon--interactive"
|
|
181
|
-
onClick={(event) => {
|
|
182
|
-
event.preventDefault();
|
|
183
|
-
onClear();
|
|
184
|
-
triggerRef.current?.focus({ preventScroll: true });
|
|
185
|
-
}}
|
|
186
|
-
>
|
|
187
|
-
<Cross size={16} />
|
|
188
|
-
</button>
|
|
189
|
-
<span className="np-select-input-addon-separator" />
|
|
190
|
-
</>
|
|
191
|
-
) : null}
|
|
192
|
-
|
|
193
|
-
<span className="np-select-input-addon">
|
|
194
|
-
<ChevronDown size={16} />
|
|
195
|
-
</span>
|
|
196
|
-
</span>
|
|
197
|
-
),
|
|
198
|
-
padding: 'sm',
|
|
199
|
-
}}
|
|
200
|
-
className={className}
|
|
201
|
-
>
|
|
202
|
-
<OptionsOverlay
|
|
203
|
-
open={open}
|
|
204
|
-
renderTrigger={({ ref, getInteractionProps }) => (
|
|
205
|
-
<ListboxBase.Button
|
|
206
|
-
ref={mergeRefs([ref, triggerRef])}
|
|
207
|
-
as={SelectInputButton}
|
|
208
|
-
overrides={getInteractionProps()}
|
|
209
|
-
onClick={() => {
|
|
210
|
-
setOpen((prev) => !prev);
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
{value != null ? (
|
|
214
|
-
<SelectInputOptionContentCompactContext.Provider value>
|
|
215
|
-
{renderValue(value, true)}
|
|
216
|
-
</SelectInputOptionContentCompactContext.Provider>
|
|
217
|
-
) : (
|
|
218
|
-
<span className="np-select-input-placeholder">{placeholder}</span>
|
|
219
|
-
)}
|
|
220
|
-
</ListboxBase.Button>
|
|
221
|
-
)}
|
|
222
|
-
initialFocusRef={controllerRef}
|
|
223
|
-
padding="none"
|
|
224
|
-
onClose={() => {
|
|
225
|
-
setOpen(false);
|
|
226
|
-
}}
|
|
227
|
-
>
|
|
228
|
-
<SelectInputOptions
|
|
229
|
-
items={items}
|
|
230
|
-
renderValue={renderValue}
|
|
231
|
-
filterable={filterable}
|
|
232
|
-
filterPlaceholder={filterPlaceholder}
|
|
233
|
-
searchInputRef={searchInputRef}
|
|
234
|
-
listboxRef={listboxRef}
|
|
235
|
-
/>
|
|
236
|
-
</OptionsOverlay>
|
|
237
|
-
</InputGroup>
|
|
238
|
-
</SelectInputHasValueContext.Provider>
|
|
239
|
-
)}
|
|
240
|
-
</ListboxBase>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
interface SelectInputButtonProps extends ButtonInputProps {
|
|
245
|
-
overrides?: { [key: string]: unknown };
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const SelectInputButton = forwardRef(function SelectInputButton(
|
|
249
|
-
{ overrides, ...restProps }: SelectInputButtonProps,
|
|
250
|
-
ref: React.ForwardedRef<HTMLButtonElement>,
|
|
251
|
-
) {
|
|
252
|
-
return <ButtonInput ref={ref} {...restProps} {...overrides} />;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
interface SelectInputOptionsContainerProps extends React.ComponentPropsWithRef<'div'> {
|
|
256
|
-
onAriaActiveDescendantChange: (value: React.AriaAttributes['aria-activedescendant']) => void;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContainer(
|
|
260
|
-
{
|
|
261
|
-
'aria-orientation': ariaOrientation,
|
|
262
|
-
'aria-activedescendant': ariaActiveDescendant,
|
|
263
|
-
role,
|
|
264
|
-
tabIndex,
|
|
265
|
-
onAriaActiveDescendantChange,
|
|
266
|
-
onKeyDown,
|
|
267
|
-
...restProps
|
|
268
|
-
}: SelectInputOptionsContainerProps,
|
|
269
|
-
ref: React.ForwardedRef<HTMLDivElement>,
|
|
270
|
-
) {
|
|
271
|
-
const handleAriaActiveDescendantChange = useEffectEvent(onAriaActiveDescendantChange);
|
|
272
|
-
useEffect(() => {
|
|
273
|
-
handleAriaActiveDescendantChange(ariaActiveDescendant);
|
|
274
|
-
}, [ariaActiveDescendant, handleAriaActiveDescendantChange]);
|
|
275
|
-
|
|
276
|
-
return (
|
|
277
|
-
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
278
|
-
<div
|
|
279
|
-
ref={ref}
|
|
280
|
-
onKeyDown={(event) => {
|
|
281
|
-
// Prevent absorbing dismissal requests too early
|
|
282
|
-
if (event.key !== 'Escape') {
|
|
283
|
-
onKeyDown?.(event);
|
|
284
|
-
}
|
|
285
|
-
}}
|
|
286
|
-
{...restProps}
|
|
287
|
-
/>
|
|
288
|
-
);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
interface SelectInputOptionsProps<T = string>
|
|
292
|
-
extends Pick<SelectInputProps<T>, 'items' | 'renderValue' | 'filterable' | 'filterPlaceholder'> {
|
|
293
|
-
searchInputRef: React.RefObject<HTMLInputElement>;
|
|
294
|
-
listboxRef: React.RefObject<HTMLDivElement>;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function SelectInputOptions<T>({
|
|
298
|
-
items,
|
|
299
|
-
renderValue = wrapInFragment,
|
|
300
|
-
filterable,
|
|
301
|
-
filterPlaceholder,
|
|
302
|
-
searchInputRef,
|
|
303
|
-
listboxRef,
|
|
304
|
-
}: SelectInputOptionsProps<T>) {
|
|
305
|
-
const [query, setQuery] = useState('');
|
|
306
|
-
const needle = useMemo(() => (query ? searchableString(query) : null), [query]);
|
|
307
|
-
|
|
308
|
-
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
309
|
-
useEffect(() => {
|
|
310
|
-
if (listboxContainerRef.current != null) {
|
|
311
|
-
listboxContainerRef.current.style.setProperty(
|
|
312
|
-
'--initial-height',
|
|
313
|
-
`${listboxContainerRef.current.offsetHeight}px`,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}, []);
|
|
317
|
-
|
|
318
|
-
const listboxId = useId();
|
|
319
|
-
|
|
320
|
-
const controllerRef = filterable ? searchInputRef : listboxRef;
|
|
321
|
-
|
|
322
|
-
return (
|
|
323
|
-
<ListboxBase.Options
|
|
324
|
-
as={SelectInputOptionsContainer}
|
|
325
|
-
static
|
|
326
|
-
className="np-select-input-options-container"
|
|
327
|
-
onAriaActiveDescendantChange={(value: React.AriaAttributes['aria-activedescendant']) => {
|
|
328
|
-
if (controllerRef.current != null) {
|
|
329
|
-
if (value != null) {
|
|
330
|
-
controllerRef.current.setAttribute('aria-activedescendant', value);
|
|
331
|
-
} else {
|
|
332
|
-
controllerRef.current.removeAttribute('aria-activedescendant');
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}}
|
|
336
|
-
>
|
|
337
|
-
{filterable ? (
|
|
338
|
-
<div className="np-select-input-query-container">
|
|
339
|
-
<SearchInput
|
|
340
|
-
ref={searchInputRef}
|
|
341
|
-
shape="rectangle"
|
|
342
|
-
placeholder={filterPlaceholder}
|
|
343
|
-
value={query}
|
|
344
|
-
aria-controls={listboxId}
|
|
345
|
-
onKeyDown={(event) => {
|
|
346
|
-
// Prevent interfering with the matcher of Headless UI
|
|
347
|
-
// https://mathiasbynens.be/notes/javascript-unicode#regex
|
|
348
|
-
if (/^.$/u.test(event.key)) {
|
|
349
|
-
event.stopPropagation();
|
|
350
|
-
}
|
|
351
|
-
}}
|
|
352
|
-
onChange={(event) => {
|
|
353
|
-
setQuery(event.currentTarget.value);
|
|
354
|
-
}}
|
|
355
|
-
/>
|
|
356
|
-
</div>
|
|
357
|
-
) : null}
|
|
358
|
-
|
|
359
|
-
<div
|
|
360
|
-
ref={listboxContainerRef}
|
|
361
|
-
className={classNames(
|
|
362
|
-
'np-select-input-listbox-container',
|
|
363
|
-
items.some((item) => item.type === 'group') &&
|
|
364
|
-
'np-select-input-listbox-container--has-group',
|
|
365
|
-
)}
|
|
366
|
-
>
|
|
367
|
-
<div
|
|
368
|
-
ref={listboxRef}
|
|
369
|
-
id={listboxId}
|
|
370
|
-
role="listbox"
|
|
371
|
-
aria-orientation="vertical"
|
|
372
|
-
tabIndex={0}
|
|
373
|
-
className="np-select-input-listbox"
|
|
374
|
-
>
|
|
375
|
-
{(needle == null ? items : dedupeSelectInputItems(items)).map((item, index) => (
|
|
376
|
-
<SelectInputItemView
|
|
377
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
378
|
-
key={index}
|
|
379
|
-
item={item}
|
|
380
|
-
renderValue={renderValue}
|
|
381
|
-
needle={needle}
|
|
382
|
-
/>
|
|
383
|
-
))}
|
|
384
|
-
</div>
|
|
385
|
-
</div>
|
|
386
|
-
</ListboxBase.Options>
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
interface SelectInputItemViewProps<
|
|
391
|
-
T = string,
|
|
392
|
-
I extends SelectInputItem<T | undefined> = SelectInputItem<T | undefined>,
|
|
393
|
-
> extends Required<Pick<SelectInputProps<T>, 'renderValue'>> {
|
|
394
|
-
item: I;
|
|
395
|
-
needle: string | null;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function SelectInputItemView<T>({ item, renderValue, needle }: SelectInputItemViewProps<T>) {
|
|
399
|
-
switch (item.type) {
|
|
400
|
-
case 'option': {
|
|
401
|
-
if (
|
|
402
|
-
item.value != null &&
|
|
403
|
-
(!needle ||
|
|
404
|
-
inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
|
|
405
|
-
haystack.includes(needle),
|
|
406
|
-
))
|
|
407
|
-
) {
|
|
408
|
-
return (
|
|
409
|
-
<SelectInputOption value={item.value} disabled={item.disabled}>
|
|
410
|
-
{renderValue(item.value, false)}
|
|
411
|
-
</SelectInputOption>
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
case 'group': {
|
|
417
|
-
return <SelectInputGroupItemView item={item} renderValue={renderValue} needle={needle} />;
|
|
418
|
-
}
|
|
419
|
-
case 'separator': {
|
|
420
|
-
if (needle == null) {
|
|
421
|
-
return <hr className="np-select-input-separator-item" aria-hidden />;
|
|
422
|
-
}
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
interface SelectInputGroupItemViewProps<T = string>
|
|
430
|
-
extends SelectInputItemViewProps<T, SelectInputGroupItem<T | undefined>> {}
|
|
431
|
-
|
|
432
|
-
function SelectInputGroupItemView<T>({
|
|
433
|
-
item,
|
|
434
|
-
renderValue,
|
|
435
|
-
needle,
|
|
436
|
-
}: SelectInputGroupItemViewProps<T>) {
|
|
437
|
-
const headerId = useId();
|
|
438
|
-
|
|
439
|
-
return (
|
|
440
|
-
// An empty container may be rendered when no options match `needle`
|
|
441
|
-
// However, pre-filtering would result in worse performance overall
|
|
442
|
-
<section
|
|
443
|
-
role="group"
|
|
444
|
-
aria-labelledby={headerId}
|
|
445
|
-
className={classNames(needle == null && 'np-select-input-group-item--without-needle')}
|
|
446
|
-
>
|
|
447
|
-
{needle == null ? (
|
|
448
|
-
<header
|
|
449
|
-
id={headerId}
|
|
450
|
-
role="presentation"
|
|
451
|
-
className="np-select-input-group-item-header np-text-title-group"
|
|
452
|
-
>
|
|
453
|
-
{item.label}
|
|
454
|
-
</header>
|
|
455
|
-
) : null}
|
|
456
|
-
{item.options.map((option, index) => (
|
|
457
|
-
<SelectInputItemView
|
|
458
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
459
|
-
key={index}
|
|
460
|
-
item={option}
|
|
461
|
-
renderValue={renderValue}
|
|
462
|
-
needle={needle}
|
|
463
|
-
/>
|
|
464
|
-
))}
|
|
465
|
-
</section>
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
interface SelectInputOptionProps<T = string> {
|
|
470
|
-
value: T;
|
|
471
|
-
disabled?: boolean;
|
|
472
|
-
children?: React.ReactNode;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function SelectInputOption<T>({ value, disabled, children }: SelectInputOptionProps<T>) {
|
|
476
|
-
const parentHasValue = useContext(SelectInputHasValueContext);
|
|
477
|
-
|
|
478
|
-
// Avoid flash during exit transition
|
|
479
|
-
const { current: cachedParentHasValue } = useRef(parentHasValue);
|
|
480
|
-
|
|
481
|
-
return (
|
|
482
|
-
<ListboxBase.Option
|
|
483
|
-
as="div"
|
|
484
|
-
value={value}
|
|
485
|
-
disabled={disabled}
|
|
486
|
-
className={({ active, disabled: uiDisabled }) =>
|
|
487
|
-
classNames(
|
|
488
|
-
'np-select-input-option-container np-text-body-large',
|
|
489
|
-
active && 'np-select-input-option-container--active',
|
|
490
|
-
uiDisabled && 'np-select-input-option-container--disabled',
|
|
491
|
-
)
|
|
492
|
-
}
|
|
493
|
-
>
|
|
494
|
-
{({ selected }) => (
|
|
495
|
-
<>
|
|
496
|
-
{cachedParentHasValue ? (
|
|
497
|
-
<Check
|
|
498
|
-
size={16}
|
|
499
|
-
className={classNames(!selected && 'np-select-input-option-check--not-selected')}
|
|
500
|
-
/>
|
|
501
|
-
) : null}
|
|
502
|
-
<div className="np-select-input-option">{children}</div>
|
|
503
|
-
</>
|
|
504
|
-
)}
|
|
505
|
-
</ListboxBase.Option>
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
export interface SelectInputOptionContentProps {
|
|
510
|
-
title: string;
|
|
511
|
-
note?: string;
|
|
512
|
-
description?: string;
|
|
513
|
-
icon?: React.ReactNode;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
export function SelectInputOptionContent({
|
|
517
|
-
title,
|
|
518
|
-
note,
|
|
519
|
-
description,
|
|
520
|
-
icon,
|
|
521
|
-
}: SelectInputOptionContentProps) {
|
|
522
|
-
const compact = useContext(SelectInputOptionContentCompactContext);
|
|
523
|
-
|
|
524
|
-
return (
|
|
525
|
-
<div className="np-select-input-option-content-container np-text-body-large">
|
|
526
|
-
{icon ? (
|
|
527
|
-
<div
|
|
528
|
-
className={classNames(
|
|
529
|
-
'np-select-input-option-content-icon',
|
|
530
|
-
!compact && 'np-select-input-option-content-icon--not-compact',
|
|
531
|
-
)}
|
|
532
|
-
>
|
|
533
|
-
{icon}
|
|
534
|
-
</div>
|
|
535
|
-
) : null}
|
|
536
|
-
|
|
537
|
-
<div className="np-select-input-option-content-text">
|
|
538
|
-
<div
|
|
539
|
-
className={classNames(
|
|
540
|
-
'np-select-input-option-content-text-line-1',
|
|
541
|
-
compact && 'np-select-input-option-content-text-compact',
|
|
542
|
-
)}
|
|
543
|
-
>
|
|
544
|
-
<h4 className="d-inline np-text-body-large">{title}</h4>
|
|
545
|
-
{note ? (
|
|
546
|
-
<span className="np-select-input-option-content-text-secondary np-text-body-default">
|
|
547
|
-
{note}
|
|
548
|
-
</span>
|
|
549
|
-
) : null}
|
|
550
|
-
</div>
|
|
551
|
-
|
|
552
|
-
{description ? (
|
|
553
|
-
<div
|
|
554
|
-
className={classNames(
|
|
555
|
-
'np-select-input-option-content-text-secondary np-text-body-default',
|
|
556
|
-
compact && 'np-select-input-option-content-text-compact',
|
|
557
|
-
)}
|
|
558
|
-
>
|
|
559
|
-
{description}
|
|
560
|
-
</div>
|
|
561
|
-
) : null}
|
|
562
|
-
</div>
|
|
563
|
-
</div>
|
|
564
|
-
);
|
|
565
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
.np-bottom-sheet-v2-container {
|
|
2
|
-
position: relative;
|
|
3
|
-
z-index: 50;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.np-bottom-sheet-v2-backdrop-container {
|
|
7
|
-
&--enter, &--leave {
|
|
8
|
-
transition-property: opacity;
|
|
9
|
-
transition-timing-function: ease-out;
|
|
10
|
-
transition-duration: 150ms;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
&--enter-from, &--leave-to {
|
|
14
|
-
opacity: 0;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.np-bottom-sheet-v2-backdrop {
|
|
19
|
-
position: fixed;
|
|
20
|
-
inset: 0px;
|
|
21
|
-
background-color: var(--color-content-primary);
|
|
22
|
-
opacity: 0.4;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.np-bottom-sheet-v2 {
|
|
26
|
-
position: fixed;
|
|
27
|
-
inset: 0px;
|
|
28
|
-
display: flex;
|
|
29
|
-
flex-direction: column;
|
|
30
|
-
justify-content: flex-end;
|
|
31
|
-
padding-left: var(--size-8);
|
|
32
|
-
padding-right: var(--size-8);
|
|
33
|
-
padding-top: var(--size-64);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.np-bottom-sheet-v2-content {
|
|
37
|
-
max-height: 100%;
|
|
38
|
-
|
|
39
|
-
&--enter, &--leave {
|
|
40
|
-
transition-property: transform;
|
|
41
|
-
transition-timing-function: ease-out;
|
|
42
|
-
transition-duration: 300ms;
|
|
43
|
-
|
|
44
|
-
@media (prefers-reduced-motion: reduce) {
|
|
45
|
-
& {
|
|
46
|
-
transition-property: opacity;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
&--enter-from, &--leave-to {
|
|
52
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
53
|
-
& {
|
|
54
|
-
transform: translateY(100%);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@media (prefers-reduced-motion: reduce) {
|
|
59
|
-
& {
|
|
60
|
-
opacity: 0;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.np-bottom-sheet-v2-content-inner-container {
|
|
67
|
-
display: flex;
|
|
68
|
-
height: 100%;
|
|
69
|
-
flex-direction: column;
|
|
70
|
-
border-top-left-radius: 32px; /* TODO: Tokenize */
|
|
71
|
-
border-top-right-radius: 32px; /* TODO: Tokenize */
|
|
72
|
-
background-color: var(--color-background-elevated);
|
|
73
|
-
box-shadow: 0 0 40px rgb(69 71 69 / 0.2);
|
|
74
|
-
|
|
75
|
-
&:focus {
|
|
76
|
-
outline: none;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.np-bottom-sheet-v2-header {
|
|
81
|
-
align-self: flex-end;
|
|
82
|
-
padding: var(--size-16);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.np-bottom-sheet-v2-content-inner {
|
|
86
|
-
padding-top: 0px;
|
|
87
|
-
display: grid;
|
|
88
|
-
row-gap: var(--size-8);
|
|
89
|
-
overflow-y: auto;
|
|
90
|
-
grid-template-rows: repeat(1, minmax(0, 1fr));
|
|
91
|
-
|
|
92
|
-
&--has-title {
|
|
93
|
-
grid-template-rows: auto 1fr;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
&--padding-md {
|
|
97
|
-
padding: var(--size-16);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.np-bottom-sheet-v2-title {
|
|
102
|
-
color: var(--color-content-primary);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.np-bottom-sheet-v2-body {
|
|
106
|
-
color: var(--color-content-secondary);
|
|
107
|
-
}
|