@transferwise/components 46.140.1 → 46.142.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/avatarLayout/AvatarLayout.js +15 -1
- package/build/avatarLayout/AvatarLayout.js.map +1 -1
- package/build/avatarLayout/AvatarLayout.mjs +15 -1
- package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/avatarView/AvatarView.js +6 -2
- package/build/avatarView/AvatarView.js.map +1 -1
- package/build/avatarView/AvatarView.mjs +6 -2
- package/build/avatarView/AvatarView.mjs.map +1 -1
- package/build/avatarView/Dot.js +8 -0
- package/build/avatarView/Dot.js.map +1 -1
- package/build/avatarView/Dot.mjs +8 -0
- package/build/avatarView/Dot.mjs.map +1 -1
- 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/circle/Circle.js +6 -2
- package/build/common/circle/Circle.js.map +1 -1
- package/build/common/circle/Circle.mjs +6 -2
- package/build/common/circle/Circle.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/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 +18 -12
- package/build/expressiveMoneyInput/amountInput/AmountInput.js.map +1 -1
- package/build/expressiveMoneyInput/amountInput/AmountInput.mjs +19 -13
- 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/field/Field.js +63 -32
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.messages.js +14 -0
- package/build/field/Field.messages.js.map +1 -0
- package/build/field/Field.messages.mjs +10 -0
- package/build/field/Field.messages.mjs.map +1 -0
- package/build/field/Field.mjs +65 -34
- package/build/field/Field.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/i18n/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.json.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/inputs/TextArea.js +5 -0
- package/build/inputs/TextArea.js.map +1 -1
- package/build/inputs/TextArea.mjs +6 -1
- package/build/inputs/TextArea.mjs.map +1 -1
- package/build/inputs/contexts.js +16 -0
- package/build/inputs/contexts.js.map +1 -1
- package/build/inputs/contexts.mjs +16 -2
- package/build/inputs/contexts.mjs.map +1 -1
- package/build/main.css +42 -8
- 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/avatarView/AvatarView.css +4 -4
- package/build/styles/avatarView/Dot.css +4 -4
- package/build/styles/css/neptune.css +15 -1
- package/build/styles/expressiveMoneyInput/ExpressiveMoneyInput.css +2 -0
- package/build/styles/expressiveMoneyInput/amountInput/AmountInput.css +2 -0
- package/build/styles/field/Field.css +19 -3
- package/build/styles/main.css +42 -8
- package/build/styles/styles/less/neptune.css +15 -1
- 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/avatarView/AvatarView.d.ts +1 -1
- package/build/types/avatarView/AvatarView.d.ts.map +1 -1
- package/build/types/avatarView/Dot.d.ts.map +1 -1
- package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
- package/build/types/common/circle/Circle.d.ts +1 -1
- package/build/types/common/circle/Circle.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/field/Field.d.ts.map +1 -1
- package/build/types/field/Field.messages.d.ts +8 -0
- package/build/types/field/Field.messages.d.ts.map +1 -0
- 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/inputs/TextArea.d.ts.map +1 -1
- package/build/types/inputs/contexts.d.ts +6 -0
- package/build/types/inputs/contexts.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/test-utils/index.d.ts +2 -0
- package/build/types/test-utils/index.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/avatarLayout/AvatarLayout.story.tsx +1 -1
- package/src/avatarLayout/AvatarLayout.tsx +4 -0
- package/src/avatarView/AvatarView.css +4 -4
- package/src/avatarView/AvatarView.story.tsx +17 -13
- package/src/avatarView/AvatarView.tsx +5 -1
- package/src/avatarView/Dot.css +4 -4
- package/src/avatarView/Dot.less +6 -6
- package/src/avatarView/Dot.tsx +2 -0
- 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/circle/Circle.tsx +5 -1
- 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/monthCalendar/table/MonthCalendarTable.tsx +1 -5
- package/src/dateLookup/yearCalendar/table/YearCalendarTable.tsx +1 -1
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.css +2 -0
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.test.story.tsx +43 -0
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.tsx +1 -1
- package/src/expressiveMoneyInput/amountInput/AmountInput.css +2 -0
- package/src/expressiveMoneyInput/amountInput/AmountInput.less +2 -0
- package/src/expressiveMoneyInput/amountInput/AmountInput.tsx +23 -16
- package/src/expressiveMoneyInput/hooks/useInputStyle.ts +20 -8
- package/src/expressiveMoneyInput/hooks/useSelectionRange.ts +2 -0
- package/src/field/Field.css +19 -3
- package/src/field/Field.less +17 -3
- package/src/field/Field.messages.ts +8 -0
- package/src/field/Field.story.tsx +5 -1
- package/src/field/Field.test.tsx +90 -0
- package/src/field/Field.tsx +84 -37
- package/src/header/Header.tsx +2 -2
- package/src/i18n/en.json +1 -0
- 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/inputs/TextArea.story.tsx +97 -0
- package/src/inputs/TextArea.test.story.tsx +142 -0
- package/src/inputs/TextArea.tsx +7 -2
- package/src/inputs/contexts.tsx +18 -1
- package/src/main.css +42 -8
- 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/_typography.less +28 -6
- package/src/styles/less/neptune.css +15 -1
- package/src/tabs/Tabs.tsx +1 -1
- package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +1 -0
- package/src/tooltip/Tooltip.tsx +3 -0
- package/src/uploadInput/UploadInput.test.tsx +19 -0
- package/src/uploadInput/UploadInput.tsx +28 -24
package/src/inputs/TextArea.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { forwardRef } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { Merge } from '../utils';
|
|
5
5
|
import { inputClassNameBase } from './_common';
|
|
6
|
-
import { useInputAttributes } from './contexts';
|
|
6
|
+
import { useTextareaCharacterCount, useInputAttributes } from './contexts';
|
|
7
7
|
|
|
8
8
|
export interface TextAreaProps extends Merge<
|
|
9
9
|
React.ComponentPropsWithRef<'textarea'>,
|
|
@@ -13,15 +13,20 @@ export interface TextAreaProps extends Merge<
|
|
|
13
13
|
> {}
|
|
14
14
|
|
|
15
15
|
export const TextArea = forwardRef(function TextArea(
|
|
16
|
-
{ className, ...restProps }: TextAreaProps,
|
|
16
|
+
{ className, maxLength, ...restProps }: TextAreaProps,
|
|
17
17
|
reference: React.ForwardedRef<HTMLTextAreaElement | null>,
|
|
18
18
|
) {
|
|
19
19
|
const inputAttributes = useInputAttributes();
|
|
20
|
+
const value = restProps.value ?? restProps.defaultValue ?? '';
|
|
21
|
+
const currentLength = typeof value === 'string' ? value.length : String(value).length;
|
|
22
|
+
|
|
23
|
+
useTextareaCharacterCount(currentLength, maxLength);
|
|
20
24
|
|
|
21
25
|
return (
|
|
22
26
|
<textarea
|
|
23
27
|
ref={reference}
|
|
24
28
|
className={clsx(className, inputClassNameBase(), 'np-text-area')}
|
|
29
|
+
maxLength={maxLength}
|
|
25
30
|
{...inputAttributes}
|
|
26
31
|
{...restProps}
|
|
27
32
|
/>
|
package/src/inputs/contexts.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
1
|
+
import { createContext, useContext, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
type FieldLabelContextType = {
|
|
4
4
|
id?: string;
|
|
@@ -36,6 +36,23 @@ export function useFieldLabelRef() {
|
|
|
36
36
|
return useContext(FieldLabelContext)?.ref;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export type TextareaCharacterCountState = { current: number; max: number } | null;
|
|
40
|
+
|
|
41
|
+
const TextareaCharacterCountContext = createContext<
|
|
42
|
+
((state: TextareaCharacterCountState) => void) | undefined
|
|
43
|
+
>(undefined);
|
|
44
|
+
export const TextareaCharacterCountProvider = TextareaCharacterCountContext.Provider;
|
|
45
|
+
|
|
46
|
+
export function useTextareaCharacterCount(current: number, max: number | undefined) {
|
|
47
|
+
const setCharacterCount = useContext(TextareaCharacterCountContext);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (setCharacterCount && max != null) {
|
|
50
|
+
setCharacterCount({ current, max });
|
|
51
|
+
return () => setCharacterCount(null);
|
|
52
|
+
}
|
|
53
|
+
}, [setCharacterCount, current, max]);
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
export interface WithInputAttributesProps {
|
|
40
57
|
inputAttributes: ReturnType<typeof useInputAttributes>;
|
|
41
58
|
}
|
package/src/main.css
CHANGED
|
@@ -3238,7 +3238,16 @@ a,
|
|
|
3238
3238
|
.np-text-display-extra-large,
|
|
3239
3239
|
.np-text-display-large,
|
|
3240
3240
|
.np-text-display-medium,
|
|
3241
|
-
.np-text-display-small
|
|
3241
|
+
.np-text-display-small,
|
|
3242
|
+
.display-1--forced,
|
|
3243
|
+
.display-2--forced,
|
|
3244
|
+
.display-3--forced,
|
|
3245
|
+
.display-4--forced,
|
|
3246
|
+
.display-5--forced,
|
|
3247
|
+
.np-text-display-extra-large--forced,
|
|
3248
|
+
.np-text-display-large--forced,
|
|
3249
|
+
.np-text-display-medium--forced,
|
|
3250
|
+
.np-text-display-small--forced {
|
|
3242
3251
|
font-family: 'Wise Sans', 'Inter', sans-serif;
|
|
3243
3252
|
font-family: var(--font-family-display);
|
|
3244
3253
|
font-synthesis: none;
|
|
@@ -3285,9 +3294,14 @@ a,
|
|
|
3285
3294
|
* of Japanese ones for the logged out ones (exposed by the Editorial DS). Unfortunately,
|
|
3286
3295
|
* font files are browser-cached and we carried over to launchpad, where it causes issues
|
|
3287
3296
|
* for unsupported locales, especially those that share glyphs, like Japanese and Chinese.
|
|
3297
|
+
* There are exceptions for small UI parts where Wise Sans is fine or expected — e.g. the
|
|
3298
|
+
* numeric input of ExpressiveMoneyInput.
|
|
3299
|
+
* Add `--forced` BEM modifier to the original class name to guarantee it.
|
|
3288
3300
|
*/
|
|
3289
3301
|
font-family: 'Inter', Helvetica, Arial, sans-serif;
|
|
3290
3302
|
font-family: var(--font-family-regular);
|
|
3303
|
+
line-height: 1.2;
|
|
3304
|
+
line-height: var(--line-height-title);
|
|
3291
3305
|
}
|
|
3292
3306
|
|
|
3293
3307
|
/* DEPRECATED(.np-text-display-extra-large): use .np-text-display-large instead */
|
|
@@ -26633,10 +26647,10 @@ a[data-toggle="tooltip"] {
|
|
|
26633
26647
|
}
|
|
26634
26648
|
|
|
26635
26649
|
.np-dot-mask {
|
|
26636
|
-
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
26637
|
-
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
26638
|
-
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
26639
|
-
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
26650
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black calc(var(--np-dot-size) / 2 + var(--np-dot-offset) + 0.5px));
|
|
26651
|
+
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black calc(var(--np-dot-size) / 2 + var(--np-dot-offset) + 0.5px));
|
|
26652
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black calc(var(--np-dot-size) / 2 + var(--np-dot-offset) + 0.5px));
|
|
26653
|
+
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black calc(var(--np-dot-size) / 2 + var(--np-dot-offset) + 0.5px));
|
|
26640
26654
|
}
|
|
26641
26655
|
|
|
26642
26656
|
.np-dot-badge {
|
|
@@ -29935,18 +29949,36 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29935
29949
|
stroke-dasharray: var(--wds-list-item-spotlight-strokeDashSize) var(--wds-list-item-spotlight-strokeDashSize);
|
|
29936
29950
|
}
|
|
29937
29951
|
|
|
29938
|
-
.np-field-control
|
|
29939
|
-
.np-field__prompt {
|
|
29952
|
+
.np-field-control {
|
|
29940
29953
|
margin-top: 4px;
|
|
29941
29954
|
margin-top: var(--size-4);
|
|
29942
29955
|
}
|
|
29943
29956
|
|
|
29957
|
+
.np-field-validation {
|
|
29958
|
+
display: flex;
|
|
29959
|
+
align-items: flex-start;
|
|
29960
|
+
margin-top: 4px;
|
|
29961
|
+
margin-top: var(--size-4);
|
|
29962
|
+
gap: 8px;
|
|
29963
|
+
gap: var(--size-8);
|
|
29964
|
+
}
|
|
29965
|
+
|
|
29966
|
+
.np-field-textarea-char-counter {
|
|
29967
|
+
min-width: 55px;
|
|
29968
|
+
text-align: right;
|
|
29969
|
+
margin-left: auto;
|
|
29970
|
+
padding: 4px 0;
|
|
29971
|
+
padding: var(--size-4) 0;
|
|
29972
|
+
color: #768e9c;
|
|
29973
|
+
color: var(--color-content-tertiary);
|
|
29974
|
+
}
|
|
29975
|
+
|
|
29944
29976
|
.np-field .form-group--typeahead[class],
|
|
29945
29977
|
.np-field .np-checkbox-label[class] {
|
|
29946
29978
|
margin-bottom: 0;
|
|
29947
29979
|
}
|
|
29948
29980
|
|
|
29949
|
-
.np-field:has(.wds-radio-group) .np-
|
|
29981
|
+
.np-field:has(.wds-radio-group) .np-field-validation {
|
|
29950
29982
|
margin-top: 12px;
|
|
29951
29983
|
margin-top: var(--size-12);
|
|
29952
29984
|
}
|
|
@@ -31052,6 +31084,7 @@ button.np-link {
|
|
|
31052
31084
|
flex-grow: 1;
|
|
31053
31085
|
text-align: right;
|
|
31054
31086
|
background-color: transparent;
|
|
31087
|
+
line-height: inherit;
|
|
31055
31088
|
}
|
|
31056
31089
|
|
|
31057
31090
|
.wds-amount-input-input:focus-visible {
|
|
@@ -31062,6 +31095,7 @@ button.np-link {
|
|
|
31062
31095
|
flex-grow: 0;
|
|
31063
31096
|
display: flex;
|
|
31064
31097
|
align-items: center;
|
|
31098
|
+
line-height: inherit;
|
|
31065
31099
|
}
|
|
31066
31100
|
|
|
31067
31101
|
.wds-currency-selector:disabled {
|
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 = {
|
|
@@ -8,7 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
/* DEPRECATED: use .np-text-*-title instead */
|
|
10
10
|
/* stylelint-disable-next-line selector-list-comma-newline-after */
|
|
11
|
-
.h1,
|
|
11
|
+
.h1,
|
|
12
|
+
.h2,
|
|
13
|
+
.h3,
|
|
14
|
+
.h4,
|
|
15
|
+
.h5,
|
|
16
|
+
.h6,
|
|
17
|
+
.title-1,
|
|
18
|
+
.title-2,
|
|
19
|
+
.title-3,
|
|
20
|
+
.title-4,
|
|
21
|
+
.title-5,
|
|
12
22
|
h1,
|
|
13
23
|
h2,
|
|
14
24
|
h3,
|
|
@@ -114,8 +124,12 @@ h6,
|
|
|
114
124
|
|
|
115
125
|
/* DEPRECATED: use .np-text-body-default instead */
|
|
116
126
|
/* stylelint-disable-next-line selector-list-comma-newline-after */
|
|
117
|
-
.body-2,
|
|
118
|
-
body,
|
|
127
|
+
.body-2,
|
|
128
|
+
.body-3,
|
|
129
|
+
.small,
|
|
130
|
+
.tiny,
|
|
131
|
+
body,
|
|
132
|
+
small,
|
|
119
133
|
.np-text-body-default {
|
|
120
134
|
font-size: var(--font-size-14);
|
|
121
135
|
line-height: 155%;
|
|
@@ -139,7 +153,8 @@ body, small,
|
|
|
139
153
|
|
|
140
154
|
/* DEPRECATED: use .np-text-body-large instead */
|
|
141
155
|
/* stylelint-disable-next-line selector-list-comma-newline-after */
|
|
142
|
-
.body-1,
|
|
156
|
+
.body-1,
|
|
157
|
+
.value,
|
|
143
158
|
.np-text-body-large {
|
|
144
159
|
font-weight: var(--font-weight-regular);
|
|
145
160
|
font-size: var(--font-size-16);
|
|
@@ -204,8 +219,11 @@ a,
|
|
|
204
219
|
.np-text-display-large,
|
|
205
220
|
.np-text-display-medium,
|
|
206
221
|
.np-text-display-small {
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
&,
|
|
223
|
+
&--forced {
|
|
224
|
+
font-family: var(--font-family-display);
|
|
225
|
+
font-synthesis: none;
|
|
226
|
+
}
|
|
209
227
|
|
|
210
228
|
:lang(ja) &,
|
|
211
229
|
:lang(th) &,
|
|
@@ -216,8 +234,12 @@ a,
|
|
|
216
234
|
* of Japanese ones for the logged out ones (exposed by the Editorial DS). Unfortunately,
|
|
217
235
|
* font files are browser-cached and we carried over to launchpad, where it causes issues
|
|
218
236
|
* for unsupported locales, especially those that share glyphs, like Japanese and Chinese.
|
|
237
|
+
* There are exceptions for small UI parts where Wise Sans is fine or expected — e.g. the
|
|
238
|
+
* numeric input of ExpressiveMoneyInput.
|
|
239
|
+
* Add `--forced` BEM modifier to the original class name to guarantee it.
|
|
219
240
|
*/
|
|
220
241
|
font-family: var(--font-family-regular);
|
|
242
|
+
line-height: var(--line-height-title);
|
|
221
243
|
}
|
|
222
244
|
}
|
|
223
245
|
|
|
@@ -3238,7 +3238,16 @@ a,
|
|
|
3238
3238
|
.np-text-display-extra-large,
|
|
3239
3239
|
.np-text-display-large,
|
|
3240
3240
|
.np-text-display-medium,
|
|
3241
|
-
.np-text-display-small
|
|
3241
|
+
.np-text-display-small,
|
|
3242
|
+
.display-1--forced,
|
|
3243
|
+
.display-2--forced,
|
|
3244
|
+
.display-3--forced,
|
|
3245
|
+
.display-4--forced,
|
|
3246
|
+
.display-5--forced,
|
|
3247
|
+
.np-text-display-extra-large--forced,
|
|
3248
|
+
.np-text-display-large--forced,
|
|
3249
|
+
.np-text-display-medium--forced,
|
|
3250
|
+
.np-text-display-small--forced {
|
|
3242
3251
|
font-family: 'Wise Sans', 'Inter', sans-serif;
|
|
3243
3252
|
font-family: var(--font-family-display);
|
|
3244
3253
|
font-synthesis: none;
|
|
@@ -3285,9 +3294,14 @@ a,
|
|
|
3285
3294
|
* of Japanese ones for the logged out ones (exposed by the Editorial DS). Unfortunately,
|
|
3286
3295
|
* font files are browser-cached and we carried over to launchpad, where it causes issues
|
|
3287
3296
|
* for unsupported locales, especially those that share glyphs, like Japanese and Chinese.
|
|
3297
|
+
* There are exceptions for small UI parts where Wise Sans is fine or expected — e.g. the
|
|
3298
|
+
* numeric input of ExpressiveMoneyInput.
|
|
3299
|
+
* Add `--forced` BEM modifier to the original class name to guarantee it.
|
|
3288
3300
|
*/
|
|
3289
3301
|
font-family: 'Inter', Helvetica, Arial, sans-serif;
|
|
3290
3302
|
font-family: var(--font-family-regular);
|
|
3303
|
+
line-height: 1.2;
|
|
3304
|
+
line-height: var(--line-height-title);
|
|
3291
3305
|
}
|
|
3292
3306
|
|
|
3293
3307
|
/* DEPRECATED(.np-text-display-extra-large): use .np-text-display-large instead */
|
package/src/tabs/Tabs.tsx
CHANGED
|
@@ -254,7 +254,7 @@ export default class Tabs extends Component<TabsProps, TabsState> {
|
|
|
254
254
|
};
|
|
255
255
|
|
|
256
256
|
onKeyDown = (index: number) => (event: React.KeyboardEvent<HTMLLIElement>) => {
|
|
257
|
-
if (event
|
|
257
|
+
if (event?.key === 'Enter') {
|
|
258
258
|
this.switchTab(index);
|
|
259
259
|
}
|
|
260
260
|
};
|
package/src/tooltip/Tooltip.tsx
CHANGED
|
@@ -51,6 +51,7 @@ const Tooltip = ({
|
|
|
51
51
|
middleware: [
|
|
52
52
|
offset(16),
|
|
53
53
|
flip({ fallbackPlacements: [Position.TOP] }),
|
|
54
|
+
// eslint-disable-next-line react-hooks/refs -- arrowRef is passed to floating-ui middleware, legitimate API usage
|
|
54
55
|
arrowMiddleware({ element: arrowRef, padding: 8 }),
|
|
55
56
|
],
|
|
56
57
|
whileElementsMounted: open ? autoUpdate : undefined,
|
|
@@ -63,6 +64,7 @@ const Tooltip = ({
|
|
|
63
64
|
};
|
|
64
65
|
|
|
65
66
|
return (
|
|
67
|
+
/* eslint-disable react-hooks/refs -- setReference and setFloating are callback refs from floating-ui, safe to pass during render */
|
|
66
68
|
<span
|
|
67
69
|
ref={refs.setReference}
|
|
68
70
|
className="tw-tooltip-container"
|
|
@@ -96,6 +98,7 @@ const Tooltip = ({
|
|
|
96
98
|
</div>
|
|
97
99
|
</div>
|
|
98
100
|
</span>
|
|
101
|
+
/* eslint-enable react-hooks/refs */
|
|
99
102
|
);
|
|
100
103
|
};
|
|
101
104
|
|
|
@@ -66,6 +66,25 @@ describe('UploadInput', () => {
|
|
|
66
66
|
jest.useRealTimers();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
describe('onFilesChange mount behavior', () => {
|
|
70
|
+
it('should not call onFilesChange on initial mount', () => {
|
|
71
|
+
const onFilesChange = jest.fn();
|
|
72
|
+
renderComponent({
|
|
73
|
+
...props,
|
|
74
|
+
files: [
|
|
75
|
+
{
|
|
76
|
+
id: 1,
|
|
77
|
+
filename: 'existing-file.pdf',
|
|
78
|
+
status: Status.SUCCEEDED,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
onFilesChange,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(onFilesChange).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
69
88
|
describe('single file upload', () => {
|
|
70
89
|
it('should trigger onUploadFiles & onFilesChange with a single FormData entry containing `file` field', async () => {
|
|
71
90
|
const date = Date.now();
|