@bundle-stats/ui 4.13.0-beta.2 → 4.13.0-beta.20

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 (279) hide show
  1. package/lib/app/app.js +4 -8
  2. package/lib/app/app.js.map +1 -1
  3. package/lib/app/app.module.css +2 -2
  4. package/lib/assets/icons.svg +70 -67
  5. package/lib/assets/icons.svg.js +14 -12
  6. package/lib/assets/icons.svg.js.map +1 -1
  7. package/lib/components/bundle-assets/bundle-assets.js +47 -8
  8. package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
  9. package/lib/components/bundle-assets/bundle-assets.module.css +0 -5
  10. package/lib/components/bundle-assets-totals/bundle-assets-totals.js +25 -14
  11. package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  12. package/lib/components/bundle-modules/bundle-modules.i18n.js +1 -1
  13. package/lib/components/bundle-modules/bundle-modules.js +45 -14
  14. package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
  15. package/lib/components/bundle-modules/bundle-modules.module.css +0 -5
  16. package/lib/components/bundle-packages/bundle-packages.js +45 -6
  17. package/lib/components/bundle-packages/bundle-packages.js.map +1 -1
  18. package/lib/components/bundle-packages/bundle-packages.module.css +0 -5
  19. package/lib/components/delta/delta.js +1 -1
  20. package/lib/components/delta/delta.js.map +1 -1
  21. package/lib/components/delta/delta.module.css +10 -1
  22. package/lib/components/index.js +1 -3
  23. package/lib/components/index.js.map +1 -1
  24. package/lib/components/jobs-header/jobs-header.module.css +0 -4
  25. package/lib/components/metrics-display-selector/metrics-display-selector.js +42 -5
  26. package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
  27. package/lib/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
  28. package/lib/components/metrics-table/metrics-table.i18n.js +1 -6
  29. package/lib/components/metrics-table/metrics-table.i18n.js.map +1 -1
  30. package/lib/components/metrics-table/metrics-table.js +4 -37
  31. package/lib/components/metrics-table/metrics-table.js.map +1 -1
  32. package/lib/components/metrics-table/metrics-table.module.css +1 -44
  33. package/lib/components/{total-size-type-title → metrics-table-header}/index.js +1 -1
  34. package/lib/components/metrics-table-header/index.js.map +1 -0
  35. package/lib/components/metrics-table-header/metrics-table-header.i18n.js +9 -0
  36. package/lib/components/metrics-table-header/metrics-table-header.i18n.js.map +1 -0
  37. package/lib/components/metrics-table-header/metrics-table-header.js +79 -0
  38. package/lib/components/metrics-table-header/metrics-table-header.js.map +1 -0
  39. package/lib/components/metrics-table-header/metrics-table-header.module.css +51 -0
  40. package/lib/components/metrics-table-options/metrics-table-options.js +2 -2
  41. package/lib/components/metrics-table-options/metrics-table-options.js.map +1 -1
  42. package/lib/components/metrics-table-title/metrics-table-title.js +1 -1
  43. package/lib/components/metrics-table-title/metrics-table-title.js.map +1 -1
  44. package/lib/components/metrics-table-title/metrics-table-title.module.css +4 -4
  45. package/lib/components/metrics-treemap/index.js +4 -0
  46. package/lib/components/metrics-treemap/index.js.map +1 -1
  47. package/lib/components/metrics-treemap/metrics-treemap.constants.js +3 -0
  48. package/lib/components/metrics-treemap/metrics-treemap.constants.js.map +1 -0
  49. package/lib/components/metrics-treemap/metrics-treemap.i18n.js +6 -0
  50. package/lib/components/metrics-treemap/metrics-treemap.i18n.js.map +1 -0
  51. package/lib/components/metrics-treemap/metrics-treemap.js +96 -70
  52. package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
  53. package/lib/components/metrics-treemap/metrics-treemap.module.css +163 -51
  54. package/lib/components/metrics-treemap/metrics-treemap.utils.js +109 -0
  55. package/lib/components/metrics-treemap/metrics-treemap.utils.js.map +1 -0
  56. package/lib/components/module-info/module-info.js +1 -2
  57. package/lib/components/module-info/module-info.js.map +1 -1
  58. package/lib/components/run-info/run-info.module.css +7 -7
  59. package/lib/components/sort-button/sort-button.module.css +1 -0
  60. package/lib/css/variables.css +14 -13
  61. package/lib/hooks/metrics-display-type.js +45 -24
  62. package/lib/hooks/metrics-display-type.js.map +1 -1
  63. package/lib/hooks/search-params.js +1 -0
  64. package/lib/hooks/search-params.js.map +1 -1
  65. package/lib/layout/box/box.module.css +4 -2
  66. package/lib/layout/flex-stack/flex-stack.js +3 -4
  67. package/lib/layout/flex-stack/flex-stack.js.map +1 -1
  68. package/lib/layout/flex-stack/flex-stack.module.css +19 -23
  69. package/lib/ui/button/button.js +5 -4
  70. package/lib/ui/button/button.js.map +1 -1
  71. package/lib/ui/button/button.module.css +55 -53
  72. package/lib/ui/dropdown/dropdown.js +10 -40
  73. package/lib/ui/dropdown/dropdown.js.map +1 -1
  74. package/lib/ui/dropdown/index.js.map +1 -1
  75. package/lib/ui/filters/filters.js +33 -35
  76. package/lib/ui/filters/filters.js.map +1 -1
  77. package/lib/ui/filters/filters.module.css +55 -67
  78. package/lib/ui/hover-card/hover-card.js +1 -1
  79. package/lib/ui/hover-card/hover-card.js.map +1 -1
  80. package/lib/ui/icon/icon.js +2 -1
  81. package/lib/ui/icon/icon.js.map +1 -1
  82. package/lib/ui/input-search/input-search.js +1 -1
  83. package/lib/ui/input-search/input-search.js.map +1 -1
  84. package/lib/ui/loader/loader.js +1 -1
  85. package/lib/ui/loader/loader.js.map +1 -1
  86. package/lib/ui/table/table.module.css +30 -0
  87. package/lib/ui/tag/tag.module.css +3 -3
  88. package/lib/ui/toolbar/toolbar.module.css +1 -3
  89. package/lib-esm/app/app.js +4 -8
  90. package/lib-esm/app/app.js.map +1 -1
  91. package/lib-esm/app/app.module.css +2 -2
  92. package/lib-esm/assets/icons.svg +70 -67
  93. package/lib-esm/assets/icons.svg.js +14 -12
  94. package/lib-esm/assets/icons.svg.js.map +1 -1
  95. package/lib-esm/components/bundle-assets/bundle-assets.js +48 -9
  96. package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
  97. package/lib-esm/components/bundle-assets/bundle-assets.module.css +0 -5
  98. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +26 -15
  99. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  100. package/lib-esm/components/bundle-modules/bundle-modules.i18n.js +1 -1
  101. package/lib-esm/components/bundle-modules/bundle-modules.js +46 -15
  102. package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
  103. package/lib-esm/components/bundle-modules/bundle-modules.module.css +0 -5
  104. package/lib-esm/components/bundle-packages/bundle-packages.js +46 -7
  105. package/lib-esm/components/bundle-packages/bundle-packages.js.map +1 -1
  106. package/lib-esm/components/bundle-packages/bundle-packages.module.css +0 -5
  107. package/lib-esm/components/delta/delta.js +1 -1
  108. package/lib-esm/components/delta/delta.js.map +1 -1
  109. package/lib-esm/components/delta/delta.module.css +10 -1
  110. package/lib-esm/components/index.js +0 -1
  111. package/lib-esm/components/index.js.map +1 -1
  112. package/lib-esm/components/jobs-header/jobs-header.module.css +0 -4
  113. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +42 -5
  114. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
  115. package/lib-esm/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
  116. package/lib-esm/components/metrics-table/metrics-table.i18n.js +0 -5
  117. package/lib-esm/components/metrics-table/metrics-table.i18n.js.map +1 -1
  118. package/lib-esm/components/metrics-table/metrics-table.js +4 -37
  119. package/lib-esm/components/metrics-table/metrics-table.js.map +1 -1
  120. package/lib-esm/components/metrics-table/metrics-table.module.css +1 -44
  121. package/lib-esm/components/metrics-table-header/index.js +2 -0
  122. package/lib-esm/components/metrics-table-header/index.js.map +1 -0
  123. package/lib-esm/components/metrics-table-header/metrics-table-header.i18n.js +6 -0
  124. package/lib-esm/components/metrics-table-header/metrics-table-header.i18n.js.map +1 -0
  125. package/lib-esm/components/metrics-table-header/metrics-table-header.js +49 -0
  126. package/lib-esm/components/metrics-table-header/metrics-table-header.js.map +1 -0
  127. package/lib-esm/components/metrics-table-header/metrics-table-header.module.css +51 -0
  128. package/lib-esm/components/metrics-table-options/metrics-table-options.js +3 -3
  129. package/lib-esm/components/metrics-table-options/metrics-table-options.js.map +1 -1
  130. package/lib-esm/components/metrics-table-title/metrics-table-title.js +1 -1
  131. package/lib-esm/components/metrics-table-title/metrics-table-title.js.map +1 -1
  132. package/lib-esm/components/metrics-table-title/metrics-table-title.module.css +4 -4
  133. package/lib-esm/components/metrics-treemap/index.js +1 -0
  134. package/lib-esm/components/metrics-treemap/index.js.map +1 -1
  135. package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js +2 -0
  136. package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js.map +1 -0
  137. package/lib-esm/components/metrics-treemap/metrics-treemap.i18n.js +3 -0
  138. package/lib-esm/components/metrics-treemap/metrics-treemap.i18n.js.map +1 -0
  139. package/lib-esm/components/metrics-treemap/metrics-treemap.js +98 -71
  140. package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
  141. package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +163 -51
  142. package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js +105 -0
  143. package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js.map +1 -0
  144. package/lib-esm/components/module-info/module-info.js +1 -2
  145. package/lib-esm/components/module-info/module-info.js.map +1 -1
  146. package/lib-esm/components/run-info/run-info.module.css +7 -7
  147. package/lib-esm/components/sort-button/sort-button.module.css +1 -0
  148. package/lib-esm/css/variables.css +14 -13
  149. package/lib-esm/hooks/metrics-display-type.js +46 -24
  150. package/lib-esm/hooks/metrics-display-type.js.map +1 -1
  151. package/lib-esm/hooks/search-params.js +1 -0
  152. package/lib-esm/hooks/search-params.js.map +1 -1
  153. package/lib-esm/layout/box/box.module.css +4 -2
  154. package/lib-esm/layout/flex-stack/flex-stack.js +3 -4
  155. package/lib-esm/layout/flex-stack/flex-stack.js.map +1 -1
  156. package/lib-esm/layout/flex-stack/flex-stack.module.css +19 -23
  157. package/lib-esm/ui/button/button.js +5 -4
  158. package/lib-esm/ui/button/button.js.map +1 -1
  159. package/lib-esm/ui/button/button.module.css +55 -53
  160. package/lib-esm/ui/dropdown/dropdown.js +8 -39
  161. package/lib-esm/ui/dropdown/dropdown.js.map +1 -1
  162. package/lib-esm/ui/dropdown/index.js.map +1 -1
  163. package/lib-esm/ui/filters/filters.js +34 -36
  164. package/lib-esm/ui/filters/filters.js.map +1 -1
  165. package/lib-esm/ui/filters/filters.module.css +55 -67
  166. package/lib-esm/ui/hover-card/hover-card.js +1 -1
  167. package/lib-esm/ui/hover-card/hover-card.js.map +1 -1
  168. package/lib-esm/ui/icon/icon.js +2 -1
  169. package/lib-esm/ui/icon/icon.js.map +1 -1
  170. package/lib-esm/ui/input-search/input-search.js +1 -1
  171. package/lib-esm/ui/input-search/input-search.js.map +1 -1
  172. package/lib-esm/ui/loader/loader.js +1 -1
  173. package/lib-esm/ui/loader/loader.js.map +1 -1
  174. package/lib-esm/ui/table/table.module.css +30 -0
  175. package/lib-esm/ui/tag/tag.module.css +3 -3
  176. package/lib-esm/ui/toolbar/toolbar.module.css +1 -3
  177. package/package.json +10 -10
  178. package/src/app/app.jsx +4 -12
  179. package/src/app/app.module.css +2 -2
  180. package/src/assets/icons/check.svg +4 -0
  181. package/src/assets/icons.svg +70 -67
  182. package/src/assets/icons.svg.jsx +12 -12
  183. package/src/components/bundle-assets/bundle-assets.jsx +85 -12
  184. package/src/components/bundle-assets/bundle-assets.module.css +0 -5
  185. package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +71 -35
  186. package/src/components/bundle-modules/bundle-modules.i18n.ts +1 -1
  187. package/src/components/bundle-modules/bundle-modules.module.css +0 -5
  188. package/src/components/bundle-modules/bundle-modules.tsx +123 -45
  189. package/src/components/bundle-packages/bundle-packages.jsx +79 -10
  190. package/src/components/bundle-packages/bundle-packages.module.css +0 -5
  191. package/src/components/delta/delta.jsx +1 -1
  192. package/src/components/delta/delta.module.css +10 -1
  193. package/src/components/index.js +0 -1
  194. package/src/components/jobs-header/jobs-header.module.css +0 -4
  195. package/src/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
  196. package/src/components/metrics-display-selector/metrics-display-selector.stories.tsx +33 -0
  197. package/src/components/metrics-display-selector/metrics-display-selector.tsx +86 -28
  198. package/src/components/metrics-table/metrics-table.i18n.ts +0 -7
  199. package/src/components/metrics-table/metrics-table.module.css +1 -44
  200. package/src/components/metrics-table/metrics-table.tsx +17 -153
  201. package/src/components/metrics-table-header/index.ts +1 -0
  202. package/src/components/metrics-table-header/metrics-table-header.i18n.ts +6 -0
  203. package/src/components/metrics-table-header/metrics-table-header.module.css +51 -0
  204. package/src/components/metrics-table-header/metrics-table-header.tsx +172 -0
  205. package/src/components/metrics-table-options/metrics-table-options.jsx +4 -10
  206. package/src/components/metrics-table-title/metrics-table-title.jsx +1 -1
  207. package/src/components/metrics-table-title/metrics-table-title.module.css +4 -4
  208. package/src/components/metrics-treemap/__tests__/metrics-treemap.utils.ts +188 -0
  209. package/src/components/metrics-treemap/index.ts +1 -0
  210. package/src/components/metrics-treemap/metrics-treemap.constants.ts +52 -0
  211. package/src/components/metrics-treemap/metrics-treemap.i18n.ts +2 -0
  212. package/src/components/metrics-treemap/metrics-treemap.module.css +163 -51
  213. package/src/components/metrics-treemap/metrics-treemap.stories.tsx +44 -10
  214. package/src/components/metrics-treemap/metrics-treemap.tsx +340 -142
  215. package/src/components/metrics-treemap/metrics-treemap.utils.ts +162 -0
  216. package/src/components/module-info/module-info.tsx +1 -1
  217. package/src/components/run-info/run-info.module.css +7 -7
  218. package/src/components/sort-button/sort-button.module.css +1 -0
  219. package/src/css/variables.css +14 -13
  220. package/src/hooks/metrics-display-type.ts +79 -29
  221. package/src/hooks/search-params.js +1 -0
  222. package/src/layout/box/box.module.css +4 -2
  223. package/src/layout/flex-stack/flex-stack.module.css +19 -23
  224. package/src/layout/flex-stack/flex-stack.tsx +2 -8
  225. package/src/ui/button/button.module.css +55 -53
  226. package/src/ui/button/button.stories.tsx +16 -9
  227. package/src/ui/button/button.tsx +8 -7
  228. package/src/ui/dropdown/dropdown.stories.tsx +25 -0
  229. package/src/ui/dropdown/dropdown.tsx +66 -0
  230. package/src/ui/filters/filters.module.css +55 -67
  231. package/src/ui/filters/filters.stories.tsx +109 -0
  232. package/src/ui/filters/filters.tsx +128 -167
  233. package/src/ui/hover-card/hover-card.tsx +1 -1
  234. package/src/ui/icon/icon.tsx +2 -1
  235. package/src/ui/input-search/input-search.tsx +1 -0
  236. package/src/ui/loader/loader.jsx +1 -1
  237. package/src/ui/table/table.module.css +30 -0
  238. package/src/ui/tag/tag.module.css +3 -3
  239. package/src/ui/toolbar/toolbar.module.css +1 -3
  240. package/types/components/bundle-assets-totals/bundle-assets-totals.d.ts +5 -1
  241. package/types/components/bundle-modules/bundle-modules.i18n.d.ts +1 -1
  242. package/types/components/delta/delta.d.ts +2 -2
  243. package/types/components/index.d.ts +0 -1
  244. package/types/components/metrics-display-selector/metrics-display-selector.d.ts +5 -3
  245. package/types/components/metrics-table/metrics-table.d.ts +4 -10
  246. package/types/components/metrics-table/metrics-table.i18n.d.ts +0 -5
  247. package/types/components/metrics-table-header/index.d.ts +1 -0
  248. package/types/components/metrics-table-header/metrics-table-header.d.ts +15 -0
  249. package/types/components/metrics-table-header/metrics-table-header.i18n.d.ts +5 -0
  250. package/types/components/metrics-treemap/index.d.ts +1 -0
  251. package/types/components/metrics-treemap/metrics-treemap.constants.d.ts +20 -0
  252. package/types/components/metrics-treemap/metrics-treemap.d.ts +7 -5
  253. package/types/components/metrics-treemap/metrics-treemap.i18n.d.ts +2 -0
  254. package/types/components/metrics-treemap/metrics-treemap.utils.d.ts +5 -0
  255. package/types/hooks/metrics-display-type.d.ts +8 -2
  256. package/types/ui/dropdown/dropdown.d.ts +12 -47
  257. package/types/ui/dropdown/index.d.ts +1 -1
  258. package/types/ui/filters/filters.d.ts +2 -7
  259. package/types/ui/icon/icon.d.ts +4 -2
  260. package/types/ui/loader/loader.d.ts +2 -2
  261. package/lib/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  262. package/lib/components/total-size-type-title/index.js.map +0 -1
  263. package/lib/components/total-size-type-title/total-size-type-title.js +0 -32
  264. package/lib/components/total-size-type-title/total-size-type-title.js.map +0 -1
  265. package/lib/components/total-size-type-title/total-size-type-title.module.css +0 -14
  266. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  267. package/lib-esm/components/total-size-type-title/index.js +0 -2
  268. package/lib-esm/components/total-size-type-title/index.js.map +0 -1
  269. package/lib-esm/components/total-size-type-title/total-size-type-title.js +0 -25
  270. package/lib-esm/components/total-size-type-title/total-size-type-title.js.map +0 -1
  271. package/lib-esm/components/total-size-type-title/total-size-type-title.module.css +0 -14
  272. package/src/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  273. package/src/components/total-size-type-title/index.js +0 -1
  274. package/src/components/total-size-type-title/total-size-type-title.jsx +0 -41
  275. package/src/components/total-size-type-title/total-size-type-title.module.css +0 -14
  276. package/src/ui/dropdown/dropdown.jsx +0 -88
  277. package/types/components/total-size-type-title/index.d.ts +0 -1
  278. package/types/components/total-size-type-title/total-size-type-title.d.ts +0 -14
  279. /package/src/ui/dropdown/{index.js → index.ts} +0 -0
