@transferwise/components 0.0.0-experimental-27ef3d4 → 0.0.0-experimental-1dc962d
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/inputs/SelectInput.js +36 -6
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +37 -7
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/inputs/_BottomSheet.js +2 -5
- package/build/inputs/_BottomSheet.js.map +1 -1
- package/build/inputs/_BottomSheet.mjs +3 -6
- package/build/inputs/_BottomSheet.mjs.map +1 -1
- package/build/inputs/_Popover.js +2 -2
- package/build/inputs/_Popover.js.map +1 -1
- package/build/inputs/_Popover.mjs +2 -2
- package/build/inputs/_Popover.mjs.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +2 -1
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
- package/build/types/inputs/_Popover.d.ts.map +1 -1
- package/package.json +4 -5
- package/src/inputs/SelectInput.spec.tsx +61 -3
- package/src/inputs/SelectInput.story.tsx +68 -0
- package/src/inputs/SelectInput.tsx +66 -21
- package/src/inputs/_BottomSheet.tsx +4 -7
- package/src/inputs/_Popover.tsx +1 -2
- package/src/moneyInput/MoneyInput.spec.tsx +7 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_Popover.mjs","sources":["../../src/inputs/_Popover.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n type Placement,\n shift,\n size as floatingSize,\n useDismiss,\n useFloating,\n useInteractions,\n useRole,\n} from '@floating-ui/react';\nimport { Transition } from '@headlessui/react';\nimport { FocusScope } from '@react-aria/focus';\nimport { ThemeProvider, useTheme } from '@wise/components-theming';\nimport { clsx } from 'clsx';\nimport { useState } from 'react';\n\nimport { PreventScroll } from '../common/preventScroll/PreventScroll';\n\nexport interface PopoverProps {\n placement?: Placement;\n open: boolean;\n renderTrigger: (args: {\n ref: React.RefCallback<Element>;\n getInteractionProps: (customEventHandlers?: React.HTMLProps<Element>) => {\n [key: string]: unknown;\n };\n }) => React.ReactNode;\n title?: string;\n size?: 'md' | 'lg';\n padding?: 'none' | 'md';\n children?: React.ReactNode;\n onClose?: () => void;\n onCloseEnd?: () => void;\n}\n\nconst floatingPadding = 16;\n\nexport function Popover({\n placement,\n open,\n renderTrigger,\n title,\n size = 'md',\n padding = 'md',\n children,\n onClose,\n onCloseEnd,\n}: PopoverProps) {\n const { refs, floatingStyles, context } = useFloating<Element>({\n placement,\n middleware: [\n offset(8),\n flip({ padding: floatingPadding }),\n shift(),\n floatingSize({\n padding: floatingPadding,\n apply: ({ elements, rects, availableHeight }) => {\n elements.floating.style.setProperty('--max-height', `${availableHeight}px`);\n elements.floating.style.setProperty('--width', `${rects.reference.width}px`);\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n open,\n onOpenChange: (value) => {\n if (!value) {\n onClose?.();\n }\n },\n });\n\n const dismiss = useDismiss(context);\n const role = useRole(context);\n const { getReferenceProps, getFloatingProps } = useInteractions([role, dismiss]);\n\n const [floatingKey, setFloatingKey] = useState(0);\n\n const { theme, screenMode } = useTheme();\n\n return (\n <>\n {open ? <PreventScroll /> : null}\n {renderTrigger({\n ref: refs.setReference,\n getInteractionProps: getReferenceProps,\n })}\n\n <FloatingPortal>\n <ThemeProvider theme=\"personal\" screenMode={theme === 'personal' ? screenMode : 'light'}>\n <Transition\n
|
|
1
|
+
{"version":3,"file":"_Popover.mjs","sources":["../../src/inputs/_Popover.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n type Placement,\n shift,\n size as floatingSize,\n useDismiss,\n useFloating,\n useInteractions,\n useRole,\n} from '@floating-ui/react';\nimport { Transition } from '@headlessui/react';\nimport { FocusScope } from '@react-aria/focus';\nimport { ThemeProvider, useTheme } from '@wise/components-theming';\nimport { clsx } from 'clsx';\nimport { useState } from 'react';\n\nimport { PreventScroll } from '../common/preventScroll/PreventScroll';\n\nexport interface PopoverProps {\n placement?: Placement;\n open: boolean;\n renderTrigger: (args: {\n ref: React.RefCallback<Element>;\n getInteractionProps: (customEventHandlers?: React.HTMLProps<Element>) => {\n [key: string]: unknown;\n };\n }) => React.ReactNode;\n title?: string;\n size?: 'md' | 'lg';\n padding?: 'none' | 'md';\n children?: React.ReactNode;\n onClose?: () => void;\n onCloseEnd?: () => void;\n}\n\nconst floatingPadding = 16;\n\nexport function Popover({\n placement,\n open,\n renderTrigger,\n title,\n size = 'md',\n padding = 'md',\n children,\n onClose,\n onCloseEnd,\n}: PopoverProps) {\n const { refs, floatingStyles, context } = useFloating<Element>({\n placement,\n middleware: [\n offset(8),\n flip({ padding: floatingPadding, crossAxis: false }),\n shift(),\n floatingSize({\n padding: floatingPadding,\n apply: ({ elements, rects, availableHeight }) => {\n elements.floating.style.setProperty('--max-height', `${availableHeight}px`);\n elements.floating.style.setProperty('--width', `${rects.reference.width}px`);\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n open,\n onOpenChange: (value) => {\n if (!value) {\n onClose?.();\n }\n },\n });\n\n const dismiss = useDismiss(context);\n const role = useRole(context);\n const { getReferenceProps, getFloatingProps } = useInteractions([role, dismiss]);\n\n const [floatingKey, setFloatingKey] = useState(0);\n\n const { theme, screenMode } = useTheme();\n\n return (\n <>\n {open ? <PreventScroll /> : null}\n {renderTrigger({\n ref: refs.setReference,\n getInteractionProps: getReferenceProps,\n })}\n\n <FloatingPortal>\n <ThemeProvider theme=\"personal\" screenMode={theme === 'personal' ? screenMode : 'light'}>\n <Transition\n show={open}\n leave=\"transition-opacity\"\n leaveTo=\"opacity-0\"\n beforeEnter={() => {\n setFloatingKey((prev) => prev + 1);\n }}\n afterLeave={onCloseEnd}\n >\n <FocusScope>\n <FloatingFocusManager context={context}>\n <div\n key={floatingKey} // Force inner state invalidation on open\n ref={refs.setFloating}\n className={clsx('np-popover-v2-container', {\n 'np-popover-v2-container--size-md': size === 'md',\n 'np-popover-v2-container--size-lg': size === 'lg',\n })}\n style={floatingStyles}\n {...getFloatingProps()}\n >\n <div\n className={clsx('np-popover-v2', title && 'np-popover-v2--has-title', {\n 'np-popover-v2--padding-md': padding === 'md',\n })}\n >\n {title ? (\n <h2 className=\"np-popover-v2-title np-text-title-body\">{title}</h2>\n ) : null}\n <div className=\"np-popover-v2-content np-text-body-default\">{children}</div>\n </div>\n </div>\n </FloatingFocusManager>\n </FocusScope>\n </Transition>\n </ThemeProvider>\n </FloatingPortal>\n </>\n );\n}\n"],"names":["floatingPadding","Popover","placement","open","renderTrigger","title","size","padding","children","onClose","onCloseEnd","refs","floatingStyles","context","useFloating","middleware","offset","flip","crossAxis","shift","floatingSize","apply","elements","rects","availableHeight","floating","style","setProperty","reference","width","whileElementsMounted","autoUpdate","onOpenChange","value","dismiss","useDismiss","role","useRole","getReferenceProps","getFloatingProps","useInteractions","floatingKey","setFloatingKey","useState","theme","screenMode","useTheme","_jsxs","_Fragment","_jsx","PreventScroll","ref","setReference","getInteractionProps","FloatingPortal","ThemeProvider","Transition","show","leave","leaveTo","beforeEnter","prev","afterLeave","FocusScope","FloatingFocusManager","setFloating","className","clsx"],"mappings":"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"}
|
|
@@ -69,6 +69,7 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
|
69
69
|
}) => React.ReactNode;
|
|
70
70
|
filterable?: boolean;
|
|
71
71
|
filterPlaceholder?: string;
|
|
72
|
+
sortFilteredOptions?: (a: SelectInputOptionItem<NonNullable<T>>, b: SelectInputOptionItem<NonNullable<T>>, searchQuery: string) => number;
|
|
72
73
|
disabled?: boolean;
|
|
73
74
|
size?: 'sm' | 'md' | 'lg';
|
|
74
75
|
className?: string;
|
|
@@ -86,7 +87,7 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
|
86
87
|
onClose?: () => void;
|
|
87
88
|
onClear?: () => void;
|
|
88
89
|
}
|
|
89
|
-
export declare function SelectInput<T = string, M extends boolean = false>({ id: idProp, parentId, name, multiple, placeholder, autocomplete, items, defaultValue, value: controlledValue, compareValues, renderValue, renderFooter, renderTrigger, filterable, filterPlaceholder, disabled, size, className, UNSAFE_triggerButtonProps, triggerRef: externalTriggerRef, onFilterChange, onChange, onOpen, onClose, onClear, }: SelectInputProps<T, M>): import("react").JSX.Element;
|
|
90
|
+
export declare function SelectInput<T = string, M extends boolean = false>({ id: idProp, parentId, name, multiple, placeholder, autocomplete, items, defaultValue, value: controlledValue, compareValues, renderValue, renderFooter, renderTrigger, filterable, filterPlaceholder, sortFilteredOptions, disabled, size, className, UNSAFE_triggerButtonProps, triggerRef: externalTriggerRef, onFilterChange, onChange, onOpen, onClose, onClear, }: SelectInputProps<T, M>): import("react").JSX.Element;
|
|
90
91
|
type SelectInputTriggerButtonElementType = 'button' | React.ComponentType;
|
|
91
92
|
export type SelectInputTriggerButtonProps<T extends SelectInputTriggerButtonElementType = 'button'> = Merge<React.ComponentPropsWithoutRef<T>, {
|
|
92
93
|
as?: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectInput.d.ts","sourceRoot":"","sources":["../../../src/inputs/SelectInput.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SelectInput.d.ts","sourceRoot":"","sources":["../../../src/inputs/SelectInput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/inputs/_BottomSheet.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QACrB,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,mBAAmB,EAAE,CAAC,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;YACvE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,aAAa,EACb,KAAK,EACL,eAAe,EACf,OAAc,EACd,QAAQ,EACR,OAAO,EACP,UAAU,GACX,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"_BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/inputs/_BottomSheet.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QACrB,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,mBAAmB,EAAE,CAAC,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;YACvE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,aAAa,EACb,KAAK,EACL,eAAe,EACf,OAAc,EACd,QAAQ,EACR,OAAO,EACP,UAAU,GACX,EAAE,gBAAgB,+BAyFlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_Popover.d.ts","sourceRoot":"","sources":["../../../src/inputs/_Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EAOf,MAAM,oBAAoB,CAAC;AAS5B,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,CAAC,IAAI,EAAE;QACpB,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,mBAAmB,EAAE,CAAC,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;YACvE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAID,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,IAAI,EACJ,aAAa,EACb,KAAK,EACL,IAAW,EACX,OAAc,EACd,QAAQ,EACR,OAAO,EACP,UAAU,GACX,EAAE,YAAY,+
|
|
1
|
+
{"version":3,"file":"_Popover.d.ts","sourceRoot":"","sources":["../../../src/inputs/_Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EAOf,MAAM,oBAAoB,CAAC;AAS5B,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,CAAC,IAAI,EAAE;QACpB,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,mBAAmB,EAAE,CAAC,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK;YACvE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAID,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,IAAI,EACJ,aAAa,EACb,KAAK,EACL,IAAW,EACX,OAAc,EACd,QAAQ,EACR,OAAO,EACP,UAAU,GACX,EAAE,YAAY,+BAiFd"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-1dc962d",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@storybook/react-webpack5": "10.2.0-alpha.13",
|
|
57
57
|
"@testing-library/dom": "^10.4.1",
|
|
58
58
|
"@testing-library/jest-dom": "^6.9.1",
|
|
59
|
-
"@testing-library/react": "^16.3.
|
|
59
|
+
"@testing-library/react": "^16.3.1",
|
|
60
60
|
"@testing-library/user-event": "^14.6.1",
|
|
61
61
|
"@transferwise/icons": "^4.0.2",
|
|
62
62
|
"@tsconfig/recommended": "^1.0.13",
|
|
@@ -78,7 +78,6 @@
|
|
|
78
78
|
"jest": "^30.2.0",
|
|
79
79
|
"jest-environment-jsdom": "^29.7.0",
|
|
80
80
|
"jest-fetch-mock": "^3.0.3",
|
|
81
|
-
"jsdom-testing-mocks": "^1.16.0",
|
|
82
81
|
"lodash.times": "^4.3.2",
|
|
83
82
|
"react-intl": "^7.1.11",
|
|
84
83
|
"rollup": "^4.54.0",
|
|
@@ -88,7 +87,7 @@
|
|
|
88
87
|
"storybook-addon-test-codegen": "^3.0.1",
|
|
89
88
|
"@transferwise/less-config": "3.1.2",
|
|
90
89
|
"@transferwise/neptune-css": "14.26.1",
|
|
91
|
-
"@wise/components-theming": "1.10.
|
|
90
|
+
"@wise/components-theming": "1.10.1",
|
|
92
91
|
"@wise/wds-configs": "0.0.0"
|
|
93
92
|
},
|
|
94
93
|
"peerDependencies": {
|
|
@@ -104,7 +103,7 @@
|
|
|
104
103
|
"dependencies": {
|
|
105
104
|
"@babel/runtime": "^7.28.4",
|
|
106
105
|
"@floating-ui/react": "^0.27.16",
|
|
107
|
-
"@headlessui/react": "^
|
|
106
|
+
"@headlessui/react": "^1.7.19",
|
|
108
107
|
"@popperjs/core": "^2.11.8",
|
|
109
108
|
"@react-aria/focus": "^3.21.3",
|
|
110
109
|
"@react-aria/overlays": "^3.31.0",
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { screen, waitFor, within } from '@testing-library/react';
|
|
2
2
|
import { userEvent } from '@testing-library/user-event';
|
|
3
|
-
import { mockAnimationsApi } from 'jsdom-testing-mocks';
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
render,
|
|
6
|
+
mockMatchMedia,
|
|
7
|
+
mockResizeObserver,
|
|
8
|
+
mockRequestAnimationFrame,
|
|
9
|
+
} from '../test-utils';
|
|
6
10
|
|
|
7
11
|
import { SelectInput, type SelectInputOptionItem, type SelectInputProps } from './SelectInput';
|
|
8
12
|
import { Field } from '../field/Field';
|
|
9
13
|
|
|
10
14
|
mockMatchMedia();
|
|
11
15
|
mockResizeObserver();
|
|
12
|
-
|
|
16
|
+
mockRequestAnimationFrame();
|
|
13
17
|
|
|
14
18
|
describe('SelectInput', () => {
|
|
15
19
|
it('renders placeholder', () => {
|
|
@@ -382,6 +386,60 @@ describe('SelectInput', () => {
|
|
|
382
386
|
expect(within(listbox).getByText('Item Two')).toBeInTheDocument();
|
|
383
387
|
});
|
|
384
388
|
|
|
389
|
+
it('sorts filtered options using sortFilteredOptions prop', async () => {
|
|
390
|
+
interface Country {
|
|
391
|
+
code: string;
|
|
392
|
+
name: string;
|
|
393
|
+
keywords: string[];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const countries: Country[] = [
|
|
397
|
+
{ code: 'AD', name: 'Andorra', keywords: ['united states dollar'] },
|
|
398
|
+
{ code: 'DE', name: 'Germany', keywords: ['EUR'] },
|
|
399
|
+
{ code: 'US', name: 'United States', keywords: ['United States dollar', 'USD'] },
|
|
400
|
+
{ code: 'ZM', name: 'Zambia', keywords: ['USD', 'united states dollar'] },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
render(
|
|
404
|
+
<SelectInput<Country>
|
|
405
|
+
items={countries.map((country) => ({
|
|
406
|
+
type: 'option',
|
|
407
|
+
value: country,
|
|
408
|
+
filterMatchers: country.keywords,
|
|
409
|
+
}))}
|
|
410
|
+
renderValue={(country) => country.name}
|
|
411
|
+
filterable
|
|
412
|
+
sortFilteredOptions={(a, b, searchQuery) => {
|
|
413
|
+
const query = searchQuery.toLowerCase();
|
|
414
|
+
const nameA = a.value.name.toLowerCase();
|
|
415
|
+
const nameB = b.value.name.toLowerCase();
|
|
416
|
+
|
|
417
|
+
const aMatch = nameA.includes(query);
|
|
418
|
+
const bMatch = nameB.includes(query);
|
|
419
|
+
|
|
420
|
+
if (aMatch && !bMatch) return -1;
|
|
421
|
+
if (!aMatch && bMatch) return 1;
|
|
422
|
+
|
|
423
|
+
return nameA.localeCompare(nameB);
|
|
424
|
+
}}
|
|
425
|
+
/>,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const trigger = screen.getByRole('combobox');
|
|
429
|
+
await userEvent.click(trigger);
|
|
430
|
+
|
|
431
|
+
const searchInput = screen.getByRole('combobox', { expanded: true });
|
|
432
|
+
await userEvent.type(searchInput, 'united');
|
|
433
|
+
|
|
434
|
+
const listbox = screen.getByRole('listbox');
|
|
435
|
+
const options = within(listbox).getAllByRole('option');
|
|
436
|
+
|
|
437
|
+
expect(options).toHaveLength(3);
|
|
438
|
+
expect(options[0]).toHaveTextContent('United States');
|
|
439
|
+
expect(options[1]).toHaveTextContent('Andorra');
|
|
440
|
+
expect(options[2]).toHaveTextContent('Zambia');
|
|
441
|
+
});
|
|
442
|
+
|
|
385
443
|
describe('listbox label', () => {
|
|
386
444
|
const fieldLabel = 'Fruits';
|
|
387
445
|
const triggerLabel = 'Select fruit';
|
|
@@ -616,3 +616,71 @@ export const WithAutocomplete: Story<string> = {
|
|
|
616
616
|
);
|
|
617
617
|
},
|
|
618
618
|
};
|
|
619
|
+
|
|
620
|
+
interface CountryWithCurrency extends Country {
|
|
621
|
+
keywords: string[];
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const countriesWithCurrency: CountryWithCurrency[] = [
|
|
625
|
+
{ code: 'AD', name: 'Andorra', keywords: ['united states dollar'] },
|
|
626
|
+
{ code: 'DE', name: 'Germany', keywords: ['EUR'] },
|
|
627
|
+
{ code: 'US', name: 'United States', keywords: ['USD'] },
|
|
628
|
+
{ code: 'ZM', name: 'Zambia', keywords: ['USD', 'united states dollar'] },
|
|
629
|
+
];
|
|
630
|
+
|
|
631
|
+
function countryWithCurrencyOption(country: CountryWithCurrency) {
|
|
632
|
+
return {
|
|
633
|
+
type: 'option',
|
|
634
|
+
value: country,
|
|
635
|
+
filterMatchers: [country.name, country.code, ...country.keywords],
|
|
636
|
+
} satisfies SelectInputItem<CountryWithCurrency>;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export const WithCustomSearchResultSorting: Story<CountryWithCurrency> = {
|
|
640
|
+
args: {
|
|
641
|
+
items: countriesWithCurrency.map(countryWithCurrencyOption),
|
|
642
|
+
compareValues: 'code',
|
|
643
|
+
renderValue: (country) => (
|
|
644
|
+
<SelectInputOptionContent
|
|
645
|
+
title={country.name}
|
|
646
|
+
icon={<Flag code={country.code} intrinsicSize={24} />}
|
|
647
|
+
/>
|
|
648
|
+
),
|
|
649
|
+
value: countriesWithCurrency[0],
|
|
650
|
+
filterable: true,
|
|
651
|
+
filterPlaceholder: 'Type a currency / country',
|
|
652
|
+
sortFilteredOptions: (a, b, searchQuery) => {
|
|
653
|
+
const normalizedQuery = searchQuery.toLowerCase();
|
|
654
|
+
const nameA = a.value.name.toLowerCase();
|
|
655
|
+
const nameB = b.value.name.toLowerCase();
|
|
656
|
+
|
|
657
|
+
// Prioritize countries where name contains the search query
|
|
658
|
+
const aNameMatch = nameA.includes(normalizedQuery);
|
|
659
|
+
const bNameMatch = nameB.includes(normalizedQuery);
|
|
660
|
+
|
|
661
|
+
if (aNameMatch && !bNameMatch) return -1;
|
|
662
|
+
if (!aNameMatch && bNameMatch) return 1;
|
|
663
|
+
|
|
664
|
+
// Then sort alphabetically
|
|
665
|
+
return nameA.localeCompare(nameB);
|
|
666
|
+
},
|
|
667
|
+
size: 'lg',
|
|
668
|
+
} satisfies Story<CountryWithCurrency>['args'],
|
|
669
|
+
decorators: [
|
|
670
|
+
(Story) => (
|
|
671
|
+
<div>
|
|
672
|
+
<p className="m-b-3 np-text-body-default" style={{ maxWidth: '600px' }}>
|
|
673
|
+
This example demonstrates custom sorting with the <code>sortFilteredOptions</code> prop.
|
|
674
|
+
<br />
|
|
675
|
+
<br />
|
|
676
|
+
Try searching for "united" - Without the custom sorter, it would just be the
|
|
677
|
+
provided options order.
|
|
678
|
+
<br />
|
|
679
|
+
With a customer sorter function in this story, the countries with "United" in
|
|
680
|
+
their name appear first, followed by countries that only match via keywords.
|
|
681
|
+
</p>
|
|
682
|
+
<Story />
|
|
683
|
+
</div>
|
|
684
|
+
),
|
|
685
|
+
],
|
|
686
|
+
};
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Listbox as ListboxBase,
|
|
3
|
-
ListboxButton,
|
|
4
|
-
ListboxOption,
|
|
5
|
-
ListboxOptions,
|
|
6
|
-
} from '@headlessui/react';
|
|
1
|
+
import { Listbox as ListboxBase } from '@headlessui/react';
|
|
7
2
|
import { Check, ChevronDown, Cross, CrossCircle } from '@transferwise/icons';
|
|
8
3
|
import { clsx } from 'clsx';
|
|
9
4
|
import mergeProps from 'merge-props';
|
|
@@ -165,6 +160,38 @@ function filterSelectInputItems<T>(
|
|
|
165
160
|
});
|
|
166
161
|
}
|
|
167
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Flattens and sorts filtered options using the provided comparator.
|
|
165
|
+
* Extracts all options from groups, filters out undefined values (deduplicated items),
|
|
166
|
+
* sorts them, and returns as a flat list of option items.
|
|
167
|
+
*/
|
|
168
|
+
function sortSelectInputItems<T>(
|
|
169
|
+
items: readonly SelectInputItem<T | undefined>[],
|
|
170
|
+
compareFn: (
|
|
171
|
+
a: SelectInputOptionItem<NonNullable<T>>,
|
|
172
|
+
b: SelectInputOptionItem<NonNullable<T>>,
|
|
173
|
+
searchQuery: string,
|
|
174
|
+
) => number,
|
|
175
|
+
searchQuery: string,
|
|
176
|
+
): SelectInputItem<NonNullable<T>>[] {
|
|
177
|
+
const flattenedOption = items.flatMap((item) => {
|
|
178
|
+
if (item.type === 'option') {
|
|
179
|
+
return item.value !== undefined ? [item as SelectInputOptionItem<NonNullable<T>>] : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (item.type === 'group') {
|
|
183
|
+
return item.options.filter(
|
|
184
|
+
(option): option is SelectInputOptionItem<NonNullable<T>> => option.value !== undefined,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return [];
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// eslint-disable-next-line functional/immutable-data
|
|
192
|
+
return flattenedOption.sort((a, b) => compareFn(a, b, searchQuery));
|
|
193
|
+
}
|
|
194
|
+
|
|
168
195
|
export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
169
196
|
id?: string;
|
|
170
197
|
/**
|
|
@@ -215,6 +242,11 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
|
215
242
|
}) => React.ReactNode;
|
|
216
243
|
filterable?: boolean;
|
|
217
244
|
filterPlaceholder?: string;
|
|
245
|
+
sortFilteredOptions?: (
|
|
246
|
+
a: SelectInputOptionItem<NonNullable<T>>,
|
|
247
|
+
b: SelectInputOptionItem<NonNullable<T>>,
|
|
248
|
+
searchQuery: string,
|
|
249
|
+
) => number;
|
|
218
250
|
disabled?: boolean;
|
|
219
251
|
size?: 'sm' | 'md' | 'lg';
|
|
220
252
|
className?: string;
|
|
@@ -309,6 +341,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
309
341
|
renderTrigger = defaultRenderTrigger,
|
|
310
342
|
filterable,
|
|
311
343
|
filterPlaceholder,
|
|
344
|
+
sortFilteredOptions,
|
|
312
345
|
disabled,
|
|
313
346
|
size = 'md',
|
|
314
347
|
className,
|
|
@@ -394,8 +427,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
394
427
|
<ListboxBase
|
|
395
428
|
name={name}
|
|
396
429
|
multiple={multiple}
|
|
397
|
-
defaultValue={defaultValue
|
|
398
|
-
value={controlledValue
|
|
430
|
+
defaultValue={defaultValue}
|
|
431
|
+
value={controlledValue}
|
|
399
432
|
by={compareValues}
|
|
400
433
|
disabled={disabled}
|
|
401
434
|
onChange={
|
|
@@ -412,7 +445,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
412
445
|
multiple && Array.isArray(value) ? value.length === 0 : value == null;
|
|
413
446
|
return (
|
|
414
447
|
<OptionsOverlay
|
|
415
|
-
placement="bottom-
|
|
448
|
+
placement="bottom-start"
|
|
416
449
|
open={open}
|
|
417
450
|
renderTrigger={({ ref, getInteractionProps }) => (
|
|
418
451
|
<SelectInputTriggerButtonPropsContext.Provider
|
|
@@ -498,6 +531,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
498
531
|
renderFooter={renderFooter}
|
|
499
532
|
filterable={filterable}
|
|
500
533
|
filterPlaceholder={filterPlaceholder}
|
|
534
|
+
sortFilteredOptions={sortFilteredOptions}
|
|
501
535
|
searchInputRef={searchInputRef}
|
|
502
536
|
listboxRef={listboxRef}
|
|
503
537
|
filterQuery={deferredFilterQuery}
|
|
@@ -542,7 +576,7 @@ export function SelectInputTriggerButton<T extends SelectInputTriggerButtonEleme
|
|
|
542
576
|
);
|
|
543
577
|
|
|
544
578
|
return (
|
|
545
|
-
<
|
|
579
|
+
<ListboxBase.Button
|
|
546
580
|
ref={ref}
|
|
547
581
|
as={PolymorphicWithOverrides}
|
|
548
582
|
role="combobox"
|
|
@@ -610,6 +644,7 @@ interface SelectInputOptionsProps<T = string> extends Pick<
|
|
|
610
644
|
| 'id'
|
|
611
645
|
| 'parentId'
|
|
612
646
|
| 'compareValues'
|
|
647
|
+
| 'sortFilteredOptions'
|
|
613
648
|
> {
|
|
614
649
|
searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
|
|
615
650
|
listboxRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
@@ -631,6 +666,7 @@ function SelectInputOptions<T = string>({
|
|
|
631
666
|
renderFooter,
|
|
632
667
|
filterable = false,
|
|
633
668
|
filterPlaceholder,
|
|
669
|
+
sortFilteredOptions,
|
|
634
670
|
searchInputRef,
|
|
635
671
|
listboxRef,
|
|
636
672
|
filterQuery,
|
|
@@ -690,12 +726,22 @@ function SelectInputOptions<T = string>({
|
|
|
690
726
|
};
|
|
691
727
|
}, [compareValuesProp]);
|
|
692
728
|
|
|
693
|
-
const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] =
|
|
694
|
-
needle
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
729
|
+
const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] = useMemo(() => {
|
|
730
|
+
if (needle == null) {
|
|
731
|
+
return items;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const filtered = filterSelectInputItems(dedupeSelectInputItems(items, compareValues), (item) =>
|
|
735
|
+
selectInputOptionItemIncludesNeedle(item, needle),
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
if (sortFilteredOptions) {
|
|
739
|
+
return sortSelectInputItems(filtered, sortFilteredOptions, filterQuery);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return filtered;
|
|
743
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
744
|
+
}, [needle, items, compareValues]);
|
|
699
745
|
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
700
746
|
|
|
701
747
|
const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
|
|
@@ -771,8 +817,7 @@ function SelectInputOptions<T = string>({
|
|
|
771
817
|
};
|
|
772
818
|
|
|
773
819
|
return (
|
|
774
|
-
<
|
|
775
|
-
modal
|
|
820
|
+
<ListboxBase.Options
|
|
776
821
|
as={SelectInputOptionsContainer}
|
|
777
822
|
static
|
|
778
823
|
className="np-select-input-options-container"
|
|
@@ -929,7 +974,7 @@ function SelectInputOptions<T = string>({
|
|
|
929
974
|
</footer>
|
|
930
975
|
) : null}
|
|
931
976
|
</section>
|
|
932
|
-
</
|
|
977
|
+
</ListboxBase.Options>
|
|
933
978
|
);
|
|
934
979
|
}
|
|
935
980
|
|
|
@@ -1038,7 +1083,7 @@ function SelectInputOption<T = string>({ value, disabled, children }: SelectInpu
|
|
|
1038
1083
|
const itemsCount = useContext(SelectInputItemsCountContext);
|
|
1039
1084
|
const itemPosition = useContext(SelectInputItemPositionContext);
|
|
1040
1085
|
return (
|
|
1041
|
-
<
|
|
1086
|
+
<ListboxBase.Option
|
|
1042
1087
|
as="div"
|
|
1043
1088
|
value={value}
|
|
1044
1089
|
aria-setsize={itemsCount}
|
|
@@ -1064,7 +1109,7 @@ function SelectInputOption<T = string>({ value, disabled, children }: SelectInpu
|
|
|
1064
1109
|
/>
|
|
1065
1110
|
</>
|
|
1066
1111
|
)}
|
|
1067
|
-
</
|
|
1112
|
+
</ListboxBase.Option>
|
|
1068
1113
|
);
|
|
1069
1114
|
}
|
|
1070
1115
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useInteractions,
|
|
7
7
|
useRole,
|
|
8
8
|
} from '@floating-ui/react';
|
|
9
|
-
import { Transition
|
|
9
|
+
import { Transition } from '@headlessui/react';
|
|
10
10
|
import { FocusScope } from '@react-aria/focus';
|
|
11
11
|
import { ThemeProvider, useTheme } from '@wise/components-theming';
|
|
12
12
|
import { clsx } from 'clsx';
|
|
@@ -73,7 +73,6 @@ export function BottomSheet({
|
|
|
73
73
|
<FloatingPortal>
|
|
74
74
|
<ThemeProvider theme="personal" screenMode={theme === 'personal' ? screenMode : 'light'}>
|
|
75
75
|
<Transition
|
|
76
|
-
as="div"
|
|
77
76
|
show={open}
|
|
78
77
|
className="np-bottom-sheet-v2-container"
|
|
79
78
|
beforeEnter={() => {
|
|
@@ -81,8 +80,7 @@ export function BottomSheet({
|
|
|
81
80
|
}}
|
|
82
81
|
afterLeave={onCloseEnd}
|
|
83
82
|
>
|
|
84
|
-
<
|
|
85
|
-
as="div"
|
|
83
|
+
<Transition.Child
|
|
86
84
|
className="np-bottom-sheet-v2-backdrop"
|
|
87
85
|
enterFrom="np-bottom-sheet-v2-backdrop--closed"
|
|
88
86
|
leaveTo="np-bottom-sheet-v2-backdrop--closed"
|
|
@@ -94,9 +92,8 @@ export function BottomSheet({
|
|
|
94
92
|
<Fragment
|
|
95
93
|
key={floatingKey} // Force inner state invalidation on open
|
|
96
94
|
>
|
|
97
|
-
<
|
|
95
|
+
<Transition.Child
|
|
98
96
|
ref={refs.setFloating}
|
|
99
|
-
as="div"
|
|
100
97
|
className="np-bottom-sheet-v2-content"
|
|
101
98
|
enterFrom="np-bottom-sheet-v2-content--closed"
|
|
102
99
|
leaveTo="np-bottom-sheet-v2-content--closed"
|
|
@@ -124,7 +121,7 @@ export function BottomSheet({
|
|
|
124
121
|
{children}
|
|
125
122
|
</div>
|
|
126
123
|
</div>
|
|
127
|
-
</
|
|
124
|
+
</Transition.Child>
|
|
128
125
|
</Fragment>
|
|
129
126
|
</FloatingFocusManager>
|
|
130
127
|
</FocusScope>
|
package/src/inputs/_Popover.tsx
CHANGED
|
@@ -54,7 +54,7 @@ export function Popover({
|
|
|
54
54
|
placement,
|
|
55
55
|
middleware: [
|
|
56
56
|
offset(8),
|
|
57
|
-
flip({ padding: floatingPadding }),
|
|
57
|
+
flip({ padding: floatingPadding, crossAxis: false }),
|
|
58
58
|
shift(),
|
|
59
59
|
floatingSize({
|
|
60
60
|
padding: floatingPadding,
|
|
@@ -92,7 +92,6 @@ export function Popover({
|
|
|
92
92
|
<FloatingPortal>
|
|
93
93
|
<ThemeProvider theme="personal" screenMode={theme === 'personal' ? screenMode : 'light'}>
|
|
94
94
|
<Transition
|
|
95
|
-
as="div"
|
|
96
95
|
show={open}
|
|
97
96
|
leave="transition-opacity"
|
|
98
97
|
leaveTo="opacity-0"
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { mockAnimationsApi } from 'jsdom-testing-mocks';
|
|
2
1
|
import {
|
|
3
2
|
mockMatchMedia,
|
|
3
|
+
mockRequestAnimationFrame,
|
|
4
4
|
mockResizeObserver,
|
|
5
5
|
render,
|
|
6
6
|
screen,
|
|
7
7
|
userEvent,
|
|
8
|
-
waitFor,
|
|
9
|
-
within,
|
|
10
8
|
} from '../test-utils';
|
|
11
9
|
|
|
12
10
|
import { MoneyInput, CurrencyItem, CurrencyOptionItem, Field } from '..';
|
|
13
11
|
import { MoneyInputPropsWithInputAttributes } from './MoneyInput';
|
|
12
|
+
import { within } from '@testing-library/react';
|
|
14
13
|
import messages from './MoneyInput.messages';
|
|
15
14
|
|
|
16
15
|
mockMatchMedia();
|
|
17
16
|
mockResizeObserver();
|
|
18
|
-
|
|
17
|
+
mockRequestAnimationFrame();
|
|
19
18
|
|
|
20
19
|
describe('Money Input', () => {
|
|
21
20
|
const popularCurrencies: CurrencyOptionItem[] = [
|
|
@@ -115,30 +114,12 @@ describe('Money Input', () => {
|
|
|
115
114
|
it('calls onCurrencyChange when the user selects a different currency', async () => {
|
|
116
115
|
customRender();
|
|
117
116
|
await openDropdown();
|
|
118
|
-
await userEvent.keyboard('eur');
|
|
119
|
-
|
|
120
|
-
expect(screen.getAllByRole('option')).toHaveLength(1);
|
|
121
|
-
});
|
|
122
|
-
await userEvent.keyboard('{Enter}');
|
|
123
|
-
|
|
124
|
-
await waitFor(() => {
|
|
125
|
-
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(1);
|
|
126
|
-
});
|
|
117
|
+
await userEvent.keyboard('eur{Enter}');
|
|
118
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(1);
|
|
127
119
|
expect(initialProps.onCurrencyChange).toHaveBeenCalledWith(popularCurrencies[0]);
|
|
128
|
-
|
|
129
|
-
await waitFor(() => {
|
|
130
|
-
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
131
|
-
});
|
|
132
120
|
await openDropdown();
|
|
133
|
-
await userEvent.keyboard('gbp');
|
|
134
|
-
|
|
135
|
-
expect(screen.getAllByRole('option')).toHaveLength(1);
|
|
136
|
-
});
|
|
137
|
-
await userEvent.keyboard('{Enter}');
|
|
138
|
-
|
|
139
|
-
await waitFor(() => {
|
|
140
|
-
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(2);
|
|
141
|
-
});
|
|
121
|
+
await userEvent.keyboard('gbp{Enter}');
|
|
122
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(2);
|
|
142
123
|
expect(initialProps.onCurrencyChange).toHaveBeenCalledWith(popularCurrencies[2]);
|
|
143
124
|
});
|
|
144
125
|
|