@bundle-stats/ui 3.2.1-beta.9 → 3.3.0-alpha.0

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 (124) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/lib/app/app.js +31 -6
  3. package/lib/app/app.stories.js +31 -3
  4. package/lib/assets/icons/alert-circle.svg +1 -0
  5. package/lib/assets/icons/arrow-right-circle.svg +1 -0
  6. package/lib/assets/icons/check-circle.svg +1 -0
  7. package/lib/assets/icons/close.svg +1 -1
  8. package/lib/assets/icons.svg +50 -1
  9. package/lib/components/browsertime-table/browsertime-table.js +1 -1
  10. package/lib/components/browsertime-table/browsertime-table.stories.js +1 -1
  11. package/lib/components/budget-info/budget-info.js +53 -0
  12. package/lib/components/budget-info/budget-info.module.css +28 -0
  13. package/lib/components/budget-info/budget-info.stories.js +42 -0
  14. package/lib/components/budget-info/index.js +18 -0
  15. package/lib/components/budget-insights/budget-insights.js +200 -0
  16. package/lib/components/budget-insights/budget-insights.module.css +53 -0
  17. package/lib/components/budget-insights/budget-insights.stories.js +104 -0
  18. package/lib/components/budget-insights/index.js +18 -0
  19. package/lib/components/bundle-assets/bundle-assets.js +3 -3
  20. package/lib/components/bundle-assets/bundle-assets.utils.js +2 -2
  21. package/lib/components/bundle-assets/index.js +3 -3
  22. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +1 -1
  23. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +45 -8
  24. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  25. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +58 -43
  26. package/lib/components/bundle-modules/bundle-modules.js +3 -3
  27. package/lib/components/bundle-modules/bundle-modules.utils.js +2 -2
  28. package/lib/components/bundle-modules/index.js +3 -3
  29. package/lib/components/bundle-packages/bundle-packages.js +1 -1
  30. package/lib/components/bundle-packages/bundle-packages.utils.js +2 -2
  31. package/lib/components/bundle-packages/index.js +1 -1
  32. package/lib/components/lighthouse-table/lighthouse-table.js +1 -1
  33. package/lib/components/metrics-table/metrics-table.js +1 -1
  34. package/lib/components/metrics-table-search/metrics-table-search.js +1 -1
  35. package/lib/components/metrics-table-search/metrics-table-search.stories.js +1 -1
  36. package/lib/components/summary/summary.js +40 -62
  37. package/lib/components/summary/summary.module.css +31 -14
  38. package/lib/components/summary/summary.stories.js +29 -0
  39. package/lib/components/summary-item/summary-item.js +30 -13
  40. package/lib/components/summary-item/summary-item.module.css +18 -9
  41. package/lib/components/summary-item/summary-item.stories.js +67 -52
  42. package/lib/constants.js +6 -6
  43. package/lib/css/variables.css +15 -11
  44. package/lib/hooks/search-params.js +2 -2
  45. package/lib/layout/box/box.js +2 -2
  46. package/lib/layout/stack/stack.js +13 -3
  47. package/lib/query-state.js +1 -1
  48. package/lib/stories/wrapper.js +2 -2
  49. package/lib/ui/alert/alert.js +13 -3
  50. package/lib/ui/filters/filters.container.js +5 -5
  51. package/lib/ui/filters/filters.js +2 -2
  52. package/lib/ui/filters/filters.stories.js +3 -3
  53. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js +3 -3
  54. package/lib/ui/icon/icon.js +3 -0
  55. package/lib/ui/icon/icon.stories.js +40 -22
  56. package/lib/ui/panels/panels.js +2 -2
  57. package/lib/ui/popover/popover.js +3 -3
  58. package/lib/ui/popover/popover.module.css +2 -1
  59. package/lib/ui/tabs/tabs.js +2 -2
  60. package/lib/ui/tooltip/tooltip.module.css +1 -0
  61. package/lib/utils.js +2 -2
  62. package/lib-esm/app/app.js +57 -31
  63. package/lib-esm/app/app.stories.js +29 -1
  64. package/lib-esm/assets/icons/alert-circle.svg +1 -0
  65. package/lib-esm/assets/icons/arrow-right-circle.svg +1 -0
  66. package/lib-esm/assets/icons/check-circle.svg +1 -0
  67. package/lib-esm/assets/icons/close.svg +1 -1
  68. package/lib-esm/assets/icons.svg +50 -1
  69. package/lib-esm/components/budget-info/budget-info.js +37 -0
  70. package/lib-esm/components/budget-info/budget-info.module.css +28 -0
  71. package/lib-esm/components/budget-info/budget-info.stories.js +26 -0
  72. package/lib-esm/components/budget-info/index.js +1 -0
  73. package/lib-esm/components/budget-insights/budget-insights.js +147 -0
  74. package/lib-esm/components/budget-insights/budget-insights.module.css +53 -0
  75. package/lib-esm/components/budget-insights/budget-insights.stories.js +84 -0
  76. package/lib-esm/components/budget-insights/index.js +1 -0
  77. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +24 -7
  78. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  79. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +46 -21
  80. package/lib-esm/components/summary/summary.js +26 -45
  81. package/lib-esm/components/summary/summary.module.css +31 -14
  82. package/lib-esm/components/summary/summary.stories.js +27 -0
  83. package/lib-esm/components/summary-item/summary-item.js +28 -12
  84. package/lib-esm/components/summary-item/summary-item.module.css +18 -9
  85. package/lib-esm/components/summary-item/summary-item.stories.js +52 -42
  86. package/lib-esm/constants.js +4 -4
  87. package/lib-esm/css/variables.css +15 -11
  88. package/lib-esm/layout/box/box.js +2 -2
  89. package/lib-esm/layout/stack/stack.js +6 -3
  90. package/lib-esm/ui/alert/alert.js +6 -3
  91. package/lib-esm/ui/filters/filters.container.js +39 -41
  92. package/lib-esm/ui/icon/icon.js +3 -0
  93. package/lib-esm/ui/icon/icon.stories.js +22 -11
  94. package/lib-esm/ui/popover/popover.js +2 -2
  95. package/lib-esm/ui/popover/popover.module.css +2 -1
  96. package/lib-esm/ui/tooltip/tooltip.module.css +1 -0
  97. package/package.json +15 -15
  98. package/src/app/app.jsx +24 -8
  99. package/src/assets/icons/alert-circle.svg +1 -0
  100. package/src/assets/icons/arrow-right-circle.svg +1 -0
  101. package/src/assets/icons/check-circle.svg +1 -0
  102. package/src/assets/icons/close.svg +1 -1
  103. package/src/assets/icons.svg +50 -1
  104. package/src/components/budget-info/budget-info.jsx +49 -0
  105. package/src/components/budget-info/budget-info.module.css +28 -0
  106. package/src/components/budget-info/index.js +1 -0
  107. package/src/components/budget-insights/budget-insights.jsx +152 -0
  108. package/src/components/budget-insights/budget-insights.module.css +53 -0
  109. package/src/components/budget-insights/index.js +1 -0
  110. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +28 -13
  111. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  112. package/src/components/summary/summary.jsx +26 -44
  113. package/src/components/summary/summary.module.css +31 -14
  114. package/src/components/summary-item/summary-item.jsx +26 -24
  115. package/src/components/summary-item/summary-item.module.css +18 -9
  116. package/src/constants.js +7 -7
  117. package/src/css/variables.css +15 -11
  118. package/src/layout/box/box.jsx +1 -1
  119. package/src/layout/stack/stack.jsx +2 -2
  120. package/src/ui/alert/alert.jsx +7 -2
  121. package/src/ui/icon/icon.jsx +3 -0
  122. package/src/ui/popover/popover.jsx +3 -3
  123. package/src/ui/popover/popover.module.css +2 -1
  124. package/src/ui/tooltip/tooltip.module.css +1 -0
