@transferwise/components 46.115.0 → 46.116.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 (65) hide show
  1. package/build/criticalBanner/CriticalCommsBanner.js +1 -0
  2. package/build/criticalBanner/CriticalCommsBanner.js.map +1 -1
  3. package/build/criticalBanner/CriticalCommsBanner.mjs +1 -0
  4. package/build/criticalBanner/CriticalCommsBanner.mjs.map +1 -1
  5. package/build/main.css +428 -36
  6. package/build/mocks.js +7 -0
  7. package/build/mocks.js.map +1 -1
  8. package/build/mocks.mjs +7 -1
  9. package/build/mocks.mjs.map +1 -1
  10. package/build/sentimentSurface/SentimentSurface.js +43 -0
  11. package/build/sentimentSurface/SentimentSurface.js.map +1 -0
  12. package/build/sentimentSurface/SentimentSurface.mjs +39 -0
  13. package/build/sentimentSurface/SentimentSurface.mjs.map +1 -0
  14. package/build/sentimentSurface/classMap.js +17 -0
  15. package/build/sentimentSurface/classMap.js.map +1 -0
  16. package/build/sentimentSurface/classMap.mjs +14 -0
  17. package/build/sentimentSurface/classMap.mjs.map +1 -0
  18. package/build/statusIcon/StatusIcon.js +10 -1
  19. package/build/statusIcon/StatusIcon.js.map +1 -1
  20. package/build/statusIcon/StatusIcon.mjs +10 -1
  21. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  22. package/build/styles/main.css +428 -36
  23. package/build/styles/sentimentSurface/SentimentSurface.css +424 -0
  24. package/build/styles/statusIcon/StatusIcon.css +4 -36
  25. package/build/types/criticalBanner/CriticalCommsBanner.d.ts +2 -1
  26. package/build/types/criticalBanner/CriticalCommsBanner.d.ts.map +1 -1
  27. package/build/types/mocks.d.ts +1 -0
  28. package/build/types/mocks.d.ts.map +1 -1
  29. package/build/types/sentimentSurface/SentimentSurface.d.ts +30 -0
  30. package/build/types/sentimentSurface/SentimentSurface.d.ts.map +1 -0
  31. package/build/types/sentimentSurface/SentimentSurface.types.d.ts +80 -0
  32. package/build/types/sentimentSurface/SentimentSurface.types.d.ts.map +1 -0
  33. package/build/types/sentimentSurface/classMap.d.ts +4 -0
  34. package/build/types/sentimentSurface/classMap.d.ts.map +1 -0
  35. package/build/types/sentimentSurface/index.d.ts +3 -0
  36. package/build/types/sentimentSurface/index.d.ts.map +1 -0
  37. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  38. package/build/types/test-utils/window-mock.d.ts +1 -0
  39. package/build/types/test-utils/window-mock.d.ts.map +1 -1
  40. package/package.json +3 -8
  41. package/src/criticalBanner/CriticalCommsBanner.tsx +3 -2
  42. package/src/expressiveMoneyInput/ExpressiveMoneyInput.spec.tsx +229 -0
  43. package/src/expressiveMoneyInput/amountInput/AmountInput.spec.tsx +282 -0
  44. package/src/expressiveMoneyInput/currencySelector/CurrencySelector.spec.tsx +160 -0
  45. package/src/inputs/SelectInput.spec.tsx +7 -1
  46. package/src/main.css +428 -36
  47. package/src/main.less +2 -0
  48. package/src/mocks.ts +7 -0
  49. package/src/moneyInput/MoneyInput.spec.tsx +9 -1
  50. package/src/provider/theme/ThemeProvider.story.tsx +78 -11
  51. package/src/sentimentSurface/SentimentSurface.css +424 -0
  52. package/src/sentimentSurface/SentimentSurface.docs.mdx +527 -0
  53. package/src/sentimentSurface/SentimentSurface.less +296 -0
  54. package/src/sentimentSurface/SentimentSurface.spec.tsx +140 -0
  55. package/src/sentimentSurface/SentimentSurface.story.tsx +340 -0
  56. package/src/sentimentSurface/SentimentSurface.tests.story.tsx +123 -0
  57. package/src/sentimentSurface/SentimentSurface.tsx +72 -0
  58. package/src/sentimentSurface/SentimentSurface.types.ts +104 -0
  59. package/src/sentimentSurface/classMap.ts +15 -0
  60. package/src/sentimentSurface/index.ts +8 -0
  61. package/src/statusIcon/StatusIcon.css +4 -36
  62. package/src/statusIcon/StatusIcon.less +3 -41
  63. package/src/statusIcon/StatusIcon.tsx +14 -1
  64. package/src/test-utils/jest.setup.ts +0 -5
  65. package/src/test-utils/window-mock.ts +5 -0
