@transferwise/components 46.155.0 → 46.156.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.map +1 -1
- package/build/AvatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/AvatarView/AvatarView.js.map +1 -1
- package/build/AvatarView/AvatarView.mjs.map +1 -1
- package/build/AvatarView/Dot/Dot.js.map +1 -1
- package/build/AvatarView/Dot/Dot.mjs.map +1 -1
- package/build/Body/Body.js.map +1 -1
- package/build/Body/Body.mjs.map +1 -1
- package/build/Chips/Chip/Chip.js.map +1 -1
- package/build/Chips/Chip/Chip.mjs.map +1 -1
- package/build/DefinitionList/DefinitionList.js +2 -1
- package/build/DefinitionList/DefinitionList.js.map +1 -1
- package/build/DefinitionList/DefinitionList.mjs +2 -1
- package/build/DefinitionList/DefinitionList.mjs.map +1 -1
- package/build/Display/Display.js.map +1 -1
- package/build/Display/Display.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js +2 -2
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js.map +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js +4 -4
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js.map +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js +2 -2
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js +3 -3
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
- package/build/IconButton/IconButton.js.map +1 -1
- package/build/IconButton/IconButton.mjs.map +1 -1
- package/build/Link/Link.js.map +1 -1
- package/build/Link/Link.mjs.map +1 -1
- package/build/Markup/Markup.js +138 -0
- package/build/Markup/Markup.js.map +1 -0
- package/build/Markup/Markup.mjs +133 -0
- package/build/Markup/Markup.mjs.map +1 -0
- package/build/Markup/constants.js +21 -0
- package/build/Markup/constants.js.map +1 -0
- package/build/Markup/constants.mjs +15 -0
- package/build/Markup/constants.mjs.map +1 -0
- package/build/Markup/utils/parseMarkup.js +263 -0
- package/build/Markup/utils/parseMarkup.js.map +1 -0
- package/build/Markup/utils/parseMarkup.mjs +258 -0
- package/build/Markup/utils/parseMarkup.mjs.map +1 -0
- package/build/Markup/utils/sanitise.js +111 -0
- package/build/Markup/utils/sanitise.js.map +1 -0
- package/build/Markup/utils/sanitise.mjs +108 -0
- package/build/Markup/utils/sanitise.mjs.map +1 -0
- package/build/Nudge/Nudge.js +11 -2
- package/build/Nudge/Nudge.js.map +1 -1
- package/build/Nudge/Nudge.mjs +12 -3
- package/build/Nudge/Nudge.mjs.map +1 -1
- package/build/Sticky/Sticky.js +1 -0
- package/build/Sticky/Sticky.js.map +1 -1
- package/build/Sticky/Sticky.mjs +1 -0
- package/build/Sticky/Sticky.mjs.map +1 -1
- package/build/Title/Title.js +1 -0
- package/build/Title/Title.js.map +1 -1
- package/build/Title/Title.mjs +1 -0
- package/build/Title/Title.mjs.map +1 -1
- package/build/_deprecated/Select/Option/Option.js.map +1 -1
- package/build/_deprecated/Select/Option/Option.mjs.map +1 -1
- package/build/_deprecated/Select/SearchBox/SearchBox.js.map +1 -1
- package/build/_deprecated/Select/SearchBox/SearchBox.mjs.map +1 -1
- package/build/common/Circle/Circle.js.map +1 -1
- package/build/common/Circle/Circle.mjs.map +1 -1
- package/build/i18n/hu.json +1 -1
- package/build/i18n/hu.json.js +1 -1
- package/build/i18n/hu.json.mjs +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -0
- package/build/index.mjs.map +1 -1
- package/build/main.css +71 -1
- package/build/styles/DefinitionList/DefinitionList.css +30 -0
- package/build/styles/Markup/Markup.css +28 -0
- package/build/styles/Markup/_stories/Vulnerability/Vulnerability.css +69 -0
- package/build/styles/Nudge/Nudge.css +1 -1
- package/build/styles/Sticky/Sticky.css +4 -0
- package/build/styles/main.css +71 -1
- package/build/types/AvatarLayout/AvatarLayout.d.ts +2 -2
- package/build/types/AvatarLayout/AvatarLayout.d.ts.map +1 -1
- package/build/types/AvatarLayout/index.d.ts +1 -1
- package/build/types/AvatarLayout/index.d.ts.map +1 -1
- package/build/types/AvatarView/AvatarView.d.ts +2 -2
- package/build/types/AvatarView/AvatarView.d.ts.map +1 -1
- package/build/types/AvatarView/Dot/Dot.d.ts +1 -1
- package/build/types/AvatarView/Dot/Dot.d.ts.map +1 -1
- package/build/types/AvatarView/index.d.ts +1 -1
- package/build/types/AvatarView/index.d.ts.map +1 -1
- package/build/types/Body/Body.d.ts +13 -2
- package/build/types/Body/Body.d.ts.map +1 -1
- package/build/types/Body/index.d.ts +1 -0
- package/build/types/Body/index.d.ts.map +1 -1
- package/build/types/Chips/Chip/Chip.d.ts +2 -2
- package/build/types/Chips/Chip/Chip.d.ts.map +1 -1
- package/build/types/Chips/index.d.ts +1 -1
- package/build/types/Chips/index.d.ts.map +1 -1
- package/build/types/DefinitionList/DefinitionList.d.ts.map +1 -1
- package/build/types/Display/Display.d.ts +2 -2
- package/build/types/Display/Display.d.ts.map +1 -1
- package/build/types/Display/index.d.ts +1 -0
- package/build/types/Display/index.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts +3 -3
- package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts +3 -3
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts +4 -4
- package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts +1 -1
- package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/index.d.ts +1 -1
- package/build/types/ExpressiveMoneyInput/index.d.ts.map +1 -1
- package/build/types/IconButton/IconButton.d.ts +2 -2
- package/build/types/IconButton/IconButton.d.ts.map +1 -1
- package/build/types/IconButton/index.d.ts +1 -1
- package/build/types/IconButton/index.d.ts.map +1 -1
- package/build/types/Link/Link.d.ts +2 -2
- package/build/types/Link/Link.d.ts.map +1 -1
- package/build/types/Link/index.d.ts +1 -1
- package/build/types/Link/index.d.ts.map +1 -1
- package/build/types/Markup/Markup.d.ts +18 -0
- package/build/types/Markup/Markup.d.ts.map +1 -0
- package/build/types/Markup/Markup.types.d.ts +77 -0
- package/build/types/Markup/Markup.types.d.ts.map +1 -0
- package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts +12 -0
- package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts.map +1 -0
- package/build/types/Markup/constants.d.ts +6 -0
- package/build/types/Markup/constants.d.ts.map +1 -0
- package/build/types/Markup/index.d.ts +3 -0
- package/build/types/Markup/index.d.ts.map +1 -0
- package/build/types/Markup/utils/parseMarkup.d.ts +8 -0
- package/build/types/Markup/utils/parseMarkup.d.ts.map +1 -0
- package/build/types/Markup/utils/sanitise.d.ts +4 -0
- package/build/types/Markup/utils/sanitise.d.ts.map +1 -0
- package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/Nudge/Nudge.d.ts +28 -5
- package/build/types/Nudge/Nudge.d.ts.map +1 -1
- package/build/types/Nudge/index.d.ts +1 -1
- package/build/types/Nudge/index.d.ts.map +1 -1
- package/build/types/Sticky/Sticky.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +13 -3
- package/build/types/Title/Title.d.ts.map +1 -1
- package/build/types/Title/index.d.ts +1 -0
- package/build/types/Title/index.d.ts.map +1 -1
- package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
- package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
- package/build/types/_deprecated/Select/Option/Option.d.ts +2 -2
- package/build/types/_deprecated/Select/Option/Option.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/Circle/index.d.ts +1 -1
- package/build/types/common/Circle/index.d.ts.map +1 -1
- package/build/types/index.d.ts +6 -0
- package/build/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/AvatarLayout/AvatarLayout.tsx +3 -3
- package/src/AvatarLayout/index.ts +1 -1
- package/src/AvatarView/AvatarView.tsx +4 -4
- package/src/AvatarView/Dot/Dot.tsx +1 -1
- package/src/AvatarView/index.ts +1 -1
- package/src/Body/Body.tsx +2 -2
- package/src/Body/index.ts +1 -0
- package/src/Chips/Chip/Chip.tsx +2 -2
- package/src/Chips/index.ts +1 -1
- package/src/DefinitionList/DefinitionList.css +30 -0
- package/src/DefinitionList/DefinitionList.less +24 -0
- package/src/DefinitionList/DefinitionList.tsx +1 -0
- package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +168 -0
- package/src/Display/Display.tsx +2 -2
- package/src/Display/index.ts +1 -0
- package/src/Drawer/Drawer.test.tsx +2 -3
- package/src/ExpressiveMoneyInput/AmountInput/AmountInput.tsx +4 -4
- package/src/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.tsx +1 -1
- package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +1 -1
- package/src/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.tsx +3 -3
- package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.test.tsx +1 -1
- package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.tsx +5 -8
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.story.tsx +1 -1
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.test.story.tsx +1 -1
- package/src/ExpressiveMoneyInput/hooks/useInputStyle.ts +1 -1
- package/src/ExpressiveMoneyInput/index.ts +1 -1
- package/src/IconButton/IconButton.tsx +2 -2
- package/src/IconButton/_stories/IconButton.story.tsx +8 -8
- package/src/IconButton/index.ts +1 -1
- package/src/Link/Link.tsx +2 -2
- package/src/Link/index.ts +1 -1
- package/src/Markup/Markup.css +28 -0
- package/src/Markup/Markup.injection.test.tsx +163 -0
- package/src/Markup/Markup.less +29 -0
- package/src/Markup/Markup.test.tsx +502 -0
- package/src/Markup/Markup.tsx +175 -0
- package/src/Markup/Markup.types.ts +62 -0
- package/src/Markup/_stories/Markup.docs.mdx +75 -0
- package/src/Markup/_stories/Markup.security.docs.mdx +343 -0
- package/src/Markup/_stories/Markup.story.tsx +266 -0
- package/src/Markup/_stories/Vulnerability/Vulnerability.css +69 -0
- package/src/Markup/_stories/Vulnerability/Vulnerability.tsx +30 -0
- package/src/Markup/constants.ts +23 -0
- package/src/Markup/index.ts +2 -0
- package/src/Markup/utils/parseMarkup.test.ts +706 -0
- package/src/Markup/utils/parseMarkup.ts +265 -0
- package/src/Markup/utils/sanitise.test.ts +499 -0
- package/src/Markup/utils/sanitise.ts +129 -0
- package/src/Nudge/Nudge.css +1 -1
- package/src/Nudge/Nudge.less +1 -1
- package/src/Nudge/Nudge.test.tsx +43 -0
- package/src/Nudge/Nudge.tsx +43 -8
- package/src/Nudge/index.ts +1 -1
- package/src/Sticky/Sticky.css +4 -0
- package/src/Sticky/Sticky.less +6 -0
- package/src/Sticky/Sticky.test.tsx +1 -0
- package/src/Sticky/Sticky.tsx +1 -0
- package/src/Sticky/_stories/Sticky.test.story.tsx +177 -0
- package/src/Title/Title.tsx +25 -20
- package/src/Title/index.ts +1 -0
- package/src/_deprecated/LegacyListItem/_stories/LegacyListItem.story.tsx +2 -3
- package/src/_deprecated/Select/Option/Option.test.tsx +1 -1
- package/src/_deprecated/Select/Option/Option.tsx +2 -2
- package/src/_deprecated/Select/SearchBox/SearchBox.tsx +2 -2
- package/src/common/Circle/Circle.tsx +2 -2
- package/src/common/Circle/index.ts +1 -1
- package/src/i18n/hu.json +1 -1
- package/src/index.ts +6 -0
- package/src/main.css +71 -1
- package/src/main.less +2 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { formatAmount } from '@transferwise/formatting';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { AnimatePresence } from '
|
|
3
|
+
import { AnimatePresence } from 'motion/react';
|
|
4
4
|
import { type ChangeEvent, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { useIntl } from 'react-intl';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
8
8
|
import { AnimatedNumber } from '../AnimatedNumber/AnimatedNumber';
|
|
9
9
|
import { useFocus } from '../hooks/useFocus';
|
|
10
10
|
import { useInputStyle } from '../hooks/useInputStyle';
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isInputPossiblyOverflowing,
|
|
20
20
|
} from './utils';
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type AmountInputProps = {
|
|
23
23
|
id: string;
|
|
24
24
|
describedById?: string;
|
|
25
25
|
amount?: number | null;
|
|
@@ -38,7 +38,7 @@ export const AmountInput = ({
|
|
|
38
38
|
onChange,
|
|
39
39
|
onFocusChange,
|
|
40
40
|
loading,
|
|
41
|
-
}:
|
|
41
|
+
}: AmountInputProps) => {
|
|
42
42
|
const intl = useIntl();
|
|
43
43
|
const { focus, setFocus, visualFocus, setVisualFocus } = useFocus();
|
|
44
44
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
SelectInputOptionContent,
|
|
6
6
|
SelectInputTriggerButton,
|
|
7
7
|
} from '../../Inputs/SelectInput';
|
|
8
|
-
import { CurrencyType,
|
|
8
|
+
import type { CurrencyType, ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
9
9
|
import { ChevronDown } from '@transferwise/icons';
|
|
10
10
|
import { Flag } from '@wise/art';
|
|
11
11
|
import {
|
|
@@ -34,7 +34,7 @@ export interface CurrencySection {
|
|
|
34
34
|
|
|
35
35
|
export type CurrencyOptions = CurrencySection[];
|
|
36
36
|
|
|
37
|
-
export type
|
|
37
|
+
export type CurrencySelectorProps = {
|
|
38
38
|
id: string;
|
|
39
39
|
labelId: string;
|
|
40
40
|
options?: CurrencyOptions;
|
|
@@ -53,7 +53,7 @@ export const CurrencySelector = ({
|
|
|
53
53
|
addons,
|
|
54
54
|
onOpen,
|
|
55
55
|
onSearchChange,
|
|
56
|
-
}:
|
|
56
|
+
}: CurrencySelectorProps) => {
|
|
57
57
|
const intl = useIntl();
|
|
58
58
|
|
|
59
59
|
const allCurrencyOptions = useMemo(() => getUniqueCurrencies(options), [options]);
|
|
@@ -2,7 +2,7 @@ import { screen, render, mockMatchMedia, mockResizeObserver, waitFor } from '../
|
|
|
2
2
|
import { Sentiment } from '../common';
|
|
3
3
|
|
|
4
4
|
import ExpressiveMoneyInput from './ExpressiveMoneyInput';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ExpressiveMoneyInputProps } from './ExpressiveMoneyInput';
|
|
6
6
|
import { Confetti } from '@transferwise/icons';
|
|
7
7
|
|
|
8
8
|
const defaultProps: ExpressiveMoneyInputProps = {
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import Body from '../Body';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { AnimatePresence, motion } from '
|
|
3
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
4
4
|
import { useId, type ReactNode } from 'react';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
type Props as CurrencySelectorProps,
|
|
8
|
-
CurrencySelector,
|
|
9
|
-
} from './CurrencySelector/CurrencySelector';
|
|
6
|
+
import { CurrencySelector, type CurrencySelectorProps } from './CurrencySelector/CurrencySelector';
|
|
10
7
|
import { CommonProps } from '../common';
|
|
11
8
|
import { AmountInput } from './AmountInput/AmountInput';
|
|
12
9
|
import { Chevron } from './Chevron/Chevron';
|
|
@@ -26,7 +23,7 @@ type CustomCurrencySelectorInstanceType = {
|
|
|
26
23
|
type CurrencySelectorType = DefaultCurrencySelectorInstanceType &
|
|
27
24
|
CustomCurrencySelectorInstanceType;
|
|
28
25
|
|
|
29
|
-
export type
|
|
26
|
+
export type ExpressiveMoneyInputProps = {
|
|
30
27
|
label?: ReactNode;
|
|
31
28
|
currencySelector?: CurrencySelectorType;
|
|
32
29
|
amount?: AmountType;
|
|
@@ -65,7 +62,7 @@ export type Props = {
|
|
|
65
62
|
* direct contributions by consuming product teams are highly encouraged.
|
|
66
63
|
*
|
|
67
64
|
* > ⚠️ **Prerequisite:** <br />The component depends on the
|
|
68
|
-
* [
|
|
65
|
+
* [motion](https://www.npmjs.com/package/motion) package, which has been
|
|
69
66
|
* deliberately excluded from the Design System bundle and kept as an opt-in only.
|
|
70
67
|
* **Make sure to add it to your project dependencies.**
|
|
71
68
|
*/
|
|
@@ -81,7 +78,7 @@ export default function ExpressiveMoneyInput({
|
|
|
81
78
|
autoFocus,
|
|
82
79
|
loading,
|
|
83
80
|
onFocusChange,
|
|
84
|
-
}:
|
|
81
|
+
}: ExpressiveMoneyInputProps) {
|
|
85
82
|
const inputId = useId();
|
|
86
83
|
const labelId = useId();
|
|
87
84
|
const customAlertId = useId();
|
|
@@ -19,7 +19,7 @@ import Modal from '../../Modal';
|
|
|
19
19
|
import ListItem from '../../ListItem';
|
|
20
20
|
import List from '../../List';
|
|
21
21
|
import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '..';
|
|
22
|
-
import type {
|
|
22
|
+
import type { CurrencySelectorProps } from '../CurrencySelector/CurrencySelector';
|
|
23
23
|
|
|
24
24
|
const getCurrencySelectorProps = ({
|
|
25
25
|
logEvents = false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
|
-
import ExpressiveMoneyInput, {
|
|
3
|
+
import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof ExpressiveMoneyInput> = {
|
|
6
6
|
component: ExpressiveMoneyInput,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { ExpressiveMoneyInputProps, CurrencyType } from './ExpressiveMoneyInput';
|
|
2
2
|
export { default } from './ExpressiveMoneyInput';
|
|
@@ -3,7 +3,7 @@ import { PrimitiveAnchor, PrimitiveButton } from '../Primitives';
|
|
|
3
3
|
import Circle from '../common/Circle';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type IconButtonProps = {
|
|
7
7
|
/** @default 48 */
|
|
8
8
|
size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
|
|
9
9
|
/** @default 'primary' */
|
|
@@ -27,7 +27,7 @@ const IconButton = forwardRef(function IconButton(
|
|
|
27
27
|
priority = 'primary',
|
|
28
28
|
type = 'default',
|
|
29
29
|
...props
|
|
30
|
-
}:
|
|
30
|
+
}: IconButtonProps,
|
|
31
31
|
ref,
|
|
32
32
|
) {
|
|
33
33
|
const isLink = href !== '#';
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
createSandboxStory,
|
|
19
19
|
globalScope,
|
|
20
20
|
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
21
|
-
import IconButton, {
|
|
21
|
+
import IconButton, { type IconButtonProps } from '../IconButton';
|
|
22
22
|
import SentimentSurface from '../../SentimentSurface';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
|
|
47
47
|
type Story = StoryObj<typeof IconButton>;
|
|
48
48
|
|
|
49
|
-
const sizes:
|
|
49
|
+
const sizes: IconButtonProps['size'][] = [16, 24, 32, 40, 48, 56, 72];
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Explore size, priority, type, and disabled state via the controls panel.
|
|
@@ -107,7 +107,7 @@ export const Priority: Story = {
|
|
|
107
107
|
</div>
|
|
108
108
|
),
|
|
109
109
|
],
|
|
110
|
-
render: (args:
|
|
110
|
+
render: (args: IconButtonProps) => (
|
|
111
111
|
<>
|
|
112
112
|
<IconButton priority="primary" type={args.type} aria-label="Primary" onClick={fn()}>
|
|
113
113
|
<Plus />
|
|
@@ -152,7 +152,7 @@ export const Type: Story = {
|
|
|
152
152
|
</div>
|
|
153
153
|
),
|
|
154
154
|
],
|
|
155
|
-
render: (args:
|
|
155
|
+
render: (args: IconButtonProps) => (
|
|
156
156
|
<>
|
|
157
157
|
<IconButton priority={args.priority} type="default" aria-label="Default" onClick={fn()}>
|
|
158
158
|
<Plus />
|
|
@@ -195,7 +195,7 @@ export const Size: Story = {
|
|
|
195
195
|
</div>
|
|
196
196
|
),
|
|
197
197
|
],
|
|
198
|
-
render: (args:
|
|
198
|
+
render: (args: IconButtonProps) => (
|
|
199
199
|
<>
|
|
200
200
|
{sizes.map((size) => (
|
|
201
201
|
<div
|
|
@@ -254,7 +254,7 @@ export const Disabled: Story = {
|
|
|
254
254
|
target: { table: { disable: true } },
|
|
255
255
|
onClick: { table: { disable: true } },
|
|
256
256
|
},
|
|
257
|
-
render: (args:
|
|
257
|
+
render: (args: IconButtonProps) => (
|
|
258
258
|
<IconButton disabled priority={args.priority} type={args.type} aria-label="Disabled">
|
|
259
259
|
<Plus />
|
|
260
260
|
</IconButton>
|
|
@@ -284,7 +284,7 @@ export const AsAnchor: Story = {
|
|
|
284
284
|
disabled: { table: { disable: true } },
|
|
285
285
|
onClick: { table: { disable: true } },
|
|
286
286
|
},
|
|
287
|
-
render: (args:
|
|
287
|
+
render: (args: IconButtonProps) => (
|
|
288
288
|
<IconButton {...args} aria-label="External link">
|
|
289
289
|
<ArrowLeft />
|
|
290
290
|
</IconButton>
|
|
@@ -305,7 +305,7 @@ export const SentimentAwareness: Story = {
|
|
|
305
305
|
options: [16, 24, 32, 40, 48, 56, 72],
|
|
306
306
|
},
|
|
307
307
|
},
|
|
308
|
-
render: (args:
|
|
308
|
+
render: (args: IconButtonProps) => {
|
|
309
309
|
const priorities = [
|
|
310
310
|
{ key: 'primary', label: 'primary' },
|
|
311
311
|
{ key: 'secondary', label: 'secondary' },
|
package/src/IconButton/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { IconButtonProps } from './IconButton';
|
|
2
2
|
export { default } from './IconButton';
|
package/src/Link/Link.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { LinkLarge, LinkDefault, Typography } from '../common';
|
|
|
8
8
|
|
|
9
9
|
import messages from './Link.messages';
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type LinkProps = PrimitiveAnchorProps &
|
|
12
12
|
Pick<PrimitiveButtonProps, 'onClick'> & { type?: LinkLarge | LinkDefault };
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -26,7 +26,7 @@ const Link = ({
|
|
|
26
26
|
onClick,
|
|
27
27
|
disabled,
|
|
28
28
|
...props
|
|
29
|
-
}:
|
|
29
|
+
}: LinkProps) => {
|
|
30
30
|
const { formatMessage } = useIntl();
|
|
31
31
|
const isButton = Boolean(onClick) && href === undefined;
|
|
32
32
|
const isBlank = target === '_blank';
|
package/src/Link/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default } from './Link';
|
|
2
|
-
export type {
|
|
2
|
+
export type { LinkProps } from './Link';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.wds-markup-emphasis {
|
|
2
|
+
font-weight: 600;
|
|
3
|
+
font-weight: var(--font-weight-semi-bold);
|
|
4
|
+
font-style: normal;
|
|
5
|
+
}
|
|
6
|
+
.wds-markup-emphasis--important {
|
|
7
|
+
color: #37517e;
|
|
8
|
+
color: var(--color-content-primary);
|
|
9
|
+
}
|
|
10
|
+
.wds-markup-emphasis--positive {
|
|
11
|
+
color: #008026;
|
|
12
|
+
color: var(--color-content-positive);
|
|
13
|
+
}
|
|
14
|
+
.wds-markup-emphasis--negative {
|
|
15
|
+
color: #cf2929;
|
|
16
|
+
color: var(--color-content-negative);
|
|
17
|
+
}
|
|
18
|
+
.wds-markup-strong {
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
font-weight: var(--font-weight-semi-bold);
|
|
21
|
+
}
|
|
22
|
+
.wds-markup-strikethrough {
|
|
23
|
+
-webkit-text-decoration: line-through;
|
|
24
|
+
text-decoration: line-through;
|
|
25
|
+
}
|
|
26
|
+
.wds-markup-paragraph {
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render } from '../test-utils';
|
|
4
|
+
|
|
5
|
+
import { Markup } from './Markup';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Proof of Concept: Markup Injection via Attacker-Controlled Data
|
|
9
|
+
*
|
|
10
|
+
* These tests demonstrate Laszlo's concern: when untrusted data (e.g. a payee name,
|
|
11
|
+
* company name, or any string from an external source) is interpolated into a markup
|
|
12
|
+
* template string, the attacker can break out of their structural context and inject
|
|
13
|
+
* arbitrary allowed markup — links, emphasis, etc.
|
|
14
|
+
*
|
|
15
|
+
* This is NOT about XSS (script execution is blocked). It's about content spoofing
|
|
16
|
+
* and phishing within trusted UI.
|
|
17
|
+
*/
|
|
18
|
+
describe('Markup injection via attacker-controlled data', () => {
|
|
19
|
+
describe('the vulnerability: structural injection', () => {
|
|
20
|
+
it('default allowedHrefs blocks injected external links', () => {
|
|
21
|
+
const attackerControlledName =
|
|
22
|
+
'Acme Corp</strong>' +
|
|
23
|
+
'<link href="https://evil.example.com">' +
|
|
24
|
+
'<important>ACTION REQUIRED: Verify your identity</important>' +
|
|
25
|
+
'</link>' +
|
|
26
|
+
'<strong>';
|
|
27
|
+
|
|
28
|
+
const template = `Transfer <strong>£35.00</strong> to <strong>${attackerControlledName}</strong>`;
|
|
29
|
+
|
|
30
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
31
|
+
|
|
32
|
+
// The default allowlist blocks external domains — the link is NOT rendered
|
|
33
|
+
const injectedLink = container.querySelector('a[href="https://evil.example.com"]');
|
|
34
|
+
expect(injectedLink).not.toBeInTheDocument();
|
|
35
|
+
|
|
36
|
+
// But the structural injection still works (emphasis renders)
|
|
37
|
+
const injectedEmphasis = container.querySelector('.wds-markup-emphasis--important');
|
|
38
|
+
expect(injectedEmphasis).toBeInTheDocument();
|
|
39
|
+
expect(injectedEmphasis).toHaveTextContent('ACTION REQUIRED: Verify your identity');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('injected external links render when allowLinks="all"', () => {
|
|
43
|
+
const attackerControlledName =
|
|
44
|
+
'Acme Corp</strong>' +
|
|
45
|
+
'<link href="https://evil.example.com">' +
|
|
46
|
+
'<important>ACTION REQUIRED: Verify your identity</important>' +
|
|
47
|
+
'</link>' +
|
|
48
|
+
'<strong>';
|
|
49
|
+
|
|
50
|
+
const template = `Transfer <strong>£35.00</strong> to <strong>${attackerControlledName}</strong>`;
|
|
51
|
+
|
|
52
|
+
const { container } = render(<Markup allowLinks="all">{template}</Markup>);
|
|
53
|
+
|
|
54
|
+
// With allowLinks="all", the link renders — demonstrating the structural injection
|
|
55
|
+
const injectedLink = container.querySelector('a[href="https://evil.example.com"]');
|
|
56
|
+
expect(injectedLink).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('default allowedHrefs blocks phishing links in paragraph injection', () => {
|
|
60
|
+
const attackerNote =
|
|
61
|
+
'Thanks!</p>' +
|
|
62
|
+
'<p><important>Your account has been compromised. ' +
|
|
63
|
+
'Contact support at </important>' +
|
|
64
|
+
'<link href="https://phishing.example.com">this secure link</link></p>' +
|
|
65
|
+
'<p>';
|
|
66
|
+
|
|
67
|
+
const template = `<p>Note from sender: ${attackerNote}</p>`;
|
|
68
|
+
|
|
69
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
70
|
+
|
|
71
|
+
const phishingLink = container.querySelector('a[href="https://phishing.example.com"]');
|
|
72
|
+
expect(phishingLink).not.toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('attacker can inject misleading accessibility labels', () => {
|
|
76
|
+
const attackerValue =
|
|
77
|
+
'normal text</strong>' +
|
|
78
|
+
'<strong accessibilityLabel="Security alert: click to verify">' +
|
|
79
|
+
'<link href="https://evil.example.com">here</link>' +
|
|
80
|
+
'</strong>' +
|
|
81
|
+
'<strong>';
|
|
82
|
+
|
|
83
|
+
const template = `Your balance: <strong>${attackerValue}</strong>`;
|
|
84
|
+
|
|
85
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
86
|
+
|
|
87
|
+
const misleadingElement = container.querySelector(
|
|
88
|
+
'[aria-label="Security alert: click to verify"]',
|
|
89
|
+
);
|
|
90
|
+
expect(misleadingElement).toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('what IS blocked (XSS / script execution)', () => {
|
|
95
|
+
it('cannot inject arbitrary HTML elements', () => {
|
|
96
|
+
const xssAttempt = '<img src=x onerror=alert(1)>';
|
|
97
|
+
const template = `Hello <strong>${xssAttempt}</strong>`;
|
|
98
|
+
|
|
99
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
100
|
+
|
|
101
|
+
// <img> is not in the allow-list, so it's rendered as text
|
|
102
|
+
expect(container.querySelector('img')).not.toBeInTheDocument();
|
|
103
|
+
expect(container).toHaveTextContent(/<img src=x onerror=alert\(1\)>/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('cannot inject script tags', () => {
|
|
107
|
+
const xssAttempt = '<script>alert("xss")</script>';
|
|
108
|
+
const template = `Hello <strong>${xssAttempt}</strong>`;
|
|
109
|
+
|
|
110
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
111
|
+
|
|
112
|
+
expect(container.querySelector('script')).not.toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('blocks javascript: URLs in links', () => {
|
|
116
|
+
// Even if attacker injects a link, javascript: URLs are blocked
|
|
117
|
+
const attack = 'click</strong><link href="javascript:alert(1)">me</link><strong>';
|
|
118
|
+
const template = `<strong>${attack}</strong>`;
|
|
119
|
+
|
|
120
|
+
const { container } = render(<Markup>{template}</Markup>);
|
|
121
|
+
|
|
122
|
+
// The link renders but without the dangerous href (falls back to Fragment)
|
|
123
|
+
expect(container.querySelector('a[href*="javascript"]')).not.toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('the general nature of this problem', () => {
|
|
128
|
+
it('is identical to HTML injection via innerHTML', () => {
|
|
129
|
+
// This is the same class of bug — mixing code and data in a string
|
|
130
|
+
const attackerName = '<b>URGENT</b>';
|
|
131
|
+
const div = document.createElement('div');
|
|
132
|
+
div.innerHTML = `Hello ${attackerName}`;
|
|
133
|
+
|
|
134
|
+
// The attacker's data became structure
|
|
135
|
+
expect(div.querySelector('b')).not.toBeNull();
|
|
136
|
+
expect(div.querySelector('b')!).toHaveTextContent('URGENT');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('react JSX prevents this by design', () => {
|
|
140
|
+
// In contrast, React treats variables as pure data — never as structure
|
|
141
|
+
const attackerName = '<strong>URGENT</strong>';
|
|
142
|
+
|
|
143
|
+
const { container } = render(<span>Hello {attackerName}</span>);
|
|
144
|
+
|
|
145
|
+
// React escapes the string — it renders as visible text, not as a <strong> tag
|
|
146
|
+
expect(container.querySelector('strong')).not.toBeInTheDocument();
|
|
147
|
+
expect(container).toHaveTextContent(/<strong>URGENT<\/strong>/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('demonstrates why Markup reintroduces the problem React solved', () => {
|
|
151
|
+
// The same attacker string that React handles safely...
|
|
152
|
+
const attackerName = '<strong>URGENT: Call +44 123 456 789 NOW</strong>';
|
|
153
|
+
|
|
154
|
+
// ...becomes dangerous when passed through Markup's string parser
|
|
155
|
+
const { container } = render(<Markup>{`Hello ${attackerName}`}</Markup>);
|
|
156
|
+
|
|
157
|
+
// The attacker's data has been promoted to structure
|
|
158
|
+
const injected = container.querySelector('strong');
|
|
159
|
+
expect(injected).toBeInTheDocument();
|
|
160
|
+
expect(injected).toHaveTextContent('URGENT: Call +44 123 456 789 NOW');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.wds-markup-emphasis {
|
|
2
|
+
font-weight: var(--font-weight-semi-bold);
|
|
3
|
+
font-style: normal;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.wds-markup-emphasis--important {
|
|
7
|
+
color: var(--color-content-primary);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.wds-markup-emphasis--positive {
|
|
11
|
+
color: var(--color-content-positive);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.wds-markup-emphasis--negative {
|
|
15
|
+
color: var(--color-content-negative);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.wds-markup-strong {
|
|
19
|
+
font-weight: var(--font-weight-semi-bold);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @see https://www.webaxe.org/strikethrough-html-accessibility/
|
|
23
|
+
.wds-markup-strikethrough {
|
|
24
|
+
text-decoration: line-through;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.wds-markup-paragraph {
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|