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

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 (182) hide show
  1. package/lib/app/app.js +3 -7
  2. package/lib/app/app.js.map +1 -1
  3. package/lib/assets/icons.svg +26 -20
  4. package/lib/assets/icons.svg.js +27 -23
  5. package/lib/assets/icons.svg.js.map +1 -1
  6. package/lib/components/bundle-assets/bundle-assets.js +11 -3
  7. package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
  8. package/lib/components/bundle-assets/index.js +3 -4
  9. package/lib/components/bundle-assets/index.js.map +1 -1
  10. package/lib/components/bundle-assets-totals/bundle-assets-totals.js +77 -0
  11. package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -0
  12. package/lib/components/bundle-assets-totals/bundle-assets-totals.module.css +7 -0
  13. package/lib/{ui/horizontal-bar-chart → components/bundle-assets-totals}/index.js +1 -1
  14. package/lib/components/bundle-assets-totals/index.js.map +1 -0
  15. package/lib/components/bundle-modules/bundle-modules.js +9 -2
  16. package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
  17. package/lib/components/bundle-modules/index.js +3 -4
  18. package/lib/components/bundle-modules/index.js.map +1 -1
  19. package/lib/components/bundle-packages/bundle-packages.js +10 -2
  20. package/lib/components/bundle-packages/bundle-packages.js.map +1 -1
  21. package/lib/components/bundle-packages/index.js +3 -4
  22. package/lib/components/bundle-packages/index.js.map +1 -1
  23. package/lib/components/index.js +3 -5
  24. package/lib/components/index.js.map +1 -1
  25. package/lib/components/{bundle-assets-totals-table → metrics-display-selector}/index.js +1 -1
  26. package/lib/components/metrics-display-selector/index.js.map +1 -0
  27. package/lib/components/metrics-display-selector/metrics-display-selector.js +21 -0
  28. package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -0
  29. package/lib/components/metrics-display-selector/metrics-display-selector.module.css +10 -0
  30. package/lib/components/{bundle-assets-totals-chart-bars → metrics-treemap}/index.js +1 -1
  31. package/lib/components/metrics-treemap/index.js.map +1 -0
  32. package/lib/components/metrics-treemap/metrics-treemap.js +168 -0
  33. package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -0
  34. package/lib/components/metrics-treemap/metrics-treemap.module.css +184 -0
  35. package/lib/constants.js +6 -1
  36. package/lib/constants.js.map +1 -1
  37. package/lib/hooks/entry-info.js +12 -0
  38. package/lib/hooks/entry-info.js.map +1 -0
  39. package/lib/hooks/metrics-display-type.js +40 -0
  40. package/lib/hooks/metrics-display-type.js.map +1 -0
  41. package/lib/query-state.js +1 -1
  42. package/lib/query-state.js.map +1 -1
  43. package/lib/ui/button/button.module.css +1 -0
  44. package/lib/ui/icon/icon.js +2 -0
  45. package/lib/ui/icon/icon.js.map +1 -1
  46. package/lib/ui/index.js +1 -3
  47. package/lib/ui/index.js.map +1 -1
  48. package/lib-esm/app/app.js +3 -7
  49. package/lib-esm/app/app.js.map +1 -1
  50. package/lib-esm/assets/icons.svg +26 -20
  51. package/lib-esm/assets/icons.svg.js +27 -23
  52. package/lib-esm/assets/icons.svg.js.map +1 -1
  53. package/lib-esm/components/bundle-assets/bundle-assets.js +11 -3
  54. package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
  55. package/lib-esm/components/bundle-assets/index.js +5 -6
  56. package/lib-esm/components/bundle-assets/index.js.map +1 -1
  57. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +47 -0
  58. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -0
  59. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.module.css +7 -0
  60. package/lib-esm/components/bundle-assets-totals/index.js +2 -0
  61. package/lib-esm/components/bundle-assets-totals/index.js.map +1 -0
  62. package/lib-esm/components/bundle-modules/bundle-modules.js +9 -2
  63. package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
  64. package/lib-esm/components/bundle-modules/index.js +3 -4
  65. package/lib-esm/components/bundle-modules/index.js.map +1 -1
  66. package/lib-esm/components/bundle-packages/bundle-packages.js +10 -2
  67. package/lib-esm/components/bundle-packages/bundle-packages.js.map +1 -1
  68. package/lib-esm/components/bundle-packages/index.js +5 -6
  69. package/lib-esm/components/bundle-packages/index.js.map +1 -1
  70. package/lib-esm/components/index.js +1 -2
  71. package/lib-esm/components/index.js.map +1 -1
  72. package/lib-esm/components/metrics-display-selector/index.js +2 -0
  73. package/lib-esm/components/metrics-display-selector/index.js.map +1 -0
  74. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +14 -0
  75. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -0
  76. package/lib-esm/components/metrics-display-selector/metrics-display-selector.module.css +10 -0
  77. package/lib-esm/components/metrics-treemap/index.js +2 -0
  78. package/lib-esm/components/metrics-treemap/index.js.map +1 -0
  79. package/lib-esm/components/metrics-treemap/metrics-treemap.js +140 -0
  80. package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -0
  81. package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +184 -0
  82. package/lib-esm/constants.js +5 -0
  83. package/lib-esm/constants.js.map +1 -1
  84. package/lib-esm/hooks/entry-info.js +8 -0
  85. package/lib-esm/hooks/entry-info.js.map +1 -0
  86. package/lib-esm/hooks/metrics-display-type.js +36 -0
  87. package/lib-esm/hooks/metrics-display-type.js.map +1 -0
  88. package/lib-esm/query-state.js +1 -1
  89. package/lib-esm/query-state.js.map +1 -1
  90. package/lib-esm/ui/button/button.module.css +1 -0
  91. package/lib-esm/ui/icon/icon.js +2 -0
  92. package/lib-esm/ui/icon/icon.js.map +1 -1
  93. package/lib-esm/ui/index.js +0 -1
  94. package/lib-esm/ui/index.js.map +1 -1
  95. package/package.json +7 -4
  96. package/src/app/app.jsx +4 -8
  97. package/src/assets/icons/table.svg +3 -0
  98. package/src/assets/icons/treemap.svg +4 -0
  99. package/src/assets/icons.svg +26 -20
  100. package/src/assets/icons.svg.jsx +91 -55
  101. package/src/components/bundle-assets/bundle-assets.jsx +25 -11
  102. package/src/components/bundle-assets/index.jsx +10 -5
  103. package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +94 -0
  104. package/src/components/bundle-assets-totals/bundle-assets-totals.module.css +7 -0
  105. package/src/components/bundle-assets-totals/index.js +1 -0
  106. package/src/components/bundle-modules/bundle-modules.tsx +26 -12
  107. package/src/components/bundle-modules/index.tsx +4 -3
  108. package/src/components/bundle-packages/bundle-packages.jsx +24 -10
  109. package/src/components/bundle-packages/index.jsx +10 -5
  110. package/src/components/index.js +1 -2
  111. package/src/components/metrics-display-selector/index.tsx +1 -0
  112. package/src/components/metrics-display-selector/metrics-display-selector.module.css +10 -0
  113. package/src/components/metrics-display-selector/metrics-display-selector.tsx +47 -0
  114. package/src/components/metrics-treemap/index.ts +1 -0
  115. package/src/components/metrics-treemap/metrics-treemap.module.css +184 -0
  116. package/src/components/metrics-treemap/metrics-treemap.stories.tsx +23 -0
  117. package/src/components/metrics-treemap/metrics-treemap.tsx +308 -0
  118. package/src/{constants.js → constants.ts} +5 -0
  119. package/src/hooks/entry-info.ts +19 -0
  120. package/src/hooks/metrics-display-type.ts +56 -0
  121. package/src/query-state.tsx +1 -1
  122. package/src/types.d.ts +2 -0
  123. package/src/ui/button/button.module.css +1 -0
  124. package/src/ui/icon/icon.tsx +2 -0
  125. package/src/ui/index.js +0 -1
  126. package/types/components/bundle-assets/bundle-assets.d.ts +2 -1
  127. package/types/components/{bundle-assets-totals-table/bundle-assets-totals-table.d.ts → bundle-assets-totals/bundle-assets-totals.d.ts} +2 -2
  128. package/types/components/bundle-assets-totals/index.d.ts +1 -0
  129. package/types/components/bundle-modules/bundle-modules.d.ts +2 -1
  130. package/types/components/bundle-modules/index.d.ts +1 -1
  131. package/types/components/bundle-packages/bundle-packages.d.ts +1 -0
  132. package/types/components/index.d.ts +1 -2
  133. package/types/components/metrics-display-selector/index.d.ts +1 -0
  134. package/types/components/metrics-display-selector/metrics-display-selector.d.ts +9 -0
  135. package/types/components/metrics-treemap/index.d.ts +1 -0
  136. package/types/components/metrics-treemap/metrics-treemap.d.ts +9 -0
  137. package/types/constants.d.ts +19 -15
  138. package/types/hooks/entry-info.d.ts +8 -0
  139. package/types/hooks/metrics-display-type.d.ts +4 -0
  140. package/types/ui/icon/icon.d.ts +4 -0
  141. package/types/ui/index.d.ts +0 -1
  142. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +0 -95
  143. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js.map +0 -1
  144. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.module.css +0 -42
  145. package/lib/components/bundle-assets-totals-chart-bars/index.js.map +0 -1
  146. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +0 -57
  147. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js.map +0 -1
  148. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +0 -3
  149. package/lib/components/bundle-assets-totals-table/index.js.map +0 -1
  150. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js +0 -102
  151. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js.map +0 -1
  152. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.module.css +0 -42
  153. package/lib/ui/horizontal-bar-chart/index.js.map +0 -1
  154. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +0 -66
  155. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js.map +0 -1
  156. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.module.css +0 -42
  157. package/lib-esm/components/bundle-assets-totals-chart-bars/index.js +0 -2
  158. package/lib-esm/components/bundle-assets-totals-chart-bars/index.js.map +0 -1
  159. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +0 -27
  160. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js.map +0 -1
  161. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +0 -3
  162. package/lib-esm/components/bundle-assets-totals-table/index.js +0 -2
  163. package/lib-esm/components/bundle-assets-totals-table/index.js.map +0 -1
  164. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.js +0 -96
  165. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.js.map +0 -1
  166. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.module.css +0 -42
  167. package/lib-esm/ui/horizontal-bar-chart/index.js +0 -2
  168. package/lib-esm/ui/horizontal-bar-chart/index.js.map +0 -1
  169. package/src/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.jsx +0 -106
  170. package/src/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.module.css +0 -42
  171. package/src/components/bundle-assets-totals-chart-bars/index.js +0 -1
  172. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +0 -55
  173. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +0 -3
  174. package/src/components/bundle-assets-totals-table/index.js +0 -1
  175. package/src/ui/horizontal-bar-chart/horizontal-bar-chart.jsx +0 -156
  176. package/src/ui/horizontal-bar-chart/horizontal-bar-chart.module.css +0 -42
  177. package/src/ui/horizontal-bar-chart/index.js +0 -1
  178. package/types/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.d.ts +0 -22
  179. package/types/components/bundle-assets-totals-chart-bars/index.d.ts +0 -1
  180. package/types/components/bundle-assets-totals-table/index.d.ts +0 -1
  181. package/types/ui/horizontal-bar-chart/horizontal-bar-chart.d.ts +0 -23
  182. package/types/ui/horizontal-bar-chart/index.d.ts +0 -1