@@ -1,35 +1,67 @@
1
- import React, { RefObject, forwardRef, useCallback, useMemo, useRef } from 'react';
1
+ import React, {
2
+ type ComponentProps,
3
+ type MouseEventHandler,
4
+ type ReactNode,
5
+ type Ref,
6
+ type RefObject,
7
+ forwardRef,
8
+ useCallback,
9
+ useMemo,
10
+ useRef,
11
+ } from 'react';
2
12
  import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-use';
3
13
  import cx from 'classnames';
4
- import type { ReportMetricRow, MetricRunInfo, MetricRunInfoBaseline } from '@bundle-stats/utils';
5
- import { hierarchy, treemap, treemapSquarify, window } from 'd3';
6
- import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState, useTooltip } from 'ariakit/tooltip';
14
+ import { HierarchyRectangularNode, hierarchy, treemap, treemapSquarify } from 'd3';
15
+ import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState } from 'ariakit/tooltip';
16
+ import {
17
+ type ReportMetricRow,
18
+ type MetricRunInfo,
19
+ type MetricRunInfoBaseline,
20
+ getMetricRunInfo,
21
+ METRIC_TYPE_CONFIGS,
22
+ } from '@bundle-stats/utils';
7
23
 
8
24
  import { Stack } from '../../layout/stack';