@@ -1 +1,50 @@
1
- <svg width="16" height="16"><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="afeather afeather-arrow-down" id="arrow" xmlns="http://www.w3.org/2000/svg"><path d="M12 5v14M19 12l-7 7-7-7"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bfeather bfeather-x-circle" id="cancel" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6M9 9l6 6"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="cfeather cfeather-clock" id="clock" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="dfeather dfeather-x" id="close" xmlns="http://www.w3.org/2000/svg"><path d="M18 6L6 18M6 6l12 12"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="efeather efeather-git-commit" id="commit" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="4"/><path d="M1.05 12H7M17.01 12h5.95"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ffeather ffeather-bar-chart" id="filter" xmlns="http://www.w3.org/2000/svg"><path d="M12 20V10M18 20V4M6 20v-4"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="gfeather gfeather-help-circle" id="help" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="hfeather hfeather-menu" id="menu" xmlns="http://www.w3.org/2000/svg"><path d="M3 12h18M3 6h18M3 18h18"/></symbol><symbol viewBox="0 0 24 24" fill="currentColor" id="sort" xmlns="http://www.w3.org/2000/svg"><path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"/><path d="M0 0h24v24H0z" fill="none"/></symbol><symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="jfeather jfeather-alert-triangle" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01"/></symbol></svg>
1
+ <svg width="16" height="16">
2
+ <symbol fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle" viewBox="0 0 24 24" id="alert-circle" xmlns="http://www.w3.org/2000/svg">
3
+ <circle cx="12" cy="12" r="10"/>
4
+ <path d="M12 8v4M12 16h.01"/>
5
+ </symbol>
6
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down" id="arrow" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M12 5v14M19 12l-7 7-7-7"/>
8
+ </symbol>
9
+ <symbol fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right-circle" viewBox="0 0 24 24" id="arrow-right-circle" xmlns="http://www.w3.org/2000/svg">
10
+ <circle cx="12" cy="12" r="10"/>
11
+ <path d="M12 16l4-4-4-4M8 12h8"/>
12
+ </symbol>
13
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle" id="cancel" xmlns="http://www.w3.org/2000/svg">
14
+ <circle cx="12" cy="12" r="10"/>
15
+ <path d="M15 9l-6 6M9 9l6 6"/>
16
+ </symbol>
17
+ <symbol fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-circle" viewBox="0 0 24 24" id="check-circle" xmlns="http://www.w3.org/2000/svg">
18
+ <path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>
19
+ <path d="M22 4L12 14.01l-3-3"/>
20
+ </symbol>
21
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clock" id="clock" xmlns="http://www.w3.org/2000/svg">
22
+ <circle cx="12" cy="12" r="10"/>
23
+ <path d="M12 6v6l4 2"/>
24
+ </symbol>
25
+ <symbol fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle" viewBox="0 0 24 24" id="close" xmlns="http://www.w3.org/2000/svg">
26
+ <circle cx="12" cy="12" r="10"/>
27
+ <path d="M15 9l-6 6M9 9l6 6"/>
28
+ </symbol>
29
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-commit" id="commit" xmlns="http://www.w3.org/2000/svg">
30
+ <circle cx="12" cy="12" r="4"/>
31
+ <path d="M1.05 12H7M17.01 12h5.95"/>
32
+ </symbol>
33
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart" id="filter" xmlns="http://www.w3.org/2000/svg">
34
+ <path d="M12 20V10M18 20V4M6 20v-4"/>
35
+ </symbol>
36
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle" id="help" xmlns="http://www.w3.org/2000/svg">
37
+ <circle cx="12" cy="12" r="10"/>
38
+ <path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01"/>
39
+ </symbol>
40
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu" id="menu" xmlns="http://www.w3.org/2000/svg">
41
+ <path d="M3 12h18M3 6h18M3 18h18"/>
42
+ </symbol>
43
+ <symbol viewBox="0 0 24 24" fill="currentColor" id="sort" xmlns="http://www.w3.org/2000/svg">
44
+ <path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"/>
45
+ <path d="M0 0h24v24H0z" fill="none"/>
46
+ </symbol>
47
+ <symbol viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle" id="warning" xmlns="http://www.w3.org/2000/svg">
48
+ <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01"/>
49
+ </symbol>
50
+ </svg>
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import cx from 'classnames';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { Icon } from '../../ui/icon';
6
+ import { Tooltip } from '../../ui/tooltip';
7
+
8
+ import css from './budget-info.module.css';
9
+
10
+ export const BudgetInfo = ({ className = '', budget, metric }) => {
11
+ const rootClassName = cx(css.root, budget.failed ? css.over : css.under, className);
12
+
13
+ return (
14
+ <Tooltip
15
+ className={rootClassName}
16
+ title={
17
+ <>
18
+ <strong>{metric.label}</strong>
19
+ {` is ${budget.failed ? 'over' : 'under'} `}
20
+ {metric.formatter(budget.budgetValue)}
21
+ {` budget`}
22
+ </>
23
+ }
24
+ >
25
+ <Icon
26
+ className={css.icon}
27
+ glyph={budget.failed ? Icon.ICONS.ALERT_CIRCLE : Icon.ICONS.CHECK_CIRCLE}
28
+ size="medium"
29
+ />
30
+ </Tooltip>
31
+ );
32
+ };
33
+
34
+ BudgetInfo.propTypes = {
35
+ budget: PropTypes.shape({
36
+ currentValue: PropTypes.number,
37
+ budgetValue: PropTypes.number,
38
+ failed: PropTypes.bool,
39
+ }).isRequired,
40
+ metric: PropTypes.shape({
41
+ label: PropTypes.string,
42
+ formatter: PropTypes.func,
43
+ }).isRequired,
44
+ className: PropTypes.string,
45
+ };
46
+
47
+ BudgetInfo.defaultProps = {
48
+ className: '',
49
+ };
@@ -0,0 +1,28 @@
1
+ .root {
2
+ display: inline-block;
3
+ line-height: 1em;
4
+ }
5
+
6
+ /* Budget over */
7
+ .over .icon {
8
+ color: var(--color-danger-light);
9
+ }
10
+
11
+ .over .icon:hover,
12
+ .over .icon:focus,
13
+ .over .icon:active
14
+ {
15
+ color: var(--color-danger);
16
+ }
17
+
18
+ /* Budget under */
19
+ .under .icon {
20
+ color: var(--color-success-light);
21
+ }
22
+
23
+ .under .icon:hover,
24
+ .under .icon:focus,
25
+ .under .icon:active
26
+ {
27
+ color: var(--color-success);
28
+ }
@@ -0,0 +1 @@
1
+ export * from './budget-info';
@@ -0,0 +1,152 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import cx from 'classnames';
4
+ import { METRIC_COMPONENT_LINKS, getGlobalMetricType } from '@bundle-stats/utils';
5
+
6
+ import { Box } from '../../layout/box';
7
+ import { Stack } from '../../layout/stack';
8
+ import { FlexStack } from '../../layout/flex-stack';
9
+ import { Alert } from '../../ui/alert';
10
+ import { Icon } from '../../ui/icon';
11
+ import { ComponentLink } from '../component-link';
12
+ import css from './budget-insights.module.css';
13
+
14
+ const Budget = (props) => {
15
+ const { className, metricKey, CustomLink, failed, budgetValue } = props;
16
+ const metric = getGlobalMetricType(metricKey);
17
+ const componentLinkOptions = METRIC_COMPONENT_LINKS.get(metricKey);
18
+
19
+ return (
20
+ <CustomLink className={cx(css.budget, className)} {...componentLinkOptions?.link}>
21
+ <span className={css.budgetLabel}>{metric.label}</span>
22
+ {` is ${failed ? 'over' : 'under'} `}
23
+ <span className={css.budgetValue}>{metric.formatter(budgetValue)}</span>
24
+ {` budget`}
25
+ </CustomLink>
26
+ );
27
+ };
28
+
29
+ Budget.propTypes = {
30
+ className: PropTypes.string,
31
+ metricKey: PropTypes.string.isRequired,
32
+ failed: PropTypes.bool.isRequired,
33
+ currentValue: PropTypes.number.isRequired,
34
+ budgetValue: PropTypes.number.isRequired,
35
+ CustomLink: PropTypes.elementType.isRequired,
36
+ };
37
+
38
+ Budget.defaultProps = {
39
+ className: '',
40
+ };
41
+
42
+ const BudgetsGroup = (props) => {
43
+ const { className, kind, source, budgets, CustomLink } = props;
44
+ const [showBudgets, setShowBudgets] = useState(false);
45
+
46
+ const isFailed = kind === 'danger';
47
+
48
+ return (
49
+ <Alert kind={kind} padding="none" className={cx(css.group, css[kind], className)}>
50
+ <Box
51
+ padding={['xsmall', 'small']}
52
+ className={css.groupHeader}
53
+ as="button"
54
+ type="button"
55
+ onClick={() => setShowBudgets(!showBudgets)}
56
+ >
57
+ <FlexStack space="xxsmall" className={css.groupTitle}>
58
+ <Icon
59
+ className={css.groupIcon}
60
+ glyph={isFailed ? Icon.ICONS.ALERT_CIRCLE : Icon.ICONS.CHECK_CIRCLE}
61
+ />
62
+ <span>
63
+ <strong>{budgets.length}</strong>
64
+ {` budget ${budgets.length > 1 ? 'checks' : 'check'} ${isFailed ? 'failed' : 'passed'}`}
65
+ </span>
66
+ <span className={css.groupTitleToggle}>{showBudgets ? 'hide' : 'show'} all</span>
67
+ </FlexStack>
68
+ </Box>
69
+
70
+ {showBudgets && (
71
+ <Box padding={['xsmall', 'small']} className={css.groupContent}>
72
+ <Stack space="none">
73
+ {budgets.map(([key, budget]) => (
74
+ <Budget
75
+ key={key}
76
+ source={source}
77
+ metricKey={`${source}.${key}`}
78
+ CustomLink={CustomLink}
79
+ {...budget}
80
+ />
81
+ ))}
82
+ </Stack>
83
+ </Box>
84
+ )}
85
+ </Alert>
86
+ );
87
+ };
88
+
89
+ BudgetsGroup.propTypes = {
90
+ className: PropTypes.string,
91
+ kind: PropTypes.oneOf(['success', 'danger']).isRequired,
92
+ source: PropTypes.string.isRequired,
93
+ budgets: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
94
+ CustomLink: PropTypes.elementType.isRequired,
95
+ };
96
+
97
+ BudgetsGroup.defaultProps = {
98
+ className: '',
99
+ };
100
+
101
+ export const BudgetInsights = (props) => {
102
+ const { className, source, budgets, CustomLink } = props;
103
+
104
+ const [failedBudgets, passedBudgets] = useMemo(() => {
105
+ const passed = [];
106
+ const failed = [];
107
+
108
+ Object.entries(budgets).forEach(([key, budget]) => {
109
+ if (budget.failed) {
110
+ failed.push([key, budget]);
111
+ } else {
112
+ passed.push([key, budget]);
113
+ }
114
+ });
115
+
116
+ return [failed, passed];
117
+ }, [budgets]);
118
+
119
+ return (
120
+ <Stack className={className} space="xsmall">
121
+ {failedBudgets.length > 0 && (
122
+ <BudgetsGroup
123
+ kind="danger"
124
+ source={source}
125
+ budgets={failedBudgets}
126
+ CustomLink={CustomLink}
127
+ />
128
+ )}
129
+
130
+ {passedBudgets.length > 0 && (
131
+ <BudgetsGroup
132
+ kind="success"
133
+ source={source}
134
+ budgets={passedBudgets}
135
+ CustomLink={CustomLink}
136
+ />
137
+ )}
138
+ </Stack>
139
+ );
140
+ };
141
+
142
+ BudgetInsights.propTypes = {
143
+ className: PropTypes.string,
144
+ source: PropTypes.string.isRequired,
145
+ budgets: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
146
+ CustomLink: PropTypes.elementType,
147
+ };
148
+
149
+ BudgetInsights.defaultProps = {
150
+ className: '',
151
+ CustomLink: ComponentLink,
152
+ };
@@ -0,0 +1,53 @@
1
+ .groupHeader {
2
+ width: 100%;
3
+ border: none;
4
+ background: none;
5
+ cursor: pointer;
6
+ color: inherit;
7
+ }
8
+
9
+ .groupTitle {
10
+ width: 100%;
11
+ align-items: center;
12
+ }
13
+
14
+ .groupTitle:hover {
15
+ color: var(--color-text-dark);
16
+ text-decoration: underline;
17
+ }
18
+
19
+ .groupTitleToggle {
20
+ margin: 0 0 0 auto !important;
21
+ }
22
+
23
+ .danger .groupIcon {
24
+ color: var(--color-danger);
25
+ }
26
+
27
+ .success .groupIcon {
28
+ color: var(--color-success);
29
+ }
30
+
31
+ .groupContent {
32
+ padding-top: 0px;
33
+ background: transparent;
34
+ }
35
+
36
+ .budget {
37
+ display: block;
38
+ color: var(--color-text);
39
+ text-decoration: none;
40
+ }
41
+
42
+ .budgetLabel,
43
+ .budgetValue {
44
+ color: var(--color-text-dark);
45
+ font-weight: 500;
46
+ }
47
+
48
+ .budget:hover,
49
+ .budget:focus,
50
+ .budget:active {
51
+ color: var(--color-text);
52
+ text-decoration: underline;
53
+ }
@@ -0,0 +1 @@
1
+ export * from './budget-insights';
@@ -1,29 +1,44 @@
1
- import React from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { get } from 'lodash';
3
4
  import { getBundleAssetsFileTypeComponentLink } from '@bundle-stats/utils';
