@transferwise/components 46.116.1 → 46.117.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.
Files changed (59) hide show
  1. package/build/main.css +60 -131
  2. package/build/prompt/InlinePrompt/InlinePrompt.js +14 -8
  3. package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
  4. package/build/prompt/InlinePrompt/InlinePrompt.mjs +15 -9
  5. package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
  6. package/build/sentimentSurface/SentimentSurface.js +6 -5
  7. package/build/sentimentSurface/SentimentSurface.js.map +1 -1
  8. package/build/sentimentSurface/SentimentSurface.mjs +6 -5
  9. package/build/sentimentSurface/SentimentSurface.mjs.map +1 -1
  10. package/build/styles/button/Button.css +17 -17
  11. package/build/styles/button/Button.vars.css +16 -16
  12. package/build/styles/iconButton/IconButton.css +8 -8
  13. package/build/styles/link/Link.css +1 -0
  14. package/build/styles/main.css +60 -131
  15. package/build/styles/prompt/InlinePrompt/InlinePrompt.css +26 -105
  16. package/build/styles/sentimentSurface/SentimentSurface.css +8 -1
  17. package/build/types/prompt/InlinePrompt/InlinePrompt.d.ts +19 -3
  18. package/build/types/prompt/InlinePrompt/InlinePrompt.d.ts.map +1 -1
  19. package/build/types/sentimentSurface/SentimentSurface.d.ts +5 -4
  20. package/build/types/sentimentSurface/SentimentSurface.d.ts.map +1 -1
  21. package/build/types/sentimentSurface/SentimentSurface.types.d.ts +4 -16
  22. package/build/types/sentimentSurface/SentimentSurface.types.d.ts.map +1 -1
  23. package/build/types/test-utils/story-config.d.ts +24 -0
  24. package/build/types/test-utils/story-config.d.ts.map +1 -1
  25. package/package.json +12 -11
  26. package/src/button/Button.css +17 -17
  27. package/src/button/Button.less +1 -1
  28. package/src/button/Button.story.tsx +75 -110
  29. package/src/button/Button.tests.story.tsx +189 -0
  30. package/src/button/Button.vars.css +16 -16
  31. package/src/button/Button.vars.less +58 -18
  32. package/src/iconButton/IconButton.css +8 -8
  33. package/src/iconButton/IconButton.less +35 -4
  34. package/src/iconButton/IconButton.story.tsx +72 -3
  35. package/src/link/Link.css +1 -0
  36. package/src/link/Link.less +1 -0
  37. package/src/link/Link.story.tsx +28 -0
  38. package/src/main.css +60 -131
  39. package/src/prompt/InlinePrompt/InlinePrompt.css +26 -105
  40. package/src/prompt/InlinePrompt/InlinePrompt.less +31 -119
  41. package/src/prompt/InlinePrompt/InlinePrompt.spec.tsx +87 -29
  42. package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +223 -31
  43. package/src/prompt/InlinePrompt/InlinePrompt.tsx +42 -11
  44. package/src/sentimentSurface/SentimentSurface.css +8 -1
  45. package/src/sentimentSurface/SentimentSurface.docs.mdx +32 -495
  46. package/src/sentimentSurface/SentimentSurface.less +121 -114
  47. package/src/sentimentSurface/SentimentSurface.spec.tsx +31 -11
  48. package/src/sentimentSurface/SentimentSurface.story.tsx +323 -108
  49. package/src/sentimentSurface/SentimentSurface.tests.story.tsx +90 -40
  50. package/src/sentimentSurface/SentimentSurface.tsx +16 -9
  51. package/src/sentimentSurface/SentimentSurface.types.ts +5 -20
  52. package/src/test-utils/story-config.ts +0 -1
  53. package/build/sentimentSurface/classMap.js +0 -17
  54. package/build/sentimentSurface/classMap.js.map +0 -1
  55. package/build/sentimentSurface/classMap.mjs +0 -14
  56. package/build/sentimentSurface/classMap.mjs.map +0 -1
  57. package/build/types/sentimentSurface/classMap.d.ts +0 -4
  58. package/build/types/sentimentSurface/classMap.d.ts.map +0 -1
  59. package/src/sentimentSurface/classMap.ts +0 -15