@@ -0,0 +1,308 @@
1
+ import React, { RefObject, forwardRef, useCallback, useMemo, useRef } from 'react';
2
+ import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-use';
3
+ 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';
7
+
8
+ import { Stack } from '../../layout/stack';
9
+ import { FileName } from '../../ui/file-name';
10
+ import { Delta } from '../delta';
11
+ import { RunInfo } from '../run-info';
12
+ import css from './metrics-treemap.module.css';
13
+
14
+ const SQUARIFY_RATIO = 1.78;
15
+ const PADDING_OUTER = 0;
16
+ const PADDING_INNER = 1;
17
+
18
+ type TileSizeDisplay = 'minimal' | 'small' | 'default';
19
+
20
+ /**
21
+ * Resolve the tile size using predefined values to avoid
22
+ * computing the size of the content for every tile
23
+ */
24
+ const PADDING_TOP = 4;
25
+ const PADDING_BOTTOM = 2; // substracted to allow longer texts to appear
26
+ const PADDING_LEFT = 16;
27
+ const PADDING_RIGHT = 4; // substracted to allow longer texts to not break
28
+ const VERTICAL_SPACING = 4;
29
+ const LINE_HEIGHT = 16;
30
+ const LINE_HEIGHT_SMALL = 13.3;
31
+
32
+ function resolveLeafSizeDisplay(width: number, height: number): TileSizeDisplay {
33
+ if (
34
+ height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 &&
35
+ width > PADDING_LEFT + PADDING_RIGHT + 128
36
+ ) {
37
+ return 'default';
38
+ }
39
+
40
+ if (
41
+ height > PADDING_TOP + PADDING_BOTTOM + LINE_HEIGHT_SMALL * 2 &&
42
+ width > PADDING_LEFT + PADDING_BOTTOM + 48
43
+ ) {
44
+ return 'small';
45
+ }
46
+
47
+ return 'minimal';
48
+ }
49
+
50
+ interface LeafTooltipContentProps {
51
+ item: ReportMetricRow;
52
+ }
53
+
54
+ const LeafTooltipContent = (props: LeafTooltipContentProps) => {
55
+ const { item } = props;
56
+
57
+ const currentRun = item.runs[0] as MetricRunInfo;
58
+ const baselineRun = item.runs[item.runs.length - 1] as MetricRunInfoBaseline;
59
+
60
+ return (
61
+ <Stack space="small" className={css.leafTooltipContent}>
62
+ <h3 className={css.leafTooltipContentTitle}>
63
+ <FileName as="code" name={item.label} />
64
+ </h3>
65
+ <RunInfo
66
+ current={currentRun.displayValue}
67
+ delta={currentRun.displayDeltaPercentage}
68
+ deltaType={currentRun.deltaType}
69
+ baseline={baselineRun?.displayValue || '0B'}
70
+ />
71
+ </Stack>
72
+ );
73
+ };
74
+
75
+ interface LeafContentProps {
76
+ sizeDisplay: 'minimal' | 'small' | 'default';
77
+ item: ReportMetricRow;
78
+ runInfo: MetricRunInfo;
79
+ }
80
+
81
+ const LeafContent = forwardRef((props: LeafContentProps, ref: React.Ref<HTMLDivElement>) => {
82
+ const { sizeDisplay, item, runInfo } = props;
83
+
84
+ if (sizeDisplay === 'minimal') {
85
+ return <div className={css.leafContent} ref={ref} />;
86
+ }
87
+
88
+ return (
89
+ <div className={css.leafContent} ref={ref}>
90
+ <p className={css.leadContentLabel}>{item.label}</p>
91
+ {sizeDisplay !== 'small' && (
92
+ <p className={css.leadContentValue}>
93
+ <span className={css.leadContentMetric}>{runInfo.displayValue}</span>
94
+ <Delta
95
+ className={css.leadContentDelta}
96
+ displayValue={runInfo.displayDeltaPercentage}
97
+ deltaType={runInfo.deltaType}
98
+ />
99
+ </p>
100
+ )}
101
+ </div>
102
+ );
103
+ });
104
+
105
+ const LeafContentWithTooltip = (props: LeafContentProps & { parentRef: RefObject<Element> }) => {
106
+ const { sizeDisplay, item, runInfo, parentRef } = props;
107
+
108
+ const pointer = useMouseHovered(parentRef, { whenHovered: true });
109
+
110
+ // Return custom rect based on the mouse position
111
+ const getAnchorRect = useCallback(() => {
112
+ // Skip custom component rect area if the pointer position is empty
113
+ if (pointer.docX === 0 && pointer.docY === 0 && pointer.elW === 0 && pointer.elH === 0) {
114
+ return null;
115
+ }
116
+
117
+ const newRect = {
118
+ x: pointer.docX - (document?.defaultView?.pageXOffset ?? 0),
119
+ y: pointer.docY - (document?.defaultView?.pageYOffset ?? 0),
120
+ w: 1,
121
+ h: 1,
122
+ };
123
+
124
+ return newRect;
125
+ }, [pointer.docX, pointer.docY]);
126
+
127
+ const tooltipState = useTooltipState({ gutter: 8, getAnchorRect, timeout: 100 });
128
+
129
+ // Update tooltip position when poiner values are changing
130
+ useDebounce(tooltipState.render, 10, [pointer.docX, pointer.docY]);
131
+
132
+ return (
133
+ <>
134
+ <TooltipAnchor state={tooltipState} className={css.leafContentTooltipAnchor}>
135
+ <LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
136
+ </TooltipAnchor>
137
+ <Tooltip state={tooltipState} className={css.tooltip}>
138
+ <TooltipArrow state={tooltipState} size={16} className={css.leafTooltipArrow} />
139
+ <LeafTooltipContent item={item} />
140
+ </Tooltip>
141
+ </>
142
+ );
143
+ };
144
+
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;
166
+ width: number;
167
+ height: number;
168
+ data: TreeNode;
169
+ onClick?: (id: string) => void;
170
+ }
171
+
172
+ const Leaf = (props: LeafProps) => {
173
+ const { x, y, width, height, data, onClick } = props;
174
+
175
+ const { item } = data;
176
+ const runInfo = item.runs?.[0] as MetricRunInfo;
177
+
178
+ const sizeDisplay = useMemo(() => resolveLeafSizeDisplay(width, height), [width, height]);
179
+ const handleOnClick = useCallback(() => onClick?.(item.key), [item.key]);
180
+
181
+ const leafContentRef = useRef<HTMLElement>(null);
182
+ const hover = useHoverDirty(leafContentRef);
183
+
184
+ const leafClassName = cx(
185
+ css.leaf,
186
+ css[`leaf-${runInfo.deltaType}`],
187
+ sizeDisplay === 'small' && css.leafSmall,
188
+ );
189
+
190
+ 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} />
205
+ )}
206
+ </LeafAction>
207
+ </foreignObject>
208
+ </g>
209
+ );
210
+ };
211
+
212
+ interface UseMetricsTreemapHierarchyParams {
213
+ items: Array<ReportMetricRow>;
214
+ width: number;
215
+ height: number;
216
+ }
217
+
218
+ function useMetricsTreemapHierarchy(params: UseMetricsTreemapHierarchyParams) {
219
+ const { items, width, height } = params;
220
+
221
+ 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
+ };
243
+
244
+ const customHierarchy = hierarchy<RootTree>(treemapData);
245
+ customHierarchy.sum((node) => node.value);
246
+ // @ts-expect-error
247
+ customHierarchy.sort((a, b) => b.value - a.value);
248
+
249
+ return customHierarchy;
250
+ }, [items]);
251
+
252
+ const cells = useMemo(() => {
253
+ const createTremapLayout = treemap()
254
+ .size([width, height])
255
+ .tile(treemapSquarify.ratio(SQUARIFY_RATIO))
256
+ .paddingOuter(PADDING_OUTER)
257
+ .paddingInner(PADDING_INNER);
258
+ // @ts-expect-error
259
+ const tremapLayout = createTremapLayout(treemapHierarchy);
260
+
261
+ return tremapLayout.leaves().filter((leaf) => Boolean(leaf.parent));
262
+ }, [height, width, treemapHierarchy]);
263
+
264
+ return cells;
265
+ }
266
+
267
+ interface MetricsTreemapProps {
268
+ items: Array<ReportMetricRow>;
269
+ emptyMessage?: React.ReactNode;
270
+ onItemClick?: (entryId: string) => void;
271
+ }
272
+
273
+ export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps<'div'>) => {
274
+ const { className = '', items, onItemClick, emptyMessage = 'No data', ...restProps } = props;
275
+
276
+ const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
277
+ const cells = useMetricsTreemapHierarchy({ items, width, height });
278
+
279
+ 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>
301
+ ) : (
302
+ <div className={css.emptyMessage}>
303
+ <div className={css.emptyMessageWrapper}>{emptyMessage}</div>
304
+ </div>
305
+ )}
306
+ </div>
307
+ );
308
+ };
@@ -40,3 +40,8 @@ export const SORT = {
40
40
  ASC: 'asc',
41
41
  DESC: 'desc',
42
42
  };
