@bundle-stats/ui 4.13.0-beta.0 → 4.13.0-beta.11

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 (217) 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/components/bundle-assets/bundle-assets.js +12 -5
  5. package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
  6. package/lib/components/bundle-assets/bundle-assets.module.css +0 -5
  7. package/lib/components/bundle-assets/index.js +2 -2
  8. package/lib/components/bundle-assets/index.js.map +1 -1
  9. package/lib/components/bundle-assets-totals/bundle-assets-totals.js +22 -11
  10. package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  11. package/lib/components/bundle-modules/bundle-modules.js +22 -12
  12. package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
  13. package/lib/components/bundle-modules/bundle-modules.module.css +0 -5
  14. package/lib/components/bundle-modules/index.js +2 -2
  15. package/lib/components/bundle-modules/index.js.map +1 -1
  16. package/lib/components/bundle-packages/bundle-packages.js +10 -3
  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/bundle-packages/index.js +2 -2
  20. package/lib/components/bundle-packages/index.js.map +1 -1
  21. package/lib/components/index.js +1 -3
  22. package/lib/components/index.js.map +1 -1
  23. package/lib/components/jobs-header/jobs-header.module.css +0 -4
  24. package/lib/components/metrics-display-selector/metrics-display-selector.js +4 -10
  25. package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
  26. package/lib/components/metrics-table/metrics-table.i18n.js +1 -6
  27. package/lib/components/metrics-table/metrics-table.i18n.js.map +1 -1
  28. package/lib/components/metrics-table/metrics-table.js +4 -37
  29. package/lib/components/metrics-table/metrics-table.js.map +1 -1
  30. package/lib/components/metrics-table/metrics-table.module.css +1 -44
  31. package/lib/components/{total-size-type-title → metrics-table-header}/index.js +1 -1
  32. package/lib/components/metrics-table-header/index.js.map +1 -0
  33. package/lib/components/metrics-table-header/metrics-table-header.i18n.js +9 -0
  34. package/lib/components/metrics-table-header/metrics-table-header.i18n.js.map +1 -0
  35. package/lib/components/metrics-table-header/metrics-table-header.js +79 -0
  36. package/lib/components/metrics-table-header/metrics-table-header.js.map +1 -0
  37. package/lib/components/metrics-table-header/metrics-table-header.module.css +51 -0
  38. package/lib/components/metrics-table-options/metrics-table-options.js +2 -2
  39. package/lib/components/metrics-table-options/metrics-table-options.js.map +1 -1
  40. package/lib/components/metrics-table-title/metrics-table-title.js +1 -1
  41. package/lib/components/metrics-table-title/metrics-table-title.js.map +1 -1
  42. package/lib/components/metrics-table-title/metrics-table-title.module.css +4 -4
  43. package/lib/components/metrics-treemap/metrics-treemap.js +66 -51
  44. package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
  45. package/lib/components/metrics-treemap/metrics-treemap.module.css +89 -56
  46. package/lib/components/sort-button/sort-button.module.css +1 -0
  47. package/lib/css/variables.css +14 -14
  48. package/lib/hooks/entry-info.js +12 -0
  49. package/lib/hooks/entry-info.js.map +1 -0
  50. package/lib/layout/flex-stack/flex-stack.js +3 -4
  51. package/lib/layout/flex-stack/flex-stack.js.map +1 -1
  52. package/lib/layout/flex-stack/flex-stack.module.css +19 -23
  53. package/lib/query-state.js +1 -1
  54. package/lib/query-state.js.map +1 -1
  55. package/lib/ui/button/button.js +5 -4
  56. package/lib/ui/button/button.js.map +1 -1
  57. package/lib/ui/button/button.module.css +55 -52
  58. package/lib/ui/dropdown/dropdown.js +10 -40
  59. package/lib/ui/dropdown/dropdown.js.map +1 -1
  60. package/lib/ui/dropdown/index.js.map +1 -1
  61. package/lib/ui/filters/filters.js +33 -35
  62. package/lib/ui/filters/filters.js.map +1 -1
  63. package/lib/ui/filters/filters.module.css +55 -67
  64. package/lib/ui/hover-card/hover-card.js +1 -1
  65. package/lib/ui/hover-card/hover-card.js.map +1 -1
  66. package/lib/ui/icon/icon.js +1 -1
  67. package/lib/ui/toolbar/toolbar.module.css +1 -3
  68. package/lib-esm/app/app.js +4 -8
  69. package/lib-esm/app/app.js.map +1 -1
  70. package/lib-esm/app/app.module.css +2 -2
  71. package/lib-esm/components/bundle-assets/bundle-assets.js +12 -5
  72. package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
  73. package/lib-esm/components/bundle-assets/bundle-assets.module.css +0 -5
  74. package/lib-esm/components/bundle-assets/index.js +3 -3
  75. package/lib-esm/components/bundle-assets/index.js.map +1 -1
  76. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +23 -12
  77. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  78. package/lib-esm/components/bundle-modules/bundle-modules.js +22 -12
  79. package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
  80. package/lib-esm/components/bundle-modules/bundle-modules.module.css +0 -5
  81. package/lib-esm/components/bundle-modules/index.js +2 -2
  82. package/lib-esm/components/bundle-modules/index.js.map +1 -1
  83. package/lib-esm/components/bundle-packages/bundle-packages.js +10 -3
  84. package/lib-esm/components/bundle-packages/bundle-packages.js.map +1 -1
  85. package/lib-esm/components/bundle-packages/bundle-packages.module.css +0 -5
  86. package/lib-esm/components/bundle-packages/index.js +3 -3
  87. package/lib-esm/components/bundle-packages/index.js.map +1 -1
  88. package/lib-esm/components/index.js +0 -1
  89. package/lib-esm/components/index.js.map +1 -1
  90. package/lib-esm/components/jobs-header/jobs-header.module.css +0 -4
  91. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +4 -10
  92. package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
  93. package/lib-esm/components/metrics-table/metrics-table.i18n.js +0 -5
  94. package/lib-esm/components/metrics-table/metrics-table.i18n.js.map +1 -1
  95. package/lib-esm/components/metrics-table/metrics-table.js +4 -37
  96. package/lib-esm/components/metrics-table/metrics-table.js.map +1 -1
  97. package/lib-esm/components/metrics-table/metrics-table.module.css +1 -44
  98. package/lib-esm/components/metrics-table-header/index.js +2 -0
  99. package/lib-esm/components/metrics-table-header/index.js.map +1 -0
  100. package/lib-esm/components/metrics-table-header/metrics-table-header.i18n.js +6 -0
  101. package/lib-esm/components/metrics-table-header/metrics-table-header.i18n.js.map +1 -0
  102. package/lib-esm/components/metrics-table-header/metrics-table-header.js +49 -0
  103. package/lib-esm/components/metrics-table-header/metrics-table-header.js.map +1 -0
  104. package/lib-esm/components/metrics-table-header/metrics-table-header.module.css +51 -0
  105. package/lib-esm/components/metrics-table-options/metrics-table-options.js +3 -3
  106. package/lib-esm/components/metrics-table-options/metrics-table-options.js.map +1 -1
  107. package/lib-esm/components/metrics-table-title/metrics-table-title.js +1 -1
  108. package/lib-esm/components/metrics-table-title/metrics-table-title.js.map +1 -1
  109. package/lib-esm/components/metrics-table-title/metrics-table-title.module.css +4 -4
  110. package/lib-esm/components/metrics-treemap/metrics-treemap.js +69 -53
  111. package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
  112. package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +89 -56
  113. package/lib-esm/components/sort-button/sort-button.module.css +1 -0
  114. package/lib-esm/css/variables.css +14 -14
  115. package/lib-esm/hooks/entry-info.js +8 -0
  116. package/lib-esm/hooks/entry-info.js.map +1 -0
  117. package/lib-esm/layout/flex-stack/flex-stack.js +3 -4
  118. package/lib-esm/layout/flex-stack/flex-stack.js.map +1 -1
  119. package/lib-esm/layout/flex-stack/flex-stack.module.css +19 -23
  120. package/lib-esm/query-state.js +1 -1
  121. package/lib-esm/query-state.js.map +1 -1
  122. package/lib-esm/ui/button/button.js +5 -4
  123. package/lib-esm/ui/button/button.js.map +1 -1
  124. package/lib-esm/ui/button/button.module.css +55 -52
  125. package/lib-esm/ui/dropdown/dropdown.js +8 -39
  126. package/lib-esm/ui/dropdown/dropdown.js.map +1 -1
  127. package/lib-esm/ui/dropdown/index.js.map +1 -1
  128. package/lib-esm/ui/filters/filters.js +34 -36
  129. package/lib-esm/ui/filters/filters.js.map +1 -1
  130. package/lib-esm/ui/filters/filters.module.css +55 -67
  131. package/lib-esm/ui/hover-card/hover-card.js +1 -1
  132. package/lib-esm/ui/hover-card/hover-card.js.map +1 -1
  133. package/lib-esm/ui/icon/icon.js +1 -1
  134. package/lib-esm/ui/toolbar/toolbar.module.css +1 -3
  135. package/package.json +8 -10
  136. package/src/app/app.jsx +4 -12
  137. package/src/app/app.module.css +2 -2
  138. package/src/components/bundle-assets/bundle-assets.jsx +24 -7
  139. package/src/components/bundle-assets/bundle-assets.module.css +0 -5
  140. package/src/components/bundle-assets/index.jsx +8 -3
  141. package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +55 -24
  142. package/src/components/bundle-modules/bundle-modules.module.css +0 -5
  143. package/src/components/bundle-modules/bundle-modules.tsx +60 -42
  144. package/src/components/bundle-modules/index.tsx +2 -2
  145. package/src/components/bundle-packages/bundle-packages.jsx +19 -5
  146. package/src/components/bundle-packages/bundle-packages.module.css +0 -5
  147. package/src/components/bundle-packages/index.jsx +8 -3
  148. package/src/components/index.js +0 -1
  149. package/src/components/jobs-header/jobs-header.module.css +0 -4
  150. package/src/components/metrics-display-selector/metrics-display-selector.tsx +8 -12
  151. package/src/components/metrics-table/metrics-table.i18n.ts +0 -7
  152. package/src/components/metrics-table/metrics-table.module.css +1 -44
  153. package/src/components/metrics-table/metrics-table.tsx +17 -153
  154. package/src/components/metrics-table-header/index.ts +1 -0
  155. package/src/components/metrics-table-header/metrics-table-header.i18n.ts +6 -0
  156. package/src/components/metrics-table-header/metrics-table-header.module.css +51 -0
  157. package/src/components/metrics-table-header/metrics-table-header.tsx +172 -0
  158. package/src/components/metrics-table-options/metrics-table-options.jsx +4 -10
  159. package/src/components/metrics-table-title/metrics-table-title.jsx +1 -1
  160. package/src/components/metrics-table-title/metrics-table-title.module.css +4 -4
  161. package/src/components/metrics-treemap/metrics-treemap.module.css +89 -56
  162. package/src/components/metrics-treemap/metrics-treemap.tsx +151 -118
  163. package/src/components/sort-button/sort-button.module.css +1 -0
  164. package/src/css/variables.css +14 -14
  165. package/src/hooks/entry-info.ts +19 -0
  166. package/src/layout/flex-stack/flex-stack.module.css +19 -23
  167. package/src/layout/flex-stack/flex-stack.tsx +2 -8
  168. package/src/query-state.tsx +1 -1
  169. package/src/ui/button/button.module.css +55 -52
  170. package/src/ui/button/button.stories.tsx +16 -9
  171. package/src/ui/button/button.tsx +8 -7
  172. package/src/ui/dropdown/dropdown.stories.tsx +25 -0
  173. package/src/ui/dropdown/dropdown.tsx +66 -0
  174. package/src/ui/filters/filters.module.css +55 -67
  175. package/src/ui/filters/filters.stories.tsx +109 -0
  176. package/src/ui/filters/filters.tsx +128 -167
  177. package/src/ui/hover-card/hover-card.tsx +1 -1
  178. package/src/ui/icon/icon.tsx +1 -1
  179. package/src/ui/toolbar/toolbar.module.css +1 -3
  180. package/types/components/bundle-assets-totals/bundle-assets-totals.d.ts +5 -1
  181. package/types/components/index.d.ts +0 -1
  182. package/types/components/metrics-display-selector/metrics-display-selector.d.ts +2 -2
  183. package/types/components/metrics-table/metrics-table.d.ts +4 -10
  184. package/types/components/metrics-table/metrics-table.i18n.d.ts +0 -5
  185. package/types/components/metrics-table-header/index.d.ts +1 -0
  186. package/types/components/metrics-table-header/metrics-table-header.d.ts +15 -0
  187. package/types/components/metrics-table-header/metrics-table-header.i18n.d.ts +5 -0
  188. package/types/hooks/entry-info.d.ts +8 -0
  189. package/types/ui/dropdown/dropdown.d.ts +12 -47
  190. package/types/ui/dropdown/index.d.ts +1 -1
  191. package/types/ui/filters/filters.d.ts +2 -7
  192. package/types/ui/icon/icon.d.ts +2 -2
  193. package/lib/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  194. package/lib/components/total-size-type-title/index.js.map +0 -1
  195. package/lib/components/total-size-type-title/total-size-type-title.js +0 -32
  196. package/lib/components/total-size-type-title/total-size-type-title.js.map +0 -1
  197. package/lib/components/total-size-type-title/total-size-type-title.module.css +0 -14
  198. package/lib/hooks/show-entry.js +0 -13
  199. package/lib/hooks/show-entry.js.map +0 -1
  200. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  201. package/lib-esm/components/total-size-type-title/index.js +0 -2
  202. package/lib-esm/components/total-size-type-title/index.js.map +0 -1
  203. package/lib-esm/components/total-size-type-title/total-size-type-title.js +0 -25
  204. package/lib-esm/components/total-size-type-title/total-size-type-title.js.map +0 -1
  205. package/lib-esm/components/total-size-type-title/total-size-type-title.module.css +0 -14
  206. package/lib-esm/hooks/show-entry.js +0 -9
  207. package/lib-esm/hooks/show-entry.js.map +0 -1
  208. package/src/components/bundle-assets-totals/bundle-assets-totals.module.css +0 -7
  209. package/src/components/total-size-type-title/index.js +0 -1
  210. package/src/components/total-size-type-title/total-size-type-title.jsx +0 -41
  211. package/src/components/total-size-type-title/total-size-type-title.module.css +0 -14
  212. package/src/hooks/show-entry.ts +0 -17
  213. package/src/ui/dropdown/dropdown.jsx +0 -88
  214. package/types/components/total-size-type-title/index.d.ts +0 -1
  215. package/types/components/total-size-type-title/total-size-type-title.d.ts +0 -14
  216. package/types/hooks/show-entry.d.ts +0 -10
  217. /package/src/ui/dropdown/{index.js → index.ts} +0 -0
