@transferwise/components 0.0.0-experimental-d1715ff → 0.0.0-experimental-3064bdb

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 (44) hide show
  1. package/build/i18n/en.json +2 -0
  2. package/build/i18n/en.json.js +2 -0
  3. package/build/i18n/en.json.js.map +1 -1
  4. package/build/i18n/en.json.mjs +2 -0
  5. package/build/i18n/en.json.mjs.map +1 -1
  6. package/build/main.css +214 -0
  7. package/build/styles/main.css +214 -0
  8. package/build/styles/table/Table.css +214 -0
  9. package/build/types/table/Table.d.ts +23 -0
  10. package/build/types/table/Table.d.ts.map +1 -0
  11. package/build/types/table/Table.messages.d.ts +12 -0
  12. package/build/types/table/Table.messages.d.ts.map +1 -0
  13. package/build/types/table/TableCell.d.ts +37 -0
  14. package/build/types/table/TableCell.d.ts.map +1 -0
  15. package/build/types/table/TableHeader.d.ts +12 -0
  16. package/build/types/table/TableHeader.d.ts.map +1 -0
  17. package/build/types/table/TableRow.d.ts +17 -0
  18. package/build/types/table/TableRow.d.ts.map +1 -0
  19. package/build/types/table/TableStatusText.d.ts +9 -0
  20. package/build/types/table/TableStatusText.d.ts.map +1 -0
  21. package/build/types/table/index.d.ts +6 -0
  22. package/build/types/table/index.d.ts.map +1 -0
  23. package/package.json +5 -5
  24. package/src/i18n/en.json +2 -0
  25. package/src/main.css +214 -0
  26. package/src/main.less +1 -0
  27. package/src/table/Table.css +214 -0
  28. package/src/table/Table.less +253 -0
  29. package/src/table/Table.messages.ts +12 -0
  30. package/src/table/Table.spec.tsx +87 -0
  31. package/src/table/Table.story.tsx +352 -0
  32. package/src/table/Table.tsx +121 -0
  33. package/src/table/TableCell.spec.tsx +298 -0
  34. package/src/table/TableCell.tsx +153 -0
  35. package/src/table/TableHeader.spec.tsx +58 -0
  36. package/src/table/TableHeader.tsx +50 -0
  37. package/src/table/TableRow.spec.tsx +104 -0
  38. package/src/table/TableRow.tsx +62 -0
  39. package/src/table/TableStatusText.spec.tsx +53 -0
  40. package/src/table/TableStatusText.tsx +35 -0
  41. package/src/table/index.ts +11 -0
  42. package/src/test-utils/assets/avatar-rectangle-fox.webp +0 -0
  43. package/src/test-utils/assets/avatar-square-dude.webp +0 -0
  44. package/src/test-utils/assets/tapestry-01.png +0 -0
