@transferwise/components 0.0.0-experimental-fdc11fa → 0.0.0-experimental-88ddab3

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 (67) hide show
  1. package/build/index.esm.js +658 -13
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +661 -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 +13 -7
  37. package/src/common/hooks/useMedia.ts +15 -0
  38. package/src/common/hooks/useScreenSize.ts +7 -0
  39. package/src/common/preventScroll/PreventScroll.tsx +6 -0
  40. package/src/index.ts +8 -0
  41. package/src/inputs/Input.css +1 -1
  42. package/src/inputs/Input.less +14 -0
  43. package/src/inputs/Input.tsx +6 -2
  44. package/src/inputs/InputGroup.css +1 -1
  45. package/src/inputs/InputGroup.less +6 -1
  46. package/src/inputs/SearchInput.story.tsx +40 -0
  47. package/src/inputs/SearchInput.tsx +35 -0
  48. package/src/inputs/SelectInput.css +1 -0
  49. package/src/inputs/SelectInput.less +183 -0
  50. package/src/inputs/SelectInput.story.tsx +260 -0
  51. package/src/inputs/SelectInput.tsx +552 -0
  52. package/src/inputs/TextArea.css +1 -1
  53. package/src/inputs/TextArea.less +5 -0
  54. package/src/inputs/_BottomSheet.less +107 -0
  55. package/src/inputs/_BottomSheet.tsx +128 -0
  56. package/src/inputs/_ButtonInput.less +7 -0
  57. package/src/inputs/_ButtonInput.tsx +27 -0
  58. package/src/inputs/_Popover.less +38 -0
  59. package/src/inputs/_Popover.tsx +118 -0
  60. package/src/inputs/_common.less +0 -4
  61. package/src/inputs/_common.ts +0 -1
  62. package/src/main.css +1 -1
  63. package/src/main.less +4 -0
  64. package/src/select/searchBox/__snapshots__/SearchBox.spec.js.snap +1 -1
  65. package/src/ssr.spec.js +7 -0
  66. package/src/utilities/wrapInFragment.tsx +3 -0
  67. /package/src/dateLookup/dateTrigger/{DateTrigger.messages.js → DateTrigger.messages.ts} +0 -0
