@transferwise/components 45.14.1 → 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 (98) hide show
  1. package/build/i18n/en.json +1 -0
  2. package/build/index.esm.js +695 -53
  3. package/build/index.esm.js.map +1 -1
  4. package/build/index.js +698 -53
  5. package/build/index.js.map +1 -1
  6. package/build/main.css +1 -1
  7. package/build/styles/common/closeButton/CloseButton.css +1 -1
  8. package/build/styles/inputs/Input.css +1 -1
  9. package/build/styles/inputs/InputGroup.css +1 -1
  10. package/build/styles/inputs/SelectInput.css +1 -0
  11. package/build/styles/inputs/TextArea.css +1 -1
  12. package/build/styles/main.css +1 -1
  13. package/build/styles/promoCard/PromoCard.css +1 -1
  14. package/build/styles/stepper/Stepper.css +1 -1
  15. package/build/types/common/hooks/useMedia.d.ts +2 -0
  16. package/build/types/common/hooks/useMedia.d.ts.map +1 -0
  17. package/build/types/common/hooks/useScreenSize.d.ts +3 -0
  18. package/build/types/common/hooks/useScreenSize.d.ts.map +1 -0
  19. package/build/types/common/preventScroll/PreventScroll.d.ts +2 -0
  20. package/build/types/common/preventScroll/PreventScroll.d.ts.map +1 -0
  21. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts +7 -7
  22. package/build/types/dateLookup/dateTrigger/DateTrigger.messages.d.ts.map +1 -1
  23. package/build/types/index.d.ts +4 -0
  24. package/build/types/index.d.ts.map +1 -1
  25. package/build/types/inputs/Input.d.ts +1 -0
  26. package/build/types/inputs/Input.d.ts.map +1 -1
  27. package/build/types/inputs/SearchInput.d.ts +10 -0
  28. package/build/types/inputs/SearchInput.d.ts.map +1 -0
  29. package/build/types/inputs/SelectInput.d.ts +41 -0
  30. package/build/types/inputs/SelectInput.d.ts.map +1 -0
  31. package/build/types/inputs/_BottomSheet.d.ts +17 -0
  32. package/build/types/inputs/_BottomSheet.d.ts.map +1 -0
  33. package/build/types/inputs/_ButtonInput.d.ts +6 -0
  34. package/build/types/inputs/_ButtonInput.d.ts.map +1 -0
  35. package/build/types/inputs/_Popover.d.ts +18 -0
  36. package/build/types/inputs/_Popover.d.ts.map +1 -0
  37. package/build/types/inputs/_common.d.ts.map +1 -1
  38. package/build/types/link/Link.d.ts.map +1 -1
  39. package/build/types/link/Link.messages.d.ts +8 -0
  40. package/build/types/link/Link.messages.d.ts.map +1 -0
  41. package/build/types/logo/Logo.d.ts.map +1 -1
  42. package/build/types/stepper/Stepper.d.ts.map +1 -1
  43. package/build/types/tile/Tile.d.ts.map +1 -1
  44. package/build/types/utilities/wrapInFragment.d.ts +3 -0
  45. package/build/types/utilities/wrapInFragment.d.ts.map +1 -0
  46. package/package.json +28 -22
  47. package/src/avatar/Avatar.story.tsx +16 -14
  48. package/src/common/closeButton/CloseButton.css +1 -1
  49. package/src/common/hooks/useMedia.spec.ts +39 -0
  50. package/src/common/hooks/useMedia.ts +15 -0
  51. package/src/common/hooks/useScreenSize.ts +7 -0
  52. package/src/common/preventScroll/PreventScroll.tsx +6 -0
  53. package/src/decision/Decision.story.js +11 -11
  54. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +12 -12
  55. package/src/i18n/en.json +1 -0
  56. package/src/index.ts +8 -0
  57. package/src/inputs/Input.css +1 -1
  58. package/src/inputs/Input.less +14 -0
  59. package/src/inputs/Input.tsx +6 -2
  60. package/src/inputs/InputGroup.css +1 -1
  61. package/src/inputs/InputGroup.less +5 -0
  62. package/src/inputs/SearchInput.story.tsx +40 -0
  63. package/src/inputs/SearchInput.tsx +35 -0
  64. package/src/inputs/SelectInput.css +1 -0
  65. package/src/inputs/SelectInput.less +183 -0
  66. package/src/inputs/SelectInput.spec.tsx +120 -0
  67. package/src/inputs/SelectInput.story.tsx +259 -0
  68. package/src/inputs/SelectInput.tsx +565 -0
  69. package/src/inputs/TextArea.css +1 -1
  70. package/src/inputs/TextArea.less +5 -0
  71. package/src/inputs/_BottomSheet.less +107 -0
  72. package/src/inputs/_BottomSheet.tsx +128 -0
  73. package/src/inputs/_ButtonInput.less +7 -0
  74. package/src/inputs/_ButtonInput.tsx +27 -0
  75. package/src/inputs/_Popover.less +38 -0
  76. package/src/inputs/_Popover.tsx +118 -0
  77. package/src/inputs/_common.less +0 -4
  78. package/src/inputs/_common.ts +0 -1
  79. package/src/link/Link.messages.ts +8 -0
  80. package/src/link/Link.tsx +6 -2
  81. package/src/logo/Logo.js +3 -21
  82. package/src/logo/__snapshots__/Logo.spec.js.snap +78 -30
  83. package/src/main.css +1 -1
  84. package/src/main.less +4 -0
  85. package/src/navigationOption/NavigationOption.story.js +3 -5
  86. package/src/promoCard/PromoCard.css +1 -1
  87. package/src/radio/Radio.story.js +3 -2
  88. package/src/radioGroup/RadioGroup.story.js +2 -1
  89. package/src/select/searchBox/__snapshots__/SearchBox.spec.js.snap +1 -1
  90. package/src/ssr.spec.js +7 -0
  91. package/src/stepper/Stepper.css +1 -1
  92. package/src/stepper/Stepper.less +1 -9
  93. package/src/stepper/Stepper.spec.js +4 -4
  94. package/src/stepper/Stepper.tsx +2 -5
  95. package/src/tile/Tile.js +5 -11
  96. package/src/tile/__snapshots__/Tile.spec.js.snap +7 -9
  97. package/src/utilities/wrapInFragment.tsx +3 -0
  98. /package/src/dateLookup/dateTrigger/{DateTrigger.messages.js → DateTrigger.messages.ts} +0 -0
@@ -0,0 +1,565 @@
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 +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)}.np-text-area{min-height:72px;min-height:var(--size-72);overscroll-behavior:none;scroll-padding-bottom:8px;scroll-padding-bottom:var(--size-8);scroll-padding-top:8px;scroll-padding-top:var(--size-8)}
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-text-area{border-radius:10px!important;border-radius:var(--radius-small)!important;min-height:72px;min-height:var(--size-72);overscroll-behavior:none;scroll-padding-bottom:8px;scroll-padding-bottom:var(--size-8);scroll-padding-top:8px;scroll-padding-top:var(--size-8)}.np-text-area::-moz-placeholder{color:#768e9c;color:var(--color-content-tertiary)}.np-text-area::placeholder{color:#768e9c;color:var(--color-content-tertiary)}
@@ -5,4 +5,9 @@
5
5
  scroll-padding-top: var(--size-8);
6
6
  scroll-padding-bottom: var(--size-8);
7
7
  overscroll-behavior: none;
8
+ border-radius: var(--radius-small) !important;
9
+
10
+ &::placeholder {
11
+ color: var(--color-content-tertiary);
12
+ }
8
13
  }
@@ -0,0 +1,107 @@
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
+ }