@bundle-stats/ui 3.3.0-alpha.1 → 3.4.0-alpha.1

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 (178) hide show
  1. package/CHANGELOG.md +94 -13
  2. package/lib/app/app.js +137 -102
  3. package/lib/app/app.module.css +11 -4
  4. package/lib/app/header/header.js +1 -10
  5. package/lib/components/asset-info/asset-info.module.css +1 -0
  6. package/lib/components/bundle-assets/bundle-assets.js +104 -52
  7. package/lib/components/bundle-assets/bundle-assets.module.css +24 -26
  8. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +13 -3
  9. package/lib/components/bundle-modules/bundle-modules.js +53 -67
  10. package/lib/components/bundle-packages/bundle-packages.js +100 -44
  11. package/lib/components/bundle-packages/bundle-packages.module.css +17 -21
  12. package/lib/components/delta/delta.module.css +1 -0
  13. package/lib/components/job-header/job-header.js +3 -1
  14. package/lib/components/job-header/job-header.module.css +6 -8
  15. package/lib/components/jobs-header/jobs-header.module.css +5 -0
  16. package/lib/components/metrics-table/metrics-table.js +72 -59
  17. package/lib/components/metrics-table/metrics-table.module.css +18 -7
  18. package/lib/components/metrics-table-search/metrics-table-search.module.css +5 -4
  19. package/lib/components/metrics-table-title/metrics-table-title.js +9 -7
  20. package/lib/components/metrics-table-title/metrics-table-title.module.css +2 -0
  21. package/lib/components/module-info/module-info.js +2 -2
  22. package/lib/components/module-info/module-info.module.css +1 -0
  23. package/lib/components/summary/summary.js +1 -1
  24. package/lib/components/summary-item/summary-item.js +15 -18
  25. package/lib/components/summary-item/summary-item.module.css +1 -23
  26. package/lib/components/total-size-type-title/total-size-type-title.js +7 -3
  27. package/lib/css/default.css +14 -3
  28. package/lib/css/variables.css +17 -17
  29. package/lib/hooks/search-params.js +31 -31
  30. package/lib/layout/box/box.module.css +9 -0
  31. package/lib/layout/footer/footer.module.css +1 -1
  32. package/lib/layout/index.js +8 -0
  33. package/lib/layout/separator.js +48 -0
  34. package/lib/layout/separator.module.css +5 -0
  35. package/lib/ui/dropdown/dropdown.js +21 -26
  36. package/lib/ui/dropdown/dropdown.module.css +11 -12
  37. package/lib/ui/dropdown/dropdown.stories.js +2 -9
  38. package/lib/ui/empty-set/empty-set.js +5 -4
  39. package/lib/ui/empty-set/empty-set.module.css +2 -2
  40. package/lib/ui/filters/filters.container.js +1 -1
  41. package/lib/ui/filters/filters.js +22 -7
  42. package/lib/ui/filters/filters.module.css +3 -3
  43. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js +1 -1
  44. package/lib/ui/hover-card/hover-card.js +102 -0
  45. package/lib/ui/hover-card/hover-card.module.css +29 -0
  46. package/lib/ui/hover-card/hover-card.stories.js +56 -0
  47. package/lib/ui/hover-card/index.js +18 -0
  48. package/lib/ui/icon/icon.js +6 -6
  49. package/lib/ui/icon/icon.module.css +1 -0
  50. package/lib/ui/index.js +8 -8
  51. package/lib/ui/table/table.js +5 -74
  52. package/lib/ui/table/table.module.css +41 -25
  53. package/lib/ui/table/table.stories.js +1 -1
  54. package/lib/ui/{popover → tag}/index.js +4 -4
  55. package/lib/ui/tag/tag.js +72 -0
  56. package/lib/ui/tag/tag.module.css +55 -0
  57. package/lib/ui/tag/tag.stories.js +38 -0
  58. package/lib/ui/tooltip/tooltip.js +10 -9
  59. package/lib/ui/tooltip/tooltip.module.css +2 -1
  60. package/lib-esm/app/app.js +132 -102
  61. package/lib-esm/app/app.module.css +11 -4
  62. package/lib-esm/app/header/header.js +1 -9
  63. package/lib-esm/components/asset-info/asset-info.module.css +1 -0
  64. package/lib-esm/components/bundle-assets/bundle-assets.js +97 -56
  65. package/lib-esm/components/bundle-assets/bundle-assets.module.css +24 -26
  66. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +7 -3
  67. package/lib-esm/components/bundle-modules/bundle-modules.js +35 -50
  68. package/lib-esm/components/bundle-packages/bundle-packages.js +88 -41
  69. package/lib-esm/components/bundle-packages/bundle-packages.module.css +17 -21
  70. package/lib-esm/components/delta/delta.module.css +1 -0
  71. package/lib-esm/components/job-header/job-header.js +2 -1
  72. package/lib-esm/components/job-header/job-header.module.css +6 -8
  73. package/lib-esm/components/jobs-header/jobs-header.module.css +5 -0
  74. package/lib-esm/components/metrics-table/metrics-table.js +61 -53
  75. package/lib-esm/components/metrics-table/metrics-table.module.css +18 -7
  76. package/lib-esm/components/metrics-table-search/metrics-table-search.module.css +5 -4
  77. package/lib-esm/components/metrics-table-title/metrics-table-title.js +8 -7
  78. package/lib-esm/components/metrics-table-title/metrics-table-title.module.css +2 -0
  79. package/lib-esm/components/module-info/module-info.js +2 -2
  80. package/lib-esm/components/module-info/module-info.module.css +1 -0
  81. package/lib-esm/components/summary/summary.js +1 -1
  82. package/lib-esm/components/summary-item/summary-item.js +14 -18
  83. package/lib-esm/components/summary-item/summary-item.module.css +1 -23
  84. package/lib-esm/components/total-size-type-title/total-size-type-title.js +6 -3
  85. package/lib-esm/css/default.css +14 -3
  86. package/lib-esm/css/variables.css +17 -17
  87. package/lib-esm/hooks/search-params.js +27 -28
  88. package/lib-esm/layout/box/box.module.css +9 -0
  89. package/lib-esm/layout/footer/footer.module.css +1 -1
  90. package/lib-esm/layout/index.js +1 -0
  91. package/lib-esm/layout/separator.js +28 -0
  92. package/lib-esm/layout/separator.module.css +5 -0
  93. package/lib-esm/ui/dropdown/dropdown.js +20 -25
  94. package/lib-esm/ui/dropdown/dropdown.module.css +11 -12
  95. package/lib-esm/ui/dropdown/dropdown.stories.js +2 -7
  96. package/lib-esm/ui/empty-set/empty-set.js +4 -4
  97. package/lib-esm/ui/empty-set/empty-set.module.css +2 -2
  98. package/lib-esm/ui/filters/filters.js +20 -5
  99. package/lib-esm/ui/filters/filters.module.css +3 -3
  100. package/lib-esm/ui/hover-card/hover-card.js +83 -0
  101. package/lib-esm/ui/hover-card/hover-card.module.css +29 -0
  102. package/lib-esm/ui/hover-card/hover-card.stories.js +35 -0
  103. package/lib-esm/ui/hover-card/index.js +1 -0
  104. package/lib-esm/ui/icon/icon.js +3 -3
  105. package/lib-esm/ui/icon/icon.module.css +1 -0
  106. package/lib-esm/ui/index.js +1 -1
  107. package/lib-esm/ui/table/table.js +5 -65
  108. package/lib-esm/ui/table/table.module.css +41 -25
  109. package/lib-esm/ui/table/table.stories.js +1 -1
  110. package/lib-esm/ui/tag/index.js +1 -0
  111. package/lib-esm/ui/tag/tag.js +45 -0
  112. package/lib-esm/ui/tag/tag.module.css +55 -0
  113. package/lib-esm/ui/tag/tag.stories.js +23 -0
  114. package/lib-esm/ui/tooltip/tooltip.js +10 -9
  115. package/lib-esm/ui/tooltip/tooltip.module.css +2 -1
  116. package/package.json +33 -31
  117. package/src/app/app.jsx +142 -145
  118. package/src/app/app.module.css +11 -4
  119. package/src/app/header/header.jsx +0 -10
  120. package/src/components/asset-info/asset-info.module.css +1 -0
  121. package/src/components/bundle-assets/bundle-assets.jsx +85 -31
  122. package/src/components/bundle-assets/bundle-assets.module.css +24 -26
  123. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +2 -0
  124. package/src/components/bundle-modules/bundle-modules.jsx +36 -36
  125. package/src/components/bundle-packages/bundle-packages.jsx +89 -39
  126. package/src/components/bundle-packages/bundle-packages.module.css +17 -21
  127. package/src/components/delta/delta.module.css +1 -0
  128. package/src/components/job-header/job-header.jsx +2 -1
  129. package/src/components/job-header/job-header.module.css +6 -8
  130. package/src/components/jobs-header/jobs-header.module.css +5 -0
  131. package/src/components/metrics-table/metrics-table.jsx +53 -44
  132. package/src/components/metrics-table/metrics-table.module.css +18 -7
  133. package/src/components/metrics-table-search/metrics-table-search.module.css +5 -4
  134. package/src/components/metrics-table-title/metrics-table-title.jsx +20 -21
  135. package/src/components/metrics-table-title/metrics-table-title.module.css +2 -0
  136. package/src/components/module-info/module-info.jsx +3 -7
  137. package/src/components/module-info/module-info.module.css +1 -0
  138. package/src/components/summary/summary.jsx +1 -1
  139. package/src/components/summary-item/summary-item.jsx +21 -27
  140. package/src/components/summary-item/summary-item.module.css +1 -23
  141. package/src/components/total-size-type-title/total-size-type-title.jsx +6 -9
  142. package/src/css/default.css +14 -3
  143. package/src/css/variables.css +17 -17
  144. package/src/hooks/search-params.js +42 -33
  145. package/src/layout/box/box.module.css +9 -0
  146. package/src/layout/footer/footer.module.css +1 -1
  147. package/src/layout/index.js +1 -0
  148. package/src/layout/separator.jsx +25 -0
  149. package/src/layout/separator.module.css +5 -0
  150. package/src/ui/dropdown/dropdown.jsx +16 -22
  151. package/src/ui/dropdown/dropdown.module.css +11 -12
  152. package/src/ui/empty-set/empty-set.jsx +25 -21
  153. package/src/ui/empty-set/empty-set.module.css +2 -2
  154. package/src/ui/filters/filters.jsx +64 -53
  155. package/src/ui/filters/filters.module.css +3 -3
  156. package/src/ui/hover-card/hover-card.jsx +73 -0
  157. package/src/ui/hover-card/hover-card.module.css +29 -0
  158. package/src/ui/hover-card/index.js +1 -0
  159. package/src/ui/icon/icon.jsx +11 -7
  160. package/src/ui/icon/icon.module.css +1 -0
  161. package/src/ui/index.js +1 -1
  162. package/src/ui/table/table.jsx +5 -52
  163. package/src/ui/table/table.module.css +41 -25
  164. package/src/ui/tag/index.js +1 -0
  165. package/src/ui/tag/tag.jsx +49 -0
  166. package/src/ui/tag/tag.module.css +55 -0
  167. package/src/ui/tooltip/tooltip.jsx +10 -14
  168. package/src/ui/tooltip/tooltip.module.css +2 -1
  169. package/lib/ui/popover/popover.js +0 -92
  170. package/lib/ui/popover/popover.module.css +0 -66
  171. package/lib/ui/popover/popover.stories.js +0 -26
  172. package/lib-esm/ui/popover/index.js +0 -1
  173. package/lib-esm/ui/popover/popover.js +0 -71
  174. package/lib-esm/ui/popover/popover.module.css +0 -66
  175. package/lib-esm/ui/popover/popover.stories.js +0 -14
  176. package/src/ui/popover/index.js +0 -1
  177. package/src/ui/popover/popover.jsx +0 -79
  178. package/src/ui/popover/popover.module.css +0 -66