9
25
  import { FileName } from '../../ui/file-name';
10
26
  import { Delta } from '../delta';
11
27
  import { RunInfo } from '../run-info';
28
+ import type { TreeLeaf, TreeNode, Tree } from './metrics-treemap.constants';
29
+ import * as I18N from './metrics-treemap.i18n';
12
30
  import css from './metrics-treemap.module.css';
31
+ import { resolveGroupDeltaType } from './metrics-treemap.utils';
13
32
 
14
- const SQUARIFY_RATIO = 1.78;
15
- const PADDING_OUTER = 0;
33
+ const SQUARIFY_RATIO = 1.66;
34
+ const PADDING_OUTER = 1;
16
35
  const PADDING_INNER = 1;
17
-
18
- type TileSizeDisplay = 'minimal' | 'small' | 'default';
36
+ const GROUPED_PADDING_TOP = 18;
19
37
 
20
38
  /**
21
- * Resolve the tile size using predefined values to avoid
39
+ * Resolve the tile's size using predefined values to avoid
22
40
  * computing the size of the content for every tile
23
41
  */
24
42
  const PADDING_TOP = 4;
25
43
  const PADDING_BOTTOM = 2; // substracted to allow longer texts to appear
26
- const PADDING_LEFT = 16;
44
+ const PADDING_LEFT = 2;
27
45
  const PADDING_RIGHT = 4; // substracted to allow longer texts to not break
