@transferwise/components 0.0.0-experimental-5a0aa47 → 0.0.0-experimental-0b80b83

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 (43) hide show
  1. package/build/i18n/ja.json +13 -13
  2. package/build/index.js +304 -147
  3. package/build/index.js.map +1 -1
  4. package/build/index.mjs +306 -149
  5. package/build/index.mjs.map +1 -1
  6. package/build/types/alert/Alert.d.ts +58 -47
  7. package/build/types/alert/Alert.d.ts.map +1 -1
  8. package/build/types/alert/index.d.ts +1 -2
  9. package/build/types/alert/index.d.ts.map +1 -1
  10. package/build/types/alert/withArrow/alertArrowPositions.d.ts +9 -0
  11. package/build/types/alert/withArrow/alertArrowPositions.d.ts.map +1 -0
  12. package/build/types/alert/withArrow/index.d.ts +3 -0
  13. package/build/types/alert/withArrow/index.d.ts.map +1 -0
  14. package/build/types/alert/withArrow/withArrow.d.ts +11 -0
  15. package/build/types/alert/withArrow/withArrow.d.ts.map +1 -0
  16. package/build/types/common/propsValues/sentiment.d.ts +1 -0
  17. package/build/types/common/propsValues/sentiment.d.ts.map +1 -1
  18. package/build/types/index.d.ts +1 -1
  19. package/build/types/index.d.ts.map +1 -1
  20. package/build/types/inlineAlert/InlineAlert.d.ts +4 -2
  21. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  22. package/build/types/provider/Provider.d.ts +1 -1
  23. package/build/types/provider/Provider.d.ts.map +1 -1
  24. package/build/types/statusIcon/StatusIcon.d.ts +1 -1
  25. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/alert/Alert.js +196 -0
  28. package/src/alert/{Alert.spec.tsx → Alert.spec.js} +40 -43
  29. package/src/alert/Alert.story.tsx +2 -1
  30. package/src/alert/index.js +1 -0
  31. package/src/alert/withArrow/alertArrowPositions.ts +9 -0
  32. package/src/alert/withArrow/index.js +2 -0
  33. package/src/alert/withArrow/withArrow.js +50 -0
  34. package/src/alert/withArrow/withArrow.spec.js +51 -0
  35. package/src/common/propsValues/sentiment.ts +10 -0
  36. package/src/i18n/ja.json +13 -13
  37. package/src/index.ts +1 -1
  38. package/src/inlineAlert/InlineAlert.spec.tsx +7 -0
  39. package/src/inlineAlert/InlineAlert.tsx +47 -19
  40. package/src/provider/Provider.tsx +2 -1
  41. package/src/statusIcon/StatusIcon.tsx +14 -14
  42. package/src/alert/Alert.tsx +0 -223
  43. package/src/alert/index.ts +0 -2
@@ -1,15 +1,22 @@
1
1
  /* eslint-disable no-console */
2
- import { HappyEmoji } from '@transferwise/icons';
2
+ import {
3
+ InfoCircle,
4
+ CheckCircle,
5
+ AlertCircle,
6
+ Warning as AlertIcon,
7
+ HappyEmoji,
8
+ } from '@transferwise/icons';
3
9
  import { ThemeProvider } from '@wise/components-theming';
4
10
  import React from 'react';
5
11
 
6
12
  import { Sentiment, Size, Theme, Variant } from '../common';
7
13
  import { render, cleanup, screen, userEvent, fireEvent } from '../test-utils';
8
14
 
9
- import Alert, { AlertAction, AlertArrowPosition, AlertType } from './Alert';
15
+ import Alert from './Alert';
16
+ import { AlertArrowPosition } from './withArrow';
10
17
 
