@transferwise/components 46.140.0 → 46.141.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.
- package/build/avatarWrapper/AvatarWrapper.js +3 -4
- package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
- package/build/avatarWrapper/AvatarWrapper.mjs +4 -5
- package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
- package/build/button/LegacyButton.js.map +1 -1
- package/build/button/LegacyButton.mjs.map +1 -1
- package/build/common/hooks/useHasIntersected/useHasIntersected.js +6 -4
- package/build/common/hooks/useHasIntersected/useHasIntersected.js.map +1 -1
- package/build/common/hooks/useHasIntersected/useHasIntersected.mjs +6 -4
- package/build/common/hooks/useHasIntersected/useHasIntersected.mjs.map +1 -1
- package/build/common/liveRegion/LiveRegion.js +4 -1
- package/build/common/liveRegion/LiveRegion.js.map +1 -1
- package/build/common/liveRegion/LiveRegion.mjs +4 -1
- package/build/common/liveRegion/LiveRegion.mjs.map +1 -1
- package/build/dateInput/DateInput.js +10 -10
- package/build/dateInput/DateInput.js.map +1 -1
- package/build/dateInput/DateInput.mjs +10 -10
- package/build/dateInput/DateInput.mjs.map +1 -1
- package/build/dateLookup/DateLookup.js +1 -1
- package/build/dateLookup/DateLookup.js.map +1 -1
- package/build/dateLookup/DateLookup.mjs +1 -1
- package/build/dateLookup/DateLookup.mjs.map +1 -1
- package/build/dateLookup/monthCalendar/table/MonthCalendarTable.js +1 -1
- package/build/dateLookup/monthCalendar/table/MonthCalendarTable.js.map +1 -1
- package/build/dateLookup/monthCalendar/table/MonthCalendarTable.mjs +1 -1
- package/build/dateLookup/monthCalendar/table/MonthCalendarTable.mjs.map +1 -1
- package/build/dateLookup/yearCalendar/table/YearCalendarTable.js +1 -1
- package/build/dateLookup/yearCalendar/table/YearCalendarTable.js.map +1 -1
- package/build/dateLookup/yearCalendar/table/YearCalendarTable.mjs +1 -1
- package/build/dateLookup/yearCalendar/table/YearCalendarTable.mjs.map +1 -1
- package/build/expressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
- package/build/expressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
- package/build/expressiveMoneyInput/amountInput/AmountInput.js +17 -11
- package/build/expressiveMoneyInput/amountInput/AmountInput.js.map +1 -1
- package/build/expressiveMoneyInput/amountInput/AmountInput.mjs +18 -12
- package/build/expressiveMoneyInput/amountInput/AmountInput.mjs.map +1 -1
- package/build/expressiveMoneyInput/hooks/useInputStyle.js +8 -6
- package/build/expressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
- package/build/expressiveMoneyInput/hooks/useInputStyle.mjs +9 -7
- package/build/expressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
- package/build/header/Header.js +1 -1
- package/build/header/Header.js.map +1 -1
- package/build/header/Header.mjs +1 -1
- package/build/header/Header.mjs.map +1 -1
- package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.js.map +1 -1
- package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.mjs.map +1 -1
- package/build/inputs/SelectInput/Options/SelectInputOptions.js +34 -22
- package/build/inputs/SelectInput/Options/SelectInputOptions.js.map +1 -1
- package/build/inputs/SelectInput/Options/SelectInputOptions.mjs +35 -23
- package/build/inputs/SelectInput/Options/SelectInputOptions.mjs.map +1 -1
- package/build/inputs/SelectInput/Popover/SelectInputPopover.js.map +1 -1
- package/build/inputs/SelectInput/Popover/SelectInputPopover.mjs.map +1 -1
- package/build/inputs/SelectInput/SelectInput.js +8 -6
- package/build/inputs/SelectInput/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput/SelectInput.mjs +9 -7
- package/build/inputs/SelectInput/SelectInput.mjs.map +1 -1
- package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.js.map +1 -1
- package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.mjs.map +1 -1
- package/build/main.css +58 -53
- package/build/nudge/Nudge.js +31 -15
- package/build/nudge/Nudge.js.map +1 -1
- package/build/nudge/Nudge.mjs +32 -16
- package/build/nudge/Nudge.mjs.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.js +9 -12
- package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.mjs +9 -12
- package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
- package/build/promoCard/PromoCardGroup.js +34 -16
- package/build/promoCard/PromoCardGroup.js.map +1 -1
- package/build/promoCard/PromoCardGroup.mjs +35 -17
- package/build/promoCard/PromoCardGroup.mjs.map +1 -1
- package/build/segmentedControl/SegmentedControl.js +6 -1
- package/build/segmentedControl/SegmentedControl.js.map +1 -1
- package/build/segmentedControl/SegmentedControl.mjs +7 -2
- package/build/segmentedControl/SegmentedControl.mjs.map +1 -1
- package/build/styles/css/neptune.css +58 -53
- package/build/styles/less/neptune-tokens.less +2 -2
- package/build/styles/main.css +58 -53
- package/build/styles/props/neptune-tokens.css +1 -1
- package/build/styles/styles/less/core/viewport-themes.css +46 -42
- package/build/styles/styles/less/neptune.css +58 -53
- package/build/tabs/Tabs.js +1 -1
- package/build/tabs/Tabs.js.map +1 -1
- package/build/tabs/Tabs.mjs +1 -1
- package/build/tabs/Tabs.mjs.map +1 -1
- package/build/tooltip/Tooltip.js +6 -3
- package/build/tooltip/Tooltip.js.map +1 -1
- package/build/tooltip/Tooltip.mjs +6 -3
- package/build/tooltip/Tooltip.mjs.map +1 -1
- package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
- package/build/types/common/hooks/useHasIntersected/useHasIntersected.d.ts.map +1 -1
- package/build/types/common/liveRegion/LiveRegion.d.ts.map +1 -1
- package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts.map +1 -1
- package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
- package/build/types/expressiveMoneyInput/amountInput/AmountInput.d.ts.map +1 -1
- package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts +2 -2
- package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
- package/build/types/expressiveMoneyInput/hooks/useSelectionRange.d.ts.map +1 -1
- package/build/types/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.d.ts.map +1 -1
- package/build/types/inputs/SelectInput/Options/SelectInputOptions.d.ts.map +1 -1
- package/build/types/inputs/SelectInput/Popover/SelectInputPopover.d.ts.map +1 -1
- package/build/types/inputs/SelectInput/SelectInput.d.ts.map +1 -1
- package/build/types/nudge/Nudge.d.ts.map +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/promoCard/PromoCardGroup.d.ts.map +1 -1
- package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
- package/build/types/tooltip/Tooltip.d.ts.map +1 -1
- package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
- package/build/uploadInput/UploadInput.js +29 -25
- package/build/uploadInput/UploadInput.js.map +1 -1
- package/build/uploadInput/UploadInput.mjs +29 -25
- package/build/uploadInput/UploadInput.mjs.map +1 -1
- package/package.json +3 -3
- package/src/avatarWrapper/AvatarWrapper.test.tsx +33 -3
- package/src/avatarWrapper/AvatarWrapper.tsx +5 -6
- package/src/button/LegacyButton.tsx +1 -1
- package/src/button/_stories/Button.test.story.tsx +3 -3
- package/src/common/hooks/useContainerSize.test.tsx +1 -1
- package/src/common/hooks/useHasIntersected/useHasIntersected.ts +12 -4
- package/src/common/liveRegion/LiveRegion.tsx +5 -2
- package/src/dateInput/DateInput.tsx +10 -10
- package/src/dateLookup/DateLookup.test.story.tsx +16 -0
- package/src/dateLookup/DateLookup.tsx +1 -1
- package/src/dateLookup/monthCalendar/table/MonthCalendarTable.tsx +1 -5
- package/src/dateLookup/yearCalendar/table/YearCalendarTable.tsx +1 -1
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.tsx +1 -1
- package/src/expressiveMoneyInput/amountInput/AmountInput.tsx +22 -15
- package/src/expressiveMoneyInput/hooks/useInputStyle.ts +20 -8
- package/src/expressiveMoneyInput/hooks/useSelectionRange.ts +2 -0
- package/src/header/Header.tsx +2 -2
- package/src/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.tsx +4 -0
- package/src/inputs/SelectInput/Options/SelectInputOptions.tsx +43 -27
- package/src/inputs/SelectInput/Popover/SelectInputPopover.tsx +4 -0
- package/src/inputs/SelectInput/SelectInput.tsx +21 -15
- package/src/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.tsx +1 -1
- package/src/main.css +58 -53
- package/src/nudge/Nudge.tsx +29 -20
- package/src/phoneNumberInput/PhoneNumberInput.test.tsx +16 -0
- package/src/phoneNumberInput/PhoneNumberInput.tsx +11 -13
- package/src/promoCard/PromoCard.story.tsx +3 -3
- package/src/promoCard/PromoCardGroup.tsx +39 -21
- package/src/segmentedControl/SegmentedControl.test.tsx +25 -0
- package/src/segmentedControl/SegmentedControl.tsx +7 -1
- package/src/select/Select.story.tsx +1 -1
- package/src/styles/less/core/viewport-themes.css +46 -42
- package/src/styles/less/core/viewport-themes.less +2 -45
- package/src/styles/less/neptune.css +58 -53
- package/src/tabs/Tabs.tsx +1 -1
- package/src/tooltip/Tooltip.tsx +3 -0
- package/src/uploadInput/UploadInput.test.tsx +19 -0
- package/src/uploadInput/UploadInput.tsx +28 -24
|
@@ -42,7 +42,7 @@ export const AmountInput = ({
|
|
|
42
42
|
const intl = useIntl();
|
|
43
43
|
const { focus, setFocus, visualFocus, setVisualFocus } = useFocus();
|
|
44
44
|
|
|
45
|
-
const [value, setValue] = useState<string>(
|
|
45
|
+
const [value, setValue] = useState<string>(() =>
|
|
46
46
|
amount
|
|
47
47
|
? getFormattedString({
|
|
48
48
|
value: amount,
|
|
@@ -51,6 +51,7 @@ export const AmountInput = ({
|
|
|
51
51
|
})
|
|
52
52
|
: '',
|
|
53
53
|
);
|
|
54
|
+
const prevAmountRef = useRef<string | number | null | undefined>(amount);
|
|
54
55
|
const numericValue = useMemo(() => {
|
|
55
56
|
return getUnformattedNumber({
|
|
56
57
|
value,
|
|
@@ -85,23 +86,28 @@ export const AmountInput = ({
|
|
|
85
86
|
const decimalMode = decimalSeparator && value.includes(decimalSeparator);
|
|
86
87
|
|
|
87
88
|
useEffect(() => {
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
if (prevAmountRef.current !== amount) {
|
|
90
|
+
prevAmountRef.current = amount;
|
|
91
|
+
|
|
92
|
+
// Only update the displayed value if not focused (preserve user input when focused)
|
|
93
|
+
if (!focus) {
|
|
94
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Syncing external prop to internal state when unfocused
|
|
95
|
+
setValue(
|
|
96
|
+
amount
|
|
97
|
+
? getFormattedString({
|
|
98
|
+
value: amount,
|
|
99
|
+
currency,
|
|
100
|
+
locale: intl.locale,
|
|
101
|
+
})
|
|
102
|
+
: '',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
98
105
|
}
|
|
99
|
-
|
|
100
|
-
}, [amount]);
|
|
106
|
+
}, [amount, focus, currency, intl.locale]);
|
|
101
107
|
|
|
102
108
|
useEffect(() => {
|
|
103
109
|
onFocusChange?.(visualFocus);
|
|
104
|
-
}, [visualFocus]);
|
|
110
|
+
}, [onFocusChange, visualFocus]);
|
|
105
111
|
|
|
106
112
|
const shouldReformatAfterUserInput = (newValue: string) => {
|
|
107
113
|
// don't reformat if formatting would wipe out user's input
|
|
@@ -246,6 +252,7 @@ export const AmountInput = ({
|
|
|
246
252
|
const addonContent = useMemo((): string | null | undefined => {
|
|
247
253
|
// because we're using a separate "addon" element for the placeholder decimals, there is a possibility that the input itself will become scrollable
|
|
248
254
|
// and the decimals will appear on top of the input. Safest thing to do is to just hide the addon if there is not enough room
|
|
255
|
+
// eslint-disable-next-line react-hooks/refs -- Reading layout dimensions for overflow detection
|
|
249
256
|
if (isInputPossiblyOverflowing({ ref, value })) {
|
|
250
257
|
return null;
|
|
251
258
|
}
|
|
@@ -284,7 +291,7 @@ export const AmountInput = ({
|
|
|
284
291
|
// whenever decimals are shown, we need to account for the full decimal part for the font size calculation
|
|
285
292
|
value: addonContent ? valueWithFullDecimals : value,
|
|
286
293
|
focus: visualFocus,
|
|
287
|
-
inputElement: ref
|
|
294
|
+
inputElement: ref,
|
|
288
295
|
loading,
|
|
289
296
|
});
|
|
290
297
|
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CSSProperties,
|
|
3
|
+
type RefObject,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
3
10
|
|
|
4
11
|
type InputStyleObject = {
|
|
5
12
|
value: string;
|
|
6
13
|
focus: boolean;
|
|
7
|
-
inputElement: HTMLInputElement | null;
|
|
14
|
+
inputElement: RefObject<HTMLInputElement> | null;
|
|
8
15
|
} & Pick<ExpressiveMoneyInputProps, 'loading'>;
|
|
9
16
|
|
|
10
17
|
export const useInputStyle = ({ value, focus, inputElement, loading }: InputStyleObject) => {
|
|
11
18
|
const initialRender = !useTimeout(300);
|
|
12
|
-
const inputWidth = useFirstDefinedValue(inputElement?.clientWidth, [
|
|
19
|
+
const inputWidth = useFirstDefinedValue(inputElement?.current?.clientWidth, [
|
|
20
|
+
inputElement?.current,
|
|
21
|
+
value,
|
|
22
|
+
]);
|
|
13
23
|
|
|
14
|
-
const getStyle = (): CSSProperties => {
|
|
24
|
+
const getStyle = useCallback((): CSSProperties => {
|
|
15
25
|
const fontSize = getFontSize(value, focus, inputWidth);
|
|
16
26
|
|
|
17
27
|
return {
|
|
@@ -23,13 +33,14 @@ export const useInputStyle = ({ value, focus, inputElement, loading }: InputStyl
|
|
|
23
33
|
transition: initialRender ? 'none' : undefined,
|
|
24
34
|
color: loading ? 'var(--color-interactive-secondary)' : undefined,
|
|
25
35
|
};
|
|
26
|
-
};
|
|
36
|
+
}, [value, focus, inputWidth, loading, initialRender]);
|
|
27
37
|
|
|
28
|
-
const [style, setStyle] = useState(getStyle());
|
|
38
|
+
const [style, setStyle] = useState<CSSProperties>(() => getStyle());
|
|
29
39
|
|
|
30
40
|
useLayoutEffect(() => {
|
|
41
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Computing style based on layout measurements
|
|
31
42
|
setStyle(getStyle());
|
|
32
|
-
}, [value, focus, loading, inputWidth]);
|
|
43
|
+
}, [value, focus, loading, inputWidth, getStyle]);
|
|
33
44
|
|
|
34
45
|
return style;
|
|
35
46
|
};
|
|
@@ -60,10 +71,11 @@ const useFirstDefinedValue = (newValue: number | undefined, dependencies: unknow
|
|
|
60
71
|
|
|
61
72
|
useLayoutEffect(() => {
|
|
62
73
|
if (typeof newValue !== 'undefined' && typeof value === 'undefined') {
|
|
74
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Lazy initialization from prop
|
|
63
75
|
setValue(newValue);
|
|
64
76
|
}
|
|
65
77
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
-
}, [...dependencies, value]);
|
|
78
|
+
}, [...dependencies, value, newValue]);
|
|
67
79
|
|
|
68
80
|
return value;
|
|
69
81
|
};
|
|
@@ -15,9 +15,11 @@ export const useSelectionRange = () => {
|
|
|
15
15
|
selection.current = undefined;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/* eslint-disable react-hooks/refs */
|
|
18
19
|
return {
|
|
19
20
|
selection: selection.current,
|
|
20
21
|
handleSelect,
|
|
21
22
|
handleSelectionBlur,
|
|
22
23
|
};
|
|
24
|
+
/* eslint-enable react-hooks/refs */
|
|
23
25
|
};
|
package/src/header/Header.tsx
CHANGED
|
@@ -104,7 +104,7 @@ const Header: FunctionComponent<HeaderProps> = React.forwardRef(
|
|
|
104
104
|
useEffect(() => {
|
|
105
105
|
if (as === 'legend' && internalRef.current) {
|
|
106
106
|
const { parentElement } = internalRef.current;
|
|
107
|
-
if (
|
|
107
|
+
if (parentElement?.tagName.toLowerCase() !== 'fieldset') {
|
|
108
108
|
console.warn(
|
|
109
109
|
'Legends should be the first child in a fieldset, and this is not possible when including an action',
|
|
110
110
|
);
|
|
@@ -121,7 +121,7 @@ const Header: FunctionComponent<HeaderProps> = React.forwardRef(
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
return (
|
|
124
|
-
<div {...commonProps} {...props} ref={ref
|
|
124
|
+
<div {...commonProps} {...props} ref={ref}>
|
|
125
125
|
<Title as={as} type={levelTypography} className="np-header__title">
|
|
126
126
|
{title}
|
|
127
127
|
</Title>
|
|
@@ -65,10 +65,12 @@ export function SelectInputBottomSheet({
|
|
|
65
65
|
return (
|
|
66
66
|
<>
|
|
67
67
|
{open ? <PreventScroll /> : null}
|
|
68
|
+
{/* eslint-disable react-hooks/refs -- setReference is a callback ref from floating-ui, safe to pass during render */}
|
|
68
69
|
{renderTrigger?.({
|
|
69
70
|
ref: refs.setReference,
|
|
70
71
|
getInteractionProps: getReferenceProps,
|
|
71
72
|
})}
|
|
73
|
+
{/* eslint-enable react-hooks/refs */}
|
|
72
74
|
|
|
73
75
|
<FloatingPortal>
|
|
74
76
|
<ThemeProvider theme="personal" screenMode={theme === 'personal' ? screenMode : 'light'}>
|
|
@@ -94,6 +96,7 @@ export function SelectInputBottomSheet({
|
|
|
94
96
|
<Fragment
|
|
95
97
|
key={floatingKey} // Force inner state invalidation on open
|
|
96
98
|
>
|
|
99
|
+
{/* eslint-disable react-hooks/refs -- setFloating is a callback ref from floating-ui, safe to pass during render */}
|
|
97
100
|
<TransitionChild
|
|
98
101
|
ref={refs.setFloating}
|
|
99
102
|
as="div"
|
|
@@ -102,6 +105,7 @@ export function SelectInputBottomSheet({
|
|
|
102
105
|
leaveTo="np-bottom-sheet-v2-content--closed"
|
|
103
106
|
{...getFloatingProps()}
|
|
104
107
|
>
|
|
108
|
+
{/* eslint-enable react-hooks/refs */}
|
|
105
109
|
<div className="np-bottom-sheet-v2-header">
|
|
106
110
|
<CloseButton
|
|
107
111
|
size={Size.SMALL}
|
|
@@ -77,7 +77,7 @@ export function SelectInputOptions<T = string>({
|
|
|
77
77
|
const intl = useIntl();
|
|
78
78
|
const virtualiserHandlerRef = useRef<VirtualizerHandle>(null);
|
|
79
79
|
const controllerRef = filterable ? searchInputRef : listboxRef;
|
|
80
|
-
const
|
|
80
|
+
const initialRenderRef = useRef(true);
|
|
81
81
|
|
|
82
82
|
const needle = useMemo(() => {
|
|
83
83
|
if (filterable) {
|
|
@@ -166,28 +166,42 @@ export function SelectInputOptions<T = string>({
|
|
|
166
166
|
// Items shown once shall be kept mounted until the needle changes, otherwise
|
|
167
167
|
// the scroll position may jump around inadvertently. Pattern adopted from:
|
|
168
168
|
// https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
|
|
169
|
-
const [
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
const [virtualState, setVirtualState] = useState<{
|
|
170
|
+
needle: typeof needle;
|
|
171
|
+
length: number;
|
|
172
|
+
mountedIndexes: number[];
|
|
173
|
+
}>({
|
|
174
|
+
needle,
|
|
175
|
+
length: filteredItems.length,
|
|
176
|
+
mountedIndexes: [],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Note: virtualState.mountedIndexes is in deps but only read in the guarded branch.
|
|
180
|
+
// This means external updates to mountedIndexes will trigger this effect but hit the guard
|
|
181
|
+
// and bail out early. This is intentional and harmless - the guard ensures no unnecessary work.
|
|
172
182
|
useEffect(() => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (filteredItems.length > 0) {
|
|
185
|
-
setMountedIndexes((prevMountedIndexes) => {
|
|
186
|
-
// Create a new array with existing indexes plus the last item index
|
|
187
|
-
return [...new Set([...prevMountedIndexes, filteredItems.length - 1])]; // Sorting is redundant by nature here
|
|
183
|
+
if (virtualState.needle !== needle || virtualState.length !== filteredItems.length) {
|
|
184
|
+
const needleChanged = virtualState.needle !== needle;
|
|
185
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Syncing virtual scroll state with filtered items
|
|
186
|
+
setVirtualState({
|
|
187
|
+
needle,
|
|
188
|
+
length: filteredItems.length,
|
|
189
|
+
mountedIndexes: needleChanged
|
|
190
|
+
? [] // Reset on needle change
|
|
191
|
+
: filteredItems.length > 0
|
|
192
|
+
? [...new Set([...virtualState.mountedIndexes, filteredItems.length - 1])] // Add last index
|
|
193
|
+
: virtualState.mountedIndexes,
|
|
188
194
|
});
|
|
189
195
|
}
|
|
190
|
-
}, [
|
|
196
|
+
}, [
|
|
197
|
+
needle,
|
|
198
|
+
filteredItems.length,
|
|
199
|
+
virtualState.needle,
|
|
200
|
+
virtualState.length,
|
|
201
|
+
virtualState.mountedIndexes,
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const { mountedIndexes } = virtualState;
|
|
191
205
|
|
|
192
206
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
193
207
|
useEffect(() => {
|
|
@@ -200,7 +214,7 @@ export function SelectInputOptions<T = string>({
|
|
|
200
214
|
}, []);
|
|
201
215
|
|
|
202
216
|
useEffect(() => {
|
|
203
|
-
|
|
217
|
+
initialRenderRef.current = false;
|
|
204
218
|
}, []);
|
|
205
219
|
|
|
206
220
|
const showStatus = resultsEmpty;
|
|
@@ -251,7 +265,7 @@ export function SelectInputOptions<T = string>({
|
|
|
251
265
|
className="np-select-input-options-container"
|
|
252
266
|
onAriaActiveDescendantChange={(value: React.AriaAttributes['aria-activedescendant']) => {
|
|
253
267
|
if (controllerRef.current != null) {
|
|
254
|
-
if (!
|
|
268
|
+
if (!initialRenderRef.current && value != null) {
|
|
255
269
|
controllerRef.current.setAttribute('aria-activedescendant', value);
|
|
256
270
|
} else {
|
|
257
271
|
controllerRef.current.removeAttribute('aria-activedescendant');
|
|
@@ -288,7 +302,7 @@ export function SelectInputOptions<T = string>({
|
|
|
288
302
|
const inputValue = event.currentTarget.value;
|
|
289
303
|
|
|
290
304
|
// Free up resources and ensure not to go out of bounds
|
|
291
|
-
|
|
305
|
+
setVirtualState((prev) => ({ ...prev, mountedIndexes: [] }));
|
|
292
306
|
onFilterChange(inputValue);
|
|
293
307
|
}}
|
|
294
308
|
onInput={(event) => {
|
|
@@ -358,7 +372,7 @@ export function SelectInputOptions<T = string>({
|
|
|
358
372
|
virtualiserHandlerRef.current.viewportSize,
|
|
359
373
|
);
|
|
360
374
|
|
|
361
|
-
|
|
375
|
+
setVirtualState((prev) => {
|
|
362
376
|
// Create an array of all indexes that should be visible
|
|
363
377
|
|
|
364
378
|
const visibleIndexes = [];
|
|
@@ -368,9 +382,11 @@ export function SelectInputOptions<T = string>({
|
|
|
368
382
|
}
|
|
369
383
|
|
|
370
384
|
// Combine with previous indexes and sort
|
|
371
|
-
|
|
372
|
-
(
|
|
373
|
-
);
|
|
385
|
+
const newMountedIndexes = [
|
|
386
|
+
...new Set([...prev.mountedIndexes, ...visibleIndexes]),
|
|
387
|
+
].sort((a, b) => a - b);
|
|
388
|
+
|
|
389
|
+
return { ...prev, mountedIndexes: newMountedIndexes };
|
|
374
390
|
});
|
|
375
391
|
}}
|
|
376
392
|
>
|
|
@@ -85,10 +85,12 @@ export function SelectInputPopover({
|
|
|
85
85
|
return (
|
|
86
86
|
<>
|
|
87
87
|
{open ? <PreventScroll /> : null}
|
|
88
|
+
{/* eslint-disable react-hooks/refs -- setReference is a callback ref from floating-ui, safe to pass during render */}
|
|
88
89
|
{renderTrigger({
|
|
89
90
|
ref: refs.setReference,
|
|
90
91
|
getInteractionProps: getReferenceProps,
|
|
91
92
|
})}
|
|
93
|
+
{/* eslint-enable react-hooks/refs */}
|
|
92
94
|
|
|
93
95
|
<FloatingPortal>
|
|
94
96
|
<ThemeProvider theme="personal" screenMode={theme === 'personal' ? screenMode : 'light'}>
|
|
@@ -104,6 +106,7 @@ export function SelectInputPopover({
|
|
|
104
106
|
>
|
|
105
107
|
<FocusScope>
|
|
106
108
|
<FloatingFocusManager context={context}>
|
|
109
|
+
{/* eslint-disable react-hooks/refs -- setFloating is a callback ref from floating-ui, safe to pass during render */}
|
|
107
110
|
<div
|
|
108
111
|
key={floatingKey} // Force inner state invalidation on open
|
|
109
112
|
ref={refs.setFloating}
|
|
@@ -114,6 +117,7 @@ export function SelectInputPopover({
|
|
|
114
117
|
style={floatingStyles}
|
|
115
118
|
{...getFloatingProps()}
|
|
116
119
|
>
|
|
120
|
+
{/* eslint-enable react-hooks/refs */}
|
|
117
121
|
<div
|
|
118
122
|
className={clsx('np-popover-v2', title && 'np-popover-v2--has-title', {
|
|
119
123
|
'np-popover-v2--padding-md': padding === 'md',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mergeProps from 'merge-props';
|
|
2
|
-
import { useEffect, useRef, useState, useDeferredValue } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, useDeferredValue } from 'react';
|
|
3
3
|
import { Listbox as ListboxBase } from '@headlessui/react';
|
|
4
4
|
import { useScreenSize } from '../../common/hooks/useScreenSize';
|
|
5
5
|
import { Breakpoint } from '../../common/propsValues/breakpoint';
|
|
@@ -60,6 +60,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
60
60
|
const initialized = useRef(false);
|
|
61
61
|
const handleClose = useEffectEvent(onClose ?? (() => {}));
|
|
62
62
|
const handleOpen = useEffectEvent(onOpen ?? (() => {}));
|
|
63
|
+
|
|
63
64
|
useEffect(() => {
|
|
64
65
|
if (initialized.current) {
|
|
65
66
|
if (open) {
|
|
@@ -70,29 +71,34 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
70
71
|
} else {
|
|
71
72
|
initialized.current = true;
|
|
72
73
|
}
|
|
73
|
-
}, [
|
|
74
|
+
}, [open]);
|
|
74
75
|
|
|
75
76
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
76
77
|
const deferredFilterQuery = useDeferredValue(filterQuery);
|
|
77
|
-
const
|
|
78
|
-
_setFilterQuery(query);
|
|
79
|
-
if (query !== filterQuery) {
|
|
80
|
-
onFilterChange({
|
|
81
|
-
query,
|
|
82
|
-
queryNormalized: query ? searchableString(query) : null,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const internalTriggerRef = useRef<HTMLButtonElement | null>(null);
|
|
78
|
+
const previousFilterQueryRef = useRef(filterQuery);
|
|
88
79
|
|
|
89
|
-
const
|
|
90
|
-
|
|
80
|
+
const setFilterQuery = useCallback(
|
|
81
|
+
(query: string) => {
|
|
82
|
+
_setFilterQuery(query);
|
|
83
|
+
if (query !== previousFilterQueryRef.current) {
|
|
84
|
+
onFilterChange({
|
|
85
|
+
query,
|
|
86
|
+
queryNormalized: query ? searchableString(query) : null,
|
|
87
|
+
});
|
|
88
|
+
previousFilterQueryRef.current = query;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[onFilterChange],
|
|
92
|
+
);
|
|
91
93
|
|
|
94
|
+
const internalTriggerRef = useRef<HTMLButtonElement | null>(null);
|
|
92
95
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
93
96
|
const listboxRef = useRef<HTMLDivElement>(null);
|
|
94
97
|
const controllerRef = filterable ? searchInputRef : listboxRef;
|
|
95
98
|
|
|
99
|
+
const screenSm = useScreenSize(Breakpoint.SMALL);
|
|
100
|
+
const OptionsOverlay = screenSm ? SelectInputPopover : SelectInputBottomSheet;
|
|
101
|
+
|
|
96
102
|
/**
|
|
97
103
|
* Attempts to resolve the `listbox` label
|
|
98
104
|
* @see https://storybook.wise.design/?path=/docs/forms-selectinput-accessibility--docs#labelling
|
|
@@ -29,7 +29,7 @@ export function SelectInputTriggerButton<T extends SelectInputTriggerButtonEleme
|
|
|
29
29
|
ref={ref}
|
|
30
30
|
as={PolymorphicWithOverrides}
|
|
31
31
|
role="combobox"
|
|
32
|
-
__overrides={{ as, size, ...interactionProps }
|
|
32
|
+
__overrides={{ as, size, ...interactionProps }}
|
|
33
33
|
{...mergeProps({ onClick, onKeyDown }, restProps)}
|
|
34
34
|
/>
|
|
35
35
|
);
|
package/src/main.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Do not edit directly, this file was auto-generated.
|
|
3
|
-
* Generated on
|
|
3
|
+
* Generated on Thu, 14 May 2026 16:11:43 GMT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
:root {
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
146
|
* Do not edit directly, this file was auto-generated.
|
|
147
|
-
* Generated on
|
|
147
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
148
148
|
*/
|
|
149
149
|
|
|
150
150
|
.np-theme-personal {
|
|
@@ -328,7 +328,7 @@
|
|
|
328
328
|
|
|
329
329
|
/**
|
|
330
330
|
* Do not edit directly, this file was auto-generated.
|
|
331
|
-
* Generated on
|
|
331
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
332
332
|
*/
|
|
333
333
|
|
|
334
334
|
.np-theme-personal--forest-green {
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
|
|
513
513
|
/**
|
|
514
514
|
* Do not edit directly, this file was auto-generated.
|
|
515
|
-
* Generated on
|
|
515
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
516
516
|
*/
|
|
517
517
|
|
|
518
518
|
.np-theme-personal--bright-green {
|
|
@@ -696,7 +696,7 @@
|
|
|
696
696
|
|
|
697
697
|
/**
|
|
698
698
|
* Do not edit directly, this file was auto-generated.
|
|
699
|
-
* Generated on
|
|
699
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
700
700
|
*/
|
|
701
701
|
|
|
702
702
|
.np-theme-personal--dark {
|
|
@@ -880,7 +880,7 @@
|
|
|
880
880
|
|
|
881
881
|
/**
|
|
882
882
|
* Do not edit directly, this file was auto-generated.
|
|
883
|
-
* Generated on
|
|
883
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
884
884
|
*/
|
|
885
885
|
|
|
886
886
|
.np-theme-platform {
|
|
@@ -1064,7 +1064,7 @@
|
|
|
1064
1064
|
|
|
1065
1065
|
/**
|
|
1066
1066
|
* Do not edit directly, this file was auto-generated.
|
|
1067
|
-
* Generated on
|
|
1067
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
1068
1068
|
*/
|
|
1069
1069
|
|
|
1070
1070
|
.np-theme-platform--forest-green {
|
|
@@ -1248,7 +1248,7 @@
|
|
|
1248
1248
|
|
|
1249
1249
|
/**
|
|
1250
1250
|
* Do not edit directly, this file was auto-generated.
|
|
1251
|
-
* Generated on
|
|
1251
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
1252
1252
|
*/
|
|
1253
1253
|
|
|
1254
1254
|
.np-theme-business {
|
|
@@ -1433,7 +1433,7 @@
|
|
|
1433
1433
|
|
|
1434
1434
|
/**
|
|
1435
1435
|
* Do not edit directly, this file was auto-generated.
|
|
1436
|
-
* Generated on
|
|
1436
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
1437
1437
|
*/
|
|
1438
1438
|
|
|
1439
1439
|
.np-theme-business--dark {
|
|
@@ -1618,7 +1618,7 @@
|
|
|
1618
1618
|
|
|
1619
1619
|
/**
|
|
1620
1620
|
* Do not edit directly, this file was auto-generated.
|
|
1621
|
-
* Generated on
|
|
1621
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
1622
1622
|
*/
|
|
1623
1623
|
|
|
1624
1624
|
.np-theme-business--forest-green {
|
|
@@ -1803,7 +1803,7 @@
|
|
|
1803
1803
|
|
|
1804
1804
|
/**
|
|
1805
1805
|
* Do not edit directly, this file was auto-generated.
|
|
1806
|
-
* Generated on
|
|
1806
|
+
* Generated on Thu, 14 May 2026 16:11:44 GMT
|
|
1807
1807
|
*/
|
|
1808
1808
|
|
|
1809
1809
|
.np-theme-business--bright-green {
|
|
@@ -4351,48 +4351,53 @@ a.text-inverse:focus {
|
|
|
4351
4351
|
|
|
4352
4352
|
@media (max-width: 320px) {
|
|
4353
4353
|
.np-theme-personal {
|
|
4354
|
-
--
|
|
4355
|
-
--
|
|
4356
|
-
--
|
|
4357
|
-
--
|
|
4358
|
-
--size-
|
|
4359
|
-
--size-
|
|
4360
|
-
--size-
|
|
4361
|
-
--size-
|
|
4362
|
-
--size-
|
|
4363
|
-
--size-
|
|
4364
|
-
--size-
|
|
4365
|
-
--size-
|
|
4366
|
-
--size-
|
|
4367
|
-
--size-
|
|
4368
|
-
--size-
|
|
4369
|
-
--size-
|
|
4370
|
-
--size-
|
|
4371
|
-
--size-
|
|
4372
|
-
--size-
|
|
4373
|
-
--size-
|
|
4374
|
-
--size-
|
|
4375
|
-
--size-
|
|
4376
|
-
--size-
|
|
4377
|
-
--size-
|
|
4378
|
-
--size-
|
|
4379
|
-
--size-
|
|
4380
|
-
--size-
|
|
4381
|
-
--size-
|
|
4382
|
-
--size-
|
|
4383
|
-
--size-
|
|
4384
|
-
--size-
|
|
4385
|
-
--size-x-
|
|
4386
|
-
--size-
|
|
4387
|
-
--
|
|
4388
|
-
--
|
|
4389
|
-
--
|
|
4390
|
-
--
|
|
4391
|
-
--space-
|
|
4392
|
-
--
|
|
4393
|
-
--
|
|
4394
|
-
--
|
|
4395
|
-
--
|
|
4354
|
+
--padding-x-small: 4px;
|
|
4355
|
+
--padding-small: 8px;
|
|
4356
|
+
--padding-medium: 12px;
|
|
4357
|
+
--padding-large: 16px;
|
|
4358
|
+
--size-4: 2px;
|
|
4359
|
+
--size-5: 2.5px;
|
|
4360
|
+
--size-8: 4px;
|
|
4361
|
+
--size-10: 5px;
|
|
4362
|
+
--size-12: 6px;
|
|
4363
|
+
--size-14: 7px;
|
|
4364
|
+
--size-16: 8px;
|
|
4365
|
+
--size-24: 12px;
|
|
4366
|
+
--size-32: 16px;
|
|
4367
|
+
--size-40: 20px;
|
|
4368
|
+
--size-48: 24px;
|
|
4369
|
+
--size-52: 26px;
|
|
4370
|
+
--size-56: 28px;
|
|
4371
|
+
--size-60: 30px;
|
|
4372
|
+
--size-64: 32px;
|
|
4373
|
+
--size-72: 36px;
|
|
4374
|
+
--size-80: 40px;
|
|
4375
|
+
--size-88: 44px;
|
|
4376
|
+
--size-96: 48px;
|
|
4377
|
+
--size-104: 52px;
|
|
4378
|
+
--size-112: 56px;
|
|
4379
|
+
--size-120: 60px;
|
|
4380
|
+
--size-126: 63px;
|
|
4381
|
+
--size-128: 64px;
|
|
4382
|
+
--size-146: 73px;
|
|
4383
|
+
--size-154: 77px;
|
|
4384
|
+
--size-160: 80px;
|
|
4385
|
+
--size-x-small: 12px;
|
|
4386
|
+
--size-small: 16px;
|
|
4387
|
+
--size-medium: 20px;
|
|
4388
|
+
--size-large: 24px;
|
|
4389
|
+
--size-x-large: 28px;
|
|
4390
|
+
--size-2x-large: 36px;
|
|
4391
|
+
--space-content-horizontal: 8px;
|
|
4392
|
+
--space-small: 8px;
|
|
4393
|
+
--space-medium: 16px;
|
|
4394
|
+
--space-large: 20px;
|
|
4395
|
+
--space-x-large: 28px;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
@media (max-width: 320px) {
|
|
4400
|
+
.np-theme-personal {
|
|
4396
4401
|
--input-height-base: var(--size-32);
|
|
4397
4402
|
--input-height-large: var(--input-height-small);
|
|
4398
4403
|
--input-padding: var(--input-padding-small);
|