@bundle-stats/ui 4.22.2-beta.1 → 4.22.3

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 (89) hide show
  1. package/.babelrc.js +65 -24
  2. package/globals.d.ts +13 -0
  3. package/lib/components/bundle-modules/index.js.map +1 -1
  4. package/lib/components/index.js.map +1 -1
  5. package/lib/components/metrics-table-header/metrics-table-header.js +2 -2
  6. package/lib/components/metrics-table-header/metrics-table-header.js.map +1 -1
  7. package/lib/css/default.css +1 -1
  8. package/lib/hooks/rows-sort.js +13 -12
  9. package/lib/hooks/rows-sort.js.map +1 -1
  10. package/lib/ui/button/button.js.map +1 -1
  11. package/lib/ui/empty-set/empty-set.js +3 -3
  12. package/lib/ui/empty-set/empty-set.js.map +1 -1
  13. package/lib-esm/components/bundle-modules/index.js.map +1 -1
  14. package/lib-esm/components/index.js.map +1 -1
  15. package/lib-esm/components/metrics-table-header/metrics-table-header.js +2 -2
  16. package/lib-esm/components/metrics-table-header/metrics-table-header.js.map +1 -1
  17. package/lib-esm/css/default.css +1 -1
  18. package/lib-esm/hooks/rows-sort.js +10 -8
  19. package/lib-esm/hooks/rows-sort.js.map +1 -1
  20. package/lib-esm/ui/button/button.js.map +1 -1
  21. package/lib-esm/ui/empty-set/empty-set.js +3 -3
  22. package/lib-esm/ui/empty-set/empty-set.js.map +1 -1
  23. package/package.json +26 -15
  24. package/src/app/app.stories.tsx +2 -2
  25. package/src/components/asset-info/asset-info.stories.tsx +47 -39
  26. package/src/components/asset-name/asset-name.stories.tsx +1 -3
  27. package/src/components/asset-not-predictive/asset-not-predictive.stories.tsx +22 -15
  28. package/src/components/browsertime-table/browsertime-table.stories.tsx +45 -0
  29. package/src/components/bundle-assets-totals/bundle-assets-totals.stories.tsx +47 -0
  30. package/src/components/bundle-modules/bundle-modules.stories.tsx +191 -0
  31. package/src/components/bundle-modules/bundle-modules.tsx +1 -1
  32. package/src/components/bundle-modules/index.tsx +4 -0
  33. package/src/components/bundle-packages/bundle-packages.stories.tsx +87 -0
  34. package/src/components/delta/delta.stories.tsx +57 -0
  35. package/src/components/entry-info/entry-info.stories.tsx +49 -54
  36. package/src/components/{index.js → index.ts} +4 -2
  37. package/src/components/insights/insights.stories.tsx +36 -33
  38. package/src/components/job-name/job-name.stories.tsx +46 -0
  39. package/src/components/jobs-header/jobs-header.stories.tsx +19 -15
  40. package/src/components/lighthouse-table/lighthouse-table.stories.tsx +30 -0
  41. package/src/components/metric/metric.stories.tsx +24 -22
  42. package/src/components/metric-run-info/metric-run-info.stories.tsx +45 -41
  43. package/src/components/metrics-table/metrics-table.stories.tsx +33 -13
  44. package/src/components/metrics-table/metrics-table.tsx +1 -1
  45. package/src/components/metrics-table-header/metrics-table-header.tsx +9 -4
  46. package/src/components/module-info/module-info.stories.tsx +41 -30
  47. package/src/components/package-info/package-info.stories.tsx +20 -15
  48. package/src/components/run-info/run-info.stories.tsx +39 -37
  49. package/src/components/summary/summary.stories.tsx +26 -32
  50. package/src/css/default.css +1 -1
  51. package/src/hooks/rows-sort.ts +9 -6
  52. package/src/layout/box/box.stories.tsx +35 -28
  53. package/src/layout/flex-stack/flex-stack.stories.tsx +79 -48
  54. package/src/layout/footer/footer.stories.tsx +30 -0
  55. package/src/layout/stack/stack.stories.tsx +68 -44
  56. package/src/ui/alert/alert.stories.tsx +25 -21
  57. package/src/ui/button/button.stories.tsx +181 -176
  58. package/src/ui/button/button.tsx +8 -1
  59. package/src/ui/container/container.stories.tsx +28 -21
  60. package/src/ui/control-group/control-group.stories.tsx +23 -16
  61. package/src/ui/dropdown/dropdown.stories.tsx +23 -15
  62. package/src/ui/empty-set/empty-set.jsx +16 -4
  63. package/src/ui/empty-set/empty-set.stories.tsx +41 -0
  64. package/src/ui/file-name/file-name.stories.tsx +45 -0
  65. package/src/ui/filters/filters.stories.tsx +29 -26
  66. package/src/ui/hover-card/hover-card.stories.tsx +35 -26
  67. package/src/ui/icon/icon.stories.tsx +36 -28
  68. package/src/ui/input/input.stories.tsx +17 -5
  69. package/src/ui/input-search/input-search.stories.tsx +31 -0
  70. package/src/ui/loader/loader.stories.tsx +22 -0
  71. package/src/ui/skeleton/skeleton.stories.tsx +42 -0
  72. package/src/ui/table/table.stories.tsx +244 -218
  73. package/src/ui/tag/tag.stories.tsx +11 -10
  74. package/src/ui/toolbar/toolbar.stories.tsx +41 -0
  75. package/types/components/bundle-modules/bundle-modules.d.ts +1 -1
  76. package/types/components/bundle-modules/index.d.ts +2 -1
  77. package/types/components/index.d.ts +22 -20
  78. package/types/components/metrics-table/metrics-table.d.ts +1 -1
  79. package/types/components/metrics-table-header/metrics-table-header.d.ts +1 -1
  80. package/types/hooks/rows-sort.d.ts +2 -2
  81. package/types/ui/button/button.d.ts +1 -1
  82. package/vitest.config.ts +47 -0
  83. package/vitest.setup.ts +26 -0
  84. package/lib/assets/icons.svg.js +0 -100
  85. package/lib/assets/icons.svg.js.map +0 -1
  86. package/lib-esm/assets/icons.svg.js +0 -63
  87. package/lib-esm/assets/icons.svg.js.map +0 -1
  88. package/src/assets/icons.svg.jsx +0 -279
  89. package/types/assets/icons.svg.d.ts +0 -2