4
5
  import * as webpack from '@bundle-stats/utils/lib-esm/webpack';
5
6
 
6
7
  import { ASSETS_SIZES_FILE_TYPE_MAP } from '../../constants';
8
+ import { FlexStack } from '../../layout/flex-stack';
7
9
  import { MetricsTable } from '../metrics-table';
8
10
  import { ComponentLink } from '../component-link';
11
+ import { BudgetInfo } from '../budget-info';
12
+ import css from './bundle-assets-totals-table.module.css';
9
13
 
10
14
  export const BundleAssetsTotalsTable = ({
11
15
  className,
12
16
  jobs,
13
17
  customComponentLink: CustomComponentLink,
14
18
  }) => {
15
- const items = webpack.compareBySection.sizes(jobs);
16
-
17
- const renderRowHeader = (item) => {
18
- const fileType = ASSETS_SIZES_FILE_TYPE_MAP[item.key];
19
- const { section, title, params } = getBundleAssetsFileTypeComponentLink(fileType, item.label);
20
-
21
- return (
22
- <CustomComponentLink section={section} title={title} params={params}>
23
- {item.label}
24
- </CustomComponentLink>
25
- );
26
- };
19
+ const items = useMemo(() => webpack.compareBySection.sizes(jobs), [jobs]);
20
+ const budgets = jobs[0].insights?.webpack?.budgets;
21
+
22
+ const renderRowHeader = useCallback(
23
+ (item) => {
24
+ const fileType = ASSETS_SIZES_FILE_TYPE_MAP[item.key];
25
+ const { section, title, params } = getBundleAssetsFileTypeComponentLink(fileType, item.label);
26
+ const [_, ...metricSlugs] = item.key.split('.');
27
+ const metricKey = metricSlugs.join('.');
28
+ const budget = get(budgets, metricKey);
29
+ const metric = webpack.getMetricType(metricKey);
30
+
31
+ return (
32
+ <CustomComponentLink section={section} title={title} params={params}>
33
+ <FlexStack space="xxsmall" className={css.itemTitleText}>
34
+ <span>{item.label}</span>
35
+ {budget && <BudgetInfo budget={budget} metric={metric} />}
36
+ </FlexStack>
37
+ </CustomComponentLink>
38
+ );
39
+ },
40
+ [CustomComponentLink],
41
+ );
27
42
 
28
43
  return (
29
44
  <MetricsTable
@@ -1,3 +1,3 @@
1
- .title {
2
- color: var(--color-text-light);
1
+ .itemTitleText {
2
+ align-items: center;
3
3
  }
@@ -5,62 +5,47 @@ import { get } from 'lodash';
5
5
  import { METRIC_COMPONENT_LINKS } from '@bundle-stats/utils';
6
6
 
7
7
  import { METRICS_WEBPACK_GENERAL } from '../../constants';
8
+ import { Icon } from '../../ui/icon';
8
9
  import { Box } from '../../layout/box';
9
10
  import { FlexStack } from '../../layout/flex-stack';
10
11
  import { ComponentLink } from '../component-link';
11
12
  import { SummaryItem } from '../summary-item';
12
13
  import css from './summary.module.css';
13
14
 
14
- const CustomSummaryItem = ({ className, metricOptions, SummaryItemCustomLink, ...props }) => (
15
- <Box
16
- padding={['xsmall', 'small']}
17
- className={cx(className, css.summaryItemLink)}
18
- {...props}
19
- as={SummaryItemCustomLink}
20
- {...metricOptions.link}
21
- />
22
- );
23
-
24
- CustomSummaryItem.propTypes = {
25
- className: PropTypes.string,
26
- metricOptions: PropTypes.shape({
27
- link: PropTypes.any, // eslint-disable-line react/forbid-prop-types
28
- }).isRequired,
29
- SummaryItemCustomLink: PropTypes.elementType.isRequired,
30
- };
31
-
32
- CustomSummaryItem.defaultProps = {
33
- className: '',
34
- };
15
+ const getSourceMetricId = (fullMetricId) => fullMetricId.split('.').slice(1).join('.');
35
16
 
36
17
  export const Summary = ({
37
18
  className,
38
19
  size,
39
20
  keys,
40
21
  data,
22
+ budgets,
41
23
  loading,
42
24
  showSummaryItemDelta,
43
25
  summaryItemLink: SummaryItemCustomLink,
44
26
  }) => (
45
27
  <Box outline className={cx(css.root, className)}>
46
28
  <FlexStack className={css.items}>
47
- {Array.from(METRIC_COMPONENT_LINKS).filter(([metricId]) => keys.includes(metricId)).map(
48
- ([metricId, metricOptions]) => (
49
- <SummaryItem
50
- as={CustomSummaryItem}
51
- className={css.item}
52
- metricOptions={metricOptions}
53
- SummaryItemCustomLink={SummaryItemCustomLink}
54
- size={size}
55
- key={metricId}
56
- id={metricId}
57
- data={get(data, metricId)}
58
- loading={loading}
59
- showMetricDescription
60
- showDelta={showSummaryItemDelta && metricOptions.showDelta !== false}
61
- />
62
- )
63
- )}
29
+ {Array.from(METRIC_COMPONENT_LINKS)
30
+ .filter(([metricId]) => keys.includes(getSourceMetricId(metricId)))
31
+ .map(([metricId, metricOptions]) => (
32
+ <div key={metricId} className={css.item}>
33
+ <SummaryItem
34
+ size={size}
35
+ id={metricId}
36
+ data={get(data, metricId)}
37
+ budget={get(budgets, getSourceMetricId(metricId))}
38
+ loading={loading}
39
+ showMetricDescription
40
+ showDelta={showSummaryItemDelta && metricOptions.showDelta !== false}
41
+ />
42
+ {!loading && (
43
+ <SummaryItemCustomLink {...metricOptions.link} className={css.readMore}>
44
+ <Icon className={css.readMoreIcon} glyph={Icon.ICONS.ARROW_RIGHT_CIRLCE} />
45
+ </SummaryItemCustomLink>
46
+ )}
47
+ </div>
48
+ ))}
64
49
  </FlexStack>
65
50
  </Box>
66
51
  );
@@ -68,6 +53,7 @@ export const Summary = ({
68
53
  Summary.defaultProps = {
69
54
  className: '',
70
55
  data: null,
56
+ budgets: null,
71
57
  loading: false,
72
58
  size: '',
73
59
  keys: METRICS_WEBPACK_GENERAL,
@@ -79,12 +65,8 @@ Summary.propTypes = {
79
65
  className: PropTypes.string,
80
66
  size: PropTypes.string,
81
67
  keys: PropTypes.arrayOf(PropTypes.string),
82
- data: PropTypes.shape({
83
- [PropTypes.string]: PropTypes.shape({
84
- baseline: PropTypes.number,
85
- current: PropTypes.number,
86
- }),
87
- }),
68
+ data: PropTypes.object, // eslint-disable-line react/forbid-prop-types
69
+ budgets: PropTypes.object, // eslint-disable-line react/forbid-prop-types
88
70
  loading: PropTypes.bool,
89
71
  showSummaryItemDelta: PropTypes.bool,
90
72
  summaryItemLink: PropTypes.elementType,
@@ -3,35 +3,52 @@
3
3
  overflow: auto;
4
4
  }
5
5
 
6
- .items {}
7
-
8
6
  .item {
7
+ padding: var(--space-small);
9
8
  flex: 1 0 25%; /* force equal sizes */
10
9
  position: relative;
11
10
  z-index: 1;
12
11
  }
13
12
 
14
- .item:focus-visible {
15
- background: var(--color-highlight);
16
- outline: none;
17
- }
18
-
19
13
  .item::before {
20
14
  content: '';
21
15
  display: block;
22
16
  width: 1px;
23
17
  position: absolute;
24
18
  left: -1px;
25
- top: var(--space-xsmall);
26
- bottom: var(--space-xsmall);
19
+ top: var(--space-small);
20
+ bottom: var(--space-small);
27
21
  background: var(--color-outline);
28
22
  }
29
23
 
30
- .item:hover {
31
- background: var(--color-highlight);
24
+ .readMore {
25
+ display: block;
26
+ position: absolute;
27
+ right: 0;
28
+ bottom: 0;
29
+ padding: var(--space-small);
30
+ color: var(--color-text-ultra-light);
31
+ transition: var(--transition-out);
32
+ }
33
+
34
+ .readMore:hover,
35
+ .readMore:focus {
36
+ color: var(--color-primary);
37
+ transition: var(--transition-in);
38
+ }
39
+
40
+ .readMoreIcon {
41
+ display: block;
42
+ }
43
+
44
+ /** hover state */
45
+ .readMore {
46
+ opacity: 0;
32
47
  }
33
48
 
34
- .summaryItemLink {
35
- color: inherit;
36
- text-decoration: none;
49
+ .item:hover .readMore,
50
+ .item:active .readMore,
51
+ .item:focus .readMore {
52
+ opacity: 1;
53
+ transition: var(--transition-in);
37
54
  }