@bundle-stats/ui 3.4.0-alpha.3 → 3.4.0-beta.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 (48) hide show
  1. package/lib/app/app.js +3 -1
  2. package/lib/app/header/header.js +2 -1
  3. package/lib/components/budget-info/budget-info.js +11 -17
  4. package/lib/components/budget-info/budget-info.stories.js +27 -12
  5. package/lib/components/budget-insights/budget-insights.js +37 -75
  6. package/lib/components/budget-insights/budget-insights.module.css +10 -2
  7. package/lib/components/budget-insights/budget-insights.stories.js +124 -63
  8. package/lib/components/bundle-assets/bundle-assets.js +2 -1
  9. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +3 -4
  10. package/lib/components/bundle-modules/bundle-modules.js +1 -1
  11. package/lib/components/summary/summary.js +6 -12
  12. package/lib/components/summary/summary.module.css +1 -45
  13. package/lib/components/summary/summary.stories.js +41 -13
  14. package/lib/components/summary-item/summary-item.js +14 -22
  15. package/lib/components/summary-item/summary-item.module.css +25 -7
  16. package/lib/components/summary-item/summary-item.stories.js +20 -6
  17. package/lib/ui/hover-card/hover-card.js +7 -1
  18. package/lib-esm/app/app.js +3 -1
  19. package/lib-esm/app/header/header.js +2 -1
  20. package/lib-esm/components/budget-info/budget-info.js +11 -12
  21. package/lib-esm/components/budget-info/budget-info.stories.js +27 -12
  22. package/lib-esm/components/budget-insights/budget-insights.js +35 -71
  23. package/lib-esm/components/budget-insights/budget-insights.module.css +10 -2
  24. package/lib-esm/components/budget-insights/budget-insights.stories.js +115 -55
  25. package/lib-esm/components/bundle-assets/bundle-assets.js +2 -1
  26. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +3 -4
  27. package/lib-esm/components/bundle-modules/bundle-modules.js +1 -1
  28. package/lib-esm/components/summary/summary.js +6 -11
  29. package/lib-esm/components/summary/summary.module.css +1 -45
  30. package/lib-esm/components/summary/summary.stories.js +41 -13
  31. package/lib-esm/components/summary-item/summary-item.js +11 -20
  32. package/lib-esm/components/summary-item/summary-item.module.css +25 -7
  33. package/lib-esm/components/summary-item/summary-item.stories.js +20 -6
  34. package/lib-esm/ui/hover-card/hover-card.js +7 -1
  35. package/package.json +25 -25
  36. package/src/app/app.jsx +2 -2
  37. package/src/app/header/header.jsx +1 -1
  38. package/src/components/budget-info/budget-info.jsx +12 -22
  39. package/src/components/budget-insights/budget-insights.jsx +49 -82
  40. package/src/components/budget-insights/budget-insights.module.css +10 -2
  41. package/src/components/bundle-assets/bundle-assets.jsx +1 -1
  42. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +3 -3
  43. package/src/components/bundle-modules/bundle-modules.jsx +1 -1
  44. package/src/components/summary/summary.jsx +13 -17
  45. package/src/components/summary/summary.module.css +1 -45
  46. package/src/components/summary-item/summary-item.jsx +11 -17
  47. package/src/components/summary-item/summary-item.module.css +25 -7
  48. package/src/ui/hover-card/hover-card.jsx +5 -0
package/src/app/app.jsx CHANGED
@@ -48,7 +48,7 @@ const Layout = ({ jobs, version, ...props }) => (
48
48
  );
49
49
 
50
50
  Layout.propTypes = {
51
- jobs: PropTypes.arrayOf(PropTypes.object),
51
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
52
52
  version: PropTypes.string,
53
53
  };
54
54
 
@@ -64,7 +64,7 @@ const JobsProvider = ({ jobs, ...restProps }) => {
64
64
  };
65
65
 
66
66
  JobsProvider.propTypes = {
67
- jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
67
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types
68
68
  };
69
69
 