@@ -0,0 +1,253 @@
1
+ .np-table {
2
+ @table-background-color: var(--color-background-neutral);
3
+ @table-border-width: var(--size-8);
4
+ @table-border-radius: var(--radius-medium);
5
+ @table-inner-border-radius: 10px;
6
+
7
+ @table-spacing-sm: var(--size-4);
8
+ @table-spacing-md: var(--size-8);
9
+ @table-spacing-lg: var(--size-12);
10
+ @table-spacing-xl: var(--size-16);
11
+
12
+ @header-min-width: 160px;
13
+ @header-min-width-loading: calc(@header-min-width * 2);
14
+
15
+ width: 100%;
16
+ background-color: transparent;
17
+
18
+ &-container {
19
+ padding: @table-spacing-md;
20
+ background-color: @table-background-color;
21
+ border-radius: @table-border-radius;
22
+
23
+ &--loading {
24
+ .np-table-header {
25
+ min-width: @header-min-width-loading;
26
+ }
27
+ }
28
+
29
+ &--center {
30
+ margin-right: auto;
31
+ margin-left: auto;
32
+ }
33
+
34
+ &--full-width {
35
+ width: 100%;
36
+ }
37
+ }
38
+
39
+ &-inner-container {
40
+ background-image:
41
+ linear-gradient(to right, var(--color-background-screen), var(--color-background-screen)),
42
+ linear-gradient(to right, var(--color-background-screen), var(--color-background-screen)),
43
+ linear-gradient(to right, rgba(0,0,0,.25), rgba(255,255,255,0)),
44
+ linear-gradient(to left, rgba(0,0,0,.25), rgba(255,255,255,0));
45
+ background-position: left center, right center, left center, right center;
46
+ background-repeat: no-repeat;
47
+ background-color: var(--color-background-screen);
48
+ background-size: 10px 100%, 10px 100%, 10px 100%, 10px 100%;
49
+ background-attachment: local, local, scroll, scroll;
50
+ overflow-x: auto;
51
+ border-radius: @table-inner-border-radius;
52
+ }
53
+
54
+ &-row {
55
+ &:last-child {
56
+ .np-table-cell {
57
+ &:first-child {
58
+ border-bottom-left-radius: @table-inner-border-radius;
59
+ }
60
+ &:last-child {
61
+ border-bottom-right-radius: @table-inner-border-radius;
62
+ }
63
+ }
64
+ }
65
+
66
+ &--clickable:hover {
67
+ .np-table-cell {
68
+ background-color: var(--color-background-screen-hover);
69
+ cursor: pointer;
70
+ }
71
+ }
72
+
73
+ &--separator {
74
+ .np-table-cell {
75
+ padding: 0;
76
+ }
77
+ }
78
+ }
79
+
80
+ &-header,
81
+ &-cell {
82
+ padding: 0;
83
+
84
+ &:first-child {
85
+ padding-left: @table-spacing-xl;
86
+
87
+ .np-table-header-content,
88
+ .np-table-cell-content {
89
+ padding-left: 0;
90
+ }
91
+ }
92
+
93
+ &:last-child {
94
+ padding-right: @table-spacing-xl;
95
+
96
+ .np-table-header-content,
97
+ .np-table-cell-content {
98
+ padding-right: 0;
99
+ }
100
+ }
101
+
102
+ .np-text-body-large-bold {
103
+ font-size: var(--font-size-14);
104
+ }
105
+ }
106
+
107
+ &-header.np-table-header--right,
108
+ &-cell.np-table-cell--right {
109
+ padding-right: calc(@table-spacing-md + @table-spacing-lg);
110
+ > .np-text-body-default {
111
+ text-align: right;
112
+ }
113
+ }
114
+
115
+ &-header.np-table-header--right .np-table-header-content,
116
+ &-cell.np-table-cell--right .np-table-content {
117
+ justify-content: end;
118
+ }
119
+
120
+ &-header-content,
121
+ &-content {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: start;
125
+ }
126
+
127
+ &-header {
128
+ min-width: @header-min-width;
129
+ padding-right: @table-spacing-md;
130
+ padding-bottom: @table-spacing-md;
131
+ padding-left: @table-spacing-md;
132
+ background-color: var(--color-background-neutral);
133
+ color: var(--color-content-primary);
134
+
135
+ &-content {
136
+ padding-top: 5px;
137
+ padding-bottom: 5px;
138
+ white-space: nowrap;
139
+ }
140
+
141
+ &--has-error {
142
+ color: var(--color-sentiment-negative);
143
+ }
144
+ }
145
+
146
+ &-cell {
147
+ padding: @table-spacing-xl @table-spacing-md;
148
+
149
+ &--primary {
150
+ min-width: 200px;
151
+ }
152
+
153
+ &--currency {
154
+ .np-text-body-default {
155
+ white-space: nowrap;
156
+ }
157
+ }
158
+
159
+ // TODO: In next iterations this block of code should be removed after we'll have `24px` status icons
160
+ &--status {
161
+ .status-circle {
162
+ width: 24px;
163
+ height: 24px;
164
+
165
+ .status-icon > svg {
166
+ width: 21px;
167
+ height: 21px;
168
+ }
169
+ }
170
+ }
171
+
172
+ .tw-chevron {
173
+ margin-left: @table-spacing-md;
174
+ }
175
+
176
+ &-separator {
177
+ margin-top: @table-spacing-sm;
178
+ margin-bottom: @table-spacing-sm;
179
+ height: 1px;
180
+ padding: 0;
181
+ background-color: @table-background-color;
182
+ }
183
+
184
+ .np-text-body-default {
185
+ line-height: var(--line-height-22);
186
+ }
187
+
188
+ .np-text-body-large-bold {
189
+ display: flex;
190
+ align-items: center;
191
+ color: var(--color-content-primary);
192
+ white-space: nowrap;
193
+ }
194
+
195
+ .np-table-content--success {
196
+ color: var(--color-sentiment-positive);
197
+ }
198
+
199
+ .np-table-content--error {
200
+ color: var(--color-sentiment-negative);
201
+ }
202
+
203
+ .tw-loader {
204
+ // The loading area of the table should equals to 5 rows of the table for huge screens and 3 rows for small screens
205
+ margin: 150px auto;
206
+ @media (--screen-400-zoom) {
207
+ margin-top: 70px;
208
+ margin-bottom: 70px;
209
+ }
210
+ }
211
+ }
212
+
213
+ &-content {
214
+ gap: @table-spacing-lg;
215
+
216
+ &--success,
217
+ &--error {
218
+ gap: @table-spacing-sm;
219
+
220
+ .np-table-content-text {
221
+ line-height: 155%;
222
+ }
223
+ }
224
+
225
+ &-media {
226
+ flex-shrink: 0;
227
+ }
228
+
229
+ &-body {
230
+ display: flex;
231
+ flex-direction: column;
232
+ font-size: var(--font-size-12);
233
+ color: var(--color-content-tertiary);
234
+ }
235
+
236
+ &--reversed {
237
+ flex-direction: row-reverse;
238
+
239
+ .np-table-content-body {
240
+ align-items: end;
241
+ }
242
+ }
243
+ }
244
+
245
+ &-empty-data {
246
+ display: flex;
247
+ align-items: center;
248
+
249
+ .status-circle {
250
+ margin-right: @table-spacing-lg;
251
+ }
252
+ }
253
+ }
@@ -0,0 +1,12 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ refreshPage: {
5
+ id: 'neptune.Table.refreshPage',
6
+ defaultMessage: 'Refresh page',
7
+ },
8
+ emptyData: {
9
+ id: 'neptune.Table.emptyData',
10
+ defaultMessage: 'No results found',
11
+ },
12
+ });
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { userEvent } from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+ import { IntlProvider } from 'react-intl';
6
+ import Table, { TableProps } from './Table';
7
+ import { mockMatchMedia } from '../test-utils';
8
+
9
+ mockMatchMedia();
10
+
11
+ const renderWithIntl = (component: React.ReactNode) => {
12
+ return render(<IntlProvider locale="en">{component}</IntlProvider>);
13
+ };
14
+
15
+ describe('Table Component', () => {
16
+ const defaultProps: TableProps = {
17
+ data: {
18
+ headers: [{ header: 'Header 1' }, { header: 'Header 2' }],
19
+ content: [
20
+ {
21
+ rowContent: [
22
+ { content: { text: 'Cell content 1' } },
23
+ { content: { text: 'Cell content 2' } },
24
+ ],
25
+ },
26
+ {
27
+ rowContent: [
28
+ { content: { text: 'Cell content 3' } },
29
+ { content: { text: 'Cell content 4' } },
30
+ ],
31
+ },
32
+ ],
33
+ onRowClick: jest.fn(),
34
+ },
35
+ loading: false,
36
+ className: '',
37
+ fullWidth: true,
38
+ error: undefined,
39
+ onRetry: jest.fn(),
40
+ };
41
+
42
+ it('renders table headers and content', () => {
43
+ renderWithIntl(<Table {...defaultProps} />);
44
+ expect(screen.getByText('Header 1')).toBeInTheDocument();
45
+ expect(screen.getByText('Header 2')).toBeInTheDocument();
46
+ expect(screen.getByText('Cell content 1')).toBeInTheDocument();
47
+ expect(screen.getByText('Cell content 2')).toBeInTheDocument();
48
+ expect(screen.getByText('Cell content 3')).toBeInTheDocument();
49
+ expect(screen.getByText('Cell content 4')).toBeInTheDocument();
50
+ });
51
+
52
+ it('renders loading state', () => {
53
+ renderWithIntl(<Table {...defaultProps} loading />);
54
+ expect(screen.getAllByTestId('np-table-loader')).toHaveLength(1);
55
+ });
56
+
57
+ it('renders empty data message', () => {
58
+ renderWithIntl(<Table {...defaultProps} data={{ headers: [], content: [] }} />);
59
+ expect(screen.getAllByTestId('np-table-empty-data')).toHaveLength(1);
60
+ });
61
+
62
+ it('renders error message', () => {
63
+ const errorProps: TableProps = {
64
+ ...defaultProps,
65
+ error: { message: 'Error occurred', action: { text: 'Retry' } },
66
+ };
67
+ renderWithIntl(<Table {...errorProps} />);
68
+ expect(screen.getByText('Error occurred')).toBeInTheDocument();
69
+ expect(screen.getByText('Retry')).toBeInTheDocument();
70
+ });
71
+
72
+ it('renders with custom className', () => {
73
+ renderWithIntl(<Table {...defaultProps} className="custom-class" />);
74
+ expect(screen.getByTestId('np-table-container')).toHaveClass('custom-class');
75
+ });
76
+
77
+ it('renders with fullWidth set to false', () => {
78
+ renderWithIntl(<Table {...defaultProps} fullWidth={false} />);
79
+ expect(screen.getByTestId('np-table-container')).toHaveClass('np-table-container--center');
80
+ });
81
+
82
+ it('calls onRowClick when a row is clicked', async () => {
83
+ renderWithIntl(<Table {...defaultProps} />);
84
+ await userEvent.click(screen.getByText('Cell content 1'));
85
+ expect(defaultProps.data.onRowClick).toHaveBeenCalled();
86
+ });
87
+ });
@@ -0,0 +1,352 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import Table from './Table';
3
+ import { TableHeaderType } from './TableHeader';
4
+ import { lorem1000 } from '../test-utils';
5
+ import { TableRowType, TableRowClickableType } from './TableRow';
6
+
7
+ export default {
8
+ component: Table,
9
+ title: 'Option/Table',
10
+ tags: ['autodocs'],
11
+ } satisfies Meta<typeof Table>;
12
+
13
+ type Story = StoryObj<typeof Table>;
14
+
15
+ const getLoremIpsumWords = (count: number, hasWordsOnly = false) => {
16
+ const loremIpsumWords = hasWordsOnly ? lorem1000.replace(/[^a-z\s]/gi, '') : lorem1000;
17
+ return loremIpsumWords.split(' ').slice(0, count).join(' ');
18
+ };
19
+
20
+ const sampleHeading = 'Header sample';
21
+ const loremDescription = 'Description';
22
+ const lorem2 = getLoremIpsumWords(2, true);
23
+ const lorem5 = getLoremIpsumWords(5);
24
+
25
+ const tableData = {
26
+ headers: [
27
+ {
28
+ header: sampleHeading,
29
+ },
30
+ {
31
+ header: sampleHeading,
32
+ },
33
+ {
34
+ header: sampleHeading,
35
+ className: 'np-table-header--custom-class',
36
+ },
37
+ {
38
+ header: sampleHeading,
39
+ alignment: 'right',
40
+ },
41
+ {
42
+ header: sampleHeading,
43
+ hasError: true,
44
+ },
45
+ ] satisfies TableHeaderType[],
46
+ content: [
47
+ {
48
+ id: 0,
49
+ rowContent: [
50
+ {
51
+ type: 'leading',
52
+ content: {
53
+ primaryText: lorem2,
54
+ secondaryText: loremDescription,
55
+ initials: 'PT',
56
+ },
57
+ },
58
+ {
59
+ content: { text: lorem2 },
60
+ },
61
+ {
62
+ content: { text: lorem2 },
63
+ },
64
+ {
65
+ type: 'currency',
66
+ content: {
67
+ primaryCurrency: {
68
+ value: '12345.67',
69
+ currency: 'eur',
70
+ },
71
+ secondaryCurrency: {
72
+ value: '11000.00',
73
+ currency: 'gbp',
74
+ },
75
+ },
76
+ alignment: 'right',
77
+ },
78
+ {
79
+ type: 'status',
80
+ content: {
81
+ primaryText: lorem2,
82
+ secondaryText: loremDescription,
83
+ sentiment: 'negative',
84
+ },
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ id: 1,
90
+ rowContent: [
91
+ {
92
+ type: 'leading',
93
+ content: {
94
+ primaryText: lorem2,
95
+ secondaryText: loremDescription,
96
+ initials: 'PT',
97
+ status: 'error',
98
+ },
99
+ },
100
+ {
101
+ content: { text: lorem2 },
102
+ },
103
+ {
104
+ content: {
105
+ text: lorem2,
106
+ status: 'error',
107
+ },
108
+ },
109
+ {
110
+ type: 'currency',
111
+ content: {
112
+ primaryCurrency: {
113
+ value: 23456.78,
114
+ currency: 'usd',
115
+ },
116
+ secondaryCurrency: {
117
+ value: 21000.0,
118
+ currency: 'gbp',
119
+ },
120
+ status: 'error',
121
+ },
122
+ alignment: 'right',
123
+ },
124
+ {
125
+ type: 'status',
126
+ content: {
127
+ primaryText: lorem2,
128
+ secondaryText: loremDescription,
129
+ sentiment: 'neutral',
130
+ },
131
+ },
132
+ ],
133
+ },
134
+ {
135
+ id: 2,
136
+ rowContent: [
137
+ {
138
+ type: 'leading',
139
+ content: {
140
+ primaryText: lorem2,
141
+ secondaryText: loremDescription,
142
+ initials: 'PT',
143
+ status: 'success',
144
+ },
145
+ },
146
+ {
147
+ content: { text: lorem2 },
148
+ },
149
+ {
150
+ content: {
151
+ text: lorem2,
152
+ status: 'success',
153
+ },
154
+ },
155
+ {
156
+ type: 'currency',
157
+ content: {
158
+ primaryCurrency: {
159
+ value: '34567.89',
160
+ currency: 'gbp',
161
+ },
162
+ secondaryCurrency: {
163
+ value: '31000.00',
164
+ currency: 'eur',
165
+ },
166
+ status: 'success',
167
+ },
168
+ alignment: 'right',
169
+ },
170
+ {
171
+ type: 'status',
172
+ content: {
173
+ primaryText: lorem2,
174
+ secondaryText: loremDescription,
175
+ sentiment: 'positive',
176
+ },
177
+ },
178
+ ],
179
+ },
180
+ {
181
+ id: 3,
182
+ rowContent: [
183
+ {
184
+ type: 'leading',
185
+ content: {
186
+ primaryText: lorem2,
187
+ secondaryText: loremDescription,
188
+ initials: 'PT',
189
+ },
190
+ },
191
+ {
192
+ content: { text: lorem2 },
193
+ },
194
+ {
195
+ content: { text: lorem2 },
196
+ },
197
+ {
198
+ type: 'currency',
199
+ content: {
200
+ primaryCurrency: {
201
+ value: '45678.90',
202
+ currency: 'nok',
203
+ },
204
+ secondaryCurrency: {
205
+ value: '41000.00',
206
+ currency: 'gbp',
207
+ },
208
+ },
209
+ alignment: 'right',
210
+ },
211
+ {
212
+ type: 'status',
213
+ content: {
214
+ primaryText: lorem2,
215
+ secondaryText: loremDescription,
216
+ sentiment: 'warning',
217
+ },
218
+ },
219
+ ],
220
+ },
221
+ {
222
+ id: 4,
223
+ rowContent: [
224
+ {
225
+ type: 'leading',
226
+ content: {
227
+ primaryText: lorem2,
228
+ secondaryText: loremDescription,
229
+ initials: 'PT',
230
+ },
231
+ },
232
+ {
233
+ content: { text: lorem2 },
234
+ },
235
+ {
236
+ content: { text: lorem2 },
237
+ },
238
+ {
239
+ type: 'currency',
240
+ content: {
241
+ primaryCurrency: {
242
+ value: '56789.01',
243
+ currency: 'sek',
244
+ },
245
+ secondaryCurrency: {
246
+ value: '51000.00',
247
+ currency: 'gbp',
248
+ },
249
+ },
250
+ alignment: 'right',
251
+ },
252
+ {
253
+ type: 'status',
254
+ content: {
255
+ primaryText: lorem2,
256
+ secondaryText: loremDescription,
257
+ sentiment: 'pending',
258
+ },
259
+ },
260
+ ],
261
+ },
262
+ ] satisfies TableRowType[] | TableRowClickableType[],
263
+ };
264
+
265
+ export const Basic: Story = {
266
+ args: {
267
+ data: tableData,
268
+ },
269
+ render: (args) => {
270
+ return <Table {...args} />;
271
+ },
272
+ };
273
+
274
+ export const WithClickableRow: Story = {
275
+ args: {
276
+ data: {
277
+ ...tableData,
278
+ onRowClick: (rowData: TableRowType | TableRowClickableType) => {
279
+ // eslint-disable-next-line no-console
280
+ console.log(`Row clicked, data:`, rowData);
281
+ },
282
+ },
283
+ },
284
+ render: (args) => {
285
+ return <Table {...args} />;
286
+ },
287
+ };
288
+
289
+ export const WithLoading: Story = {
290
+ args: {
291
+ loading: true,
292
+ },
293
+ };
294
+
295
+ export const WithEmptyData: Story = {
296
+ args: {
297
+ data: {
298
+ headers: tableData.headers,
299
+ content: [],
300
+ },
301
+ },
302
+ };
303
+
304
+ export const WithError: Story = {
305
+ args: {
306
+ error: {
307
+ message: 'Something went wrong during data fetching',
308
+ action: {
309
+ href: '/?path=/story/option-table--with-error',
310
+ text: 'To Refresh page, click here',
311
+ },
312
+ },
313
+ },
314
+ };
315
+
316
+ export const WithSimpleContent: Story = {
317
+ args: {
318
+ data: {
319
+ headers: [
320
+ {
321
+ header: sampleHeading,
322
+ },
323
+ {
324
+ header: sampleHeading,
325
+ },
326
+ ] satisfies TableHeaderType[],
327
+ content: [
328
+ {
329
+ id: 0,
330
+ rowContent: [{ content: { text: lorem5 } }, { content: { text: lorem5 } }],
331
+ },
332
+ {
333
+ id: 1,
334
+ rowContent: [{ content: { text: lorem5 } }, { content: { text: lorem5 } }],
335
+ },
336
+ {
337
+ id: 2,
338
+ rowContent: [{ content: { text: lorem5 } }, { content: { text: lorem5 } }],
339
+ },
340
+ {
341
+ id: 3,
342
+ rowContent: [{ content: { text: lorem5 } }, { content: { text: lorem5 } }],
343
+ },
344
+ {
345
+ id: 4,
346
+ rowContent: [{ content: { text: lorem5 } }, { content: { text: lorem5 } }],
347
+ },
348
+ ] satisfies TableRowType[] | TableRowClickableType[],
349
+ },
350
+ fullWidth: false,
351
+ },
352
+ };