43
+
44
+ export enum MetricsDisplayType {
45
+ TABLE = 'table',
46
+ TREEMAP = 'treemap',
47
+ }
@@ -0,0 +1,19 @@
1
+ import { useCallback } from 'react';
2
+
3
+ interface UseEntryInfoParams {
4
+ setState: (data: { entryId: string }) => void;
5
+ }
6
+
7
+ type UseEntryInfo = [() => void, (entryId: string) => void];
8
+
9
+ /**
10
+ * Show/hide side panel entry
11
+ */
12
+ export function useEntryInfo(params: UseEntryInfoParams): UseEntryInfo {
13
+ const { setState } = params;
14
+
15
+ const hideEntryInfo = useCallback(() => setState({ entryId: '' }), [setState]);
16
+ const showEntryInfo = useCallback((entryId: string) => setState({ entryId }), [setState]);
17
+
18
+ return [hideEntryInfo, showEntryInfo];
19
+ }
@@ -0,0 +1,56 @@
1
+ import { useCallback, useEffect, useMemo } from 'react';
2
+ import { useLocalStorage } from 'react-use';
3
+ import { StringParam, useQueryParams } from 'use-query-params';
4
+
5
+ import { MetricsDisplayType } from '../constants';
6
+
7
+ const DEFAULT_DISPLAY_TYPE = MetricsDisplayType.TABLE;
8
+ const PARAM_NAME = 'dt';
9
+
10
+ type UseMetricsDisplayType = [MetricsDisplayType, (value: MetricsDisplayType) => void];
11
+
12
+ export const useMetricsDisplayType = (): UseMetricsDisplayType => {
13
+ const [{ [PARAM_NAME]: queryDisplayType }, setQueryParams] = useQueryParams({
14
+ [PARAM_NAME]: StringParam,
15
+ });
16
+ const [storageDisplayType, setStoreageDisplayType] = useLocalStorage(
17
+ PARAM_NAME,
18
+ MetricsDisplayType.TABLE,
19
+ );
20
+
21
+ const displayType = useMemo(() => {
22
+ const value = queryDisplayType || storageDisplayType;
23
+
24
+ if (!value) {
25
+ return DEFAULT_DISPLAY_TYPE;
26
+ }
27
+
28
+ if (!Object.values(MetricsDisplayType).includes(value as MetricsDisplayType)) {
29
+ return DEFAULT_DISPLAY_TYPE;
30
+ }
31
+
32
+ return value as MetricsDisplayType;
33
+ }, [queryDisplayType, storageDisplayType]);
34
+
35
+ useEffect(() => {
36
+ // Update query string display type when missing
37
+ if (queryDisplayType !== displayType) {
38
+ setQueryParams({ [PARAM_NAME]: displayType });
39
+ }
40
+
41
+ // Update storage state when query string is set and different
42
+ if (storageDisplayType !== displayType) {
43
+ setStoreageDisplayType(queryDisplayType as MetricsDisplayType);
44
+ }
45
+ }, [displayType, queryDisplayType, storageDisplayType, setQueryParams, setStoreageDisplayType]);
46
+
47
+ const setDisplayType = useCallback(
48
+ (value: MetricsDisplayType) => {
49
+ setStoreageDisplayType(value);
50
+ setQueryParams({ [PARAM_NAME]: value });
51
+ },
52
+ [setStoreageDisplayType, setQueryParams],
53
+ );
54
+
55
+ return [displayType, setDisplayType];
56
+ };
@@ -30,7 +30,7 @@ export const useComponentQueryState = (componentName: string) => {
30
30
 
31
31
  setQueryState({ [componentName]: newState });
32
32
  },