28
46
  const VERTICAL_SPACING = 4;
29
47
  const LINE_HEIGHT = 16;
30
48
  const LINE_HEIGHT_SMALL = 13.3;
31
49
 
32
- function resolveLeafSizeDisplay(width: number, height: number): TileSizeDisplay {
50
+ function resolveTileGroupSizeDisplay(width: number, height: number): TileSizeDisplay {
51
+ if (height < GROUPED_PADDING_TOP || width < 24) {
52
+ return 'minimal';
53
+ }
54
+
55
+ if (height < GROUPED_PADDING_TOP + 8) {
56
+ return 'small';
57
+ }
58
+
59
+ return 'default';
60
+ }
61
+
62
+ type TileSizeDisplay = 'minimal' | 'small' | 'default';
63
+
64
+ function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay {
33
65
  if (
34
66
  height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 &&
35
67
  width > PADDING_LEFT + PADDING_RIGHT + 128
@@ -47,19 +79,19 @@ function resolveLeafSizeDisplay(width: number, height: number): TileSizeDisplay
47
79
  return 'minimal';
48
80
  }
49
81
 
50
- interface LeafTooltipContentProps {
82
+ interface TileTooltipContentProps {
51
83
  item: ReportMetricRow;
52
84
  }
53
85
 
54
- const LeafTooltipContent = (props: LeafTooltipContentProps) => {
86
+ const TileTooltipContent = (props: TileTooltipContentProps) => {
55
87
  const { item } = props;
56
88
 
57
89
  const currentRun = item.runs[0] as MetricRunInfo;
58
90
  const baselineRun = item.runs[item.runs.length - 1] as MetricRunInfoBaseline;
59
91
 
60
92
  return (
61
- <Stack space="small" className={css.leafTooltipContent}>
62
- <h3 className={css.leafTooltipContentTitle}>
93
+ <Stack space="small" className={css.tileTooltipContent}>
94
+ <h3 className={css.tileTooltipContentTitle}>
63
95
  <FileName as="code" name={item.label} />
64
96
  </h3>
65
97
  <RunInfo
@@ -72,27 +104,40 @@ const LeafTooltipContent = (props: LeafTooltipContentProps) => {
72
104
  );
73
105
  };
74
106
 
75
- interface LeafContentProps {
76
- sizeDisplay: 'minimal' | 'small' | 'default';
107
+ interface TileContentProps {
108
+ /**
109
+ * Node label
110
+ */
111
+ label: string;
112
+ /**
113
+ * Tile id - metric label or basename for grouped tiles
114
+ */
77
115
  item: ReportMetricRow;
116
+ /**
117
+ * The estimated size of the tile
118
+ */
119
+ sizeDisplay: 'minimal' | 'small' | 'default';
120
+ /**
121
+ * Metric run info
122
+ */
78
123
  runInfo: MetricRunInfo;
79
124
  }
80
125
 
81
- const LeafContent = forwardRef((props: LeafContentProps, ref: React.Ref<HTMLDivElement>) => {
82
- const { sizeDisplay, item, runInfo } = props;
126
+ const TileContent = forwardRef((props: TileContentProps, ref: Ref<HTMLDivElement>) => {
127
+ const { label, sizeDisplay, item, runInfo } = props;
83
128
 
84
129
  if (sizeDisplay === 'minimal') {
85
- return <div className={css.leafContent} ref={ref} />;
130
+ return <div className={css.tileContent} ref={ref} />;
86
131
  }
87
132
 
88
133
  return (
89
- <div className={css.leafContent} ref={ref}>
90
- <p className={css.leadContentLabel}>{item.label}</p>
134
+ <div className={css.tileContent} ref={ref}>
135
+ <FileName as="p" className={css.tileContentLabel} name={label || item.label} />
91
136
  {sizeDisplay !== 'small' && (
92
- <p className={css.leadContentValue}>
93
- <span className={css.leadContentMetric}>{runInfo.displayValue}</span>
137
+ <p className={css.tileContentValue}>
138
+ <span className={css.tileContentMetric}>{runInfo.displayValue}</span>
94
139
  <Delta
95
- className={css.leadContentDelta}
140
+ className={css.tileContentDelta}
96
141
  displayValue={runInfo.displayDeltaPercentage}
97
142
  deltaType={runInfo.deltaType}
98
143
  />
@@ -102,8 +147,8 @@ const LeafContent = forwardRef((props: LeafContentProps, ref: React.Ref<HTMLDivE
102
147
  );
103
148
  });
104
149
 
105
- const LeafContentWithTooltip = (props: LeafContentProps & { parentRef: RefObject<Element> }) => {
106
- const { sizeDisplay, item, runInfo, parentRef } = props;
150
+ const TileContentWithTooltip = (props: TileContentProps & { parentRef: RefObject<Element> }) => {
151
+ const { label, sizeDisplay, item, runInfo, parentRef } = props;
107
152
 
108
153
  const pointer = useMouseHovered(parentRef, { whenHovered: true });
109
154
 
@@ -115,8 +160,8 @@ const LeafContentWithTooltip = (props: LeafContentProps & { parentRef: RefObject
115
160
  }
116
161
 
117
162
  const newRect = {
118
- x: pointer.docX - (document?.defaultView?.pageXOffset ?? 0),
119
- y: pointer.docY - (document?.defaultView?.pageYOffset ?? 0),
163
+ x: pointer.docX - (document?.defaultView?.scrollX ?? 0),
164
+ y: pointer.docY - (document?.defaultView?.scrollY ?? 0),
120
165
  w: 1,
121
166
  h: 1,
122
167
  };
@@ -124,180 +169,333 @@ const LeafContentWithTooltip = (props: LeafContentProps & { parentRef: RefObject
124
169
  return newRect;
125
170
  }, [pointer.docX, pointer.docY]);
126
171
 
127
- const tooltipState = useTooltipState({ gutter: 8, getAnchorRect, timeout: 100 });
172
+ const tooltipState = useTooltipState({ gutter: 12, getAnchorRect, timeout: 120 });
128
173
 
129
174
  // Update tooltip position when poiner values are changing
130
- useDebounce(tooltipState.render, 10, [pointer.docX, pointer.docY]);
175
+ useDebounce(tooltipState.render, 5, [pointer.docX, pointer.docY]);
131
176
 
132
177
  return (
133
178
  <>
134
- <TooltipAnchor state={tooltipState} className={css.leafContentTooltipAnchor}>
135
- <LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
179
+ <TooltipAnchor state={tooltipState} className={css.tileContentTooltipAnchor}>
180
+ <TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
136
181
  </TooltipAnchor>
137
182
  <Tooltip state={tooltipState} className={css.tooltip}>
138
- <TooltipArrow state={tooltipState} size={16} className={css.leafTooltipArrow} />
139
- <LeafTooltipContent item={item} />
183
+ <TooltipArrow state={tooltipState} size={16} className={css.tileTooltipArrow} />
184
+ <TileTooltipContent item={item} />
140
185
  </Tooltip>
141
186
  </>
142
187
  );
143
188
  };
144
189
 
145
- const LeafAction = forwardRef((props: React.ComponentProps<'button'>, ref) => (
146
- // @ts-expect-error
147
- <button type="button" xmlns="http://www.w3.org/1999/xhtml" ref={ref} {...props} />
148
- ));
149
-
150
- interface TreeNode {
151
- id: string;
152
- value: number;
153
- item: ReportMetricRow;
154
- children?: Array<TreeNode>;
155
- }
156
-
157
- interface RootTree {
158
- id: string;
159
- value: number;
160
- children: Array<TreeNode>;
161
- }
162
-
163
- interface LeafProps {
164
- x: number;
165
- y: number;
190
+ interface TileProps {
191
+ left: number;
192
+ top: number;
166
193
  width: number;
167
194
  height: number;
168
- data: TreeNode;
195
+ data: TreeLeaf;
169
196
  onClick?: (id: string) => void;
170
197
  }
171
198
 
172
- const Leaf = (props: LeafProps) => {
173
- const { x, y, width, height, data, onClick } = props;
199
+ const Tile = (props: TileProps) => {
200
+ const { left, top, width, height, data, onClick } = props;
174
201
 
175
- const { item } = data;
202
+ const { item, label } = data;
176
203
  const runInfo = item.runs?.[0] as MetricRunInfo;
177
204
 
178
- const sizeDisplay = useMemo(() => resolveLeafSizeDisplay(width, height), [width, height]);
179
- const handleOnClick = useCallback(() => onClick?.(item.key), [item.key]);
205
+ const sizeDisplay = useMemo(() => resolveTileSizeDisplay(width, height), [width, height]);
206
+ const handleOnClick: MouseEventHandler<HTMLDivElement> = useCallback(
207
+ (event) => {
208
+ event.stopPropagation();
209
+ onClick?.(item.key);
210
+ },
211
+ [item.key, onClick],
212
+ );
180
213
 
181
- const leafContentRef = useRef<HTMLElement>(null);
182
- const hover = useHoverDirty(leafContentRef);
214
+ const contentRef = useRef<HTMLDivElement>(null);
215
+ const hover = useHoverDirty(contentRef);
183
216
 
184
- const leafClassName = cx(
185
- css.leaf,
186
- css[`leaf-${runInfo.deltaType}`],
187
- sizeDisplay === 'small' && css.leafSmall,
217
+ const className = cx(
218
+ css.tile,
219
+ css[`tile-${runInfo.deltaType}`],
220
+ sizeDisplay === 'small' && css.tileSizeSmall,
221
+ sizeDisplay === 'default' && css.tileSizeDefault,
222
+ left === 0 && css.tileFirstCol,
188
223
  );
189
224
 
225
+ /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */
190
226
  return (
191
- <g className={leafClassName}>
192
- <rect x={x} y={y} width={width} height={height} className={css.leafBackground} />
193
- <rect x={x} y={y} width={width} height={height} className={css.leafBackdrop} />
194
- <foreignObject height={height} width={width} x={x} y={y} className={css.leafWrapper}>
195
- <LeafAction onClick={handleOnClick} ref={leafContentRef} className={css.leafAction}>
196
- {hover ? (
197
- <LeafContentWithTooltip
198
- sizeDisplay={sizeDisplay}
199
- item={item}
200
- runInfo={runInfo}
201
- parentRef={leafContentRef}
202
- />
203
- ) : (
204
- <LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
227
+ <div
228
+ onClick={handleOnClick}
229
+ role="button"
230
+ aria-label={I18N.TILE_LABEL}
231
+ ref={contentRef}
232
+ style={{ left, top, width, height }}
233
+ className={className}
234
+ >
235
+ {hover ? (
236
+ <TileContentWithTooltip
237
+ label={label}
238
+ sizeDisplay={sizeDisplay}
239
+ item={item}
240
+ runInfo={runInfo}
241
+ parentRef={contentRef}
242
+ />
243
+ ) : (
244
+ <TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
245
+ )}
246
+ </div>
247
+ );
248
+ };
249
+
250
+ interface TileGroupProps extends ComponentProps<'div'> {
251
+ /**
252
+ * Node title
253
+ */
254
+ title?: string;
255
+ /**
256
+ * Group sum
257
+ */
258
+ total?: Tree['total'];
259
+ /**
260
+ * Node id
261
+ */
262
+ id: string;
263
+ /**
264
+ * Node children
265
+ */
266
+ childNodes: HierarchyRectangularNode<TreeNode>['children'];
267
+ /**
268
+ * On click tile handler
269
+ */
270
+ onItemClick: TileProps['onClick'];
271
+ /**
272
+ * On click group title
273
+ */
274
+ onGroupClick?: (groupId: string) => void;
275
+
276
+ /**
277
+ * Node rectangle dimensions
278
+ */
279
+ width?: number;
280
+ height?: number;
281
+
282
+ /**
283
+ * Node rectangle relative coordinates (relative to parent)
284
+ */
285
+ left: number;
286
+ top: number;
287
+
288
+ /**
289
+ * Node rectangle absolute coordinates (relative to canvas)
290
+ */
291
+ absoluteLeft: number;
292
+ absoluteTop: number;
293
+ }
294
+
295
+ const TileGroup = (props: TileGroupProps) => {
296
+ const {
297
+ title = '',
298
+ total,
299
+ id,
300
+ childNodes,
301
+ onItemClick,
302
+ onGroupClick,
303
+ left,
304
+ top,
305
+ width,
306
+ height,
307
+ absoluteLeft,
308
+ absoluteTop,
309
+ } = props;
310
+
311
+ const onClick: MouseEventHandler<HTMLDivElement> = useCallback(
312
+ (event) => {
313
+ onGroupClick?.(id);
314
+ event.stopPropagation();
315
+ },
316
+ [onGroupClick, id],
317
+ );
318
+
319
+ const displaySize = useMemo(
320
+ () => resolveTileGroupSizeDisplay(width || 0, height || 0),
321
+ [width, height],
322
+ );
323
+
324
+ const metricRunInfo =
325
+ total &&
326
+ getMetricRunInfo(METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE, total.current, total.baseline);
327
+ const groupDeltaType = resolveGroupDeltaType(metricRunInfo);
328
+ const rootClassName = cx(css.tileGroup, css[`tileGroup--${groupDeltaType}`]);
329
+
330
+ if (title && displaySize === 'minimal') {
331
+ return (
332
+ <div
333
+ onClick={onClick}
334
+ role="button"
335
+ aria-label={I18N.TILE_GROUP_LABEL}
336
+ className={rootClassName}
337
+ style={{ left, top, width, height }}
338
+ />
339
+ );
340
+ }
341
+
342
+ if (title && displaySize === 'small') {
343
+ return (
344
+ <div
345
+ onClick={onClick}
346
+ role="button"
347
+ aria-label={I18N.TILE_GROUP_LABEL}
348
+ style={{ left, top, width, height }}
349
+ className={rootClassName}
350
+ >
351
+ <div className={css.tileGroupTitle}>{title}</div>
352
+ </div>
353
+ );
354
+ }
355
+
356
+ return (
357
+ <div
358
+ onClick={onClick}
359
+ role="button"
360
+ aria-label={I18N.TILE_GROUP_LABEL}
361
+ className={cx(rootClassName, css.tileGroupSizeDefault)}
362
+ style={{ left, top, width, height }}
363
+ >
364
+ {title && (
365
+ <div className={css.tileGroupTitle}>
366
+ {title}
367
+ {metricRunInfo && (
368
+ <span className={css.tileGroupTitleTotal}>
369
+ {`${metricRunInfo.displayValue}`}
370
+ {'displayDelta' in metricRunInfo && ` (${metricRunInfo.displayDeltaPercentage})`}
371
+ </span>
205
372
  )}
206
- </LeafAction>
207
- </foreignObject>
208
- </g>
373
+ </div>
374
+ )}
375
+ {childNodes?.map((childNode) => {
376
+ if ('children' in childNode) {
377
+ const groupData = childNode.data as Tree;
378
+
379
+ return (
380
+ <TileGroup
381
+ title={groupData.label}
382
+ total={groupData.total}
383
+ id={groupData.id}
384
+ childNodes={childNode.children}
385
+ left={childNode.x0 - absoluteLeft}
386
+ top={childNode.y0 - absoluteTop}
387
+ absoluteLeft={childNode.x0}
388
+ absoluteTop={childNode.y0}
389
+ width={childNode.x1 - childNode.x0}
390
+ height={childNode.y1 - childNode.y0}
391
+ onItemClick={onItemClick}
392
+ onGroupClick={onGroupClick}
393
+ key={groupData.id}
394
+ />
395
+ );
396
+ }
397
+
398
+ return (
399
+ <Tile
400
+ data={childNode.data as TreeLeaf}
401
+ left={childNode.x0 - absoluteLeft}
402
+ top={childNode.y0 - absoluteTop}
403
+ width={childNode.x1 - childNode.x0}
404
+ height={childNode.y1 - childNode.y0}
405
+ onClick={onItemClick}
406
+ key={childNode.data.id}
407
+ />
408
+ );
409
+ })}
410
+ </div>
209
411
  );
210
412
  };
211
413
 
212
414
  interface UseMetricsTreemapHierarchyParams {
213
- items: Array<ReportMetricRow>;
415
+ treeNodes: Tree;
214
416
  width: number;
215
417
  height: number;
418
+ nested: boolean;
216
419
  }
217
420
 
218
421
  function useMetricsTreemapHierarchy(params: UseMetricsTreemapHierarchyParams) {
219
- const { items, width, height } = params;
422
+ const { treeNodes, width, height, nested } = params;
220
423
 
221
424
  const treemapHierarchy = useMemo(() => {
222
- const children = items.map((item) => {
223
- /**
224
- * Set leaf value to the largest run value to allow to
225
- * display the impact of deleted / items
226
- */
227
- const values = item.runs.map((run) => run?.value || 0);
228
- const value = Math.max(...values);
229
-
230
- return {
231
- id: item.key,
232
- value,
233
- item,
234
- };
235
- });
236
-
237
- const treemapData = {
238
- id: 'root',
239
- name: 'root',
240
- value: 0,
241
- children,
242
- };
425
+ const customHierarchy = hierarchy<Tree>(treeNodes);
243
426
 
244
- const customHierarchy = hierarchy<RootTree>(treemapData);
245
427
  customHierarchy.sum((node) => node.value);
428
+ // sort by value descending for all cases
246
429
  // @ts-expect-error
247
430
  customHierarchy.sort((a, b) => b.value - a.value);
248
431
 
249
432
  return customHierarchy;
250
- }, [items]);
433
+ }, [treeNodes]);
251
434
 
252
- const cells = useMemo(() => {
253
- const createTremapLayout = treemap()
435
+ const treemapNodes = useMemo(() => {
436
+ let createTremapLayout = treemap()
254
437
  .size([width, height])
255
438
  .tile(treemapSquarify.ratio(SQUARIFY_RATIO))
256
439
  .paddingOuter(PADDING_OUTER)
257
440
  .paddingInner(PADDING_INNER);
258
- // @ts-expect-error
259
- const tremapLayout = createTremapLayout(treemapHierarchy);
260
441
 
261
- return tremapLayout.leaves().filter((leaf) => Boolean(leaf.parent));
442
+ // Add padding top for group title
443
+ if (nested) {
444
+ createTremapLayout = createTremapLayout.paddingTop(GROUPED_PADDING_TOP);
445
+ }
446
+
447
+ // @ts-expect-error
448
+ return createTremapLayout(treemapHierarchy) as HierarchyRectangularNode<TreeNode>;
262
449
  }, [height, width, treemapHierarchy]);
263
450
 
264
- return cells;
451
+ return treemapNodes;
265
452
  }
266
453
 
267
454
  interface MetricsTreemapProps {
268
- items: Array<ReportMetricRow>;
269
- emptyMessage?: React.ReactNode;
455
+ treeNodes: Tree;
456
+ emptyMessage?: ReactNode;
457
+ nested?: boolean;
270
458
  onItemClick?: (entryId: string) => void;
459
+ onGroupClick?: (entryId: string) => void;
271
460
  }
272
461
 
273
- export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps<'div'>) => {
274
- const { className = '', items, onItemClick, emptyMessage = 'No data', ...restProps } = props;
462
+ export const MetricsTreemap = (props: MetricsTreemapProps & ComponentProps<'div'>) => {
463
+ const {
464
+ className = '',
465
+ treeNodes,
466
+ onItemClick,
467
+ onGroupClick,
468
+ emptyMessage = 'No data',
469
+ nested = false,
470
+ ...restProps
471
+ } = props;
275
472
 
276
473
  const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
277
- const cells = useMetricsTreemapHierarchy({ items, width, height });
474
+ const rootNode = useMetricsTreemapHierarchy({ treeNodes, width, height, nested });
278
475
 
279
476
  return (
280
- <div className={cx(css.root, className)} {...restProps} ref={containerRef}>
281
- {cells.length > 0 ? (
282
- <svg viewBox={`0 0 ${width} ${height}`} className={css.canvas}>
283
- {cells.map((leaf) => {
284
- if (!leaf.parent) {
285
- return null;
286
- }
287
-
288
- return (
289
- <Leaf
290
- x={leaf.x0}
291
- y={leaf.y0}
292
- width={leaf.x1 - leaf.x0}
293
- height={leaf.y1 - leaf.y0}
294
- data={leaf.data as TreeNode}
295
- onClick={onItemClick}
296
- key={leaf.id}
297
- />
298
- );
299
- })}
300
- </svg>
477
+ <div
478
+ className={cx(css.root, nested && css.nested, className)}
479
+ {...restProps}
480
+ ref={containerRef}
481
+ >
482
+ {rootNode.children && rootNode.children?.length > 0 ? (
483
+ <div className={css.canvas}>
484
+ <TileGroup
485
+ title={nested ? rootNode.data.label : undefined}
486
+ total={(rootNode.data as Tree).total}
487
+ id={rootNode.data.id}
488
+ childNodes={rootNode.children}
489
+ onItemClick={onItemClick}
490
+ onGroupClick={onGroupClick}
491
+ left={rootNode.x0}
492
+ top={rootNode.y0}
493
+ absoluteLeft={rootNode.x0}
494
+ absoluteTop={rootNode.y0}
495
+ width={rootNode.x1 - rootNode.x0}
496
+ height={rootNode.y1 - rootNode.y0}
497
+ />
498
+ </div>
301
499
  ) : (
302
500
  <div className={css.emptyMessage}>
303
501
  <div className={css.emptyMessageWrapper}>{emptyMessage}</div>