70
70
  const OverviewContent = () => {
@@ -16,7 +16,7 @@ export const Header = ({ className, jobs }) => (
16
16
 
17
17
  Header.propTypes = {
18
18
  className: PropTypes.string,
19
- jobs: PropTypes.arrayOf(PropTypes.object),
19
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
20
20
  };
21
21
 
22
22
  Header.defaultProps = {
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  import cx from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import { InsightType } from '@bundle-stats/utils';
5
- import * as budgetsInsightsTransformer from '@bundle-stats/utils/lib-esm/transformers/budgets-insights';
6
5
 
7
6
  import { Icon } from '../../ui/icon';
8
7
  import { Tooltip } from '../../ui/tooltip';
@@ -24,38 +23,29 @@ const INSIGHT_TYPE_MAP = {
24
23
  },
25
24
  };
26
25
 
27
- export const BudgetInfo = ({ className = '', metricId, budgetInsight }) => {
28
- const budgetInsightInfo = budgetsInsightsTransformer.getInfo(metricId, budgetInsight);
29
- const { className: insightTypeClassName, glyph } = INSIGHT_TYPE_MAP[budgetInsightInfo.type];
26
+ export const BudgetInfo = ({ className = '', budgetInsight }) => {
27
+ const { className: insightTypeClassName, glyph } = INSIGHT_TYPE_MAP[budgetInsight.type];
30
28
 
31
29
  const rootClassName = cx(css.root, insightTypeClassName, className);
32
- const { data: messageData } = budgetInsightInfo.message;
33
30
 
34
31
  return (
35
- <Tooltip
36
- className={rootClassName}
37
- title={
38
- <>
39
- <strong>{messageData.metricLabel}</strong>
40
- {` value (`}
41
- <strong>{messageData.currentValue}</strong>
42
- {`) is ${messageData.diffLabel} `}
43
- <strong>{messageData.budgetValue}</strong>
44
- {` budget `}
45
- </>
46
- }
47
- >
32
+ <Tooltip className={rootClassName} title={budgetInsight.message.text}>
48
33
  <Icon className={css.icon} glyph={glyph} size="medium" />
49
34
  </Tooltip>
50
35
  );
51
36
  };
52
37
 
53
38
  BudgetInfo.propTypes = {
54
- metricId: PropTypes.string.isRequired,
55
39
  budgetInsight: PropTypes.shape({
56
- currentValue: PropTypes.number,
57
- budgetValue: PropTypes.number,
58
- failed: PropTypes.bool,
40
+ type: PropTypes.string,
41
+ message: PropTypes.shape({
42
+ text: PropTypes.string,
43
+ }),
44
+ data: PropTypes.shape({
45
+ currentValue: PropTypes.number,
46
+ budgetValue: PropTypes.number,
47
+ failed: PropTypes.bool,
48
+ }),
59
49
  }).isRequired,
60
50
  className: PropTypes.string,
61
51
  };
@@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
4
  import { METRIC_COMPONENT_LINKS } from '@bundle-stats/utils';
5
- import * as budgetsInsightsTransformer from '@bundle-stats/utils/lib-esm/transformers/budgets-insights';
6
5
 
7
6
  import { Box } from '../../layout/box';
8
7
  import { Stack } from '../../layout/stack';
@@ -13,19 +12,15 @@ import { ComponentLink } from '../component-link';
13
12
  import css from './budget-insights.module.css';
14
13
 
15
14
  const Budget = (props) => {
16
- const { className, metricId, budgetInsight, CustomLink } = props;
15
+ const { className, metricId, budget, CustomLink } = props;
17
16
  const componentLinkOptions = METRIC_COMPONENT_LINKS.get(metricId);
18
- const budgetInsightInfo = budgetsInsightsTransformer.getInfo(metricId, budgetInsight);
19
- const { data: messageData } = budgetInsightInfo.message;
20
17
 
21
18
  return (
22
- <CustomLink className={cx(css.budget, className)} {...componentLinkOptions?.link}>
23
- <strong>{messageData.metricLabel}</strong>
24
- {` value (`}
25
- <strong>{messageData.currentValue}</strong>
26
- {`) is ${messageData.diffLabel} `}
27
- <strong>{messageData.budgetValue}</strong>
28
- {` budget `}
19
+ <CustomLink
20
+ className={cx(css.budget, className, budget.type === 'SUCCESS' && css.budgetSuccess)}
21
+ {...componentLinkOptions?.link}
22
+ >
23
+ {budget.message.text}
29
24
  </CustomLink>
30
25
  );
31
26
  };
@@ -33,10 +28,16 @@ const Budget = (props) => {
33
28
  Budget.propTypes = {
34
29
  className: PropTypes.string,
35
30
  metricId: PropTypes.string.isRequired,
36
- budgetInsight: PropTypes.shape({
37
- currentValue: PropTypes.number.isRequired,
38
- budgetValue: PropTypes.number.isRequired,
39
- failed: PropTypes.bool.isRequired,
31
+ budget: PropTypes.shape({
32
+ type: PropTypes.string,
33
+ message: PropTypes.shape({
34
+ text: PropTypes.string,
35
+ }),
36
+ data: PropTypes.shape({
37
+ currentValue: PropTypes.number.isRequired,
38
+ budgetValue: PropTypes.number.isRequired,
39
+ failed: PropTypes.bool.isRequired,
40
+ }),
40
41
  }).isRequired,
41
42
  CustomLink: PropTypes.elementType.isRequired,
42
43
  };
@@ -45,14 +46,39 @@ Budget.defaultProps = {
45
46
  className: '',
46
47
  };
47
48
 
48
- const BudgetsGroup = (props) => {
49
- const { className, kind, source, budgets, CustomLink } = props;
49
+ const BUDGET_ALERT_KIND = new Map([
50
+ ['ERROR', 'danger'],
51
+ ['WARNING', 'warning'],
52
+ ['SUCCESS', 'success'],
53
+ ]);
54
+
55
+ const BUDGET_ICON = new Map([
56
+ ['ERROR', Icon.ICONS.ALERT_CIRCLE],
57
+ ['WARNING', Icon.ICONS.WARNING],
58
+ ['SUCCESS', Icon.ICONS.CHECK_CIRCLE],
59
+ ]);
60
+
61
+ export const BudgetInsights = (props) => {
62
+ const { className, source, budgets, CustomLink } = props;
50
63
  const [showBudgets, setShowBudgets] = useState(false);
51
64
 
52
- const isFailed = kind === 'danger';
65
+ const iconGlyph = BUDGET_ICON.get(budgets.type);
66
+ const alertKind = BUDGET_ALERT_KIND.get(budgets.type);
67
+ const rootClassName = cx(css.group, className, css[`group-${alertKind}`]);
68
+
69
+ const budgetItems = useMemo(() => {
70
+ const items = Object.entries(budgets.data);
71
+
72
+ // When there are errors/warnings, sort budget items first
73
+ if (['ERROR', 'WARNING'].includes(budgets.type)) {
74
+ items.sort((a) => (a[1].type === budgets.type ? -1 : 1));
75
+ }
76
+
77
+ return items;
78
+ }, [budgets]);
53
79
 
54
80
  return (
55
- <Alert kind={kind} padding="none" className={cx(css.group, css[kind], className)}>
81
+ <Alert kind={alertKind} padding="none" className={rootClassName}>
56
82
  <Box
57
83
  padding={['xsmall', 'small']}
58
84
  className={css.groupHeader}
@@ -61,14 +87,8 @@ const BudgetsGroup = (props) => {
61
87
  onClick={() => setShowBudgets(!showBudgets)}
62
88
  >
63
89
  <FlexStack space="xxsmall" className={css.groupTitle}>
64
- <Icon
65
- className={css.groupIcon}
66
- glyph={isFailed ? Icon.ICONS.ALERT_CIRCLE : Icon.ICONS.CHECK_CIRCLE}
67
- />
68
- <span>
69
- <strong>{budgets.length}</strong>
70
- {` budget ${budgets.length > 1 ? 'checks' : 'check'} ${isFailed ? 'failed' : 'passed'}`}
71
- </span>
90
+ {iconGlyph && <Icon className={css.groupIcon} glyph={iconGlyph} />}
91
+ <span>{budgets.message.text}</span>
72
92
  <span className={css.groupTitleToggle}>{showBudgets ? 'hide' : 'show'} all</span>
73
93
  </FlexStack>
74
94
  </Box>
@@ -76,11 +96,11 @@ const BudgetsGroup = (props) => {
76
96
  {showBudgets && (
77
97
  <Box padding={['xsmall', 'small']} className={css.groupContent}>
78
98
  <Stack space="none">
79
- {budgets.map(([key, budget]) => (
99
+ {budgetItems.map(([key, budget]) => (
80
100
  <Budget
81
101
  key={key}
82
102
  metricId={`${source}.${key}`}
83
- budgetInsight={budget}
103
+ budget={budget}
84
104
  CustomLink={CustomLink}
85
105
  />
86
106
  ))}
@@ -91,59 +111,6 @@ const BudgetsGroup = (props) => {
91
111
  );
92
112
  };
93
113
 
94
- BudgetsGroup.propTypes = {
95
- className: PropTypes.string,
96
- kind: PropTypes.oneOf(['success', 'danger']).isRequired,
97
- source: PropTypes.string.isRequired,
98
- budgets: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
99
- CustomLink: PropTypes.elementType.isRequired,
100
- };
101
-
102
- BudgetsGroup.defaultProps = {
103
- className: '',
104
- };
105
-
106
- export const BudgetInsights = (props) => {
107
- const { className, source, budgets, CustomLink } = props;
108
-
109
- const [failedBudgets, passedBudgets] = useMemo(() => {
110
- const passed = [];
111
- const failed = [];
112
-
113
- Object.entries(budgets).forEach(([key, budget]) => {
114
- if (budget.failed) {
115
- failed.push([key, budget]);
116
- } else {
117
- passed.push([key, budget]);
118
- }
119
- });
120
-
121
- return [failed, passed];
122
- }, [budgets]);
123
-
124
- return (
125
- <Stack className={className} space="xsmall">
126
- {failedBudgets.length > 0 && (
127
- <BudgetsGroup
128
- kind="danger"
129
- source={source}
130
- budgets={failedBudgets}
131
- CustomLink={CustomLink}
132
- />
133
- )}
134
-
135
- {passedBudgets.length > 0 && (
136
- <BudgetsGroup
137
- kind="success"
138
- source={source}
139
- budgets={passedBudgets}
140
- CustomLink={CustomLink}
141
- />
142
- )}
143
- </Stack>
144
- );
145
- };
146
-
147
114
  BudgetInsights.propTypes = {
148
115
  className: PropTypes.string,
149
116
  source: PropTypes.string.isRequired,
@@ -20,11 +20,15 @@
20
20
  margin: 0 0 0 auto !important;
21
21
  }
22
22
 
23
- .danger .groupIcon {
23
+ .group-danger .groupIcon {
24
24
  color: var(--color-danger);
25
25
  }
26
26
 
27
- .success .groupIcon {
27
+ .group-warning .groupIcon {
28
+ color: var(--color-warning);
29
+ }
30
+
31
+ .group-success .groupIcon {
28
32
  color: var(--color-success);
29
33
  }
30
34
 
@@ -39,6 +43,10 @@
39
43
  text-decoration: none;
40
44
  }
41
45
 
46
+ .budgetSuccess {
47
+ color: var(--color-text-light);
48
+ }
49
+
42
50
  .budgetLabel,
43
51
  .budgetValue {
44
52
  color: var(--color-text-dark);
@@ -146,7 +146,7 @@ RowHeader.propTypes = {
146
146
  isChunk: PropTypes.bool,
147
147
  isInitial: PropTypes.bool,
148
148
  isEntry: PropTypes.bool,
149
- runs: PropTypes.arrayOf(PropTypes.object),
149
+ runs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
150
150
  }).isRequired,
151
151
  chunks: PropTypes.arrayOf(
152
152
  PropTypes.shape({
@@ -18,7 +18,7 @@ export const BundleAssetsTotalsTable = ({
18
18
  ...restProps
19
19
  }) => {
20
20
  const items = useMemo(() => webpack.compareBySection.sizes(jobs), [jobs]);
21
- const budgets = jobs[0].insights?.webpack?.budgets;
21
+ const budgets = jobs[0].insights?.webpack?.budgets?.data;
22
22
 
23
23
  const renderRowHeader = useCallback(
24
24
  (item) => {
@@ -30,9 +30,9 @@ export const BundleAssetsTotalsTable = ({
30
30
 
31
31
  return (
32
32
  <CustomComponentLink section={section} title={title} params={params}>
33
- <FlexStack space="xxsmall" className={css.itemTitleText}>
33
+ <FlexStack space="xxxsmall" className={css.itemTitleText}>
34
34
  <span>{item.label}</span>
35
- {budget && <BudgetInfo metricId={item.key} budgetInsight={budget} />}
35
+ {budget && <BudgetInfo budgetInsight={budget} />}
36
36
  </FlexStack>
37
37
  </CustomComponentLink>
38
38
  );
@@ -131,7 +131,7 @@ export const BundleModules = ({
131
131
  disabled: jobs.length <= 1,
132
132
  },
133
133
  [MODULE_FILTERS.DUPLICATED]: {
134
- label: 'Duplicated',
134
+ label: 'Duplicate',
135
135
  defaultValue: filters[MODULE_FILTERS.DUPLICATED],
136
136
  disabled: jobs.length <= 1,
137
137
  },
@@ -5,7 +5,6 @@ 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';
9
8
  import { Box } from '../../layout/box';
10
9
  import { FlexStack } from '../../layout/flex-stack';
11
10
  import { ComponentLink } from '../component-link';
@@ -29,22 +28,19 @@ export const Summary = ({
29
28
  {Array.from(METRIC_COMPONENT_LINKS)
30
29
  .filter(([metricId]) => keys.includes(getSourceMetricId(metricId)))
31
30
  .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>
31
+ <SummaryItem
32
+ key={metricId}
33
+ className={css.item}
34
+ as={SummaryItemCustomLink}
35
+ {...metricOptions.link}
36
+ size={size}
37
+ id={metricId}
38
+ data={get(data, metricId)}
39
+ budget={get(budgets, ['data', getSourceMetricId(metricId)])}
40
+ loading={loading}
41
+ showMetricDescription
42
+ showDelta={showSummaryItemDelta && metricOptions.showDelta !== false}
43
+ />
48
44
  ))}
49
45
  </FlexStack>
50
46
  </Box>
@@ -6,49 +6,5 @@
6
6
  .item {
7
7
  padding: var(--space-small);
8
8
  flex: 1 0 25%; /* force equal sizes */
9
- position: relative;
10
- z-index: 1;
11
- }
12
-
13
- .item::before {
14
- content: '';
15
- display: block;
16
- width: 1px;
17
- position: absolute;
18
- left: -1px;
19
- top: var(--space-small);
20
- bottom: var(--space-small);
21
- background: var(--color-outline);
22
- }
23
-
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;
47
- }
48
-
49
- .item:hover .readMore,
50
- .item:active .readMore,
51
- .item:focus .readMore {
52
- opacity: 1;
53
- transition: var(--transition-in);
9
+ text-decoration: none;
54
10
  }
@@ -4,10 +4,9 @@ import cx from 'classnames';
4
4
  import { getGlobalMetricType, getMetricRunInfo } from '@bundle-stats/utils';
5
5
 
6
6
  import { HoverCard } from '../../ui/hover-card';
7
- import { Icon } from '../../ui/icon';
8
7
  import { Skeleton } from '../../ui/skeleton';
9
- import { Stack } from '../../layout/stack';
10
8
  import { FlexStack } from '../../layout/flex-stack';
9
+ import { Stack } from '../../layout/stack';
11
10
  import { Metric } from '../metric';
12
11
  import { Delta } from '../delta';
13
12
  import { BudgetInfo } from '../budget-info';
@@ -18,9 +17,9 @@ const MetricInfo = ({ description, url }) => (
18
17
  <p>{description}</p>
19
18
  {url && (
20
19
  <p>
21
- <a href={url} className={css.readMoreLink}>
20
+ <button type="button" onClick={() => window.open(url)} className={css.readMoreLink}>
22
21
  Read more
23
- </a>
22
+ </button>
24
23
  </p>
25
24
  )}
26
25
  </Stack>
@@ -55,17 +54,16 @@ export const SummaryItem = ({
55
54
  const rootClassName = cx(css.root, className, css[size], showDelta && css.showDelta);
56
55
 
57
56
  return (
58
- <Stack space="xxxsmall" as={Component} className={rootClassName} {...props}>
59
- <FlexStack as="h3" space="xxxsmall" className={css.title}>
60
- <span className={css.titleLabel}>{metric.label}</span>
61
-
62
- {showMetricDescriptionTooltip && (
63
- <HoverCard className={css.icon} label={<Icon glyph="help" />}>
57
+ <Stack space="xxsmall" as={Component} className={rootClassName} {...props}>
58
+ <FlexStack space="xxxsmall" as="h3" className={css.title}>
59
+ {showMetricDescriptionTooltip ? (
60
+ <HoverCard label={metric.label}>
64
61
  <MetricInfo {...metric} />
65
62
  </HoverCard>
63
+ ) : (
64
+ metric.label
66
65
  )}
67
-
68
- {budget && <BudgetInfo className={css.budgetInfo} metricId={id} budgetInsight={budget} />}
66
+ {budget && <BudgetInfo className={css.budgetInfo} budgetInsight={budget} />}
69
67
  </FlexStack>
70
68
 
71
69
  {!loading ? (
@@ -134,9 +132,5 @@ SummaryItem.propTypes = {
134
132
  showDelta: PropTypes.bool,
135
133
 
136
134
  /** Budget insight data */
137
- budget: PropTypes.shape({
138
- currentBalue: PropTypes.number,
139
- budgetValue: PropTypes.number,
140
- failed: PropTypes.bool,
141
- }),
135
+ budget: PropTypes.object, // eslint-disable-line react/forbid-prop-types
142
136
  };
@@ -8,17 +8,11 @@
8
8
  color: var(--color-text-ultra-light);
9
9
  font-weight: bold;
10
10
  font-size: var(--size-xsmall);
11
- line-height: var(--space-small);
11
+ line-height: 1;
12
12
  text-transform: uppercase;
13
13
  white-space: nowrap;
14
14
  }
15
15
 
16
- .budgetInfo {
17
- position: absolute;
18
- right: 0;
19
- top: 0;
20
- }
21
-
22
16
  .currentMetric {
23
17
  display: block;
24
18
  color: var(--color-heading);
@@ -39,6 +33,30 @@
39
33
  line-height: var(--space-small);
40
34
  }
41
35
 
36
+ .readMoreLink {
37
+ appearance: none;
38
+ background: none;
39
+ margin: 0;
40
+ border: 0;
41
+ padding: 0;
42
+ color: var(--color-primary);
43
+ text-decoration: underline;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .readMoreLink:hover,
48
+ .readMoreLink:active,
49
+ .readMoreLink:focus {
50
+ color: var(--color-primary-dark);
51
+ text-decoration: none;
52
+ }
53
+
54
+ /* Hover state */
55
+ .root:hover .title {
56
+ color: var(--color-text-light);
57
+ }
58
+
59
+
42
60
  /* Loading state */
43
61
  .loading.currentMetric {
44
62
  width: 8em;
@@ -31,6 +31,10 @@ export const HoverCard = (props) => {
31
31
  // Fallback to span if no href
32
32
  const Component = resolveComponent(as, href);
33
33
 
34
+ const handleOnClick = (event) => {
35
+ event.preventDefault();
36
+ };
37
+
34
38
  return (
35
39
  <div className={cx(css.root, className)}>
36
40
  <HovercardAnchor state={state} href={href} className={css.anchor} as={Component}>
@@ -41,6 +45,7 @@ export const HoverCard = (props) => {
41
45
  state={state}
42
46
  className={cx(css.hoverCard, hoverCardClassName)}
43
47
  style={{ zIndex: 10000 }}
48
+ onClick={handleOnClick}
44
49
  >
45
50
  <HovercardArrow size={24} />
46
51
  {typeof children === 'function' ? children({ close: state.hide }) : children}