33
- [componentState],
33
+ [componentState, setQueryState],
34
34
  );
35
35
 
36
36
  return [componentState, setState];
package/src/types.d.ts CHANGED
@@ -8,8 +8,10 @@ interface FilterFieldData {
8
8
  defaultValue: boolean;
9
9
  disabled?: boolean;
10
10
  }
11
+
11
12
  type FilterGroupFieldData = {
12
13
  label: string;
13
14
  children: Array<{ key: string } & FilterFieldData>;
14
15
  };
16
+
15
17
  type FilterFieldsData = Record<string, FilterFieldData | FilterGroupFieldData>;
@@ -256,6 +256,7 @@ a.root {
256
256
  .small .glyph {
257
257
  width: calc(var(--space-xsmall) + 2px);
258
258
  height: calc(var(--space-xsmall) + 2px);
259
+ margin: 1px; /* compensate to keep the same height when the text is not provided */
259
260
  }
260
261
 
261
262
  .medium {
@@ -18,6 +18,8 @@ const ICONS = {
18
18
  GITHUB: 'github',
19
19
  INFO: 'info',
20
20
  HELP: 'help',
21
+ TABLE: 'table',
22
+ TREEMAP: 'treemap',
21
23
  MENU: 'menu',
22
24
  MORE_VERTICAL: 'more-vertical',
23
25
  SORT: 'sort',
package/src/ui/index.js CHANGED
@@ -6,7 +6,6 @@ export { Dropdown } from './dropdown';
6
6
  export { EmptySet } from './empty-set';
7
7
  export { FileName } from './file-name';
8
8
  export { Filters } from './filters';
9
- export { HorizontalBarChart } from './horizontal-bar-chart';
10
9
  export { HoverCard } from './hover-card';
11
10
  export { Icon } from './icon';
12
11
  export { Input } from './input';
@@ -47,11 +47,12 @@ export namespace BundleAssets {
47
47
  direction: PropTypes.Requireable<string>;
48
48
  }>>>;
49
49
  export let updateSort: PropTypes.Validator<(...args: any[]) => any>;
50
- export let allItems: PropTypes.Requireable<(PropTypes.InferProps<{
50
+ export let allItems: PropTypes.Validator<(PropTypes.InferProps<{
51
51
  key: PropTypes.Requireable<string>;
52
52
  }> | null | undefined)[]>;
53
53
  export let customComponentLink: PropTypes.Requireable<PropTypes.ReactComponentLike>;
54
54
  export let hideEntryInfo: PropTypes.Validator<(...args: any[]) => any>;
55
+ export let showEntryInfo: PropTypes.Validator<(...args: any[]) => any>;
55
56
  }
56
57
  }
57
58
  import React from 'react';
@@ -1,10 +1,10 @@
1
- export function BundleAssetsTotalsTable({ className, jobs, customComponentLink: CustomComponentLink, ...restProps }: {
1
+ export function BundleAssetsTotals({ className, jobs, customComponentLink: CustomComponentLink, ...restProps }: {
2
2
  [x: string]: any;
3
3
  className: any;
4
4
  jobs: any;
5
5
  customComponentLink: any;
6
6
  }): React.JSX.Element;
7
- export namespace BundleAssetsTotalsTable {
7
+ export namespace BundleAssetsTotals {
8
8
  namespace defaultProps {
9
9
  export let className: string;
10
10
  export let jobs: never[];
@@ -0,0 +1 @@
1
+ export * from "./bundle-assets-totals";
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { SortAction } from '../../types';
2
+ import { SortAction } from '../../types';
3
3
  import { ModuleMetric } from './bundle-modules.constants';
4
4
  import type { Chunk, Job, ReportMetricModuleRow } from './bundle-modules.types';
5
5
  interface BundleModulesProps extends React.ComponentProps<'div'> {
@@ -18,6 +18,7 @@ interface BundleModulesProps extends React.ComponentProps<'div'> {
18
18
  updateSearch: (newSerarch: string) => void;
19
19
  entryId?: string;
20
20
  hideEntryInfo: () => void;
21
+ showEntryInfo: (entryId: string) => void;
21
22
  moduleMetric: ModuleMetric;
22
23
  setModuleMetric: (newValue: ModuleMetric) => void;
23
24
  customComponentLink: React.ElementType;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import type { SortAction } from '../../types';
3
3
  import { BundleModules as BaseComponent } from './bundle-modules';
4
4
  import * as types from './bundle-modules.types';
5
- interface BundleModulesProps extends Omit<React.ComponentProps<typeof BaseComponent>, 'jobs' | 'items' | 'chunks' | 'updateFilters' | 'resetFilters' | 'resetAllFilters' | 'filters' | 'sort' | 'updateSort' | 'search' | 'updateSearch' | 'allItems' | 'totalRowCount' | 'hideEntryInfo'> {
5
+ interface BundleModulesProps extends Omit<React.ComponentProps<typeof BaseComponent>, 'jobs' | 'items' | 'chunks' | 'updateFilters' | 'resetFilters' | 'resetAllFilters' | 'filters' | 'sort' | 'updateSort' | 'search' | 'updateSearch' | 'allItems' | 'totalRowCount' | 'hideEntryInfo' | 'showEntryInfo'> {
6
6
  jobs: Array<types.Job>;
7
7
  filters: Record<string, boolean>;
8
8
  search?: string;
@@ -45,6 +45,7 @@ export namespace BundlePackages {
45
45
  let entryId_1: PropTypes.Requireable<string>;
46
46
  export { entryId_1 as entryId };
47
47
  export let hideEntryInfo: PropTypes.Validator<(...args: any[]) => any>;
48
+ export let showEntryInfo: PropTypes.Validator<(...args: any[]) => any>;
48
49
  }
49
50
  }
50
51
  import React from 'react';
@@ -1,7 +1,6 @@
1
1
  export { BrowsertimeTable } from "./browsertime-table";
2
2
  export { BundleAssets } from "./bundle-assets";
3
- export { BundleAssetsTotalsTable } from "./bundle-assets-totals-table";
4
- export { BundleAssetsTotalsChartBars } from "./bundle-assets-totals-chart-bars";
3
+ export { BundleAssetsTotals } from "./bundle-assets-totals";
5
4
  export { BundlePackages } from "./bundle-packages";
6
5
  export { BundleModules } from "./bundle-modules";
7
6
  export { ComponentLink } from "./component-link";
@@ -0,0 +1 @@
1
+ export * from './metrics-display-selector';
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { MetricsDisplayType } from '../../constants';
3
+ import { FlexStack } from '../../layout/flex-stack';
4
+ interface MetricsDisplaySelectorProps {
5
+ value: MetricsDisplayType;
6
+ onSelect: (newValue: MetricsDisplayType) => void;
7
+ }
8
+ export declare const MetricsDisplaySelector: (props: MetricsDisplaySelectorProps & Omit<React.ComponentProps<typeof FlexStack>, 'onSelect' | 'value'>) => React.JSX.Element;
9
+ export {};
@@ -0,0 +1 @@
1
+ export * from './metrics-treemap';
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { ReportMetricRow } from '@bundle-stats/utils';
3
+ interface MetricsTreemapProps {
4
+ items: Array<ReportMetricRow>;
5
+ emptyMessage?: React.ReactNode;
6
+ onItemClick?: (entryId: string) => void;
7
+ }
8
+ export declare const MetricsTreemap: (props: MetricsTreemapProps & React.ComponentProps<'div'>) => React.JSX.Element;
9
+ export {};
@@ -1,18 +1,22 @@
1
- export namespace URLS {
2
- let OVERVIEW: string;
3
- let ASSETS: string;
4
- let MODULES: string;
5
- let PACKAGES: string;
6
- }
7
- export const SECTION_URLS: {
1
+ export declare const URLS: {
2
+ OVERVIEW: string;
3
+ ASSETS: string;
4
+ MODULES: string;
5
+ PACKAGES: string;
6
+ };
7
+ export declare const SECTION_URLS: {
8
8
  [x: string]: string;
9
9
  };
10
- export const ASSETS_SIZES_FILE_TYPE_MAP: {};
11
- export const METRICS_WEBPACK_GENERAL: string[];
12
- export const METRICS_WEBPACK_ASSETS: string[];
13
- export const METRICS_WEBPACK_MODULES: string[];
14
- export const METRICS_WEBPACK_PACKAGES: string[];
15
- export namespace SORT {
16
- let ASC: string;
17
- let DESC: string;
10
+ export declare const ASSETS_SIZES_FILE_TYPE_MAP: {};
11
+ export declare const METRICS_WEBPACK_GENERAL: string[];
12
+ export declare const METRICS_WEBPACK_ASSETS: string[];
13
+ export declare const METRICS_WEBPACK_MODULES: string[];
14
+ export declare const METRICS_WEBPACK_PACKAGES: string[];
15
+ export declare const SORT: {
16
+ ASC: string;
17
+ DESC: string;
18
+ };
19
+ export declare enum MetricsDisplayType {
20
+ TABLE = "table",
21
+ TREEMAP = "treemap"
18
22
  }
@@ -0,0 +1,8 @@
1
+ interface UseEntryInfoParams {
2
+ setState: (data: {
3
+ entryId: string;
4
+ }) => void;
5
+ }
6
+ type UseEntryInfo = [() => void, (entryId: string) => void];
7
+ export declare function useEntryInfo(params: UseEntryInfoParams): UseEntryInfo;
8
+ export {};
@@ -0,0 +1,4 @@
1
+ import { MetricsDisplayType } from '../constants';
2
+ type UseMetricsDisplayType = [MetricsDisplayType, (value: MetricsDisplayType) => void];
3
+ export declare const useMetricsDisplayType: () => UseMetricsDisplayType;
4
+ export {};
@@ -14,6 +14,8 @@ declare const ICONS: {
14
14
  readonly GITHUB: "github";
15
15
  readonly INFO: "info";
16
16
  readonly HELP: "help";
17
+ readonly TABLE: "table";
18
+ readonly TREEMAP: "treemap";
17
19
  readonly MENU: "menu";
18
20
  readonly MORE_VERTICAL: "more-vertical";
19
21
  readonly SORT: "sort";
@@ -47,6 +49,8 @@ export declare const Icon: {
47
49
  readonly GITHUB: "github";
48
50
  readonly INFO: "info";
49
51
  readonly HELP: "help";
52
+ readonly TABLE: "table";
53
+ readonly TREEMAP: "treemap";
50
54
  readonly MENU: "menu";
51
55
  readonly MORE_VERTICAL: "more-vertical";
52
56
  readonly SORT: "sort";
@@ -6,7 +6,6 @@ export { Dropdown } from "./dropdown";
6
6
  export { EmptySet } from "./empty-set";
7
7
  export { FileName } from "./file-name";
8
8
  export { Filters } from "./filters";
9
- export { HorizontalBarChart } from "./horizontal-bar-chart";
10
9
  export { HoverCard } from "./hover-card";
11
10
  export { Icon } from "./icon";
12
11
  export { Input } from "./input";