@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
package/src/nudge/Nudge.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Illustration, Assets, type IllustrationNames } from '@wise/art';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { ReactNode, useEffect, useState, MouseEvent } from 'react';
|
|
3
|
+
import { ReactNode, useEffect, useState, MouseEvent, useCallback } from 'react';
|
|
4
4
|
|
|
5
5
|
import Body from '../body';
|
|
6
6
|
import { Typography } from '../common';
|
|
@@ -96,8 +96,27 @@ const Nudge = ({
|
|
|
96
96
|
action,
|
|
97
97
|
}: Props) => {
|
|
98
98
|
const intl = useIntl();
|
|
99
|
-
const
|
|
100
|
-
|
|
99
|
+
const getIsDismissed = useCallback(
|
|
100
|
+
() => (persistDismissal && id ? !!getLocalStorage()?.find((item) => item === id) : false),
|
|
101
|
+
[persistDismissal, id],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const [nudgeState, setNudgeState] = useState(() => ({
|
|
105
|
+
isDismissed: getIsDismissed(),
|
|
106
|
+
isMounted: false,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Setting mount state in mount effect
|
|
111
|
+
setNudgeState((prev) => ({ ...prev, isMounted: true }));
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Syncing dismissed state from localStorage on prop change
|
|
116
|
+
setNudgeState((prev) => ({ ...prev, isDismissed: getIsDismissed() }));
|
|
117
|
+
}, [getIsDismissed, id, persistDismissal]);
|
|
118
|
+
|
|
119
|
+
const { isDismissed } = nudgeState;
|
|
101
120
|
|
|
102
121
|
const handleOnDismiss = () => {
|
|
103
122
|
const dismissedNudgesStorage = getLocalStorage();
|
|
@@ -105,9 +124,9 @@ const Nudge = ({
|
|
|
105
124
|
if (persistDismissal && id) {
|
|
106
125
|
try {
|
|
107
126
|
localStorage.setItem(STORAGE_NAME, JSON.stringify([...dismissedNudgesStorage, id]));
|
|
108
|
-
} catch
|
|
127
|
+
} catch {}
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
setNudgeState((prev) => ({ ...prev, isDismissed: true }));
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
if (onDismiss) {
|
|
@@ -116,25 +135,15 @@ const Nudge = ({
|
|
|
116
135
|
};
|
|
117
136
|
|
|
118
137
|
useEffect(() => {
|
|
119
|
-
if (persistDismissal && id) {
|
|
138
|
+
if (persistDismissal && id && isPreviouslyDismissed) {
|
|
120
139
|
const dismissedNudgesStorage = getLocalStorage();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (dismissedNudgesStorage?.find((item) => item === id)) {
|
|
124
|
-
setIsDismissed(true);
|
|
125
|
-
isDismissed = true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (isPreviouslyDismissed) {
|
|
129
|
-
isPreviouslyDismissed(isDismissed);
|
|
130
|
-
}
|
|
140
|
+
const wasDismissed = !!dismissedNudgesStorage?.find((item) => item === id);
|
|
141
|
+
isPreviouslyDismissed(wasDismissed);
|
|
131
142
|
}
|
|
132
|
-
|
|
133
|
-
setIsMounted(true);
|
|
134
143
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
135
144
|
}, [id, persistDismissal]);
|
|
136
145
|
|
|
137
|
-
if (persistDismissal && (isDismissed || !isMounted)) {
|
|
146
|
+
if (persistDismissal && (isDismissed || !nudgeState.isMounted)) {
|
|
138
147
|
return null;
|
|
139
148
|
}
|
|
140
149
|
|
|
@@ -143,7 +152,7 @@ const Nudge = ({
|
|
|
143
152
|
{!!mediaName && (
|
|
144
153
|
<div className="wds-nudge-media">
|
|
145
154
|
<Illustration
|
|
146
|
-
name={mediaName
|
|
155
|
+
name={mediaName}
|
|
147
156
|
className={clsx(`wds-nudge-media-${mediaName}`)}
|
|
148
157
|
size="small"
|
|
149
158
|
disablePadding
|
|
@@ -283,6 +283,22 @@ describe('PhoneNumberInput', () => {
|
|
|
283
283
|
expect(props.onChange).toHaveBeenCalledWith('+201111111', '+20');
|
|
284
284
|
});
|
|
285
285
|
});
|
|
286
|
+
|
|
287
|
+
describe('onChange deduplication', () => {
|
|
288
|
+
it('should not call onChange when the composed phone number has not changed', () => {
|
|
289
|
+
const onChangeMock = jest.fn();
|
|
290
|
+
const { rerender } = render(
|
|
291
|
+
<PhoneNumberInput initialValue="+441234567890" onChange={onChangeMock} />,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
onChangeMock.mockClear();
|
|
295
|
+
|
|
296
|
+
// Rerender with the same initialValue - internal state should not trigger onChange
|
|
297
|
+
rerender(<PhoneNumberInput initialValue="+441234567890" onChange={onChangeMock} />);
|
|
298
|
+
|
|
299
|
+
expect(onChangeMock).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
286
302
|
});
|
|
287
303
|
|
|
288
304
|
describe('when selectProps is supplied', () => {
|
|
@@ -75,12 +75,13 @@ const PhoneNumberInput = ({
|
|
|
75
75
|
|
|
76
76
|
const { locale, formatMessage } = useIntl();
|
|
77
77
|
|
|
78
|
+
const [randomId] = useState(() => Math.random().toString(36).slice(2, 8));
|
|
79
|
+
|
|
78
80
|
const createId = (customID: string | undefined, backup: string): string => {
|
|
79
81
|
if (customID) {
|
|
80
82
|
return customID + (backup ? `-${backup}` : '');
|
|
81
83
|
}
|
|
82
|
-
|
|
83
|
-
return `${backup}-${random}`;
|
|
84
|
+
return `${backup}-${randomId}`;
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
// Link the first non-disabled input to the the Field label, if present
|
|
@@ -107,14 +108,16 @@ const PhoneNumberInput = ({
|
|
|
107
108
|
|
|
108
109
|
return explodeNumberModel(cleanValue);
|
|
109
110
|
});
|
|
110
|
-
const
|
|
111
|
+
const broadcastedValueRef = useRef<PhoneNumber>(internalValue);
|
|
111
112
|
|
|
112
113
|
const [suffixDirty, setSuffixDirty] = useState(false);
|
|
114
|
+
|
|
113
115
|
useEffect(() => {
|
|
114
|
-
if (internalValue.suffix) {
|
|
116
|
+
if (!suffixDirty && internalValue.suffix) {
|
|
117
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Tracking when suffix becomes dirty
|
|
115
118
|
setSuffixDirty(true);
|
|
116
119
|
}
|
|
117
|
-
}, [internalValue.suffix]);
|
|
120
|
+
}, [internalValue.suffix, suffixDirty]);
|
|
118
121
|
|
|
119
122
|
const countriesByPrefix = useMemo(
|
|
120
123
|
() =>
|
|
@@ -152,13 +155,8 @@ const PhoneNumberInput = ({
|
|
|
152
155
|
};
|
|
153
156
|
|
|
154
157
|
useEffect(() => {
|
|
155
|
-
if (broadcastedValue === null) {
|
|
156
|
-
setBroadcastedValue(internalValue);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
158
|
const internalPhoneNumber = `${internalValue.prefix ?? ''}${internalValue.suffix}`;
|
|
161
|
-
const broadcastedPhoneNumber = `${
|
|
159
|
+
const broadcastedPhoneNumber = `${broadcastedValueRef.current.prefix ?? ''}${broadcastedValueRef.current.suffix}`;
|
|
162
160
|
|
|
163
161
|
if (internalPhoneNumber === broadcastedPhoneNumber) {
|
|
164
162
|
return;
|
|
@@ -172,8 +170,8 @@ const PhoneNumberInput = ({
|
|
|
172
170
|
newValue,
|
|
173
171
|
internalValue.prefix ?? '', // TODO: Allow `null` in public API
|
|
174
172
|
);
|
|
175
|
-
|
|
176
|
-
}, [onChange,
|
|
173
|
+
broadcastedValueRef.current = internalValue;
|
|
174
|
+
}, [onChange, internalValue]);
|
|
177
175
|
|
|
178
176
|
useEffect(() => {
|
|
179
177
|
const labelRef = fieldLabelRef?.current;
|
|
@@ -47,7 +47,7 @@ export const TaskCard: Story = {
|
|
|
47
47
|
isSmall: true,
|
|
48
48
|
useDisplayFont: false,
|
|
49
49
|
className: 'taskCard',
|
|
50
|
-
}
|
|
50
|
+
},
|
|
51
51
|
decorators: [
|
|
52
52
|
(Story) => (
|
|
53
53
|
<div>
|
|
@@ -90,7 +90,7 @@ export const TaskCardWithCustomIcon: Story = {
|
|
|
90
90
|
args: {
|
|
91
91
|
...TaskCard.args,
|
|
92
92
|
indicatorIcon: <StarFill size={24} aria-hidden="true" />,
|
|
93
|
-
}
|
|
93
|
+
},
|
|
94
94
|
decorators: TaskCard.decorators,
|
|
95
95
|
};
|
|
96
96
|
|
|
@@ -101,7 +101,7 @@ export const TaskCardCompleted: Story = {
|
|
|
101
101
|
href: undefined,
|
|
102
102
|
indicatorIcon: 'check',
|
|
103
103
|
className: 'taskCard taskCard--completed np-theme--personal np-theme-personal--forest-green',
|
|
104
|
-
}
|
|
104
|
+
},
|
|
105
105
|
decorators: TaskCard.decorators,
|
|
106
106
|
};
|
|
107
107
|
|
|
@@ -67,8 +67,45 @@ const PromoCardGroup: FunctionComponent<PromoCardGroupProps> = ({
|
|
|
67
67
|
onChange = () => {},
|
|
68
68
|
testId,
|
|
69
69
|
}) => {
|
|
70
|
-
const [
|
|
71
|
-
|
|
70
|
+
const [promoCardState, setPromoCardState] = useState<{
|
|
71
|
+
defaultChecked: string;
|
|
72
|
+
state: string;
|
|
73
|
+
}>({
|
|
74
|
+
defaultChecked,
|
|
75
|
+
state: defaultChecked,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (promoCardState.defaultChecked !== defaultChecked) {
|
|
80
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Syncing defaultChecked prop to internal state
|
|
81
|
+
setPromoCardState({
|
|
82
|
+
defaultChecked,
|
|
83
|
+
state: defaultChecked,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}, [defaultChecked, promoCardState.defaultChecked]);
|
|
87
|
+
|
|
88
|
+
const { state } = promoCardState;
|
|
89
|
+
const setState = (newState: string) =>
|
|
90
|
+
setPromoCardState((prev) => ({ ...prev, state: newState }));
|
|
91
|
+
|
|
92
|
+
// Derive container role from children
|
|
93
|
+
const containerRole = useMemo(() => {
|
|
94
|
+
// Collect an array of types from the children PromoCard components
|
|
95
|
+
const types =
|
|
96
|
+
React.Children.map(children, (child) => {
|
|
97
|
+
if (React.isValidElement<PromoCardProps>(child) && child.props.type) {
|
|
98
|
+
return child.props.type;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
})?.filter((type): type is 'radio' | 'checkbox' => type !== null && type !== undefined) ?? [];
|
|
102
|
+
|
|
103
|
+
// Check if all types are the same
|
|
104
|
+
const allTypesAreTheSame = types.every((type) => type === types[0]);
|
|
105
|
+
|
|
106
|
+
// If all types are the same and the type is 'radio', return 'radiogroup'
|
|
107
|
+
return allTypesAreTheSame && types[0] === 'radio' ? 'radiogroup' : null;
|
|
108
|
+
}, [children]);
|
|
72
109
|
|
|
73
110
|
/**
|
|
74
111
|
* The context value for the PromoCardGroup.
|
|
@@ -103,25 +140,6 @@ const PromoCardGroup: FunctionComponent<PromoCardGroupProps> = ({
|
|
|
103
140
|
role: containerRole as AriaRoleRadioGroup | undefined, // Add the role attribute here
|
|
104
141
|
};
|
|
105
142
|
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
setState(defaultChecked);
|
|
108
|
-
|
|
109
|
-
// Collect an array of types from the children PromoCard components
|
|
110
|
-
const types =
|
|
111
|
-
React.Children.map(children, (child) => {
|
|
112
|
-
if (React.isValidElement<PromoCardProps>(child) && child.props.type) {
|
|
113
|
-
return child.props.type;
|
|
114
|
-
}
|
|
115
|
-
return null;
|
|
116
|
-
})?.filter((type): type is 'radio' | 'checkbox' => type !== null && type !== undefined) ?? [];
|
|
117
|
-
|
|
118
|
-
// Check if all types are the same
|
|
119
|
-
const allTypesAreTheSame = types.every((type) => type === types[0]);
|
|
120
|
-
|
|
121
|
-
// If all types are the same and the type is 'radio', set the container role
|
|
122
|
-
setContainerRole(allTypesAreTheSame && types[0] === 'radio' ? 'radiogroup' : null);
|
|
123
|
-
}, [defaultChecked, children]);
|
|
124
|
-
|
|
125
143
|
return (
|
|
126
144
|
<PromoCardContext.Provider value={contextValue}>
|
|
127
145
|
<div {...commonProps}>{children}</div>
|
|
@@ -188,4 +188,29 @@ describe('SegmentedControl', () => {
|
|
|
188
188
|
'SegmentedControl only supports up to 3 segments. Please refer to: https://wise.design/components/segmented-control',
|
|
189
189
|
);
|
|
190
190
|
});
|
|
191
|
+
|
|
192
|
+
describe('animation behavior', () => {
|
|
193
|
+
it('skips animation on initial render', () => {
|
|
194
|
+
renderSegmentedControl();
|
|
195
|
+
|
|
196
|
+
// On initial render, the component should render without triggering animation
|
|
197
|
+
// The isMountedRef ensures animation is skipped on first render
|
|
198
|
+
const segmentedControl = screen.getByTestId('segmented-control');
|
|
199
|
+
|
|
200
|
+
// Verify the component renders successfully
|
|
201
|
+
expect(segmentedControl).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('enables animation after value change', async () => {
|
|
205
|
+
renderSegmentedControl();
|
|
206
|
+
|
|
207
|
+
// Change value by clicking on a different segment
|
|
208
|
+
const payroll = screen.getByRole('radio', { name: 'Payroll' });
|
|
209
|
+
await userEvent.click(payroll);
|
|
210
|
+
|
|
211
|
+
// After a value change, the component should have completed at least one update cycle
|
|
212
|
+
// verifying that the isMountedRef tracking works correctly
|
|
213
|
+
expect(onChange).toHaveBeenCalledWith('payroll');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
191
216
|
});
|
|
@@ -38,6 +38,7 @@ const SegmentedControl = ({
|
|
|
38
38
|
segments,
|
|
39
39
|
onChange,
|
|
40
40
|
}: SegmentedControlProps) => {
|
|
41
|
+
const isMountedRef = useRef(false);
|
|
41
42
|
const [animate, setAnimate] = useState(false);
|
|
42
43
|
|
|
43
44
|
const segmentsRef = useRef<HTMLDivElement>(null);
|
|
@@ -67,7 +68,12 @@ const SegmentedControl = ({
|
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
useEffect(() => {
|
|
70
|
-
|
|
71
|
+
if (isMountedRef.current) {
|
|
72
|
+
setAnimate(true);
|
|
73
|
+
} else {
|
|
74
|
+
isMountedRef.current = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
updateSegmentPosition();
|
|
72
78
|
|
|
73
79
|
const handleWindowSizeChange = () => {
|
|
@@ -43,7 +43,7 @@ const ImageIcon = () => (
|
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
const isSelectOptionItem = (option: SelectItem | null): option is SelectOptionItem => {
|
|
46
|
-
return
|
|
46
|
+
return typeof option?.value !== 'undefined';
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export const Basic: Story = {
|
|
@@ -1,47 +1,51 @@
|
|
|
1
1
|
@media (max-width: 320px) {
|
|
2
2
|
.np-theme-personal {
|
|
3
|
-
--
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--size-
|
|
8
|
-
--size-
|
|
9
|
-
--size-
|
|
10
|
-
--size-
|
|
11
|
-
--size-
|
|
12
|
-
--size-
|
|
13
|
-
--size-
|
|
14
|
-
--size-
|
|
15
|
-
--size-
|
|
16
|
-
--size-
|
|
17
|
-
--size-
|
|
18
|
-
--size-
|
|
19
|
-
--size-
|
|
20
|
-
--size-
|
|
21
|
-
--size-
|
|
22
|
-
--size-
|
|
23
|
-
--size-
|
|
24
|
-
--size-
|
|
25
|
-
--size-
|
|
26
|
-
--size-
|
|
27
|
-
--size-
|
|
28
|
-
--size-
|
|
29
|
-
--size-
|
|
30
|
-
--size-
|
|
31
|
-
--size-
|
|
32
|
-
--size-
|
|
33
|
-
--size-
|
|
34
|
-
--size-x-
|
|
35
|
-
--size-
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--space-
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--
|
|
3
|
+
--padding-x-small: 4px;
|
|
4
|
+
--padding-small: 8px;
|
|
5
|
+
--padding-medium: 12px;
|
|
6
|
+
--padding-large: 16px;
|
|
7
|
+
--size-4: 2px;
|
|
8
|
+
--size-5: 2.5px;
|
|
9
|
+
--size-8: 4px;
|
|
10
|
+
--size-10: 5px;
|
|
11
|
+
--size-12: 6px;
|
|
12
|
+
--size-14: 7px;
|
|
13
|
+
--size-16: 8px;
|
|
14
|
+
--size-24: 12px;
|
|
15
|
+
--size-32: 16px;
|
|
16
|
+
--size-40: 20px;
|
|
17
|
+
--size-48: 24px;
|
|
18
|
+
--size-52: 26px;
|
|
19
|
+
--size-56: 28px;
|
|
20
|
+
--size-60: 30px;
|
|
21
|
+
--size-64: 32px;
|
|
22
|
+
--size-72: 36px;
|
|
23
|
+
--size-80: 40px;
|
|
24
|
+
--size-88: 44px;
|
|
25
|
+
--size-96: 48px;
|
|
26
|
+
--size-104: 52px;
|
|
27
|
+
--size-112: 56px;
|
|
28
|
+
--size-120: 60px;
|
|
29
|
+
--size-126: 63px;
|
|
30
|
+
--size-128: 64px;
|
|
31
|
+
--size-146: 73px;
|
|
32
|
+
--size-154: 77px;
|
|
33
|
+
--size-160: 80px;
|
|
34
|
+
--size-x-small: 12px;
|
|
35
|
+
--size-small: 16px;
|
|
36
|
+
--size-medium: 20px;
|
|
37
|
+
--size-large: 24px;
|
|
38
|
+
--size-x-large: 28px;
|
|
39
|
+
--size-2x-large: 36px;
|
|
40
|
+
--space-content-horizontal: 8px;
|
|
41
|
+
--space-small: 8px;
|
|
42
|
+
--space-medium: 16px;
|
|
43
|
+
--space-large: 20px;
|
|
44
|
+
--space-x-large: 28px;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
@media (max-width: 320px) {
|
|
48
|
+
.np-theme-personal {
|
|
45
49
|
--input-height-base: var(--size-32);
|
|
46
50
|
--input-height-large: var(--input-height-small);
|
|
47
51
|
--input-padding: var(--input-padding-small);
|
|
@@ -1,51 +1,8 @@
|
|
|
1
|
-
@import
|
|
1
|
+
@import "@transferwise/neptune-tokens/themes/personal/tokens.less";
|
|
2
|
+
@import "@transferwise/neptune-tokens/tiny-viewport.less";
|
|
2
3
|
|
|
3
4
|
@media (--screen-400-zoom) {
|
|
4
5
|
.np-theme-personal {
|
|
5
|
-
--delta: 2;
|
|
6
|
-
|
|
7
|
-
--size-4: calc(@size-4 / var(--delta));
|
|
8
|
-
--size-5: calc(@size-5 / var(--delta));
|
|
9
|
-
--size-8: calc(@size-8 / var(--delta));
|
|
10
|
-
--size-10: calc(@size-10 / var(--delta));
|
|
11
|
-
--size-12: calc(@size-12 / var(--delta));
|
|
12
|
-
--size-14: calc(@size-14 / var(--delta));
|
|
13
|
-
--size-16: calc(@size-16 / var(--delta));
|
|
14
|
-
--size-24: calc(@size-24 / var(--delta));
|
|
15
|
-
--size-32: calc(@size-32 / var(--delta));
|
|
16
|
-
--size-40: calc(@size-40 / var(--delta));
|
|
17
|
-
--size-48: calc(@size-48 / var(--delta));
|
|
18
|
-
--size-52: calc(@size-52 / var(--delta));
|
|
19
|
-
--size-56: calc(@size-56 / var(--delta));
|
|
20
|
-
--size-60: calc(@size-60 / var(--delta));
|
|
21
|
-
--size-64: calc(@size-64 / var(--delta));
|
|
22
|
-
--size-72: calc(@size-72 / var(--delta));
|
|
23
|
-
--size-80: calc(@size-80 / var(--delta));
|
|
24
|
-
--size-88: calc(@size-88 / var(--delta));
|
|
25
|
-
--size-96: calc(@size-96 / var(--delta));
|
|
26
|
-
--size-104: calc(@size-104 / var(--delta));
|
|
27
|
-
--size-112: calc(@size-112 / var(--delta));
|
|
28
|
-
--size-120: calc(@size-120 / var(--delta));
|
|
29
|
-
--size-126: calc(@size-126 / var(--delta));
|
|
30
|
-
--size-128: calc(@size-128 / var(--delta));
|
|
31
|
-
--size-146: calc(@size-146 / var(--delta));
|
|
32
|
-
--size-154: calc(@size-154 / var(--delta));
|
|
33
|
-
--size-x-small: calc(@size-x-small / var(--delta));
|
|
34
|
-
--size-small: calc(@size-small / var(--delta));
|
|
35
|
-
--size-medium: calc(@size-medium / var(--delta));
|
|
36
|
-
--size-large: calc(@size-large / var(--delta));
|
|
37
|
-
--size-x-large: calc(@size-x-large / var(--delta));
|
|
38
|
-
--size-2x-large: calc(@size-2x-large / var(--delta));
|
|
39
|
-
--space-content-horizontal: calc(@space-content-horizontal / var(--delta));
|
|
40
|
-
--space-small: calc(@space-small / var(--delta));
|
|
41
|
-
--space-medium: calc(@space-medium / var(--delta));
|
|
42
|
-
--space-large: calc(@space-large / var(--delta));
|
|
43
|
-
--space-x-large: calc(@space-x-large / var(--delta));
|
|
44
|
-
--padding-x-small: var(--size-8);
|
|
45
|
-
--padding-small: var(--size-16);
|
|
46
|
-
--padding-medium: var(--size-24);
|
|
47
|
-
--padding-large: var(--size-32);
|
|
48
|
-
|
|
49
6
|
--input-height-base: var(--size-32);
|
|
50
7
|
--input-height-large: var(--input-height-small);
|
|
51
8
|
--input-padding: var(--input-padding-small);
|