@@ -1,16 +1,22 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
4
  import { get, map } from 'lodash';
5
- import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, FILE_TYPE_LABELS } from '@bundle-stats/utils';
5
+ import {
6
+ ASSET_ENTRY_TYPE,
7
+ ASSET_FILE_TYPE,
8
+ ASSET_FILTERS,
9
+ FILE_TYPE_LABELS,
10
+ } from '@bundle-stats/utils';
6
11
 
7
12
  import config from '../../config.json';
8
13
  import I18N from '../../i18n';
9
14
  import { FlexStack } from '../../layout/flex-stack';
10
15
  import { Icon } from '../../ui/icon';
11
16
  import { FileName } from '../../ui/file-name';
12
- import { Popover } from '../../ui/popover';
17
+ import { HoverCard } from '../../ui/hover-card';
13
18
  import { Tooltip } from '../../ui/tooltip';
19
+ import { Tag } from '../../ui/tag';
14
20
  import { Filters } from '../../ui/filters';
15
21
  import { SortDropdown } from '../../ui/sort-dropdown';
16
22
  import { EmptySet } from '../../ui/empty-set';
@@ -71,48 +77,91 @@ TooltipNotPredictive.propTypes = {
71
77
  runs: PropTypes.array, // eslint-disable-line react/forbid-prop-types
72
78
  };