@@ -0,0 +1,552 @@
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
+ ...restProps
267
+ }: SelectInputOptionsContainerProps,
268
+ ref: React.ForwardedRef<HTMLDivElement>,
269
+ ) {
270
+ const handleAriaActiveDescendantChange = useEffectEvent(onAriaActiveDescendantChange);
271
+ useEffect(() => {
272
+ handleAriaActiveDescendantChange(ariaActiveDescendant);
273
+ }, [ariaActiveDescendant, handleAriaActiveDescendantChange]);
274
+
275
+ return <div ref={ref} {...restProps} />;
276
+ });
277
+
278
+ interface SelectInputOptionsProps<T = string>
279
+ extends Pick<SelectInputProps<T>, 'items' | 'renderValue' | 'filterable' | 'filterPlaceholder'> {
280
+ searchInputRef: React.RefObject<HTMLInputElement>;
281
+ listboxRef: React.RefObject<HTMLDivElement>;
282
+ }
283
+
284
+ function SelectInputOptions<T>({
285
+ items,
286
+ renderValue = wrapInFragment,
287
+ filterable,
288
+ filterPlaceholder,
289
+ searchInputRef,
290
+ listboxRef,
291
+ }: SelectInputOptionsProps<T>) {
292
+ const [query, setQuery] = useState('');
293
+ const needle = useMemo(() => (query ? searchableString(query) : null), [query]);
294
+
295
+ const listboxContainerRef = useRef<HTMLDivElement>(null);
296
+ useEffect(() => {
297
+ if (listboxContainerRef.current != null) {
298
+ listboxContainerRef.current.style.setProperty(
299
+ '--initial-height',
300
+ `${listboxContainerRef.current.offsetHeight}px`,
301
+ );
302
+ }
303
+ }, []);
304
+
305
+ const listboxId = useId();
306
+
307
+ const controllerRef = filterable ? searchInputRef : listboxRef;
308
+
309
+ return (
310
+ <ListboxBase.Options
311
+ as={SelectInputOptionsContainer}
312
+ static
313
+ className="np-select-input-options-container"
314
+ onAriaActiveDescendantChange={(value: React.AriaAttributes['aria-activedescendant']) => {
315
+ if (controllerRef.current != null) {
316
+ if (value != null) {
317
+ controllerRef.current.setAttribute('aria-activedescendant', value);
318
+ } else {
319
+ controllerRef.current.removeAttribute('aria-activedescendant');
320
+ }
321
+ }
322
+ }}
323
+ >
324
+ {filterable ? (
325
+ <div className="np-select-input-query-container">
326
+ <SearchInput
327
+ ref={searchInputRef}
328
+ shape="rectangle"
329
+ placeholder={filterPlaceholder}
330
+ value={query}
331
+ aria-controls={listboxId}
332
+ onKeyDown={(event) => {
333
+ // Prevent interfering with the matcher of Headless UI
334
+ // https://mathiasbynens.be/notes/javascript-unicode#regex
335
+ if (/^.$/u.test(event.key)) {
336
+ event.stopPropagation();
337
+ }
338
+ }}
339
+ onChange={(event) => {
340
+ setQuery(event.currentTarget.value);
341
+ }}
342
+ />
343
+ </div>
344
+ ) : null}
345
+
346
+ <div
347
+ ref={listboxContainerRef}
348
+ className={classNames(
349
+ 'np-select-input-listbox-container',
350
+ items.some((item) => item.type === 'group') &&
351
+ 'np-select-input-listbox-container--has-group',
352
+ )}
353
+ >
354
+ <div
355
+ ref={listboxRef}
356
+ id={listboxId}
357
+ role="listbox"
358
+ aria-orientation="vertical"
359
+ tabIndex={0}
360
+ className="np-select-input-listbox"
361
+ >
362
+ {(needle == null ? items : dedupeSelectInputItems(items)).map((item, index) => (
363
+ <SelectInputItemView
364
+ // eslint-disable-next-line react/no-array-index-key
365
+ key={index}
366
+ item={item}
367
+ renderValue={renderValue}
368
+ needle={needle}
369
+ />
370
+ ))}
371
+ </div>
372
+ </div>
373
+ </ListboxBase.Options>
374
+ );
375
+ }
376
+
377
+ interface SelectInputItemViewProps<
378
+ T = string,
379
+ I extends SelectInputItem<T | undefined> = SelectInputItem<T | undefined>,
380
+ > extends Required<Pick<SelectInputProps<T>, 'renderValue'>> {
381
+ item: I;
382
+ needle: string | null;
383
+ }
384
+
385
+ function SelectInputItemView<T>({ item, renderValue, needle }: SelectInputItemViewProps<T>) {
386
+ switch (item.type) {
387
+ case 'option': {
388
+ if (
389
+ item.value != null &&
390
+ (!needle ||
391
+ inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
392
+ haystack.includes(needle),
393
+ ))
394
+ ) {
395
+ return (
396
+ <SelectInputOption value={item.value} disabled={item.disabled}>
397
+ {renderValue(item.value, false)}
398
+ </SelectInputOption>
399
+ );
400
+ }
401
+ break;
402
+ }
403
+ case 'group': {
404
+ return <SelectInputGroupItemView item={item} renderValue={renderValue} needle={needle} />;
405
+ }
406
+ case 'separator': {
407
+ if (needle == null) {
408
+ return <hr className="np-select-input-separator-item" aria-hidden />;
409
+ }
410
+ break;
411
+ }
412
+ }
413
+ return null;
414
+ }
415
+
416
+ interface SelectInputGroupItemViewProps<T = string>
417
+ extends SelectInputItemViewProps<T, SelectInputGroupItem<T | undefined>> {}
418
+
419
+ function SelectInputGroupItemView<T>({
420
+ item,
421
+ renderValue,
422
+ needle,
423
+ }: SelectInputGroupItemViewProps<T>) {
424
+ const headerId = useId();
425
+
426
+ return (
427
+ // An empty container may be rendered when no options match `needle`
428
+ // However, pre-filtering would result in worse performance overall
429
+ <section
430
+ role="group"
431
+ aria-labelledby={headerId}
432
+ className={classNames(needle == null && 'np-select-input-group-item--without-needle')}
433
+ >
434
+ {needle == null ? (
435
+ <header
436
+ id={headerId}
437
+ role="presentation"
438
+ className="np-select-input-group-item-header np-text-title-group"
439
+ >
440
+ {item.label}
441
+ </header>
442
+ ) : null}
443
+ {item.options.map((option, index) => (
444
+ <SelectInputItemView
445
+ // eslint-disable-next-line react/no-array-index-key
446
+ key={index}
447
+ item={option}
448
+ renderValue={renderValue}
449
+ needle={needle}
450
+ />
451
+ ))}
452
+ </section>
453
+ );
454
+ }
455
+
456
+ interface SelectInputOptionProps<T = string> {
457
+ value: T;
458
+ disabled?: boolean;
459
+ children?: React.ReactNode;
460
+ }
461
+
462
+ function SelectInputOption<T>({ value, disabled, children }: SelectInputOptionProps<T>) {
463
+ const parentHasValue = useContext(SelectInputHasValueContext);
464
+
465
+ // Avoid flash during exit transition
466
+ const { current: cachedParentHasValue } = useRef(parentHasValue);
467
+
468
+ return (
469
+ <ListboxBase.Option
470
+ as="div"
471
+ value={value}
472
+ disabled={disabled}
473
+ className={({ active, disabled: uiDisabled }) =>
474
+ classNames(
475
+ 'np-select-input-option-container np-text-body-large',
476
+ active && 'np-select-input-option-container--active',
477
+ uiDisabled && 'np-select-input-option-container--disabled',
478
+ )
479
+ }
480
+ >
481
+ {({ selected }) => (
482
+ <>
483
+ {cachedParentHasValue ? (
484
+ <Check
485
+ size={16}
486
+ className={classNames(!selected && 'np-select-input-option-check--not-selected')}
487
+ />
488
+ ) : null}
489
+ <div className="np-select-input-option">{children}</div>
490
+ </>
491
+ )}
492
+ </ListboxBase.Option>
493
+ );
494
+ }
495
+
496
+ export interface SelectInputOptionContentProps {
497
+ title: string;
498
+ note?: string;
499
+ description?: string;
500
+ icon?: React.ReactNode;
501
+ }
502
+
503
+ export function SelectInputOptionContent({
504
+ title,
505
+ note,
506
+ description,
507
+ icon,
508
+ }: SelectInputOptionContentProps) {
509
+ const compact = useContext(SelectInputOptionContentCompactContext);
510
+
511
+ return (
512
+ <div className="np-select-input-option-content-container np-text-body-large">
513
+ {icon ? (
514
+ <div
515
+ className={classNames(
516
+ 'np-select-input-option-content-icon',
517
+ !compact && 'np-select-input-option-content-icon--not-compact',
518
+ )}
519
+ >
520
+ {icon}
521
+ </div>
522
+ ) : null}
523
+
524
+ <div className="np-select-input-option-content-text">
525
+ <div
526
+ className={classNames(
527
+ 'np-select-input-option-content-text-line-1',
528
+ compact && 'np-select-input-option-content-text-compact',
529
+ )}
530
+ >
531
+ <h4 className="d-inline np-text-body-large">{title}</h4>
532
+ {note ? (
533
+ <span className="np-select-input-option-content-text-secondary np-text-body-default">
534
+ {note}
535
+ </span>
536
+ ) : null}
537
+ </div>
538
+
539
+ {description ? (
540
+ <div
541
+ className={classNames(
542
+ 'np-select-input-option-content-text-secondary np-text-body-default',
543
+ compact && 'np-select-input-option-content-text-compact',
544
+ )}
545
+ >
546
+ {description}
547
+ </div>
548
+ ) : null}
549
+ </div>
550
+ </div>
551
+ );
552
+ }
@@ -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
+ }