@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.
Files changed (71) hide show
  1. package/build/i18n/cs.json +3 -0
  2. package/build/i18n/de.json +3 -0
  3. package/build/i18n/es.json +3 -0
  4. package/build/i18n/fr.json +3 -0
  5. package/build/i18n/hu.json +3 -0
  6. package/build/i18n/id.json +3 -0
  7. package/build/i18n/it.json +3 -0
  8. package/build/i18n/ja.json +3 -0
  9. package/build/i18n/pl.json +3 -0
  10. package/build/i18n/pt.json +3 -0
  11. package/build/i18n/ro.json +3 -0
  12. package/build/i18n/ru.json +3 -0
  13. package/build/i18n/th.json +3 -0
  14. package/build/i18n/tr.json +3 -0
  15. package/build/i18n/uk.json +3 -0
  16. package/build/i18n/zh-CN.json +3 -0
  17. package/build/index.esm.js +166 -2
  18. package/build/index.esm.js.map +1 -1
  19. package/build/index.js +166 -1
  20. package/build/index.js.map +1 -1
  21. package/build/main.css +1 -1
  22. package/build/styles/inputs/InputGroup.css +1 -0
  23. package/build/styles/main.css +1 -1
  24. package/build/types/body/Body.d.ts +2 -2
  25. package/build/types/common/hooks/useEffectEvent.d.ts +2 -0
  26. package/build/types/common/hooks/useEffectEvent.d.ts.map +1 -0
  27. package/build/types/common/hooks/useResizeObserver.d.ts +3 -0
  28. package/build/types/common/hooks/useResizeObserver.d.ts.map +1 -0
  29. package/build/types/common/panel/Panel.d.ts +1 -1
  30. package/build/types/common/responsivePanel/ResponsivePanel.d.ts +1 -1
  31. package/build/types/dimmer/Dimmer.d.ts +1 -1
  32. package/build/types/index.d.ts +2 -0
  33. package/build/types/index.d.ts.map +1 -1
  34. package/build/types/inputs/Input.d.ts +1 -1
  35. package/build/types/inputs/Input.d.ts.map +1 -1
  36. package/build/types/inputs/InputGroup.d.ts +21 -0
  37. package/build/types/inputs/InputGroup.d.ts.map +1 -0
  38. package/build/types/inputs/TextArea.d.ts +1 -1
  39. package/build/types/select/searchBox/SearchBox.d.ts +1 -1
  40. package/build/types/utilities/cssValueWithUnit.d.ts +2 -0
  41. package/build/types/utilities/cssValueWithUnit.d.ts.map +1 -0
  42. package/package.json +4 -4
  43. package/src/common/hooks/useEffectEvent.ts +22 -0
  44. package/src/common/hooks/useResizeObserver.ts +22 -0
  45. package/src/i18n/cs.json +3 -0
  46. package/src/i18n/de.json +3 -0
  47. package/src/i18n/es.json +3 -0
  48. package/src/i18n/fr.json +3 -0
  49. package/src/i18n/hu.json +3 -0
  50. package/src/i18n/id.json +3 -0
  51. package/src/i18n/it.json +3 -0
  52. package/src/i18n/ja.json +3 -0
  53. package/src/i18n/pl.json +3 -0
  54. package/src/i18n/pt.json +3 -0
  55. package/src/i18n/ro.json +3 -0
  56. package/src/i18n/ru.json +3 -0
  57. package/src/i18n/th.json +3 -0
  58. package/src/i18n/tr.json +3 -0
  59. package/src/i18n/uk.json +3 -0
  60. package/src/i18n/zh-CN.json +3 -0
  61. package/src/index.ts +2 -0
  62. package/src/inputs/Input.tsx +5 -0
  63. package/src/inputs/InputGroup.css +1 -0
  64. package/src/inputs/InputGroup.less +61 -0
  65. package/src/inputs/InputGroup.story.tsx +73 -0
  66. package/src/inputs/InputGroup.tsx +142 -0
  67. package/src/main.css +1 -1
  68. package/src/main.less +1 -0
  69. package/src/utilities/cssValueWithUnit.ts +3 -0
  70. package/build/i18n/zh-HK.json +0 -41
  71. 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
+ }