73
79
 
74
- const getRenderRowHeader = ({ labels, CustomComponentLink, chunks }) => (item) => {
75
- const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = item;
80
+ const RowHeader = ({ row, labels, chunks, CustomComponentLink }) => {
81
+ const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row;
82
+ const [showHoverCard, setHoverCard] = useState(false);
83
+ const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
76
84
 
77
- return (
78
- <Popover
79
- label={<FileName name={label} />}
80
- icon={
81
- <div className={css.assetInfoFlags}>
85
+ const content = useMemo(
86
+ () => (
87
+ <span className={css.assetName}>
88
+ <span className={css.assetNameTags}>
82
89
  {isNotPredictive && (
83
90
  <Tooltip className={css.notPredictive} title={<TooltipNotPredictive runs={runs} />}>
84
91
  <Icon className={css.notPredictiveIcon} glyph="warning" />
85
92
  </Tooltip>
86
93
  )}
87
94
  {isChunk && (
88
- <span title="Chunk" className={css.flagChunk}>
89
- c
90
- </span>
95
+ <Tag
96
+ className={cx(css.assetNameTag, css.assetNameTagChunk)}
97
+ title="Chunk"
98
+ size={Tag.SIZES.SMALL}
99
+ kind={Tag.KINDS.INFO}
100
+ />
91
101
  )}
92
102
  {isEntry && (
93
- <span title="Entrypoint" className={css.flagEntry}>
94
- e
95
- </span>
103
+ <Tag
104
+ className={cx(css.assetNameTag, css.assetNameTagEntry)}
105
+ title="Entrypoint"
106
+ size={Tag.SIZES.SMALL}
107
+ kind={Tag.KINDS.INFO}
108
+ />
96
109
  )}
97
110
  {isInitial && (
98
- <span title="Initial" className={css.flagInitial}>
99
- i
100
- </span>
111
+ <Tag
112
+ className={cx(css.assetNameTag, css.assetNameTagInitial)}
113
+ title="Initial"
114
+ size={Tag.SIZES.SMALL}
115
+ kind={Tag.KINDS.INFO}
116
+ />
101
117
  )}
102
- </div>
103
- }
104
- >
118
+ </span>
119
+ <FileName className={css.assetNameText} name={label} />
120
+ </span>
121
+ ),
122
+ [isChunk, isEntry, isInitial, isNotPredictive, runs],
123
+ );
124
+
125
+ if (!showHoverCard) {
126
+ return <span onMouseEnter={handleOnMouseEnter}>{content}</span>;
127
+ }
128
+
129
+ return (
130
+ <HoverCard label={content}>
105
131
  <AssetInfo
106
132
  className={css.assetInfo}
107
- item={item}
133
+ item={row}
108
134
  labels={labels}
109
135
  chunks={chunks}
110
136
  CustomComponentLink={CustomComponentLink}
111
137
  />
112
- </Popover>
138
+ </HoverCard>
113
139
  );
114
140
  };
115
141
 
142
+ RowHeader.propTypes = {
143
+ row: PropTypes.shape({
144
+ label: PropTypes.string,
145
+ isNotPredictive: PropTypes.bool,
146
+ isChunk: PropTypes.bool,
147
+ isInitial: PropTypes.bool,
148
+ isEntry: PropTypes.bool,
149
+ runs: PropTypes.arrayOf(PropTypes.object),
150
+ }).isRequired,
151
+ chunks: PropTypes.arrayOf(
152
+ PropTypes.shape({
153
+ id: PropTypes.string,
154
+ name: PropTypes.string,
155
+ }),
156
+ ),
157
+ labels: PropTypes.arrayOf(PropTypes.string).isRequired,
158
+ CustomComponentLink: PropTypes.elementType.isRequired,
159
+ };
160
+
161
+ RowHeader.defaultProps = {
162
+ chunks: [],
163
+ };
164
+
116
165
  export const BundleAssets = (props) => {
117
166
  const {
118
167
  className,
@@ -141,11 +190,16 @@ export const BundleAssets = (props) => {
141
190
  />
142
191
  );
143
192
 
144
- const chunks = jobs[0]?.meta?.webpack?.chunks || [];
145
-
146
- const renderRowHeader = useMemo(
147
- () => getRenderRowHeader({ labels: map(jobs, 'label'), CustomComponentLink, chunks }),
148
- [jobs, chunks],
193
+ const renderRowHeader = useCallback(
194
+ (row) => (
195
+ <RowHeader
196
+ row={row}
197
+ labels={map(jobs, 'label')}
198
+ chunks={jobs[0]?.meta?.webpack?.chunks || []}
199
+ CustomComponentLink={CustomComponentLink}
200
+ />
201
+ ),
202
+ [jobs, CustomComponentLink],
149
203
  );
150
204
 
151
205
  return (
@@ -153,7 +207,7 @@ export const BundleAssets = (props) => {
153
207
  <Toolbar
154
208
  className={css.toolbar}
155
209
  renderActions={({ actionClassName }) => (
156
- <FlexStack space="xsmall" className={cx(css.dropdown, actionClassName)}>
210
+ <FlexStack space="xxsmall" className={cx(css.dropdown, actionClassName)}>
157
211
  <SortDropdown fields={sortFields} {...sort} onChange={updateSort} />
158
212
  <MetricsTableOptions
159
213
  handleViewAll={resetAllFilters}
@@ -162,7 +216,7 @@ export const BundleAssets = (props) => {
162
216
  </FlexStack>
163
217
  )}
164
218
  >
165
- <FlexStack>
219
+ <FlexStack space="xxsmall">
166
220
  <MetricsTableSearch
167
221
  className={css.toolbarSearch}
168
222
  placeholder="Search by name"
@@ -8,6 +8,8 @@
8
8
  margin-right: -1px; /** collapse border */
9
9
  }
10
10
 
11
+ .assetName {}
12
+
11
13
  .notPredictive {
12
14
  margin-right: var(--space-xxsmall);
13
15
  display: inline-block;
@@ -21,50 +23,46 @@
21
23
  vertical-align: top;
22
24
  }
23
25
 
24
- .assetInfoFlags {
25
- display: inline-block;
26
+ .assetNameTags {
27
+ display: inline;
26
28
  margin-right: var(--space-xxxsmall);
27
29
  }
28
30
 
29
- .assetInfoFlags:empty {
31
+ .assetNameTags:empty {
30
32
  display: none;
31
33
  }
32
34
 
33
- .flag {
34
- display: inline-block;
35
- width: var(--space-xsmall);
36
- height: var(--space-xsmall);
37
- font-family: var(--font-family);
38
- font-size: 8px;
39
- font-weight: bold;
40
- background: var(--color-highlight);
41
- color: var(--color-text-light);
42
- line-height: var(--space-xsmall);
43
- text-align: center;
44
- text-transform: uppercase;
35
+ .assetNameTag {
45
36
  vertical-align: middle;
46
37
  }
47
38
 
48
- .flag + .flag {
39
+ .assetNameTag + .assetNameTag {
49
40
  margin-left: 2px;
50
41
  }
51
42
 
52
- .flagEntry {
53
- composes: flag;
43
+ .assetNameTagEntry {
54
44
  background: var(--color-info-light);
55
- color: var(--color-background);
56
45
  }
57
46
 
58
- .flagInitial {
59
- composes: flag;
47
+ .assetNameTagEntry::before {
48
+ content: 'e';
49
+ }
50
+
51
+ .assetNameTagInitial {
60
52
  background: var(--color-info-light);
61
- color: var(--color-background);
62
53
  }
63
54
 
64
- .flagChunk {
65
- composes: flag;
66
- background: var(--color-info);
67
- color: var(--color-background);
55
+ .assetNameTagInitial::before {
56
+ content: 'i';
57
+ }
58
+
59
+ .assetNameTagChunk::before {
60
+ content: 'c';
61
+ }
62
+
63
+ .assetNameText {
64
+ display: inline;
65
+ vertical-align: middle;
68
66
  }
69
67
 
70
68
  /* Tooltip not predictive */
@@ -15,6 +15,7 @@ export const BundleAssetsTotalsTable = ({
15
15
  className,
16
16
  jobs,
17
17
  customComponentLink: CustomComponentLink,
18
+ ...restProps
18
19
  }) => {
19
20
  const items = useMemo(() => webpack.compareBySection.sizes(jobs), [jobs]);
20
21
  const budgets = jobs[0].insights?.webpack?.budgets;
@@ -46,6 +47,7 @@ export const BundleAssetsTotalsTable = ({
46
47
  items={items}
47
48
  renderRowHeader={renderRowHeader}
48
49
  showHeaderSum
50
+ {...restProps}
49
51
  />
50
52
  );
51
53
  };
@@ -18,7 +18,7 @@ import { FlexStack } from '../../layout/flex-stack';
18
18
  import { EmptySet } from '../../ui/empty-set';
19
19
  import { FileName } from '../../ui/file-name';
20
20
  import { Filters } from '../../ui/filters';
21
- import { Popover } from '../../ui/popover';
21
+ import { HoverCard } from '../../ui/hover-card';
22
22
  import { SortDropdown } from '../../ui/sort-dropdown';
23
23
  import { Toolbar } from '../../ui/toolbar';
24
24
  import { MetricsTable } from '../metrics-table';
@@ -31,30 +31,25 @@ import css from './bundle-modules.module.css';
31
31
  const RowHeader = ({ row, chunks, labels, CustomComponentLink }) => {
32
32
  const chunkIds = map(chunks, 'id');
33
33
 
34
- const [showPopopver, setPopover] = useState(false);
35
- const handleOnMouseEnter = useCallback(() => setPopover(true), [showPopopver]);
36
- const content = <FileName name={row.label} />;
34
+ const [showHoverCard, setHoverCard] = useState(false);
35
+ const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
36
+ const content = useMemo(() => <FileName name={row.label} />, [row.label]);
37
+
38
+ if (!showHoverCard) {
39
+ return <div onMouseEnter={handleOnMouseEnter}>{content}</div>;
40
+ }
37
41
 
38
42
  return (
39
- <div onMouseEnter={handleOnMouseEnter}>
40
- {!showPopopver ?
41
- content
42
- : (
43
- <Popover ariaLabel="View module info" label={content}>
44
- {({ popoverToggle }) => (
45
- <ModuleInfo
46
- className={css.namePopover}
47
- item={row}
48
- chunks={chunks}
49
- chunkIds={chunkIds}
50
- labels={labels}
51
- customComponentLink={CustomComponentLink}
52
- onClick={popoverToggle}
53
- />
54
- )}
55
- </Popover>
56
- )}
57
- </div>
43
+ <HoverCard label={content}>
44
+ <ModuleInfo
45
+ className={css.namePopover}
46
+ item={row}
47
+ chunks={chunks}
48
+ chunkIds={chunkIds}
49
+ labels={labels}
50
+ customComponentLink={CustomComponentLink}
51
+ />
52
+ </HoverCard>
58
53
  );
59
54
  };
60
55
 
@@ -76,10 +71,6 @@ RowHeader.defaultProps = {
76
71
  chunks: [],
77
72
  };
78
73
 
79
- const getRenderRowHeader = ({ labels, chunks, CustomComponentLink }) => (row) => (
80
- <RowHeader row={row} chunks={chunks} labels={labels} CustomComponentLink={CustomComponentLink} />
81
- );
82
-
83
74
  export const BundleModules = ({
84
75
  className,
85
76
  jobs,
@@ -100,11 +91,18 @@ export const BundleModules = ({
100
91
  }) => {
101
92
  const rootClassName = cx(css.root, className);
102
93
 
103
- const labels = useMemo(() => map(jobs, 'label'), [jobs]);
104
- const renderRowHeader = useMemo(
105
- () => getRenderRowHeader({ labels, chunks, CustomComponentLink }),
106
- [labels, chunks],
94
+ const renderRowHeader = useCallback(
95
+ (row) => (
96
+ <RowHeader
97
+ row={row}
98
+ chunks={chunks}
99
+ labels={map(jobs, 'label')}
100
+ CustomComponentLink={CustomComponentLink}
101
+ />
102
+ ),
103
+ [jobs, chunks, CustomComponentLink],
107
104
  );
105
+
108
106
  const emptyMessage = (
109
107
  <EmptySet
110
108
  resources="modules"
@@ -184,7 +182,7 @@ export const BundleModules = ({
184
182
  </FlexStack>
185
183
  )}
186
184
  >
187
- <FlexStack>
185
+ <FlexStack space="xxsmall">
188
186
  <MetricsTableSearch
189
187
  className={css.toolbarSearch}
190
188
  search={search}
@@ -240,10 +238,12 @@ BundleModules.propTypes = {
240
238
  jobs: PropTypes.array, // eslint-disable-line react/forbid-prop-types
241
239
 
242
240
  /** Chunks data */
243
- chunks: PropTypes.arrayOf(PropTypes.shape({
244
- id: PropTypes.string,
245
- name: PropTypes.string,
246
- })).isRequired,
241
+ chunks: PropTypes.arrayOf(
242
+ PropTypes.shape({
243
+ id: PropTypes.string,
244
+ name: PropTypes.string,
245
+ }),
246
+ ).isRequired,
247
247
 
248
248
  /** total row count */
249
249
  totalRowCount: PropTypes.number,
@@ -1,4 +1,4 @@
1
- import React, {useCallback} from 'react';
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
4
  import {
@@ -15,8 +15,9 @@ import { Stack } from '../../layout/stack';
15
15
  import { FlexStack } from '../../layout/flex-stack';
16
16
  import { EmptySet } from '../../ui/empty-set';
17
17
  import { Filters } from '../../ui/filters';
18
- import { Popover } from '../../ui/popover';
18
+ import { HoverCard } from '../../ui/hover-card';
19
19
  import { SortDropdown } from '../../ui/sort-dropdown';
20
+ import { Tag } from '../../ui/tag';
20
21
  import { Toolbar } from '../../ui/toolbar';
21
22
  import { FileName } from '../../ui/file-name';
22
23
  import { ComponentLink } from '../component-link';
@@ -27,27 +28,28 @@ import { MetricsTableTitle } from '../metrics-table-title';
27
28
  import css from './bundle-packages.module.css';
28
29
 
29
30
  const PackagePopoverContent = ({ name, fullName, path, duplicate, CustomComponentLink }) => {
30
- const normalizedPackagePath = path || `node_modules/${fullName.split(PACKAGES_SEPARATOR).join('/node_modules/')}/`;
31
+ const normalizedPackagePath =
32
+ path || `node_modules/${fullName.split(PACKAGES_SEPARATOR).join('/node_modules/')}/`;
31
33
  const [normalizedName, packageId] = name.split(PACKAGE_ID_SEPARATOR);
32
34
 
33
35
  return (
34
- <Stack space="xxsmall" className={css.packagePopover}>
36
+ <Stack space="xsmall" className={css.packageHoverCard}>
35
37
  <Stack space="xxxsmall">
36
- <h3 className={css.packagePopoverTitle}>
38
+ <h3 className={css.packageHoverCardTitle}>
37
39
  {normalizedName}
38
40
  {packageId && (
39
- <span className={css.packagePopoverTitleIndex}>
41
+ <span className={css.packageHoverCardTitleIndex}>
40
42
  {`${PACKAGE_ID_SEPARATOR}${packageId}`}
41
43
  </span>
42
44
  )}
43
45
  </h3>
44
- <p className={css.packagePopoverPath}>
45
- <FileName className={css.packagePopoverPathValue} name={normalizedPackagePath} />
46
+ <p className={css.packageHoverCardPath}>
47
+ <FileName className={css.packageHoverCardPathValue} name={normalizedPackagePath} />
46
48
  </p>
47
49
  </Stack>
48
50
 
49
- <ul className={css.packagePopoverList}>
50
- <li className={css.packagePopoverItem}>
51
+ <ul className={css.packageHoverCardList}>
52
+ <li className={css.packageHoverCardItem}>
51
53
  <a
52
54
  href={`https://www.npmjs.com/package/${normalizedName}`}
53
55
  target="_blank"
@@ -56,7 +58,7 @@ const PackagePopoverContent = ({ name, fullName, path, duplicate, CustomComponen
56
58
  npmjs.com
57
59
  </a>
58
60
  </li>
59
- <li className={css.packagePopoverItem}>
61
+ <li className={css.packageHoverCardItem}>
60
62
  <a
61
63
  href={`https://bundlephobia.com/result?p=${normalizedName}`}
62
64
  target="_blank"
@@ -67,7 +69,7 @@ const PackagePopoverContent = ({ name, fullName, path, duplicate, CustomComponen
67
69
  </li>
68
70
  </ul>
69
71
 
70
- <Stack space="xxxsmall" className={css.packagePopover.actions}>
72
+ <Stack space="xxxsmall" className={css.packageHoverCardActions}>
71
73
  {duplicate && (
72
74
  <div>
73
75
  <CustomComponentLink {...getBundlePackagesByNameComponentLink(normalizedName)}>
@@ -96,38 +98,86 @@ PackagePopoverContent.defaultProps = {
96
98
  path: '',
97
99
  };
98
100
 
99
- const PackageRowHeader = ({ item, CustomComponentLink }) => {
100
- const packageNames = item.label.split(PACKAGES_SEPARATOR);
101
- const { path } = item.runs[0] || {};
101
+ const PackageName = ({ packageName, path, showDuplicateFlag, row, CustomComponentLink }) => {
102
+ const [showHoverCard, setHoverCard] = useState(false);
103
+ const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
104
+
105
+ const label = useMemo(
106
+ () => (
107
+ <>
108
+ {showDuplicateFlag && row.duplicate && (
109
+ <Tag
110
+ className={css.packageNameTagDuplicate}
111
+ title="Duplicate package"
112
+ kind={Tag.KINDS.DANGER}
113
+ size={Tag.SIZES.SMALL}
114
+ />
115
+ )}
116
+ <span className={css.packageNameLabel}>{packageName}</span>
117
+ </>
118
+ ),
119
+ [packageName, showDuplicateFlag, row],
120
+ );
121
+
122
+ if (!showHoverCard) {
123
+ return (
124
+ <span className={css.packageName} onMouseEnter={handleOnMouseEnter}>
125
+ {label}
126
+ </span>
127
+ );
128
+ }
102
129
 
103
130
  return (
104
- <span className={css.packageNames}>
105
- {packageNames.map((packageName, index) => {
106
- // Render duplicate flag only for the last entry
107
- const duplicateFlag = index === packageNames.length - 1 && item.duplicate && (
108
- <span className={css.duplicate} title="Duplicate package">
109
- D
110
- </span>
111
- );
131
+ <HoverCard className={css.packageName} label={<span className={css.packageName}>{label}</span>}>
132
+ <PackagePopoverContent
133
+ name={packageName}
134
+ path={path}
135
+ fullName={row.label}
136
+ duplicate={row.duplicate}
137
+ CustomComponentLink={CustomComponentLink}
138
+ />
139
+ </HoverCard>
140
+ );
141
+ };
112
142
 
113
- return (
114
- <Popover className={css.packageName} icon={duplicateFlag} label={packageName}>
115
- <PackagePopoverContent
116
- name={packageName}
117
- path={path}
118
- fullName={item.label}
119
- duplicate={item.duplicate}
120
- CustomComponentLink={CustomComponentLink}
121
- />
122
- </Popover>
123
- );
124
- })}
143
+ PackageName.propTypes = {
144
+ packageName: PropTypes.string.isRequired,
145
+ path: PropTypes.string.isRequired,
146
+ showDuplicateFlag: PropTypes.bool.isRequired,
147
+ row: PropTypes.shape({
148
+ label: PropTypes.string,
149
+ duplicate: PropTypes.bool,
150
+ runs: PropTypes.arrayOf(
151
+ PropTypes.shape({
152
+ path: PropTypes.string,
153
+ }),
154
+ ).isRequired,
155
+ }).isRequired,
156
+ CustomComponentLink: PropTypes.elementType.isRequired,
157
+ };
158
+
159
+ const RowHeader = ({ row, CustomComponentLink }) => {
160
+ const packageNames = row.label.split(PACKAGES_SEPARATOR);
161
+ const { path } = row.runs[0] || {};
162
+
163
+ return (
164
+ <span className={css.packageNames}>
165
+ {packageNames.map((packageName, index) => (
166
+ <PackageName
167
+ packageName={packageName}
168
+ path={path}
169
+ // Render duplicate flag only for the last entry
170
+ showDuplicateFlag={index === packageNames.length - 1}
171
+ row={row}
172
+ CustomComponentLink={CustomComponentLink}
173
+ />
174
+ ))}
125
175
  </span>
126
176
  );
127
177
  };
128
178
 
129
- PackageRowHeader.propTypes = {
130
- item: PropTypes.shape({
179
+ RowHeader.propTypes = {
180
+ row: PropTypes.shape({
131
181
  label: PropTypes.string,
132
182
  duplicate: PropTypes.bool,
133
183
  runs: PropTypes.arrayOf(
@@ -168,7 +218,7 @@ export const BundlePackages = (props) => {
168
218
  );
169
219
 
170
220
  const renderRowHeader = useCallback(
171
- (item) => <PackageRowHeader item={item} CustomComponentLink={CustomComponentLink} />,
221
+ (row) => <RowHeader row={row} CustomComponentLink={CustomComponentLink} />,
172
222
  [CustomComponentLink],
173
223
  );
174
224
 
@@ -186,7 +236,7 @@ export const BundlePackages = (props) => {
186
236
  </FlexStack>
187
237
  )}
188
238
  >
189
- <FlexStack>
239
+ <FlexStack space="xxsmall">
190
240
  <MetricsTableSearch
191
241
  className={css.toolbarSearch}
192
242
  placeholder="Search by name"
@@ -12,23 +12,8 @@
12
12
  margin-right: -1px; /** collapse border */
13
13
  }
14
14
 
15
- .duplicate {
16
- margin-right: var(--space-xxxsmall);
17
- display: inline-block;
18
- width: var(--space-small);
19
- height: var(--space-small);
20
- font-size: 10px;
21
- font-weight: bold;
22
- line-height: var(--space-small);
23
- text-align: center;
24
- text-transform: uppercase;
25
- vertical-align: middle;
26
-
27
- background: var(--color-danger);
28
- color: var(--color-light);
29
- }
30
-
31
15
  .packageName {
16
+ display: inline-block;
32
17
  color: var(--color-text-light);
33
18
  }
34
19
 
@@ -43,22 +28,33 @@
43
28
  color: var(--color-text-ultra-light);
44
29
  }
45
30
 
46
- .packagePopover {
47
- min-width: 320px;
31
+ .packageNameTagDuplicate {
32
+ margin-right: var(--space-xxxsmall);
33
+ vertical-align: middle;
34
+ }
35
+
36
+ .packageNameLabel {
37
+ vertical-align: middle;
38
+ }
39
+
40
+ .packageNameTagDuplicate::before {
41
+ content: 'd';
48
42
  }
49
43
 
50
- .packagePopoverTitleIndex {
44
+
45
+ .packageHoverCardTitleIndex {
51
46
  color: var(--color-text-light);
52
47
  font-weight: normal;
53
48
  }
54
49
 
55
- .packagePopoverPathValue {
50
+ .packageHoverCardPathValue {
51
+ width: 100%;
56
52
  padding: var(--space-xxxsmall);
57
53
  background: var(--color-highlight-warning);
58
54
  font-family: var(--font-family-monospace);
59
55
  }
60
56
 
61
- .packagePopoverList {
57
+ .packageHoverCardList {
62
58
  padding: 0;
63
59
  list-style: inside;
64
60
  }
@@ -28,6 +28,7 @@
28
28
  }
29
29
 
30
30
  .inverted {
31
+ border-radius: var(--radius-small);
31
32
  background-color: var(--color-gray-ultra-light);
32
33
  color: var(--color-light);
33
34
  }
@@ -8,6 +8,7 @@ import { SOURCE_PATH_WEBPACK_STATS, formatDate, formatTime } from '@bundle-stats
8
8
  import { FlexStack } from '../../layout/flex-stack';
9
9
  import { Stack } from '../../layout/stack';
10
10
  import { Icon } from '../../ui/icon';
11
+ import { Tag } from '../../ui/tag';
11
12
  import css from './job-header.module.css';
12
13
 
13
14
  export const JobHeader = (props) => {
@@ -20,7 +21,7 @@ export const JobHeader = (props) => {
20
21
  <Stack className={rootClassName} space="xxsmall">
21
22
  <h1 className={css.title}>
22
23
  <span>{`#${job.internalBuildNumber}`}</span>
23
- {tag && <span className={css.tag}>{tag}</span>}
24
+ {tag && <Tag className={css.tag}>{tag}</Tag>}
24
25
  </h1>
25
26
  <FlexStack className={css.meta} space="xxsmall">
26
27
  {builtAt && (