@@ -1,39 +1,231 @@
1
- import { lorem10, lorem5 } from '../../test-utils';
2
- import { InlinePrompt } from './InlinePrompt';
3
- import { CardWise, Rewards } from '@transferwise/icons';
1
+ import type { ReactElement } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-webpack5';
3
+ import { Taxi, Travel } from '@transferwise/icons';
4
+ import { lorem5 } from '../../test-utils';
5
+ import Link from '../../link';
6
+ import { InlinePrompt, InlinePromptProps } from './InlinePrompt';
4
7
 
5
- export default {
6
- title: 'Internal/InlinePrompt',
7
- component: InlinePrompt,
8
- };
9
-
10
- export const AllVariants = () => {
11
- return (
8
+ const withComponentGrid =
9
+ ({ maxWidth = 'auto', gap = '1rem' } = {}) =>
10
+ (Story: () => ReactElement) => (
12
11
  <div
13
12
  style={{
14
- display: 'grid',
15
- gridAutoFlow: 'row',
16
- rowGap: 'var(--size-12)',
17
- justifyItems: 'start',
18
- width: 'fit-content',
13
+ width: '100%',
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ gap,
17
+ maxWidth,
19
18
  }}
20
19
  >
21
- <InlinePrompt sentiment="positive">{lorem5}</InlinePrompt>
22
- <InlinePrompt media={<Rewards />} sentiment="positive">
23
- {lorem10}
24
- </InlinePrompt>
25
- <InlinePrompt sentiment="negative">{lorem5}</InlinePrompt>
26
- <InlinePrompt sentiment="negative">{lorem10}</InlinePrompt>
27
- <InlinePrompt sentiment="neutral">{lorem5}</InlinePrompt>
28
- <InlinePrompt sentiment="neutral">{lorem10}</InlinePrompt>
29
- <InlinePrompt sentiment="warning">{lorem5}</InlinePrompt>
30
- <InlinePrompt sentiment="warning">{lorem10}</InlinePrompt>
31
- <InlinePrompt sentiment="proposition">{lorem5}</InlinePrompt>
32
- <InlinePrompt sentiment="proposition">{lorem10}</InlinePrompt>
33
- <InlinePrompt sentiment="proposition" media={<CardWise />}>
34
- {lorem10}
35
- </InlinePrompt>
36
- <InlinePrompt>{lorem5}</InlinePrompt>
20
+ <Story />
37
21
  </div>
38
22
  );
23
+
24
+ export default {
25
+ title: 'Internal/InlinePrompt',
26
+ component: InlinePrompt,
27
+ args: {
28
+ loading: false,
29
+ muted: false,
30
+ children: lorem5,
31
+ },
32
+ argTypes: {
33
+ sentiment: {
34
+ control: 'select',
35
+ options: ['positive', 'negative', 'neutral', 'warning', 'proposition'],
36
+ },
37
+ loading: {
38
+ control: 'boolean',
39
+ },
40
+ muted: {
41
+ control: 'boolean',
42
+ },
43
+ children: {
44
+ control: 'text',
45
+ table: {
46
+ type: { summary: 'ReactNode' },
47
+ },
48
+ },
49
+ },
50
+ parameters: {
51
+ docs: {
52
+ toc: true,
53
+ },
54
+ },
55
+ } satisfies Meta<InlinePromptProps>;
56
+
57
+ /**
58
+ * Convenience controls for previewing rich markup,
59
+ * not otherwise possible via Storybook
60
+ */
61
+ type PreviewStoryArgs = InlinePromptProps & {
62
+ previewMedia: InlinePromptProps['media'];
63
+ };
64
+
65
+ const previewArgGroup = {
66
+ category: 'Storybook Preview options',
67
+ type: {
68
+ summary: undefined,
69
+ },
70
+ };
71
+
72
+ const previewArgTypes = {
73
+ previewMedia: {
74
+ name: 'Preview with `media`',
75
+ control: 'boolean',
76
+ mapping: {
77
+ false: undefined,
78
+ true: <Taxi />,
79
+ },
80
+ table: previewArgGroup,
81
+ },
82
+ } as const;
83
+
84
+ const getPropsForPreview = (
85
+ args: PreviewStoryArgs,
86
+ ): [InlinePromptProps, Partial<InlinePromptProps>] => {
87
+ const { previewMedia, ...props } = args;
88
+
89
+ return [
90
+ props,
91
+ {
92
+ media: previewMedia,
93
+ },
94
+ ];
95
+ };
96
+
97
+ export const Playground: StoryObj<PreviewStoryArgs> = {
98
+ tags: ['!autodocs'],
99
+ argTypes: previewArgTypes,
100
+ args: {
101
+ previewMedia: false,
102
+ },
103
+ render: (args: PreviewStoryArgs) => {
104
+ const [props, previewProps] = getPropsForPreview(args);
105
+ return <InlinePrompt {...props} {...previewProps} />;
106
+ },
107
+ };
108
+
109
+ /**
110
+ * Aside from the known `neutral`, `negative`, `warning` and `positive` sentiments, `InlinePrompt` is the first of the prompts to introduce a new `proposition` sentiment, which can be used to encourage the user to take an action that might benefit them.
111
+ *
112
+ * By default, its associated icon for the prompt is `GiftBox`.
113
+ */
114
+ export const Sentiments: StoryObj<PreviewStoryArgs> = {
115
+ argTypes: previewArgTypes,
116
+ args: {
117
+ previewMedia: false,
118
+ },
119
+ render: (args: PreviewStoryArgs) => {
120
+ const [props, previewProps] = getPropsForPreview(args);
121
+ return (
122
+ <>
123
+ <InlinePrompt {...props} {...previewProps} sentiment="neutral">
124
+ This is a neutral prompt.
125
+ </InlinePrompt>
126
+ <InlinePrompt {...props} {...previewProps} sentiment="negative">
127
+ This is a negative prompt.
128
+ </InlinePrompt>
129
+ <InlinePrompt {...props} {...previewProps} sentiment="warning">
130
+ This is a warning prompt.
131
+ </InlinePrompt>
132
+ <InlinePrompt {...props} {...previewProps} sentiment="positive">
133
+ This is a positive prompt.
134
+ </InlinePrompt>
135
+ <InlinePrompt {...props} {...previewProps} sentiment="proposition">
136
+ This is a proposition prompt.
137
+ </InlinePrompt>
138
+ </>
139
+ );
140
+ },
141
+ decorators: [withComponentGrid()],
142
+ };
143
+
144
+ /**
145
+ * Inline prompts are also used when forms need to check something in the background, like
146
+ * verifying an account number is real. Turning on the loading state will replace the icon with
147
+ * a spinner while waiting for the check to finish.
148
+ */
149
+ export const Loading: StoryObj<PreviewStoryArgs> = {
150
+ argTypes: previewArgTypes,
151
+ args: {
152
+ loading: true,
153
+ previewMedia: false,
154
+ },
155
+ render: (args: PreviewStoryArgs) => {
156
+ const [props, previewProps] = getPropsForPreview(args);
157
+ return <InlinePrompt {...props} {...previewProps} />;
158
+ },
159
+ };
160
+
161
+ /**
162
+ * Inline prompt is usually associated with a different component, such as `Input` or `ListItem`.
163
+ * When those components are disabled, the prompt is often used to communicate to the user why they
164
+ * cannot interact with them. For that reason, the prompt cannot inherit the usual disabled visual
165
+ * style, and it must retain its interactivity. To bring these 2 components visually closer to each
166
+ * other, `muted` prop introduces slightly reduced opacity and luminosity as well as a special
167
+ * backslash circle icon.
168
+ */
169
+ export const Muted: StoryObj<PreviewStoryArgs> = {
170
+ argTypes: previewArgTypes,
171
+ args: {
172
+ muted: true,
173
+ previewMedia: false,
174
+ },
175
+ render: (args: PreviewStoryArgs) => {
176
+ const [props, previewProps] = getPropsForPreview(args);
177
+ return <InlinePrompt {...props} {...previewProps} />;
178
+ },
179
+ };
180
+
181
+ /**
182
+ * While main sentiments (`warning`, `negative`, `neutral`) must retain their associated
183
+ * `StatusIcons`, the `positive` and `proposition` ones allow for Icon overrides to bring it
184
+ * closer to the prompt's content.
185
+ */
186
+ export const IconOverrides: StoryObj<PreviewStoryArgs> = {
187
+ render: (args: PreviewStoryArgs) => {
188
+ return (
189
+ <>
190
+ <InlinePrompt {...args} media={<Travel />} sentiment="positive">
191
+ Your travel account is set up and ready to use.
192
+ </InlinePrompt>
193
+ <InlinePrompt {...args} media={<Taxi />} sentiment="proposition">
194
+ Connect Wise with your taxi app to get exclusive discounts.
195
+ </InlinePrompt>
196
+ <InlinePrompt {...args} media={<Taxi />} sentiment="negative">
197
+ This icon override is ignored.
198
+ </InlinePrompt>
199
+ </>
200
+ );
201
+ },
202
+ decorators: [withComponentGrid()],
203
+ };
204
+
205
+ /**
206
+ * When configured with any of the supported sentiments, the colour scheme of the component will propagate to all of its supported descendants, such as instances of a `Link`, `Icon`, and `StatusIcon`.
207
+ */
208
+ export const SentimentAwareness: StoryObj<PreviewStoryArgs> = {
209
+ argTypes: previewArgTypes,
210
+ args: {
211
+ previewMedia: false,
212
+ },
213
+ render: (args: PreviewStoryArgs) => {
214
+ const [props, previewProps] = getPropsForPreview(args);
215
+
216
+ return (
217
+ <>
218
+ <InlinePrompt {...props} {...previewProps} sentiment="negative">
219
+ This is a negative prompt with an <Link href="#">inline link</Link>.
220
+ </InlinePrompt>
221
+ <InlinePrompt {...props} {...previewProps} sentiment="positive">
222
+ This is a positive prompt with an <Link href="#">inline link</Link>.
223
+ </InlinePrompt>
224
+ <InlinePrompt {...props} {...previewProps} sentiment="proposition">
225
+ This is a proposition prompt with an <Link href="#">inline link</Link>.
226
+ </InlinePrompt>
227
+ </>
228
+ );
229
+ },
230
+ decorators: [withComponentGrid()],
39
231
  };
@@ -4,23 +4,40 @@ import ProcessIndicator from '../../processIndicator';
4
4
  import StatusIcon from '../../statusIcon';
5
5
  import { clsx } from 'clsx';
6
6
  import Body from '../../body';
7
+ import SentimentSurface from '../../sentimentSurface';
7
8
 
8
9
  export type InlinePromptProps = {
10
+ /**
11
+ * The sentiment determines the colour scheme
12
+ */
9
13
  sentiment?:
10
14
  | `${Sentiment.POSITIVE | Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.WARNING}`
11
15
  | 'proposition';
16
+ /**
17
+ * Replaces the icon with a spinner while waiting for the short-lived action to finish.
18
+ * @default false
19
+ */
12
20
  loading?: boolean;
13
21
  /**
14
- * Use for short-lived inline prompts to avoid swap of the icon (which is bad UX for short-lived prompts, e.g. when submit form)
22
+ * While prompts cannot be fully (visually and functionally) disabled, this prop should be enabled
23
+ * they are associated with actually disabled component (e.g. a disabled list item or input).
24
+ * @default false
15
25
  */
16
26
  muted?: boolean;
27
+ /**
28
+ * Icon override for `proposition` and `positive` sentiments. Unsupported for remaining ones.
29
+ */
30
+ media?: React.ReactNode;
17
31
  id?: string;
18
32
  className?: string;
19
33
  'data-testid'?: string;
20
34
  children: React.ReactNode;
21
- media?: React.ReactNode;
22
35
  };
23
36
 
37
+ /**
38
+ * Inline prompts appear alongside a specific component on the screen. They help the user stay
39
+ * informed, fix something, or get more out of what they're doing.
40
+ */
24
41
  export const InlinePrompt = ({
25
42
  sentiment = Sentiment.POSITIVE,
26
43
  muted = false,
@@ -28,26 +45,40 @@ export const InlinePrompt = ({
28
45
  className,
29
46
  children,
30
47
  media = null,
48
+ 'data-testid': dataTestId,
31
49
  ...rest
32
50
  }: InlinePromptProps) => {
51
+ const surfaceSentiment = sentiment === Sentiment.POSITIVE ? 'success' : sentiment;
52
+
33
53
  const renderMedia = () => {
34
- if (media && ['proposition', 'positive'].includes(sentiment)) {
35
- return media;
36
- }
37
- if (sentiment === 'proposition') {
38
- return <GiftBox />;
39
- }
40
54
  if (muted) {
41
55
  return <BackslashCircle size={16} data-testid="InlinePrompt_Muted" />;
42
56
  }
43
57
  if (loading) {
44
- return <ProcessIndicator data-testid="InlinePrompt_ProcessIndicator" size="xxs" />;
58
+ return (
59
+ <ProcessIndicator
60
+ data-testid="InlinePrompt_ProcessIndicator"
61
+ size="xxs"
62
+ className="wds-inline-prompt-process-indicator"
63
+ />
64
+ );
65
+ }
66
+
67
+ if (sentiment === 'positive' && media) {
68
+ return media;
45
69
  }
70
+
71
+ if (sentiment === 'proposition') {
72
+ return media || <GiftBox />;
73
+ }
74
+
46
75
  return <StatusIcon size={16} sentiment={sentiment} />;
47
76
  };
48
77
 
49
78
  return (
50
- <div
79
+ <SentimentSurface
80
+ sentiment={surfaceSentiment}
81
+ data-testid={dataTestId}
51
82
  className={clsx(
52
83
  'wds-inline-prompt',
53
84
  `wds-inline-prompt--${sentiment}`,
@@ -61,6 +92,6 @@ export const InlinePrompt = ({
61
92
  >
62
93
  <div className="wds-inline-prompt__media-wrapper">{renderMedia()}</div>
63
94
  <Body>{children}</Body>
64
- </div>
95
+ </SentimentSurface>
65
96
  );
66
97
  };
@@ -1,3 +1,10 @@
1
+ .wds-sentiment-surface {
2
+ --ring-outline-color: var(--color-sentiment-content-primary, var(--color-content-primary));
3
+ }
4
+ .wds-sentiment-surface--hasBaseStyles {
5
+ background-color: var(--color-sentiment-background-surface);
6
+ color: var(--color-sentiment-content-primary);
7
+ }
1
8
  .np-theme-personal .wds-sentiment-surface-negative-base,
2
9
  .np-theme-personal--bright-green .wds-sentiment-surface-negative-base {
3
10
  --color-sentiment-content-primary: #CB272F;
@@ -32,7 +39,7 @@
32
39
  --color-sentiment-interactive-secondary-active: #A72027;
33
40
  --color-sentiment-interactive-secondary-neutral: #9B141B;
34
41
  --color-sentiment-interactive-secondary-neutral-hover: #831116;
35
- --color-sentiment-interactive-secondary-neutral-active: #6D0E13;
42
+ --color-sentiment-interactive-secondary-neutral-active: #6D0e13;
36
43
  --color-sentiment-interactive-control: #CB272F;
37
44
  --color-sentiment-interactive-control-hover: #B8232B;
38
45
  --color-sentiment-interactive-control-active: #A72027;