@@ -0,0 +1,340 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import SentimentSurface from './SentimentSurface';
3
+ import { storyConfig } from '../test-utils';
4
+ import type { Sentiment, Emphasis } from './SentimentSurface.types';
5
+
6
+ const withContainer = (Story: () => JSX.Element) => (
7
+ <div style={{ display: 'flex', justifyContent: 'center', padding: '2rem' }}>
8
+ <Story />
9
+ </div>
10
+ );
11
+
12
+ const withComponentGrid = (Story: () => JSX.Element) => (
13
+ <div
14
+ style={{
15
+ width: '100%',
16
+ display: 'flex',
17
+ flexDirection: 'column',
18
+ gap: '1rem',
19
+ maxWidth: '800px',
20
+ }}
21
+ >
22
+ <Story />
23
+ </div>
24
+ );
25
+
26
+ /**
27
+ * SentimentSurface is a polymorphic container component that applies contextual background colours
28
+ * and text styling based on sentiment types.
29
+ */
30
+ const meta: Meta<typeof SentimentSurface> = {
31
+ component: SentimentSurface,
32
+ title: 'Content/SentimentSurface',
33
+ argTypes: {
34
+ sentiment: {
35
+ control: 'select',
36
+ options: ['negative', 'warning', 'neutral', 'success', 'proposition'],
37
+ description: 'The sentiment type that determines the colour scheme',
38
+ },
39
+ emphasis: {
40
+ control: 'select',
41
+ options: ['base', 'elevated'],
42
+ description: 'The emphasis level affecting background and text contrast',
43
+ table: {
44
+ defaultValue: { summary: 'base' },
45
+ },
46
+ },
47
+ as: {
48
+ control: 'text',
49
+ description: 'The underlying HTML element to render',
50
+ table: {
51
+ defaultValue: { summary: 'div' },
52
+ },
53
+ },
54
+ children: {
55
+ control: 'text',
56
+ description: 'Content to render inside the surface',
57
+ },
58
+ className: {
59
+ control: 'text',
60
+ description: 'Additional CSS classes',
61
+ },
62
+ id: {
63
+ control: 'text',
64
+ description: 'Unique identifier for the component',
65
+ },
66
+ testId: {
67
+ control: 'text',
68
+ description: 'Test ID for React Testing Library',
69
+ },
70
+ },
71
+ args: {
72
+ sentiment: 'neutral',
73
+ emphasis: 'base',
74
+ children: 'This is a sentiment surface',
75
+ style: { padding: '24px', borderRadius: '16px' },
76
+ },
77
+ decorators: [withContainer],
78
+ parameters: {
79
+ docs: {
80
+ toc: true,
81
+ },
82
+ },
83
+ };
84
+
85
+ export default meta;
86
+
87
+ type Story = StoryObj<typeof SentimentSurface>;
88
+
89
+ /**
90
+ * The Playground story allows you to experiment with all the props of the SentimentSurface component.
91
+ * Use the controls below to customise the sentiment type, emphasis level, and content.
92
+ */
93
+ export const Playground: Story = {
94
+ args: {
95
+ sentiment: 'negative',
96
+ emphasis: 'base',
97
+ children: 'Customise the sentiment and emphasis using the controls below',
98
+ },
99
+ };
100
+
101
+ /**
102
+ * There are five different sentiment types that communicate different types of information or states.
103
+ */
104
+ export const Sentiments: Story = {
105
+ render: (args) => (
106
+ <>
107
+ <SentimentSurface {...args} sentiment="negative">
108
+ Negative: Your payment has failed
109
+ </SentimentSurface>
110
+ <SentimentSurface {...args} sentiment="warning">
111
+ Warning: Action required on your account
112
+ </SentimentSurface>
113
+ <SentimentSurface {...args} sentiment="neutral">
114
+ Neutral: Your account is up to date
115
+ </SentimentSurface>
116
+ <SentimentSurface {...args} sentiment="success">
117
+ Success: Your payment was successful
118
+ </SentimentSurface>
119
+ <SentimentSurface {...args} sentiment="proposition">
120
+ Proposition: Try our new feature
121
+ </SentimentSurface>
122
+ </>
123
+ ),
124
+ parameters: {
125
+ controls: { disable: true },
126
+ },
127
+ decorators: [withComponentGrid],
128
+ };
129
+
130
+ /**
131
+ * Each sentiment type has two emphasis levels – base and elevated – to provide different levels of visual prominence.
132
+ */
133
+ export const EmphasisLevels: Story = {
134
+ render: (args) => (
135
+ <>
136
+ <SentimentSurface {...args} sentiment="negative" emphasis="base">
137
+ Negative - Base emphasis
138
+ </SentimentSurface>
139
+ <SentimentSurface {...args} sentiment="negative" emphasis="elevated">
140
+ Negative - Elevated emphasis
141
+ </SentimentSurface>
142
+ <SentimentSurface {...args} sentiment="success" emphasis="base">
143
+ Success - Base emphasis
144
+ </SentimentSurface>
145
+ <SentimentSurface {...args} sentiment="success" emphasis="elevated">
146
+ Success - Elevated emphasis
147
+ </SentimentSurface>
148
+ </>
149
+ ),
150
+ parameters: {
151
+ controls: { disable: true },
152
+ },
153
+ decorators: [withComponentGrid],
154
+ };
155
+
156
+ /**
157
+ * The component can be rendered as any HTML element using the `as` prop.
158
+ */
159
+ export const PolymorphicRendering: Story = {
160
+ render: (args) => (
161
+ <>
162
+ <SentimentSurface {...args} sentiment="negative">
163
+ Rendered as div (default)
164
+ </SentimentSurface>
165
+ <SentimentSurface {...args} sentiment="negative" as="section">
166
+ Rendered as section
167
+ </SentimentSurface>
168
+ <SentimentSurface {...args} sentiment="negative" as="article">
169
+ Rendered as article
170
+ </SentimentSurface>
171
+ </>
172
+ ),
173
+ parameters: {
174
+ controls: { disable: true },
175
+ },
176
+ decorators: [withComponentGrid],
177
+ };
178
+
179
+ const sentiments: Sentiment[] = ['negative', 'warning', 'neutral', 'success', 'proposition'];
180
+ const emphasisLevels: Emphasis[] = ['base', 'elevated'];
181
+ const tokenCategories = [
182
+ {
183
+ name: 'Content',
184
+ tokens: [
185
+ '--color-sentiment-content-primary',
186
+ '--color-sentiment-content-primary-hover',
187
+ '--color-sentiment-content-primary-active',
188
+ ],
189
+ },
190
+ {
191
+ name: 'Interactive Primary',
192
+ tokens: [
193
+ '--color-sentiment-interactive-primary',
194
+ '--color-sentiment-interactive-primary-hover',
195
+ '--color-sentiment-interactive-primary-active',
196
+ ],
197
+ },
198
+ {
199
+ name: 'Interactive Secondary',
200
+ tokens: [
201
+ '--color-sentiment-interactive-secondary',
202
+ '--color-sentiment-interactive-secondary-hover',
203
+ '--color-sentiment-interactive-secondary-active',
204
+ ],
205
+ },
206
+ {
207
+ name: 'Interactive Control',
208
+ tokens: [
209
+ '--color-sentiment-interactive-control',
210
+ '--color-sentiment-interactive-control-hover',
211
+ '--color-sentiment-interactive-control-active',
212
+ ],
213
+ },
214
+ {
215
+ name: 'Background Surface',
216
+ tokens: [
217
+ '--color-sentiment-background-surface',
218
+ '--color-sentiment-background-surface-hover',
219
+ '--color-sentiment-background-surface-active',
220
+ ],
221
+ },
222
+ ];
223
+
224
+ const TokenSwatch = ({ token }: { token: string }) => (
225
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '4px' }}>
226
+ <div
227
+ style={{
228
+ width: '32px',
229
+ height: '32px',
230
+ borderRadius: '4px',
231
+ backgroundColor: `var(${token})`,
232
+ border: '1px solid rgba(0,0,0,0.1)',
233
+ flexShrink: 0,
234
+ }}
235
+ />
236
+ <code style={{ fontSize: '12px', color: 'var(--color-sentiment-content-primary)' }}>
237
+ {token}
238
+ </code>
239
+ </div>
240
+ );
241
+
242
+ /**
243
+ * Visual demonstration of the CSS custom properties (tokens) set by each sentiment and emphasis combination.
244
+ * These tokens enable child components to automatically adapt their styling to the sentiment context.
245
+ */
246
+ export const Tokens: Story = {
247
+ render: () => (
248
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '32px', maxWidth: '100%' }}>
249
+ {sentiments.map((sentiment) =>
250
+ emphasisLevels.map((emphasis) => (
251
+ <SentimentSurface
252
+ key={`${sentiment}-${emphasis}`}
253
+ sentiment={sentiment}
254
+ emphasis={emphasis}
255
+ style={{ padding: '24px', borderRadius: '16px' }}
256
+ >
257
+ <div style={{ marginBottom: '16px' }}>
258
+ <strong
259
+ style={{
260
+ fontSize: '16px',
261
+ textTransform: 'capitalize',
262
+ color: 'var(--color-sentiment-content-primary)',
263
+ }}
264
+ >
265
+ {sentiment} - {emphasis}
266
+ </strong>
267
+ </div>
268
+ <div
269
+ style={{
270
+ display: 'grid',
271
+ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
272
+ gap: '24px',
273
+ }}
274
+ >
275
+ {tokenCategories.map((category) => (
276
+ <div key={category.name}>
277
+ <div
278
+ style={{
279
+ fontSize: '12px',
280
+ fontWeight: 600,
281
+ marginBottom: '8px',
282
+ textTransform: 'uppercase',
283
+ letterSpacing: '0.5px',
284
+ opacity: 0.7,
285
+ }}
286
+ >
287
+ {category.name}
288
+ </div>
289
+ {category.tokens.map((token) => (
290
+ <TokenSwatch key={token} token={token} />
291
+ ))}
292
+ </div>
293
+ ))}
294
+ </div>
295
+ </SentimentSurface>
296
+ )),
297
+ )}
298
+ </div>
299
+ ),
300
+ parameters: {
301
+ controls: { disable: true },
302
+ },
303
+ decorators: [
304
+ (Story: () => JSX.Element) => (
305
+ <div style={{ padding: '2rem', maxWidth: '1200px' }}>
306
+ <Story />
307
+ </div>
308
+ ),
309
+ ],
310
+ };
311
+
312
+ export const AllVariants = storyConfig(
313
+ {
314
+ tags: ['!autodocs'],
315
+ parameters: {
316
+ padding: '0',
317
+ controls: { disable: true },
318
+ },
319
+ render: () => (
320
+ <div
321
+ className="sentiment-surface-variants"
322
+ style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxWidth: '800px' }}
323
+ >
324
+ {sentiments.map((sentiment) =>
325
+ emphasisLevels.map((emphasis) => (
326
+ <SentimentSurface
327
+ key={`${sentiment}-${emphasis}`}
328
+ sentiment={sentiment}
329
+ emphasis={emphasis}
330
+ style={{ padding: '24px', borderRadius: '16px' }}
331
+ >
332
+ {sentiment} - {emphasis} emphasis
333
+ </SentimentSurface>
334
+ )),
335
+ )}
336
+ </div>
337
+ ),
338
+ },
339
+ { variants: ['default', 'dark', 'bright-green', 'forest-green'] },
340
+ );
@@ -0,0 +1,123 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { expect, within } from 'storybook/test';
3
+ import SentimentSurface from './SentimentSurface';
4
+ import { storyConfig } from '../test-utils';
5
+
6
+ export default {
7
+ component: SentimentSurface,
8
+ title: 'Content/SentimentSurface/Tests',
9
+ } satisfies Meta<typeof SentimentSurface>;
10
+
11
+ type Story = StoryObj<typeof SentimentSurface>;
12
+
13
+ export const AllSentiments: Story = {
14
+ render: () => (
15
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
16
+ <SentimentSurface sentiment="negative">
17
+ Negative sentiment - Your payment has failed
18
+ </SentimentSurface>
19
+ <SentimentSurface sentiment="warning">
20
+ Warning sentiment - Action required on your account
21
+ </SentimentSurface>
22
+ <SentimentSurface sentiment="neutral">
23
+ Neutral sentiment - Your account is up to date
24
+ </SentimentSurface>
25
+ <SentimentSurface sentiment="success">
26
+ Success sentiment - Your payment was successful
27
+ </SentimentSurface>
28
+ <SentimentSurface sentiment="proposition">
29
+ Proposition sentiment - Try our new feature
30
+ </SentimentSurface>
31
+ </div>
32
+ ),
33
+ };
34
+
35
+ export const AllEmphasisLevels: Story = {
36
+ render: () => (
37
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
38
+ <SentimentSurface sentiment="neutral" emphasis="base">
39
+ Base emphasis (default)
40
+ </SentimentSurface>
41
+ <SentimentSurface sentiment="neutral" emphasis="elevated">
42
+ Elevated emphasis
43
+ </SentimentSurface>
44
+ </div>
45
+ ),
46
+ };
47
+
48
+ export const PolymorphicRendering: Story = {
49
+ play: async ({ canvasElement }) => {
50
+ const canvas = within(canvasElement);
51
+
52
+ // Test that all elements are rendered with correct tags
53
+ const divElement = canvas.getByText('Rendered as div');
54
+ await expect(divElement.tagName).toBe('DIV');
55
+
56
+ const sectionElement = canvas.getByText('Rendered as section');
57
+ await expect(sectionElement.tagName).toBe('SECTION');
58
+
59
+ const articleElement = canvas.getByText('Rendered as article');
60
+ await expect(articleElement.tagName).toBe('ARTICLE');
61
+ },
62
+ render: () => (
63
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
64
+ <SentimentSurface sentiment="neutral">Rendered as div</SentimentSurface>
65
+ <SentimentSurface sentiment="success" as="section">
66
+ Rendered as section
67
+ </SentimentSurface>
68
+ <SentimentSurface sentiment="proposition" as="article">
69
+ Rendered as article
70
+ </SentimentSurface>
71
+ </div>
72
+ ),
73
+ };
74
+
75
+ export const AllVariants = storyConfig(
76
+ {
77
+ render: () => (
78
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
79
+ {(['negative', 'warning', 'neutral', 'success', 'proposition'] as const).map(
80
+ (sentiment) => (
81
+ <div key={sentiment} style={{ display: 'contents' }}>
82
+ <SentimentSurface sentiment={sentiment} emphasis="base">
83
+ {sentiment} - base
84
+ </SentimentSurface>
85
+ <SentimentSurface sentiment={sentiment} emphasis="elevated">
86
+ {sentiment} - elevated
87
+ </SentimentSurface>
88
+ </div>
89
+ ),
90
+ )}
91
+ </div>
92
+ ),
93
+ },
94
+ { variants: ['default', 'dark'] },
95
+ );
96
+
97
+ export const NestedSentiments: Story = {
98
+ render: () => (
99
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
100
+ <SentimentSurface sentiment="negative" emphasis="base">
101
+ Outer: Negative, base
102
+ <div style={{ margin: '16px 0 0 16px' }}>
103
+ <SentimentSurface sentiment="success" emphasis="elevated">
104
+ Inner: Success, elevated
105
+ <div style={{ margin: '12px 0 0 12px' }}>
106
+ <SentimentSurface sentiment="warning" emphasis="base">
107
+ Deepest: Warning, base
108
+ </SentimentSurface>
109
+ </div>
110
+ </SentimentSurface>
111
+ </div>
112
+ </SentimentSurface>
113
+ <SentimentSurface sentiment="proposition" emphasis="elevated">
114
+ Outer: Proposition, elevated
115
+ <div style={{ margin: '16px 0 0 16px' }}>
116
+ <SentimentSurface sentiment="neutral" emphasis="base">
117
+ Inner: Neutral, base
118
+ </SentimentSurface>
119
+ </div>
120
+ </SentimentSurface>
121
+ </div>
122
+ ),
123
+ };
@@ -0,0 +1,72 @@
1
+ import { forwardRef, ElementType, ForwardedRef } from 'react';
2
+ import { clsx } from 'clsx';
3
+
4
+ import {
5
+ SentimentSurfaceComponentProps,
6
+ SentimentSurfaceComponent,
7
+ } from './SentimentSurface.types';
8
+ import { getSentimentSurfaceClassName } from './classMap';
9
+
10
+ /**
11
+ * SentimentSurface is a polymorphic container component that applies contextual background colours
12
+ * and text styling based on sentiment types (negative, warning, neutral, success, proposition).
13
+ * It's designed to visually communicate the nature or importance of its content through colour.
14
+ *
15
+ * @param {ElementType} [as='div'] - Optional prop to override the HTML element rendered.
16
+ * @param {Sentiment} sentiment - Required prop to set the sentiment type (negative, warning, neutral, success, proposition).
17
+ * @param {Emphasis} [emphasis='base'] - Optional prop to specify the emphasis level (base or elevated).
18
+ * @param {ReactNode} [children] - Content to render inside the surface.
19
+ * @param {string} [className] - Additional CSS classes to apply.
20
+ * @param {CSSProperties} [style] - Inline styles to apply.
21
+ * @param {string} [id] - Unique identifier for the component.
22
+ * @param {string} [testId] - A unique string that appears as data attribute `data-testid` in the rendered code.
23
+ *
24
+ * @component
25
+ * @example
26
+ * ```tsx
27
+ * // Basic usage with negative sentiment
28
+ * <SentimentSurface sentiment="negative">
29
+ * Your payment has failed
30
+ * </SentimentSurface>
31
+ * ```
32
+ *
33
+ * @see {@link SentimentSurface} for further information.
34
+ * @see {@link https://storybook.wise.design/?path=/docs/sentiment-surface--docs|Storybook Wise Design}
35
+ */
36
+ // @ts-expect-error - Generic forwardRef limitation. See: https://fettblog.eu/typescript-react-generic-forward-refs/
37
+ const SentimentSurface: SentimentSurfaceComponent = forwardRef(function SentimentSurface<
38
+ T extends ElementType = 'div',
39
+ >(
40
+ {
41
+ as,
42
+ sentiment,
43
+ emphasis = 'base',
44
+ className,
45
+ style,
46
+ children,
47
+ id,
48
+ testId,
49
+ ...props
50
+ }: SentimentSurfaceComponentProps<T>,
51
+ ref: ForwardedRef<HTMLElement>,
52
+ ) {
53
+ const Element = as ?? 'div';
54
+ const classNames = clsx(getSentimentSurfaceClassName(sentiment, emphasis), className);
55
+ const sentimentProps = {
56
+ ref,
57
+ id,
58
+ 'data-testid': testId,
59
+ className: classNames,
60
+ style,
61
+ ...props,
62
+ };
63
+
64
+ return (
65
+ // @ts-expect-error - Generic forwardRef limitation. See: https://fettblog.eu/typescript-react-generic-forward-refs/
66
+ <Element {...sentimentProps}>{children}</Element>
67
+ );
68
+ });
69
+
70
+ SentimentSurface.displayName = 'SentimentSurface';
71
+
72
+ export default SentimentSurface;
@@ -0,0 +1,104 @@
1
+ import {
2
+ ReactNode,
3
+ ElementType,
4
+ CSSProperties,
5
+ ComponentPropsWithoutRef,
6
+ HTMLAttributes,
7
+ } from 'react';
8
+
9
+ /**
10
+ * Sentiment types for the SentimentSurface component
11
+ */
12
+ export type Sentiment = 'negative' | 'warning' | 'neutral' | 'success' | 'proposition';
13
+
14
+ /**
15
+ * Emphasis levels for the SentimentSurface component
16
+ */
17
+ export type Emphasis = 'base' | 'elevated';
18
+
19
+ /**
20
+ * Common properties for the SentimentSurface component
21
+ */
22
+ export interface CommonProps {
23
+ /**
24
+ * The sentiment type that determines the colour scheme
25
+ */
26
+ sentiment: Sentiment;
27
+
28
+ /**
29
+ * The emphasis level affecting background and text contrast
30
+ * @default 'base'
31
+ */
32
+ emphasis?: Emphasis;
33
+
34
+ /**
35
+ * Content to render inside the surface
36
+ */
37
+ children?: ReactNode;
38
+
39
+ /**
40
+ * Additional CSS classes
41
+ */
42
+ className?: string;
43
+
44
+ /**
45
+ * Inline styles
46
+ */
47
+ style?: CSSProperties;
48
+
49
+ /**
50
+ * Unique identifier for the component
51
+ */
52
+ id?: string;
53
+
54
+ /**
55
+ * A unique string that appears as data attribute `data-testid` in the rendered code, serving as a hook for automated tests
56
+ */
57
+ testId?: string;
58
+
59
+ /**
60
+ * `data-testid` is strictly controlled through the `testId` prop.
61
+ * This lets consumers know that this data attribute will not be applied.
62
+ */
63
+ 'data-testid'?: never;
64
+ }
65
+
66
+ /**
67
+ * Props when rendering as a div or custom element
68
+ */
69
+ export type SentimentSurfaceDivProps<T extends ElementType = 'div'> = Omit<
70
+ HTMLAttributes<HTMLDivElement>,
71
+ keyof CommonProps
72
+ > &
73
+ CommonProps & {
74
+ /**
75
+ * The underlying HTML element to render
76
+ * @default 'div'
77
+ */
78
+ as?: T;
79
+ };
80
+
81
+ /**
82
+ * All possible props for the SentimentSurface component
83
+ */
84
+ export type SentimentSurfaceProps<T extends ElementType = 'div'> = SentimentSurfaceDivProps<T>;
85
+
86
+ /**
87
+ * Combines SentimentSurface props with all valid HTML attributes for the specified element type.
88
+ * Supports polymorphic rendering via the `as` prop while maintaining full type safety.
89
+ *
90
+ * @example
91
+ * <SentimentSurface sentiment="negative" role="alert" aria-live="polite">
92
+ * Error message
93
+ * </SentimentSurface>
94
+ */
95
+ export type SentimentSurfaceComponentProps<T extends ElementType = 'div'> =
96
+ SentimentSurfaceProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof SentimentSurfaceProps<T>>;
97
+
98
+ /**
99
+ * The SentimentSurface component type signature
100
+ */
101
+ export interface SentimentSurfaceComponent {
102
+ <T extends ElementType = 'div'>(props: SentimentSurfaceComponentProps<T>): ReactNode;
103
+ displayName?: string;
104
+ }
@@ -0,0 +1,15 @@
1
+ import type { Sentiment, Emphasis } from './SentimentSurface.types';
2
+
3
+ const BASE_CLASS = 'wds-sentiment-surface';
4
+
5
+ export const sentimentClassMap: Record<Sentiment, string> = {
6
+ negative: `${BASE_CLASS}-negative`,
7
+ warning: `${BASE_CLASS}-warning`,
8
+ neutral: `${BASE_CLASS}-neutral`,
9
+ success: `${BASE_CLASS}-success`,
10
+ proposition: `${BASE_CLASS}-proposition`,
11
+ };
12
+
13
+ export const getSentimentSurfaceClassName = (sentiment: Sentiment, emphasis: Emphasis): string => {
14
+ return `${BASE_CLASS} ${sentimentClassMap[sentiment]}-${emphasis}`;
15
+ };
@@ -0,0 +1,8 @@
1
+ export { default } from './SentimentSurface';
2
+ export type {
3
+ SentimentSurfaceComponent,
4
+ SentimentSurfaceProps,
5
+ CommonProps,
6
+ Sentiment,
7
+ Emphasis,
8
+ } from './SentimentSurface.types';