@transferwise/components 0.0.0-experimental-fd70ead → 0.0.0-experimental-2384dbf
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/i18n/cs.json +3 -0
- package/build/i18n/de.json +3 -0
- package/build/i18n/es.json +3 -0
- package/build/i18n/fr.json +3 -0
- package/build/i18n/hu.json +3 -0
- package/build/i18n/id.json +3 -0
- package/build/i18n/it.json +3 -0
- package/build/i18n/ja.json +3 -0
- package/build/i18n/pl.json +3 -0
- package/build/i18n/pt.json +3 -0
- package/build/i18n/ro.json +3 -0
- package/build/i18n/ru.json +3 -0
- package/build/i18n/th.json +3 -0
- package/build/i18n/tr.json +3 -0
- package/build/i18n/uk.json +3 -0
- package/build/i18n/zh-CN.json +3 -0
- package/build/index.esm.js +166 -2
- package/build/index.esm.js.map +1 -1
- package/build/index.js +166 -1
- package/build/index.js.map +1 -1
- package/build/main.css +1 -1
- package/build/styles/inputs/InputGroup.css +1 -0
- package/build/styles/main.css +1 -1
- package/build/types/body/Body.d.ts +2 -2
- package/build/types/common/hooks/useEffectEvent.d.ts +2 -0
- package/build/types/common/hooks/useEffectEvent.d.ts.map +1 -0
- package/build/types/common/hooks/useResizeObserver.d.ts +3 -0
- package/build/types/common/hooks/useResizeObserver.d.ts.map +1 -0
- package/build/types/common/panel/Panel.d.ts +1 -1
- package/build/types/common/responsivePanel/ResponsivePanel.d.ts +1 -1
- package/build/types/dimmer/Dimmer.d.ts +1 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/Input.d.ts +1 -1
- package/build/types/inputs/Input.d.ts.map +1 -1
- package/build/types/inputs/InputGroup.d.ts +21 -0
- package/build/types/inputs/InputGroup.d.ts.map +1 -0
- package/build/types/inputs/TextArea.d.ts +1 -1
- package/build/types/select/searchBox/SearchBox.d.ts +1 -1
- package/build/types/utilities/cssValueWithUnit.d.ts +2 -0
- package/build/types/utilities/cssValueWithUnit.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/common/hooks/useEffectEvent.ts +22 -0
- package/src/common/hooks/useResizeObserver.ts +22 -0
- package/src/i18n/cs.json +3 -0
- package/src/i18n/de.json +3 -0
- package/src/i18n/es.json +3 -0
- package/src/i18n/fr.json +3 -0
- package/src/i18n/hu.json +3 -0
- package/src/i18n/id.json +3 -0
- package/src/i18n/it.json +3 -0
- package/src/i18n/ja.json +3 -0
- package/src/i18n/pl.json +3 -0
- package/src/i18n/pt.json +3 -0
- package/src/i18n/ro.json +3 -0
- package/src/i18n/ru.json +3 -0
- package/src/i18n/th.json +3 -0
- package/src/i18n/tr.json +3 -0
- package/src/i18n/uk.json +3 -0
- package/src/i18n/zh-CN.json +3 -0
- package/src/index.ts +2 -0
- package/src/inputs/Input.tsx +5 -0
- package/src/inputs/InputGroup.css +1 -0
- package/src/inputs/InputGroup.less +61 -0
- package/src/inputs/InputGroup.story.tsx +73 -0
- package/src/inputs/InputGroup.tsx +142 -0
- package/src/main.css +1 -1
- package/src/main.less +1 -0
- package/src/utilities/cssValueWithUnit.ts +3 -0
- package/build/i18n/zh-HK.json +0 -41
- package/src/i18n/zh-HK.json +0 -41
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Search } from '@transferwise/icons';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import ActionButton from '../actionButton';
|
|
6
|
+
|
|
7
|
+
import { Input } from './Input';
|
|
8
|
+
import { InputGroup } from './InputGroup';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
component: InputGroup,
|
|
12
|
+
title: 'Forms/InputGroup',
|
|
13
|
+
} satisfies Meta<typeof InputGroup>;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof InputGroup>;
|
|
16
|
+
|
|
17
|
+
export const WithPrefix: Story = {
|
|
18
|
+
render: (args) => <InputGroupWithPrefix {...args} />,
|
|
19
|
+
args: {
|
|
20
|
+
disabled: false,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function InputGroupWithPrefix(args: NonNullable<Story['args']>) {
|
|
25
|
+
return (
|
|
26
|
+
<InputGroup
|
|
27
|
+
{...args}
|
|
28
|
+
addonStart={{
|
|
29
|
+
content: <Search size={24} />,
|
|
30
|
+
initialContentWidth: 24,
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Input placeholder="Search by name…" />
|
|
34
|
+
</InputGroup>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const WithSuffix: Story = {
|
|
39
|
+
render: (args) => <InputGroupWithSuffix {...args} />,
|
|
40
|
+
args: {
|
|
41
|
+
disabled: false,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function InputGroupWithSuffix(args: NonNullable<Story['args']>) {
|
|
46
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
47
|
+
const [value, setValue] = useState('Text value');
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<InputGroup
|
|
51
|
+
{...args}
|
|
52
|
+
addonEnd={{
|
|
53
|
+
content: (
|
|
54
|
+
<ActionButton
|
|
55
|
+
onClick={async () => {
|
|
56
|
+
await navigator.clipboard.writeText(value);
|
|
57
|
+
if (ref.current != null) {
|
|
58
|
+
ref.current.focus({ preventScroll: true });
|
|
59
|
+
ref.current.select();
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
Copy
|
|
64
|
+
</ActionButton>
|
|
65
|
+
),
|
|
66
|
+
interactive: true,
|
|
67
|
+
padding: 'sm',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<Input ref={ref} value={value} onChange={(event) => setValue(event.currentTarget.value)} />
|
|
71
|
+
</InputGroup>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { createContext, useContext, useMemo, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useResizeObserver } from '../common/hooks/useResizeObserver';
|
|
5
|
+
import { cssValueWithUnit } from '../utilities/cssValueWithUnit';
|
|
6
|
+
|
|
7
|
+
type InputPaddingContextType = [
|
|
8
|
+
number | string | undefined,
|
|
9
|
+
React.Dispatch<React.SetStateAction<number | string | undefined>>,
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const InputPaddingStartContext = createContext<InputPaddingContextType>([undefined, () => {}]);
|
|
13
|
+
|
|
14
|
+
const InputPaddingEndContext = createContext<InputPaddingContextType>([undefined, () => {}]);
|
|
15
|
+
|
|
16
|
+
export function useInputPaddings() {
|
|
17
|
+
const [paddingStart] = useContext(InputPaddingStartContext);
|
|
18
|
+
const [paddingEnd] = useContext(InputPaddingEndContext);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
paddingInlineStart: paddingStart,
|
|
22
|
+
paddingInlineEnd: paddingEnd,
|
|
23
|
+
} satisfies React.CSSProperties;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface InputGroupAddon {
|
|
27
|
+
content: React.ReactNode;
|
|
28
|
+
initialContentWidth?: number | string;
|
|
29
|
+
interactive?: boolean;
|
|
30
|
+
padding?: 'none' | 'sm' | 'md';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface InputGroupProps {
|
|
34
|
+
addonStart?: InputGroupAddon;
|
|
35
|
+
addonEnd?: InputGroupAddon;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
children?: React.ReactNode;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function inputPaddingInitialState({
|
|
42
|
+
initialContentWidth,
|
|
43
|
+
padding = inputAddonDefaultPadding,
|
|
44
|
+
}: Pick<
|
|
45
|
+
InputGroupAddon,
|
|
46
|
+
'initialContentWidth' | 'padding'
|
|
47
|
+
> = {}): () => InputPaddingContextType[0] {
|
|
48
|
+
return () =>
|
|
49
|
+
initialContentWidth != null
|
|
50
|
+
? `calc(${cssValueWithUnit(initialContentWidth)} + ${cssValueWithUnit(
|
|
51
|
+
inputAddonContentWidthAddendByPadding[padding],
|
|
52
|
+
)})`
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function InputGroup({
|
|
57
|
+
addonStart,
|
|
58
|
+
addonEnd,
|
|
59
|
+
disabled,
|
|
60
|
+
className,
|
|
61
|
+
children,
|
|
62
|
+
}: InputGroupProps) {
|
|
63
|
+
const [paddingStart, setPaddingStart] = useState(inputPaddingInitialState(addonStart));
|
|
64
|
+
const [paddingEnd, setPaddingEnd] = useState(inputPaddingInitialState(addonEnd));
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<InputPaddingStartContext.Provider
|
|
68
|
+
value={useMemo(() => [paddingStart, setPaddingStart], [paddingStart])}
|
|
69
|
+
>
|
|
70
|
+
<InputPaddingEndContext.Provider
|
|
71
|
+
value={useMemo(() => [paddingEnd, setPaddingEnd], [paddingEnd])}
|
|
72
|
+
>
|
|
73
|
+
<fieldset disabled={disabled} className={classNames(className, 'np-input-group')}>
|
|
74
|
+
{addonStart != null ? <InputAddon placement="start" {...addonStart} /> : null}
|
|
75
|
+
{children}
|
|
76
|
+
{addonEnd != null ? <InputAddon placement="end" {...addonEnd} /> : null}
|
|
77
|
+
</fieldset>
|
|
78
|
+
</InputPaddingEndContext.Provider>
|
|
79
|
+
</InputPaddingStartContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface InputAddonProps extends Omit<InputGroupAddon, 'initialContentWidth'> {
|
|
84
|
+
placement: 'start' | 'end';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const inputAddonContentWidthAddendByPadding = {
|
|
88
|
+
none: 0,
|
|
89
|
+
sm: '1rem',
|
|
90
|
+
md: '1.5rem',
|
|
91
|
+
} satisfies {
|
|
92
|
+
[key in NonNullable<InputAddonProps['padding']>]: InputPaddingContextType[0];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const inputAddonDefaultPadding = 'md' satisfies InputAddonProps['padding'];
|
|
96
|
+
|
|
97
|
+
function InputAddon({
|
|
98
|
+
placement,
|
|
99
|
+
content,
|
|
100
|
+
interactive,
|
|
101
|
+
padding = inputAddonDefaultPadding,
|
|
102
|
+
}: InputAddonProps) {
|
|
103
|
+
const [, setInputPadding] = useContext(
|
|
104
|
+
placement === 'start' ? InputPaddingStartContext : InputPaddingEndContext,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
108
|
+
useResizeObserver(ref, (entry) => {
|
|
109
|
+
// TODO: Remove fallback once most browsers support `borderBoxSize`
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
111
|
+
if (entry.borderBoxSize != null) {
|
|
112
|
+
setInputPadding(entry.borderBoxSize[0].inlineSize);
|
|
113
|
+
} else {
|
|
114
|
+
const targetStyle = getComputedStyle(entry.target);
|
|
115
|
+
setInputPadding(
|
|
116
|
+
entry.contentRect.width +
|
|
117
|
+
Number.parseFloat(targetStyle.paddingInlineStart) +
|
|
118
|
+
Number.parseFloat(targetStyle.paddingInlineEnd),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<span
|
|
125
|
+
ref={ref}
|
|
126
|
+
className={classNames(
|
|
127
|
+
'np-input-addon',
|
|
128
|
+
{
|
|
129
|
+
'np-input-addon--placement-start': placement === 'start',
|
|
130
|
+
'np-input-addon--placement-end': placement === 'end',
|
|
131
|
+
},
|
|
132
|
+
interactive && 'np-input-addon--interactive',
|
|
133
|
+
{
|
|
134
|
+
'np-input-addon--padding-sm': padding === 'sm',
|
|
135
|
+
'np-input-addon--padding-md': padding === 'md',
|
|
136
|
+
},
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{content}
|
|
140
|
+
</span>
|
|
141
|
+
);
|
|
142
|
+
}
|