11
18
  jest.mock('react', () => {
12
- const originReact = jest.requireActual<typeof import('react')>('react');
19
+ const originReact = jest.requireActual('react');
13
20
  const mUseReference = jest.fn();
14
21
  return {
15
22
  ...originReact,
@@ -18,18 +25,20 @@ jest.mock('react', () => {
18
25
  });
19
26
 
20
27
  describe('Alert', () => {
21
- let component: HTMLElement;
22
- let container: HTMLElement;
23
- let alert: HTMLElement;
24
- let closeButton: HTMLElement;
25
- let action: AlertAction;
28
+ let component;
29
+ let container;
30
+ let alert;
31
+ let closeButton;
32
+ let action;
26
33
 
27
- const classForType = (type: AlertType) => `alert-${type}`;
34
+ const renderIcon = (Icon) => render(<Icon size={24} />).container.innerHTML;
35
+
36
+ const classForType = (type) => `alert-${type}`;
28
37
 
29
38
  const message = 'Your card is on its way.';
30
39
 
31
40
  const origWarn = console.warn;
32
- let mockedWarn: jest.Mock;
41
+ let mockedWarn;
33
42
 
34
43
  beforeAll(() => {
35
44
  mockedWarn = jest.fn();
@@ -47,11 +56,11 @@ describe('Alert', () => {
47
56
 
48
57
  describe('defaults', () => {
49
58
  beforeEach(() => {
50
- container = render(
59
+ ({ container } = render(
51
60
  <ThemeProvider theme={Theme.LIGHT}>
52
61
  <Alert message={message} />
53
62
  </ThemeProvider>,
54
- ).container;
63
+ ));
55
64
  component = screen.getByTestId('alert');
56
65
  });
57
66
 
@@ -61,7 +70,7 @@ describe('Alert', () => {
61
70
 
62
71
  it('will be of type neutral', () => {
63
72
  expect(component).toHaveClass(classForType(Sentiment.NEUTRAL));
64
- expect(screen.getByTestId('info-icon')).toBeInTheDocument();
73
+ expect(screen.getByTestId('info-circle-icon')).toBeInTheDocument();
65
74
  });
66
75
 
67
76
  it('is not dismissible', () => {
@@ -81,9 +90,7 @@ describe('Alert', () => {
81
90
  expect(component).toHaveClass('arrow');
82
91
  expect(component).toHaveClass('arrow-bottom');
83
92
  expect(mockedWarn).toHaveBeenCalledWith(
84
- expect.stringContaining(
85
- "Alert component doesn't support 'arrow' anymore, use 'InlineAlert' instead.",
86
- ),
93
+ expect.stringContaining('Alert has deprecated the use of arrow.'),
87
94
  );
88
95
  });
89
96
 
@@ -92,9 +99,7 @@ describe('Alert', () => {
92
99
 
93
100
  expect(screen.getByText(message)).toBeInTheDocument();
94
101
  expect(mockedWarn).toHaveBeenCalledWith(
95
- expect.stringContaining(
96
- "Alert component doesn't support 'children' anymore, use 'message' instead.",
97
- ),
102
+ expect.stringContaining('Alert has deprecated the use of children.'),
98
103
  );
99
104
  });
100
105
 
@@ -103,9 +108,7 @@ describe('Alert', () => {
103
108
 
104
109
  expect(container.querySelector('button')).not.toBeInTheDocument();
105
110
  expect(mockedWarn).toHaveBeenCalledWith(
106
- expect.stringContaining(
107
- "Alert component doesn't support 'dismissible' anymore, use 'onDismiss' instead.",
108
- ),
111
+ expect.stringContaining('Alert has deprecated the use of dismissible.'),
109
112
  );
110
113
  });
111
114
 
@@ -115,9 +118,7 @@ describe('Alert', () => {
115
118
 
116
119
  expect(small.innerHTML).toStrictEqual(large.innerHTML);
117
120
  expect(mockedWarn).toHaveBeenCalledWith(
118
- expect.stringContaining(
119
- "Alert component doesn't support 'size' anymore, please remove that prop.",
120
- ),
121
+ expect.stringContaining('Alert no longer supports any possible variations in size'),
121
122
  );
122
123
  });
123
124
 
@@ -127,9 +128,9 @@ describe('Alert', () => {
127
128
  const success = screen.getByTestId('alert');
128
129
 
129
130
  expect(success).toHaveClass(classForType(Sentiment.POSITIVE));
130
- expect(screen.getByTestId('check-icon')).toBeInTheDocument();
131
+ expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument();
131
132
  expect(mockedWarn).toHaveBeenCalledWith(
132
- "Alert component has deprecated 'success' value for the 'type' prop. Please use 'positive' instead.",
133
+ 'Alert has deprecated the success value for the `type` prop. Please update to Sentiment.POSITIVE.',
133
134
  );
134
135
  });
135
136
 
@@ -139,9 +140,9 @@ describe('Alert', () => {
139
140
  const info = screen.getByTestId('alert');
140
141
 
141
142
  expect(info).toHaveClass(classForType(Sentiment.NEUTRAL));
142
- expect(screen.getByTestId('info-icon')).toBeInTheDocument();
143
+ expect(screen.getByTestId('info-circle-icon')).toBeInTheDocument();
143
144
  expect(mockedWarn).toHaveBeenCalledWith(
144
- "Alert component has deprecated 'info' value for the 'type' prop. Please use 'neutral' instead.",
145
+ 'Alert has deprecated the info value for the `type` prop. Please update to Sentiment.NEUTRAL.',
145
146
  );
146
147
  });
147
148
  });
@@ -152,11 +153,9 @@ describe('Alert', () => {
152
153
  const error = screen.getByTestId('alert');
153
154
 
154
155
  expect(error).toHaveClass(classForType(Sentiment.NEGATIVE));
155
- expect(screen.getByTestId('cross-icon')).toBeInTheDocument();
156
+ expect(screen.getByTestId('cross-circle-icon')).toBeInTheDocument();
156
157
  expect(mockedWarn).toHaveBeenCalledWith(
157
- expect.stringContaining(
158
- "Alert component has deprecated 'error' value for the 'type' prop. Please use 'negative' instead.",
159
- ),
158
+ expect.stringContaining('Alert has deprecated the error value for the `type` prop.'),
160
159
  );
161
160
  });
162
161
 
@@ -168,7 +167,7 @@ describe('Alert', () => {
168
167
  };
169
168
  render(<Alert action={action} message={message} />);
170
169
 
171
- const element = screen.getByText(action.text?.toString() ?? '');
170
+ const element = screen.getByText(action.text);
172
171
 
173
172
  expect(element).toHaveAttribute('href', action.href);
174
173
  expect(element).not.toHaveAttribute('aria-label');
@@ -184,7 +183,7 @@ describe('Alert', () => {
184
183
  };
185
184
  render(<Alert action={action} message={message} />);
186
185
 
187
- const element = screen.getByText(action.text?.toString() ?? '');
186
+ const element = screen.getByText(action.text);
188
187
 
189
188
  expect(element).toHaveAttribute('aria-label', action['aria-label']);
190
189
  expect(element).toHaveAttribute('target', action.target);
@@ -246,7 +245,7 @@ describe('Alert', () => {
246
245
  });
247
246
 
248
247
  describe('types', () => {
249
- const getComponentWithType = (type: AlertType) => {
248
+ const getComponentWithType = (type) => {
250
249
  render(<Alert type={type} message={message} />);
251
250
  return screen.getByTestId('alert');
252
251
  };
@@ -255,28 +254,28 @@ describe('Alert', () => {
255
254
  component = getComponentWithType(Sentiment.NEUTRAL);
256
255
 
257
256
  expect(component).toHaveClass(classForType(Sentiment.NEUTRAL));
258
- expect(screen.getByTestId('info-icon')).toBeInTheDocument();
257
+ expect(screen.getByTestId('info-circle-icon')).toBeInTheDocument();
259
258
  });
260
259
 
261
260
  it('renders positive', () => {
262
261
  component = getComponentWithType(Sentiment.POSITIVE);
263
262
 
264
263
  expect(component).toHaveClass(classForType(Sentiment.POSITIVE));
265
- expect(screen.getByTestId('check-icon')).toBeInTheDocument();
264
+ expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument();
266
265
  });
267
266
 
268
267
  it('renders negative', () => {
269
268
  component = getComponentWithType(Sentiment.NEGATIVE);
270
269
 
271
270
  expect(component).toHaveClass(classForType(Sentiment.NEGATIVE));
272
- expect(screen.getByTestId('cross-icon')).toBeInTheDocument();
271
+ expect(screen.getByTestId('cross-circle-icon')).toBeInTheDocument();
273
272
  });
274
273
 
275
274
  it('renders warning', () => {
276
275
  component = getComponentWithType(Sentiment.WARNING);
277
276
 
278
277
  expect(component).toHaveClass(classForType(Sentiment.WARNING));
279
- expect(screen.getByTestId('alert-icon')).toBeInTheDocument();
278
+ expect(screen.getByTestId('warning-icon')).toBeInTheDocument();
280
279
  });
281
280
 
282
281
  it('renders error alerts with aria-role alert', () => {
@@ -293,11 +292,9 @@ describe('Alert', () => {
293
292
  const { location } = window;
294
293
 
295
294
  beforeAll(() => {
296
- jest.spyOn(window, 'open').mockImplementation();
297
- // @ts-expect-error value gets set right after its deletion
298
295
  delete window.location;
296
+ jest.spyOn(window, 'open').mockImplementation();
299
297
  window.location = {
300
- ...window.location,
301
298
  assign: jest.fn(),
302
299
  };
303
300
  });
@@ -4,7 +4,8 @@ import { ClockBorderless } from '@transferwise/icons';
4
4
 
5
5
  import { Sentiment } from '../common';
6
6
 
7
- import Alert, { AlertArrowPosition } from './Alert';
7
+ import Alert from './Alert';
8
+ import { AlertArrowPosition } from './withArrow';
8
9
 
9
10
  export default {
10
11
  component: Alert,
@@ -0,0 +1 @@
1
+ export { default } from './Alert';
@@ -0,0 +1,9 @@
1
+ // TODO: consider to move this enum into component file once we migrate it on TypeScript or replace with some common enum
2
+ export enum ArrowPosition {
3
+ TOP_LEFT = 'up-left',
4
+ TOP = 'up-center',
5
+ TOP_RIGHT = 'up-right',
6
+ BOTTOM_LEFT = 'down-left',
7
+ BOTTOM = 'down-center',
8
+ BOTTOM_RIGHT = 'down-right',
9
+ }
@@ -0,0 +1,2 @@
1
+ export { ArrowPosition as AlertArrowPosition } from './alertArrowPositions';
2
+ export { default } from './withArrow';
@@ -0,0 +1,50 @@
1
+ import classNames from 'classnames';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { ArrowPosition } from './alertArrowPositions';
5
+
6
+ function withArrow(Alert, arrow) {
7
+ const AlertWithArrow = (props) => (
8
+ <Alert
9
+ {...props}
10
+ className={classNames(props.className, arrowClasses(arrow))}
11
+ arrow={undefined}
12
+ />
13
+ );
14
+
15
+ AlertWithArrow.propTypes = {
16
+ className: PropTypes.string,
17
+ };
18
+
19
+ AlertWithArrow.defaultProps = {
20
+ className: undefined,
21
+ };
22
+
23
+ return AlertWithArrow;
24
+ }
25
+
26
+ function arrowClasses(arrow) {
27
+ if (arrow) {
28
+ const classes = ['arrow'];
29
+ const { BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, TOP, TOP_RIGHT, TOP_LEFT } = ArrowPosition;
30
+
31
+ switch (arrow) {
32
+ case BOTTOM:
33
+ return classes.concat('arrow-bottom', 'arrow-center');
34
+ case BOTTOM_LEFT:
35
+ return classes.concat('arrow-bottom', 'arrow-left');
36
+ case BOTTOM_RIGHT:
37
+ return classes.concat('arrow-bottom', 'arrow-right');
38
+ case TOP:
39
+ return classes.concat('arrow-center');
40
+ case TOP_RIGHT:
41
+ return classes.concat('arrow-right');
42
+ case TOP_LEFT:
43
+ default:
44
+ return classes;
45
+ }
46
+ }
47
+ return '';
48
+ }
49
+
50
+ export default withArrow;
@@ -0,0 +1,51 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import Alert from '../Alert';
3
+
4
+ import { ArrowPosition } from './alertArrowPositions';
5
+ import withArrow from './withArrow';
6
+
7
+ describe('withArrow', () => {
8
+ const message = 'I hope you have a nice day today';
9
+
10
+ describe('default props', () => {
11
+ it(`returns an arrowless alert if you don't pass an arrow`, () => {
12
+ const ArrowLessAlert = withArrow(Alert);
13
+
14
+ render(<ArrowLessAlert message={message} />);
15
+ const component = screen.getByTestId('alert');
16
+
17
+ expect(component).not.toHaveClass('arrow');
18
+ });
19
+
20
+ it(`ignores the arrow prop passed to the new alert`, () => {
21
+ const AlertTopArrow = withArrow(Alert, ArrowPosition.TOP_LEFT);
22
+
23
+ render(<AlertTopArrow message={message} arrow={ArrowPosition.BOTTOM_LEFT} />);
24
+ const component = screen.getByTestId('alert');
25
+ expect(component).toHaveClass('arrow');
26
+ expect(component).not.toHaveClass('arrow-bottom');
27
+ });
28
+ });
29
+
30
+ describe('renders arrows', () => {
31
+ const getPointyAlert = (arrow) => withArrow(Alert, arrow);
32
+
33
+ it(`returns an bottom arrowed alert if you pass a bottom arrow`, () => {
34
+ const BottomArrowAlert = getPointyAlert(ArrowPosition.BOTTOM);
35
+ render(<BottomArrowAlert message={message} />);
36
+ const component = screen.getByTestId('alert');
37
+
38
+ expect(component).toHaveClass('arrow');
39
+ expect(component).toHaveClass('arrow-bottom');
40
+ });
41
+
42
+ it(`returns an top-right arrowed alert if you pass a top right arrow`, () => {
43
+ const BottomArrowAlert = getPointyAlert(ArrowPosition.TOP_RIGHT);
44
+ render(<BottomArrowAlert message={message} />);
45
+ const component = screen.getByTestId('alert');
46
+
47
+ expect(component).toHaveClass('arrow');
48
+ expect(component).toHaveClass('arrow-right');
49
+ });
50
+ });
51
+ });
@@ -18,3 +18,13 @@ export enum Sentiment {
18
18
  */
19
19
  SUCCESS = 'success',
20
20
  }
21
+
22
+ export type SentimentString =
23
+ | 'negative'
24
+ | 'neutral'
25
+ | 'positive'
26
+ | 'warning'
27
+ | 'pending'
28
+ | 'info'
29
+ | 'error'
30
+ | 'success';
package/src/i18n/ja.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "neptune.Button.loadingAriaLabel": "読み込み中…",
3
- "neptune.Chips.ariaLabel": "{choice} をクリア",
4
- "neptune.ClearButton.ariaLabel": "消去",
3
+ "neptune.Chips.ariaLabel": "{choice}を消去する",
4
+ "neptune.ClearButton.ariaLabel": "消去する",
5
5
  "neptune.CloseButton.ariaLabel": "閉じる",
6
6
  "neptune.DateInput.day.label": "日",
7
7
  "neptune.DateInput.day.placeholder": "日",
@@ -21,19 +21,19 @@
21
21
  "neptune.Link.opensInNewTab": "(新しいタブで開きます)",
22
22
  "neptune.MoneyInput.Select.placeholder": "選択してください...",
23
23
  "neptune.PhoneNumberInput.SelectInput.placeholder": "選択してください…",
24
- "neptune.Select.searchPlaceholder": "検索... ",
24
+ "neptune.Select.searchPlaceholder": "検索する... ",
25
25
  "neptune.SelectInput.noResultsFound": "結果が見つかりませんでした",
26
26
  "neptune.Summary.statusDone": "完了",
27
- "neptune.Summary.statusNotDone": "未対応",
27
+ "neptune.Summary.statusNotDone": "未完了",
28
28
  "neptune.Summary.statusPending": "保留中",
29
29
  "neptune.Upload.csButtonText": "別のファイルをアップロードしますか?",
30
30
  "neptune.Upload.csFailureText": "アップロードに失敗しました。もう一度やり直してください。",
31
31
  "neptune.Upload.csSuccessText": "アップロードが完了しました。",
32
32
  "neptune.Upload.csTooLargeMessage": "{maxSize}MB以下のファイルをアップロードしてください",
33
33
  "neptune.Upload.csWrongTypeMessage": "ファイルの種類はサポートされていません。別のファイルで再度お試しください",
34
- "neptune.Upload.psButtonText": "キャンセル",
35
- "neptune.Upload.psProcessingText": "アップロード中…",
36
- "neptune.Upload.usButtonText": "または、ファイルを選択する",
34
+ "neptune.Upload.psButtonText": "キャンセルする",
35
+ "neptune.Upload.psProcessingText": "アップロードしています…",
36
+ "neptune.Upload.usButtonText": "またはファイルを選択する",
37
37
  "neptune.Upload.usDropMessage": "ファイルをドロップしてアップロードする",
38
38
  "neptune.Upload.usPlaceholder": "{maxSize}MB以下のファイルをドラッグ&ドロップしてください",
39
39
  "neptune.UploadButton.allFileTypes": "すべてのファイル種類",
@@ -42,16 +42,16 @@
42
42
  "neptune.UploadButton.uploadFile": "ファイルをアップロードする",
43
43
  "neptune.UploadButton.uploadFiles": "ファイルをアップロードする",
44
44
  "neptune.UploadInput.deleteModalBody": "このファイルを削除すると、当社のシステムからも削除されます。",
45
- "neptune.UploadInput.deleteModalCancelButtonText": "キャンセル",
46
- "neptune.UploadInput.deleteModalConfirmButtonText": "削除",
45
+ "neptune.UploadInput.deleteModalCancelButtonText": "キャンセルする",
46
+ "neptune.UploadInput.deleteModalConfirmButtonText": "削除する",
47
47
  "neptune.UploadInput.deleteModalTitle": "このファイルを削除しますか?",
48
48
  "neptune.UploadInput.fileIsTooLarge": "ファイルが大きすぎます。",
49
49
  "neptune.UploadInput.fileTypeNotSupported": "このファイルの種類はサポートされていません",
50
50
  "neptune.UploadInput.maximumFilesAlreadyUploaded": "申し訳ございませんが、このアップロードに失敗しました。一度に{maxFilesAllowed}個のファイルのみ受付可能です。",
51
- "neptune.UploadItem.deleting": "削除中...",
52
- "neptune.UploadItem.removeFile": "{filename} を削除",
51
+ "neptune.UploadItem.deleting": "削除しています...",
52
+ "neptune.UploadItem.removeFile": "{filename}のファイルを削除する",
53
53
  "neptune.UploadItem.uploaded": "アップロード済み",
54
54
  "neptune.UploadItem.uploadedFile": "アップロード済みのファイル",
55
- "neptune.UploadItem.uploading": "アップロード中…",
56
- "neptune.UploadItem.uploadingFailed": "アップロードが失敗しました"
55
+ "neptune.UploadItem.uploading": "アップロードしています…",
56
+ "neptune.UploadItem.uploadingFailed": "アップロードに失敗しました"
57
57
  }
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@
4
4
  export type { AccordionProps, AccordionItem } from './accordion';
5
5
  export type { AvatarProps } from './avatar';
6
6
  export type { ActionOptionProps } from './actionOption';
7
- export type { AlertProps, AlertAction, AlertArrowPosition, AlertType } from './alert';
8
7
  export type { BadgeProps } from './badge';
9
8
  export type { CircularButtonProps } from './circularButton';
10
9
  export type { DecisionProps } from './decision/Decision';
@@ -195,6 +194,7 @@ export { AvatarType } from './avatar';
195
194
  export { InfoPresentation } from './info';
196
195
  export { UploadStep } from './upload';
197
196
  export { DecisionPresentation, DecisionType } from './decision';
197
+ export { AlertArrowPosition } from './alert/withArrow';
198
198
  export { LogoType } from './logo';
199
199
  export { FileType } from './common';
200
200
 
@@ -21,6 +21,13 @@ describe('InlineAlert', () => {
21
21
 
22
22
  expect(component).toHaveClass('alert-neutral');
23
23
  });
24
+
25
+ it('has a top left arrow', () => {
26
+ render(<InlineAlert>{message}</InlineAlert>);
27
+ const component = screen.getByRole('alert');
28
+
29
+ expect(component).toHaveClass('arrow');
30
+ });
24
31
  });
25
32
 
26
33
  describe('render with types', () => {
@@ -1,31 +1,59 @@
1
1
  import { AlertCircle as AlertCircleIcon } from '@transferwise/icons';
2
+ import { useTheme } from '@wise/components-theming';
2
3
  import classNames from 'classnames';
3
4
  import { ReactNode } from 'react';
4
5
 
6
+ import withArrow, { AlertArrowPosition } from '../alert/withArrow';
5
7
  import { Sentiment } from '../common';
8
+ import { SentimentString } from '../common/propsValues/sentiment';
6
9
 
7
10
  export interface InlineAlertProps {
8
11
  id?: string;
9
- type?: `${Sentiment}`;
12
+ type?: Sentiment | SentimentString;
10
13
  className?: string;
11
14
  children: ReactNode;
12
15
  }
13
16
 
14
- export default function InlineAlert({
15
- id,
16
- type = 'neutral',
17
- className,
18
- children,
19
- }: InlineAlertProps) {
20
- const danger = type === 'negative' || type === 'error';
21
- return (
22
- <div
23
- role="alert"
24
- id={id}
25
- className={classNames('alert alert-detach', `alert-${danger ? 'danger' : type}`, className)}
26
- >
27
- {danger && <AlertCircleIcon />}
28
- <div>{children}</div>
29
- </div>
30
- );
31
- }
17
+ const typeClassMap: Record<Sentiment.ERROR | Sentiment.NEGATIVE, string> = {
18
+ [Sentiment.ERROR]: 'danger',
19
+ [Sentiment.NEGATIVE]: 'danger',
20
+ };
21
+
22
+ const InlineAlert = ({ id, type = Sentiment.NEUTRAL, className, children }: InlineAlertProps) => {
23
+ const { isModern } = useTheme();
24
+
25
+ const typeClass = `alert-${typeClassMap[type as Sentiment.ERROR | Sentiment.NEGATIVE] || type}`;
26
+
27
+ if (isModern) {
28
+ return (
29
+ <div role="alert" id={id} className={classNames('alert alert-detach', typeClass, className)}>
30
+ {(type === 'error' || type === 'negative') && <AlertCircleIcon />}
31
+ <div>{children}</div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ const getAlertContents = ({
37
+ children,
38
+ className,
39
+ }: {
40
+ children: ReactNode;
41
+ className?: string;
42
+ }) => {
43
+ return (
44
+ <div
45
+ role="alert"
46
+ id={id}
47
+ className={classNames('alert alert-detach p-x-2 p-y-1', typeClass, className)}
48
+ >
49
+ {children}
50
+ </div>
51
+ );
52
+ };
53
+
54
+ const AlertWithArrow = withArrow(getAlertContents, AlertArrowPosition.TOP_LEFT);
55
+
56
+ return <AlertWithArrow {...{ id, type, className, children }} />;
57
+ };
58
+
59
+ export default InlineAlert;
@@ -7,7 +7,8 @@ import { DirectionProvider } from './direction';
7
7
  import { LanguageProvider } from './language';
8
8
 
9
9
  export interface ProviderProps {
10
- i18n: Pick<ResolvedIntlConfig, 'locale' | 'messages' | 'defaultRichTextElements'>;
10
+ i18n: Pick<ResolvedIntlConfig, 'locale' | 'messages'> &
11
+ Partial<Pick<ResolvedIntlConfig, 'defaultRichTextElements'>>;
11
12
  children?: React.ReactNode;
12
13
  }
13
14
 
@@ -1,27 +1,27 @@
1
1
  import { Info, Alert, Cross, Check, ClockBorderless } from '@transferwise/icons';
2
2
  import classNames from 'classnames';
3
3
 
4
- import { SizeSmall, SizeMedium, SizeLarge, Sentiment } from '../common';
4
+ import { SizeSmall, SizeMedium, SizeLarge, Sentiment, Size } from '../common';
5
5
 
6
6
  export type StatusIconProps = {
7
- sentiment: `${Sentiment}`;
7
+ sentiment: Sentiment;
8
8
  size: SizeSmall | SizeMedium | SizeLarge;
9
9
  };
10
10
 
11
- const iconTypeMap = {
12
- positive: Check,
13
- neutral: Info,
14
- warning: Alert,
15
- negative: Cross,
16
- pending: ClockBorderless,
17
- info: Info,
18
- error: Cross,
19
- success: Check,
20
- } satisfies Record<`${Sentiment}`, React.ElementType>;
11
+ const StatusIcon = ({ sentiment = Sentiment.NEUTRAL, size = Size.MEDIUM }: StatusIconProps) => {
12
+ const iconTypeMap = {
13
+ [Sentiment.POSITIVE]: Check,
14
+ [Sentiment.NEUTRAL]: Info,
15
+ [Sentiment.WARNING]: Alert,
16
+ [Sentiment.NEGATIVE]: Cross,
17
+ [Sentiment.PENDING]: ClockBorderless,
18
+ [Sentiment.INFO]: Info,
19
+ [Sentiment.ERROR]: Cross,
20
+ [Sentiment.SUCCESS]: Check,
21
+ };
21
22
 
22
- const StatusIcon = ({ sentiment = 'neutral', size = 'md' }: StatusIconProps) => {
23
+ const iconColor = [Sentiment.WARNING, Sentiment.PENDING].includes(sentiment) ? 'dark' : 'light';
23
24
  const Icon = iconTypeMap[sentiment];
24
- const iconColor = sentiment === 'warning' || sentiment === 'pending' ? 'dark' : 'light';
25
25
 
26
26
  return (
27
27
  <span