@@ -12,6 +12,7 @@
12
12
  height: 100%;
13
13
  fill: var(--color-text);
14
14
  font-family: var(--font-family);
15
+ background: var(--color-background);
15
16
  }
16
17
 
17
18
  /** Tile overlay fill background transition */
@@ -20,29 +21,14 @@
20
21
  transition-property: opacity;
21
22
  }
22
23
 
23
- .tileOverlay {
24
- fill: var(--color-outline);
25
- transition: var(--ui-transition-out);
26
- transition-property: opacity;
27
- opacity: 0.4;
28
- }
29
-
30
- .root .tile:hover .tileOverlay {
31
- transition: var(--ui-transition-in);
32
- opacity: 0.8;
33
- }
34
-
35
- /** Tile content */
36
- .tileContent {
24
+ /** Leaf wrapper button */
25
+ .leaf {
26
+ position: absolute;
37
27
  appearance: none;
38
28
  border: 0;
39
- border-radius: none;
29
+ border-radius: 0;
40
30
  padding: 0;
41
31
  background: none;
42
-
43
- width: 100%;
44
- height: 100%;
45
- padding: var(--space-xxsmall) var(--space-small);
46
32
  display: flex;
47
33
  align-items: center;
48
34
  font-family: var(--font-family-fixed);
@@ -51,77 +37,116 @@
51
37
  text-align: left;
52
38
  }
53
39
 
54
- .tileContentWrapper {
40
+ .leaf::before {
41
+ content: "";
42
+ display: block;
43
+ position: absolute;
44
+ z-index: 1;
45
+ left: 0;
46
+ top: 0;
55
47
  width: 100%;
56
- max-height: 100%;
48
+ height: 100%;
49
+ background-color: currentColor;
50
+ color: var(--color-background);
51
+ transition: var(--ui-transition-out);
52
+ transition-property: opacity;
53
+ opacity: 0.6;
54
+ }
55
+
56
+ .leaf:hover::before {
57
+ transition: var(--ui-transition-in);
58
+ opacity: 0.4;
59
+ }
60
+
61
+ .leafContent {
62
+ position: relative;
63
+ z-index: 1;
64
+ width: 100%;
65
+ height: 100%;
57
66
  display: flex;
58
67
  flex-flow: column;
59
- gap: 4px;
68
+ justify-content: center;
69
+ padding: var(--space-xxsmall) var(--space-xxsmall);
70
+ gap: var(--space-xxxsmall);
71
+ line-height: var(--line-height-heading);
72
+ }
73
+
74
+ /** Align label to other elements on the first column leafs */
75
+ .leafFirstCol .leafContent {
76
+ padding-left: var(--space-small);
60
77
  }
61
78
 
62
- .tileContentLabel {
63
- margin-right: calc(0px - var(--space-xxsmall)); /* take more space if necessary */
64
- margin-bottom: calc(0px - 2px);
65
- flex: 1 1 auto;
66
- max-width: calc(100% + var(--space-xxsmall)); /* take more space if necessary */
79
+ .leadContentLabel {
80
+ flex: 0 1 auto;
81
+ width: 100%;
82
+ min-width: 0;
83
+ max-height: calc(2em * var(--line-height-heading));
67
84
  overflow: hidden;
68
- word-break: break-word;
69
85
  }
70
86
 
71
- .tileContentValue {
87
+ .leadContentValue {
72
88
  flex: 0 0 auto;
73
89
  max-width: calc(100% + var(--space-xxsmall));
74
- margin-right: calc(0px - var(--space-xxsmall)); /* take more space if necessary */
75
90
  white-space: nowrap;
76
91
  overflow: hidden;
77
92
  }
78
93
 
79
- .tileContentMetric {
94
+ .leadContentMetric {
80
95
  font-weight: bold;
81
96
  }
82
97
 
83
- .tileContentDelta {
98
+ .leadContentDelta {
84
99
  margin-left: var(--space-xxxsmall);
85
100
  }
86
101
 
87
102
  /** Tile size variation */
88
- .tileSmall .tileContent {
103
+ .leafSmall .leafContent {
104
+ padding: var(--space-xxxsmall) var(--space-xxsmall);
105
+ gap: 0;
89
106
  font-size: var(--size-xsmall);
90
107
  }
91
108
 
92
- .tileSmall .tileContentLabel {
109
+ .leafSmall .leadContentLabel {
93
110
  margin-top: 0;
94
111
  }
95
112
 
96
113
  /** Tile diff variation */
97
- .tile-NO_CHANGE .tileContent {
114
+ .leaf-NO_CHANGE {
98
115
  color: var(--color-text-light);
99
116
  }
100
117
 
101
- .tile-HIGH_NEGATIVE .tileOverlay {
102
- fill: var(--color-red-100);
118
+ .leaf-NO_CHANGE::before {
119
+ color: var(--color-gray-50);
120
+ opacity: 0.4;
121
+ }
122
+
123
+ .leaf-NO_CHANGE:hover::before {
124
+ opacity: 0.2;
103
125
  }
104
126
 
105
- .tile-NEGATIVE .tileOverlay {
106
- fill: var(--color-red-50);
127
+ .leaf-HIGH_NEGATIVE::before {
128
+ color: var(--color-red-100);
107
129
  }
108
130
 
109
- .tile-LOW_NEGATIVE .tileOverlay {
110
- fill: var(--color-red-25);
131
+ .leaf-NEGATIVE::before {
132
+ color: var(--color-red-50);
111
133
  }
112
134
 
113
- .tile-LOW_POSITIVE .tileOverlay {
114
- fill: var(--color-green-25);
135
+ .leaf-LOW_NEGATIVE::before {
136
+ color: var(--color-red-25);
115
137
  }
116
138
 
117
- .tile-POSITIVE .tileOverlay {
118
- fill: var(--color-green-50);
139
+ .leaf-LOW_POSITIVE::before {
140
+ color: var(--color-green-25);
119
141
  }
120
142
 
121
- .tile-HIGH_POSITIVE .tileOverlay {
122
- fill: var(--color-green-100);
143
+ .leaf-POSITIVE::before {
144
+ color: var(--color-green-50);
123
145
  }
124
146
 
147
+ .leaf-HIGH_POSITIVE::before {
148
+ color: var(--color-green-100);
149
+ }
125
150
 
126
151
  /** empty message */
127
152
  .emptyMessage {
@@ -140,7 +165,12 @@
140
165
  text-align: center;
141
166
  }
142
167
 
143
- /** tooltip content */
168
+ /** tooltip */
169
+ .leafContentTooltipAnchor {
170
+ width: 100%;
171
+ height: 100%;
172
+ }
173
+
144
174
  .tooltip {
145
175
  z-index: var(--layer-xxhigh);
146
176
  padding: var(--space-xsmall) var(--space-small);
@@ -152,17 +182,20 @@
152
182
  will-change: filter;
153
183
  background: var(--color-light);
154
184
  font-size: var(--size-small);
185
+ pointer-events: none;
155
186
  }
156
187
 
157
- .tooltipArrow {
158
- border: var(--space-xxsmall) solid var(--color-background);
159
- border-width: var(--space-xxsmall) var(--space-xxsmall) 0;
160
- border-left-color: transparent;
161
- border-right-color: transparent;
162
- position: absolute;
163
- top: 100%;
188
+ /* prevent tooltip portal wrapper from capturing events */
189
+ div[role="presentation"]:has(.tooltip) {
190
+ pointer-events: none;
191
+ }
192
+
193
+
194
+ .leafTooltipArrow svg {
195
+ display: block;
196
+ fill: var(--color-background) !important;
164
197
  }
165
198
 
166
- .tooltipContentTitle {
199
+ .leafTooltipContentTitle {
167
200
  font-size: var(--size-medium);
168
201
  }
@@ -1,16 +1,9 @@
1
- import React, { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
1
+ import React, { RefObject, forwardRef, useCallback, useMemo, useRef } from 'react';
2
+ import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-use';
2
3
  import cx from 'classnames';
3
- import { useMeasure } from 'react-use';
4
4
  import type { ReportMetricRow, MetricRunInfo, MetricRunInfoBaseline } from '@bundle-stats/utils';
5
5
  import { hierarchy, treemap, treemapSquarify } from 'd3';
6
- import {
7
- FloatingPortal,
8
- useClientPoint,
9
- useFloating,
10
- useHover,
11
- useInteractions,
12
- } from '@floating-ui/react';
13
- import { arrow, offset } from '@floating-ui/react-dom';
6
+ import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState } from 'ariakit/tooltip';
14
7
 
15
8
  import { Stack } from '../../layout/stack';
16
9
  import { FileName } from '../../ui/file-name';
@@ -18,9 +11,9 @@ import { Delta } from '../delta';
18
11
  import { RunInfo } from '../run-info';
19
12
  import css from './metrics-treemap.module.css';
20
13
 
21
- const SQUARIFY_RATIO = 1.78;
14
+ const SQUARIFY_RATIO = 1.33;
22
15
  const PADDING_OUTER = 0;
23
- const PADDING_INNER = 1;
16
+ const PADDING_INNER = 2;
24
17
 
25
18
  type TileSizeDisplay = 'minimal' | 'small' | 'default';
26
19
 
@@ -36,10 +29,10 @@ const VERTICAL_SPACING = 4;
36
29
  const LINE_HEIGHT = 16;
37
30
  const LINE_HEIGHT_SMALL = 13.3;
38
31
 
39
- function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay {
32
+ function resolveLeafSizeDisplay(width: number, height: number): TileSizeDisplay {
40
33
  if (
41
34
  height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 &&
42
- width > PADDING_LEFT + PADDING_RIGHT + 96
35
+ width > PADDING_LEFT + PADDING_RIGHT + 128
43
36
  ) {
44
37
  return 'default';
45
38
  }
@@ -54,19 +47,20 @@ function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay
54
47
  return 'minimal';
55
48
  }
56
49
 
57
- interface TooltipContentProps {
58
- data: ReportMetricRow;
50
+ interface LeafTooltipContentProps {
51
+ item: ReportMetricRow;
59
52
  }
60
53
 
61
- const TooltipContent = (props: TooltipContentProps) => {
62
- const { data } = props;
63
- const currentRun = data.runs[0] as MetricRunInfo;
64
- const baselineRun = data.runs[data.runs.length - 1] as MetricRunInfoBaseline;
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;
65
59
 
66
60
  return (
67
- <Stack space="small" className={css.tooltipContent}>
68
- <h3 className={css.tooltipContentTitle}>
69
- <FileName as="code" name={data.label} />
61
+ <Stack space="small" className={css.leafTooltipContent}>
62
+ <h3 className={css.leafTooltipContentTitle}>
63
+ <FileName as="code" name={item.label} />
70
64
  </h3>
71
65
  <RunInfo
72
66
  current={currentRun.displayValue}
@@ -78,10 +72,75 @@ const TooltipContent = (props: TooltipContentProps) => {
78
72
  );
79
73
  };
80
74
 
81
- const TileButton = forwardRef((props: React.ComponentProps<'button'>, ref) => (
82
- // @ts-expect-error
83
- <button type="button" xmlns="http://www.w3.org/1999/xhtml" ref={ref} {...props} />
84
- ));
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
+ <FileName as="p" className={css.leadContentLabel} name={item.label} />
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?.scrollX ?? 0),
119
+ y: pointer.docY - (document?.defaultView?.scrollY ?? 0),
120
+ w: 1,
121
+ h: 1,
122
+ };
123
+
124
+ return newRect;
125
+ }, [pointer.docX, pointer.docY]);
126
+
127
+ const tooltipState = useTooltipState({ gutter: 12, getAnchorRect, timeout: 120 });
128
+
129
+ // Update tooltip position when poiner values are changing
130
+ useDebounce(tooltipState.render, 5, [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
+ };
85
144
 
86
145
  interface TreeNode {
87
146
  id: string;
@@ -97,106 +156,70 @@ interface RootTree {
97
156
  }
98
157
 
99
158
  interface LeafProps {
100
- x: number;
101
- y: number;
159
+ left: number;
160
+ top: number;
102
161
  width: number;
103
162
  height: number;
104
163
  data: TreeNode;
105
164
  onClick?: (id: string) => void;
106
165
  }
107
166
 
108
- const Tile = (props: LeafProps) => {
109
- const { x, y, width, height, data, onClick } = props;
167
+ const Leaf = (props: LeafProps) => {
168
+ const { left, top, width, height, data, onClick } = props;
169
+
110
170
  const { item } = data;
111
171
  const runInfo = item.runs?.[0] as MetricRunInfo;
112
172
 
113
- const [isOpen, setIsOpen] = useState(false);
114
- const arrowRef = useRef(null);
115
- const { refs, floatingStyles, context, middlewareData } = useFloating({
116
- placement: 'top',
117
- open: isOpen,
118
- onOpenChange: setIsOpen,
119
- middleware: [arrow({ element: arrowRef }), offset(16)],
120
- });
121
- const clientPoint = useClientPoint(context);
122
- const hover = useHover(context);
123
- const { getReferenceProps, getFloatingProps } = useInteractions([hover, clientPoint]);
124
-
125
- const tileSizeDisplay = useMemo(() => resolveTileSizeDisplay(width, height), [width, height]);
126
-
127
- const handleOnClick = useCallback(() => {
128
- if (onClick) {
129
- onClick(item.key);
130
- }
131
- }, [item.key]);
173
+ const sizeDisplay = useMemo(() => resolveLeafSizeDisplay(width, height), [width, height]);
174
+ const handleOnClick = useCallback(() => onClick?.(item.key), [item.key, onClick]);
175
+
176
+ const leafContentRef = useRef<HTMLButtonElement>(null);
177
+ const hover = useHoverDirty(leafContentRef);
178
+
179
+ const leafClassName = cx(
180
+ css.leaf,
181
+ css[`leaf-${runInfo.deltaType}`],
182
+ sizeDisplay === 'small' && css.leafSmall,
183
+ left === 0 && css.leafFirstCol,
184
+ );
132
185
 
133
186
  return (
134
- <g
135
- className={cx(
136
- css.tile,
137
- !item.changed && css['tile-NO_CHANGE'],
138
- css[`tile-${runInfo.deltaType}`],
139
- tileSizeDisplay === 'small' && css.tileSmall,
140
- )}
187
+ <button
188
+ type="button"
189
+ onClick={handleOnClick}
190
+ ref={leafContentRef}
191
+ style={{ left, top, width, height }}
192
+ className={leafClassName}
141
193
  >
142
- <rect x={x} y={y} width={width} height={height} className={css.tileOverlay} />
143
- <foreignObject height={height} width={width} x={x} y={y}>
144
- <TileButton
145
- onClick={handleOnClick}
146
- className={css.tileContent}
147
- ref={refs.setReference}
148
- {...getReferenceProps()}
149
- >
150
- {tileSizeDisplay !== 'minimal' && (
151
- <div className={css.tileContentWrapper}>
152
- <p className={css.tileContentLabel}>{item.label}</p>
153
- <p className={css.tileContentValue}>
154
- <span className={css.tileContentMetric}>{runInfo.displayValue}</span>
155
- <Delta
156
- className={css.tileContentDelta}
157
- displayValue={runInfo.displayDeltaPercentage}
158
- deltaType={runInfo.deltaType}
159
- />
160
- </p>
161
- </div>
162
- )}
163
- </TileButton>
164
- {isOpen && (
165
- <FloatingPortal>
166
- <div
167
- ref={refs.setFloating}
168
- style={floatingStyles}
169
- {...getFloatingProps()}
170
- className={css.tooltip}
171
- >
172
- <div
173
- ref={arrowRef}
174
- style={{ left: middlewareData.arrow?.x }}
175
- className={css.tooltipArrow}
176
- />
177
- <TooltipContent data={item} />
178
- </div>
179
- </FloatingPortal>
180
- )}
181
- </foreignObject>
182
- </g>
194
+ {hover ? (
195
+ <LeafContentWithTooltip
196
+ sizeDisplay={sizeDisplay}
197
+ item={item}
198
+ runInfo={runInfo}
199
+ parentRef={leafContentRef}
200
+ />
201
+ ) : (
202
+ <LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
203
+ )}
204
+ </button>
183
205
  );
184
206
  };
185
207
 
186
- interface MetricsTreemapProps {
208
+ interface UseMetricsTreemapHierarchyParams {
187
209
  items: Array<ReportMetricRow>;
188
- emptyMessage?: React.ReactNode;
189
- onItemClick?: (entryId: string) => void;
210
+ width: number;
211
+ height: number;
190
212
  }
191
213
 
192
- export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps<'div'>) => {
193
- const { className = '', items, onItemClick, emptyMessage = 'No data', ...restProps } = props;
194
-
195
- const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
214
+ function useMetricsTreemapHierarchy(params: UseMetricsTreemapHierarchyParams) {
215
+ const { items, width, height } = params;
196
216
 
197
217
  const treemapHierarchy = useMemo(() => {
198
218
  const children = items.map((item) => {
199
- // set tile value to the largest run value to display the impact of deleted items
219
+ /**
220
+ * Set leaf value to the largest run value to allow to
221
+ * display the impact of deleted / items
222
+ */
200
223
  const values = item.runs.map((run) => run?.value || 0);
201
224
  const value = Math.max(...values);
202
225
 
@@ -234,33 +257,43 @@ export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps
234
257
  return tremapLayout.leaves().filter((leaf) => Boolean(leaf.parent));
235
258
  }, [height, width, treemapHierarchy]);
236
259
 
260
+ return cells;
261
+ }
262
+
263
+ interface MetricsTreemapProps {
264
+ items: Array<ReportMetricRow>;
265
+ emptyMessage?: React.ReactNode;
266
+ onItemClick?: (entryId: string) => void;
267
+ }
268
+
269
+ export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps<'div'>) => {
270
+ const { className = '', items, onItemClick, emptyMessage = 'No data', ...restProps } = props;
271
+
272
+ const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
273
+ const cells = useMetricsTreemapHierarchy({ items, width, height });
274
+
237
275
  return (
238
276
  <div className={cx(css.root, className)} {...restProps} ref={containerRef}>
239
277
  {cells.length > 0 ? (
240
- <svg
241
- width={width}
242
- height={height}
243
- viewBox={`0 0 ${width} ${height}`}
244
- className={css.canvas}
245
- >
278
+ <div className={css.canvas}>
246
279
  {cells.map((leaf) => {
247
280
  if (!leaf.parent) {
248
281
  return null;
249
282
  }
250
283
 
251
284
  return (
252
- <Tile
253
- x={leaf.x0}
254
- y={leaf.y0}
285
+ <Leaf
286
+ data={leaf.data as TreeNode}
287
+ left={leaf.x0}
288
+ top={leaf.y0}
255
289
  width={leaf.x1 - leaf.x0}
256
290
  height={leaf.y1 - leaf.y0}
257
- data={leaf.data as TreeNode}
258
- key={leaf.id}
259
291
  onClick={onItemClick}
292
+ key={leaf.id}
260
293
  />
261
294
  );
262
295
  })}
263
- </svg>
296
+ </div>
264
297
  ) : (
265
298
  <div className={css.emptyMessage}>
266
299
  <div className={css.emptyMessageWrapper}>{emptyMessage}</div>
@@ -3,6 +3,7 @@
3
3
  display: inline-block;
4
4
  border-radius: var(--radius-small);
5
5
  position: relative;
6
+ color: var(--color-text-ultra-light);
6
7
  }
7
8
 
8
9
  .toggle {
@@ -29,20 +29,20 @@
29
29
  /*
30
30
  * Color scheme:
31
31
  * https://coolors.co/287acc-da444e-55b950-efcc1a-192339
32
- * http://mcg.mbitson.com/#!?mcgpalette0=%23da444e&mcgpalette1=%2355b950&mcgpalette2=%23efcc1a&mcgpalette3=%23353138&mcgpalette4=%23287acc&themename=mcgtheme
32
+ * http://mcg.mbitson.com/#!?mcgpalette0=%23da444e&mcgpalette1=%2355b950&mcgpalette2=%23efcc1a&mcgpalette3=%233e3c49&mcgpalette4=%23287acc&themename=bundle-stats
33
33
  * https://hihayk.github.io/scale/#4/5/50/90/-51/0/0/14/353138/53/49/56/white
34
34
  */
35
35
  --color-gray-25: #f9f9fa;
36
36
  --color-gray-50: #e8e8e9;
37
- --color-gray-100: #c6c5c7;
38
- --color-gray-200: #a2a0a4;
39
- --color-gray-300: #7e7b80;
40
- --color-gray-400: #59565c;
41
- --color-gray-500: #353138;
42
- --color-gray-600: #302b31;
43
- --color-gray-700: #2a252a;
44
- --color-gray-800: #231e22;
45
- --color-gray-900: #1c181b;
37
+ --color-gray-100: #c5c5c8;
38
+ --color-gray-200: #9f9ea4;
39
+ --color-gray-300: #787780;
40
+ --color-gray-400: #5b5964;
41
+ --color-gray-500: #3e3c49;
42
+ --color-gray-600: #383642;
43
+ --color-gray-700: #302e39;
44
+ --color-gray-800: #282731;
45
+ --color-gray-900: #1b1a21;
46
46
 
47
47
  --color-gray-ultra-light: var(--color-gray-200);
48
48
  --color-gray-light: var(--color-gray-300);
@@ -192,14 +192,14 @@
192
192
  --shadow-layer: 0 0 var(--space-xxxsmall) var(--color-shadow);
193
193
  --shadow-layer-high: 0 0 var(--space-xxsmall) var(--color-shadow);
194
194
 
195
- --radius-small: 4px;
196
- --radius-medium: 5px;
197
- --radius-large: 6px;
195
+ --radius-small: 5px;
196
+ --radius-medium: 7px;
197
+ --radius-large: 9px;
198
198
 
199
199
  --outline-hover: 0 0 0 3px var(--color-highlight);
200
200
 
201
201
  /** Layout */
202
- --max-width: 88rem;
202
+ --max-width: 82rem;
203
203
  --entry-info-width: 64rem;
204
204
  --entry-info-top: var(--header-height);
205
205
  }
@@ -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
+ }