@@ -1,15 +1,10 @@
1
1
  import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
2
3
  import { action } from 'storybook/actions';
3
4
 
4
5
  import { getWrapperDecorator } from '../../stories';
5
6
  import { EntryInfo } from '.';
6
7
 
7
- export default {
8
- title: 'Components/EntryInfo',
9
- component: EntryInfo,
10
- decorators: [getWrapperDecorator()],
11
- };
12
-
13
8
  const RUNS = [
14
9
  {
15
10
  name: 'static/vendor.abcd1234.js',
@@ -22,54 +17,54 @@ const RUNS = [
22
17
  },
23
18
  ];
24
19
 
25
- export const Default = () => (
26
- <EntryInfo
27
- item={{
28
- label: 'static/vendor.js',
29
- runs: RUNS as any,
30
- }}
31
- labels={['Job #2', 'Job #1']}
32
- onClose={action('CLOSE')}
33
- />
34
- );
20
+ const ITEM = {
21
+ key: 'static/vendor.js',
22
+ label: 'static/vendor.js',
23
+ biggerIsBetter: false,
24
+ changed: true,
25
+ };
26
+
27
+ const meta = {
28
+ title: 'Components/EntryInfo',
29
+ component: EntryInfo,
30
+ decorators: [getWrapperDecorator()],
31
+ args: {
32
+ item: { ...ITEM, runs: RUNS as any },
33
+ labels: ['Job #2', 'Job #1'],
34
+ onClose: action('CLOSE'),
35
+ },
36
+ } satisfies Meta<typeof EntryInfo>;
37
+
38
+ export default meta;
39
+
40
+ type Story = StoryObj<typeof meta>;
35
41
 
36
- export const Added = () => (
37
- <EntryInfo
38
- item={{
39
- label: 'static/vendor.js',
40
- runs: [RUNS[0], null] as any,
41
- }}
42
- labels={['Job #2', 'Job #1']}
43
- onClose={action('CLOSE')}
44
- />
45
- );
42
+ export const Default: Story = {};
43
+
44
+ export const Added: Story = {
45
+ args: {
46
+ item: { ...ITEM, runs: [RUNS[0], null] as any },
47
+ },
48
+ };
46
49
 
47
- export const Removed = () => (
48
- <EntryInfo
49
- item={{
50
- label: 'static/vendor.js',
51
- runs: [null, RUNS[1]] as any,
52
- }}
53
- labels={['Job #2', 'Job #1']}
54
- onClose={action('CLOSE')}
55
- />
56
- );
50
+ export const Removed: Story = {
51
+ args: {
52
+ item: { ...ITEM, runs: [null, RUNS[1]] as any },
53
+ },
54
+ };
57
55
 
58
- export const WithCustomContent = () => (
59
- <EntryInfo
60
- item={{
61
- label: 'static/vendor.js',
62
- runs: RUNS as any,
63
- }}
64
- labels={['Job #2', 'Job #1']}
65
- tags={<span>Critical tags</span>}
66
- onClose={action('CLOSE')}
67
- >
68
- <div>
69
- <EntryInfo.Meta label="Meta 1">value 1</EntryInfo.Meta>
70
- <EntryInfo.Meta label="Meta 2">value 2</EntryInfo.Meta>
71
- </div>
72
- <div>custom entry info 1</div>
73
- <div>custom entry info 2</div>
74
- </EntryInfo>
75
- );
56
+ export const WithCustomContent: Story = {
57
+ args: {
58
+ tags: <span>Critical tags</span>,
59
+ },
60
+ render: (args) => (
61
+ <EntryInfo {...args}>
62
+ <div>
63
+ <EntryInfo.Meta label="Meta 1">value 1</EntryInfo.Meta>
64
+ <EntryInfo.Meta label="Meta 2">value 2</EntryInfo.Meta>
65
+ </div>
66
+ <div>custom entry info 1</div>
67
+ <div>custom entry info 2</div>
68
+ </EntryInfo>
69
+ ),
70
+ };
@@ -11,10 +11,12 @@ export { JobName } from './job-name';
11
11
  export { JobsHeader } from './jobs-header';
12
12
  export { LighthouseTable } from './lighthouse-table';
13
13
  export { Metric } from './metric';
14
- export { MetricRunInfo, MetricRunInfoProps } from './metric-run-info';
14
+ export { MetricRunInfo } from './metric-run-info';
15
+ export type { MetricRunInfoProps } from './metric-run-info';
15
16
  export { MetricsTable } from './metrics-table';
16
17
  export { MetricsTableOptions } from './metrics-table-options';
17
18
  export { MetricsTableTitle } from './metrics-table-title';
18
19
  export { ModuleInfo } from './module-info';
19
- export { RunInfo, RunInfoProps } from './run-info';
20
+ export { RunInfo } from './run-info';
21
+ export type { RunInfoProps } from './run-info';
20
22
  export { Summary } from './summary';
@@ -1,53 +1,56 @@
1
- import React from 'react';
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { InsightType } from '@bundle-stats/utils';
2
3
 
3
4
  import { getWrapperDecorator } from '../../stories';
4
5
  import { Insights } from '.';
5
6
 
6
- export default {
7
+ const meta = {
7
8
  title: 'Components/Insights',
8
9
  component: Insights,
9
10
  decorators: [getWrapperDecorator()],
10
- };
11
+ } satisfies Meta<typeof Insights>;
11
12
 
12
- const Template = (props: any) => <Insights {...props} />;
13
+ export default meta;
13
14
 
14
- export const Default = Template.bind({});
15
+ type Story = StoryObj<typeof meta>;
15
16
 
16
- Default.args = {
17
- insights: {
18
- duplicatePackages: {
19
- type: 'error',
20
- data: {
21
- packages: { 'package-a': ['package-a', 'package-a~1'] },
22
- text: 'Bundle introduced 1 and removed 2 duplicate packages',
17
+ export const Default: Story = {
18
+ args: {
19
+ insights: {
20
+ duplicatePackages: {
21
+ type: InsightType.ERROR,
22
+ data: {
23
+ packages: { 'package-a': ['package-a', 'package-a~1'] },
24
+ text: 'Bundle introduced 1 and removed 2 duplicate packages',
25
+ },
23
26
  },
24
- },
25
- newPackages: {
26
- type: 'warning',
27
- data: {
28
- text: 'Bundle introduced 2 new packages: package-c, package-d',
29
- packages: ['package-c', 'package-d'],
27
+ newPackages: {
28
+ type: InsightType.WARNING,
29
+ data: {
30
+ text: 'Bundle introduced 2 new packages: package-c, package-d',
31
+ packages: ['package-c', 'package-d'],
32
+ },
30
33
  },
31
34
  },
32
35
  },
33
36
  };
34
37
 
35
- export const Info = Template.bind({});
36
-
37
- Info.args = {
38
- insights: {
39
- duplicatePackages: {
40
- type: 'info',
41
- data: {
42
- packages: { 'package-a': ['package-a', 'package-a~1'] },
43
- text: 'Bundle removed 1 duplicate packages',
38
+ export const Info: Story = {
39
+ args: {
40
+ insights: {
41
+ duplicatePackages: {
42
+ type: InsightType.INFO,
43
+ data: {
44
+ packages: { 'package-a': ['package-a', 'package-a~1'] },
45
+ text: 'Bundle removed 1 duplicate packages',
46
+ },
44
47
  },
45
- },
46
- newPackages: {
47
- type: 'warning',
48
- data: {
49
- text: 'Bundle introduced 2 new packages: package-c, package-d',
50
- packages: ['package-c', 'package-d'],
48
+ newPackages: {
49
+ type: InsightType.WARNING,
50
+ data: {
51
+ text: 'Bundle introduced 2 new packages: package-c, package-d',
52
+ packages: ['package-c', 'package-d'],
53
+ },
51
54
  },
52
55
  },
53
56
  },
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import { getWrapperDecorator } from '../../stories';
5
+ import { JobName } from '.';
6
+
7
+ // JobName is a plain .jsx component with defaultProps instead of inline
8
+ // destructuring defaults, so TS can't infer its props as optional on its own.
9
+ const TypedJobName = JobName as unknown as React.ComponentType<{
10
+ as?: React.ElementType;
11
+ title?: string;
12
+ internalBuildNumber?: number;
13
+ children?: React.ReactNode;
14
+ }>;
15
+
16
+ const meta = {
17
+ title: 'Components/JobName',
18
+ component: TypedJobName,
19
+ decorators: [getWrapperDecorator({ padding: '64px' })],
20
+ args: {
21
+ title: 'View job details',
22
+ },
23
+ } satisfies Meta<typeof TypedJobName>;
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ internalBuildNumber: 10,
32
+ },
33
+ };
34
+
35
+ export const CustomComponent: Story = {
36
+ args: {
37
+ internalBuildNumber: 10,
38
+ as: 'strong',
39
+ },
40
+ };
41
+
42
+ export const Render: Story = {
43
+ args: {
44
+ children: <a href="https://relative-ci.com/test">#10</a>,
45
+ },
46
+ };
@@ -1,17 +1,21 @@
1
- import React from 'react';
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
2
 
3
3
  import { getWrapperDecorator } from '../../stories';
4
4
  import { JobsHeader } from '.';
5
5
 
6
- export default {
6
+ const meta = {
7
7
  title: 'Components/JobsHeader',
8
8
  component: JobsHeader,
9
9
  decorators: [getWrapperDecorator()],
10
- };
10
+ } satisfies Meta<typeof JobsHeader>;
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof meta>;
11
15
 
12
- export const Default = () => (
13
- <JobsHeader
14
- jobs={[
16
+ export const Default: Story = {
17
+ args: {
18
+ jobs: [
15
19
  {
16
20
  internalBuildNumber: 1,
17
21
  meta: {
@@ -21,13 +25,13 @@ export const Default = () => (
21
25
  },
22
26
  },
23
27
  },
24
- ]}
25
- />
26
- );
28
+ ],
29
+ },
30
+ };
27
31
 
28
- export const MultipleJobs = () => (
29
- <JobsHeader
30
- jobs={[
32
+ export const MultipleJobs: Story = {
33
+ args: {
34
+ jobs: [
31
35
  {
32
36
  internalBuildNumber: 1,
33
37
  meta: {
@@ -46,6 +50,6 @@ export const MultipleJobs = () => (
46
50
  },
47
51
  },
48
52
  },
49
- ]}
50
- />
51
- );
53
+ ],
54
+ },
55
+ };
@@ -0,0 +1,30 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { createJobs } from '@bundle-stats/utils';
3
+
4
+ import lighthouse from '../../../__mocks__/lighthouse.json';
5
+ import { getWrapperDecorator } from '../../stories';
6
+ import { LighthouseTable } from '.';
7
+
8
+ const JOBS = createJobs([{ lighthouse }, { lighthouse }]);
9
+
10
+ const meta = {
11
+ title: 'Components/LighthouseTable',
12
+ component: LighthouseTable,
13
+ decorators: [getWrapperDecorator()],
14
+ } satisfies Meta<typeof LighthouseTable>;
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ jobs: JOBS,
23
+ },
24
+ };
25
+
26
+ export const NoBaseline: Story = {
27
+ args: {
28
+ jobs: [JOBS[1]],
29
+ },
30
+ };
@@ -1,41 +1,43 @@
1
1
  import React from 'react';
2
- import { Meta, Story } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
3
 
4
4
  import { getWrapperDecorator } from '../../stories';
5
5
  import { Delta } from '../delta';
6
6
  import { Metric } from '.';
7
7
 
8
- export default {
8
+ const meta = {
9
9
  title: 'Components/Metric',
10
10
  component: Metric,
11
11
  decorators: [getWrapperDecorator({ maxWidth: '200px' })],
12
- } as Meta;
12
+ } satisfies Meta<typeof Metric>;
13
13
 
14
- const Template: Story = (args) => <Metric {...args} />;
14
+ export default meta;
15
15
 
16
- export const Default = Template.bind({});
16
+ type Story = StoryObj<typeof meta>;
17
17
 
18
- Default.args = {
19
- value: '100',
18
+ export const Default: Story = {
19
+ args: {
20
+ value: '100',
21
+ },
20
22
  };
21
23
 
22
- export const WithUnit = Template.bind({});
23
-
24
- WithUnit.args = {
25
- value: '100',
26
- unit: 'KiB',
24
+ export const WithUnit: Story = {
25
+ args: {
26
+ value: '100',
27
+ unit: 'KiB',
28
+ },
27
29
  };
28
30
 
29
- export const WithDelta = Template.bind({});
30
-
31
- WithDelta.args = {
32
- ...WithUnit.args,
33
- children: <Delta displayValue="+10%" deltaType="POSITIVE" />,
31
+ export const WithDelta: Story = {
32
+ args: {
33
+ ...WithUnit.args,
34
+ children: <Delta displayValue="+10%" deltaType="POSITIVE" />,
35
+ },
34
36
  };
35
37
 
36
- export const Inline = Template.bind({});
37
-
38
- Inline.args = {
39
- ...WithDelta.args,
40
- inline: true,
38
+ export const Inline: Story = {
39
+ args: {
40
+ ...WithDelta.args,
41
+ inline: true,
42
+ },
41
43
  };
@@ -1,61 +1,65 @@
1
1
  import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
2
3
 
3
- import { Meta, Story } from '@storybook/react';
4
4
  import { MetricRunInfo } from '.';
5
5
 
6
- export default {
6
+ const meta = {
7
7
  title: 'Components/MetricRunInfo',
8
8
  component: MetricRunInfo,
9
- } as Meta;
9
+ } satisfies Meta<typeof MetricRunInfo>;
10
10
 
11
- const Template: Story = (args: any) => <MetricRunInfo {...args} />;
11
+ export default meta;
12
12
 
13
- export const Default = Template.bind({});
13
+ type Story = StoryObj<typeof meta>;
14
14
 
15
- Default.args = {
16
- metricId: 'webpack.totalSizeByTypeALL',
17
- current: 120 * 1024,
18
- baseline: 100 * 1024,
15
+ export const Default: Story = {
16
+ args: {
17
+ metricId: 'webpack.totalSizeByTypeALL',
18
+ current: 120 * 1024,
19
+ baseline: 100 * 1024,
20
+ },
19
21
  };
20
22
 
21
- export const NoBaseline = Template.bind({});
22
-
23
- NoBaseline.args = {
24
- metricId: 'webpack.totalSizeByTypeALL',
25
- current: 120 * 1024,
26
- showBaseline: false,
23
+ export const NoBaseline: Story = {
24
+ args: {
25
+ metricId: 'webpack.totalSizeByTypeALL',
26
+ current: 120 * 1024,
27
+ showBaseline: false,
28
+ },
27
29
  };
28
30
 
29
- export const NoDelta = Template.bind({});
30
-
31
- NoDelta.args = {
32
- ...NoBaseline.args,
33
- showDelta: false,
31
+ export const NoDelta: Story = {
32
+ args: {
33
+ ...NoBaseline.args,
34
+ showDelta: false,
35
+ },
34
36
  };
35
37
 
36
- export const MetricNoDelta = Template.bind({});
37
-
38
- MetricNoDelta.args = {
39
- metricId: 'webpack.cacheInvalidation',
40
- current: 85,
41
- baseline: 50,
38
+ export const MetricNoDelta: Story = {
39
+ args: {
40
+ metricId: 'webpack.cacheInvalidation',
41
+ current: 85,
42
+ baseline: 50,
43
+ },
42
44
  };
43
45
 
44
- export const CustomTitle = Template.bind({});
45
-
46
- CustomTitle.args = {
47
- ...Default.args,
48
- titleWrapper: ({ children, style, ...restProps }: React.ComponentProps<'h1'>) => (
49
- <h1 style={{ ...style, fontStyle: 'italic' }} {...restProps}>
50
- {children}
51
- </h1>
52
- ),
46
+ export const CustomTitle: Story = {
47
+ args: {
48
+ ...Default.args,
49
+ titleWrapper: ({ children, style, ...restProps }) => (
50
+ <h1 style={{ ...style, fontStyle: 'italic' }} {...restProps}>
51
+ {children}
52
+ </h1>
53
+ ),
54
+ },
53
55
  };
54
56
 
55
- export const CustomComponent = Template.bind({});
56
-
57
- CustomComponent.args = {
58
- ...Default.args,
59
- as: 'a',
60
- href: 'https://google.com/search?query=bundle+stats',
57
+ export const CustomComponent: Story = {
58
+ // MetricRunInfoProps only types its passthrough props as ComponentProps<'div'>,
59
+ // but they're forwarded to RunInfo, which supports polymorphic `as` at runtime.
60
+ args: {
61
+ ...Default.args,
62
+ as: 'a',
63
+ href: 'https://google.com/search?query=bundle+stats',
64
+ } as unknown as Story['args'],
61
65
  };
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
-
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
3
  import { MetricRunInfoDeltaType } from '@bundle-stats/utils';
4
+
4
5
  import { getWrapperDecorator } from '../../stories';
5
6
  import { MetricsTable } from '.';
6
7
 
@@ -204,22 +205,41 @@ const ITEMS_MULTIPLE_RUNS = [
204
205
  },
205
206
  ];
206
207
 
207
- export default {
208
+ const meta = {
208
209
  title: 'Components/MetricsTable',
209
210
  component: MetricsTable,
210
211
  decorators: [getWrapperDecorator()],
211
- };
212
+ } satisfies Meta<typeof MetricsTable>;
212
213
 
213
- export const Default = () => <MetricsTable runs={RUNS.slice(0, 1)} items={ITEMS_SINGLE_RUN} />;
214
+ export default meta;
214
215
 
215
- export const MultipleRuns = () => <MetricsTable runs={RUNS} items={ITEMS_MULTIPLE_RUNS} />;
216
+ type Story = StoryObj<typeof meta>;
216
217
 
217
- export const WithHeaderSum = () => (
218
- <MetricsTable
219
- runs={RUNS}
220
- items={[ITEMS_MULTIPLE_RUNS[0], ITEMS_MULTIPLE_RUNS[4]]}
221
- showHeaderSum
222
- />
223
- );
218
+ export const Default: Story = {
219
+ args: {
220
+ runs: RUNS.slice(0, 1),
221
+ items: ITEMS_SINGLE_RUN,
222
+ },
223
+ };
224
224
 
225
- export const Empty = () => <MetricsTable runs={RUNS} items={[]} />;
225
+ export const MultipleRuns: Story = {
226
+ args: {
227
+ runs: RUNS,
228
+ items: ITEMS_MULTIPLE_RUNS,
229
+ },
230
+ };
231
+
232
+ export const WithHeaderSum: Story = {
233
+ args: {
234
+ runs: RUNS,
235
+ items: [ITEMS_MULTIPLE_RUNS[0], ITEMS_MULTIPLE_RUNS[4]],
236
+ showHeaderSum: true,
237
+ },
238
+ };
239
+
240
+ export const Empty: Story = {
241
+ args: {
242
+ runs: RUNS,
243
+ items: [],
244
+ },
245
+ };
@@ -68,7 +68,7 @@ const Row = ({ className = '', item, renderHeader, ...restProps }: RowProps) =>
68
68
 
69
69
  interface MetricsTableProps extends Omit<React.ComponentProps<typeof Table>, 'title'> {
70
70
  runs: Array<{
71
- label: string;
71
+ label: React.ReactNode;
72
72
  internalBuildNumber: number;
73
73
  }>;
74
74
  items: Array<ReportMetricRow>;
@@ -21,7 +21,7 @@ const BASELINE_COLUMN_SPAN = 1;
21
21
  const CURRENT_COLUMN_SPAN = 3;
22
22
 
23
23
  interface JobColumnProps {
24
- job: { label: string; internalBuildNumber: number };
24
+ job: { label: React.ReactNode; internalBuildNumber: number };
25
25
  isBaseline: boolean;
26
26
  }
27
27
 
@@ -122,7 +122,7 @@ export interface MetricsTableHeaderProps {
122
122
  /**
123
123
  * Array of jobs
124
124
  */
125
- jobs: Array<{ label: string; internalBuildNumber: number }>;
125
+ jobs: Array<{ label: React.ReactNode; internalBuildNumber: number }>;
126
126
  /**
127
127
  * Report rows
128
128
  */
@@ -151,13 +151,18 @@ export const MetricsTableHeader = (
151
151
  {metricTitle || ' '}
152
152
  </Table.Th>
153
153
  {jobs.map((job, runIndex) => (
154
- <JobColumn job={job} isBaseline={runIndex === jobs.length - 1} />
154
+ <JobColumn
155
+ key={job.internalBuildNumber}
156
+ job={job}
157
+ isBaseline={runIndex === jobs.length - 1}
158
+ />
155
159
  ))}
156
160
  </Table.Tr>
157
161
  {showSum && (
158
162
  <Table.Tr>
159
- {jobs.map((_, runIndex) => (
163
+ {jobs.map((job, runIndex) => (
160
164
  <SumColumn
165
+ key={job.internalBuildNumber}
161
166
  rows={rows}
162
167
  isBaseline={runIndex === jobs.length - 1}
163
168
  runIndex={runIndex}