@automattic/jetpack-components 0.41.1 → 0.42.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.
- package/CHANGELOG.md +23 -0
- package/components/boost-score-graph/background.tsx +183 -0
- package/components/boost-score-graph/day-highlight-plugin.ts +82 -0
- package/components/boost-score-graph/get-date-format.ts +59 -0
- package/components/boost-score-graph/index.native.js +1 -0
- package/components/boost-score-graph/index.tsx +76 -0
- package/components/boost-score-graph/style-tooltip.scss +52 -0
- package/components/boost-score-graph/style-uplot.scss +5 -0
- package/components/boost-score-graph/style.scss +17 -0
- package/components/boost-score-graph/tooltip.tsx +87 -0
- package/components/boost-score-graph/tooltips-plugin.ts +98 -0
- package/components/boost-score-graph/types/declarations.d.ts +4 -0
- package/components/boost-score-graph/uplot-line-chart.tsx +193 -0
- package/components/boost-score-graph/use-boost-score-transform.ts +36 -0
- package/components/boost-score-graph/use-resize.ts +40 -0
- package/components/gridicon/index.tsx +13 -0
- package/components/{pricing-slider → number-slider}/index.tsx +12 -12
- package/components/{pricing-slider → number-slider}/style.scss +8 -8
- package/components/{pricing-slider → number-slider}/types.ts +2 -2
- package/components/popover/index.tsx +19 -0
- package/components/popover/style.scss +25 -0
- package/components/product-price/index.tsx +5 -5
- package/components/product-price/style.module.scss +8 -1
- package/index.ts +3 -1
- package/package.json +6 -3
- package/.gitattributes +0 -8
- package/components/action-button/stories/index.stories.jsx +0 -38
- package/components/action-button/test/component.jsx +0 -16
- package/components/action-popover/stories/index.stories.jsx +0 -115
- package/components/action-popover/stories/styles.module.scss +0 -7
- package/components/admin-page/stories/index.stories.tsx +0 -35
- package/components/admin-page/stories/style.module.scss +0 -13
- package/components/admin-section/stories/index.stories.tsx +0 -60
- package/components/alert/stories/index.stories.jsx +0 -24
- package/components/automattic-byline-logo/stories/index.stories.tsx +0 -20
- package/components/automattic-byline-logo/test/component.tsx +0 -28
- package/components/boost-score-bar/stories/index.stories.tsx +0 -39
- package/components/boost-score-bar/test/component.tsx +0 -102
- package/components/button/stories/index.mdx +0 -90
- package/components/button/stories/index.stories.tsx +0 -400
- package/components/button/stories/style.module.scss +0 -20
- package/components/contextual-upgrade-trigger/stories/index.stories.tsx +0 -25
- package/components/decorative-card/stories/index.stories.tsx +0 -18
- package/components/dialog/stories/boost.png +0 -0
- package/components/dialog/stories/declaration.d.ts +0 -1
- package/components/dialog/stories/index.stories.tsx +0 -118
- package/components/dialog/stories/js-components.components.dialog.mdx +0 -62
- package/components/dialog/stories/js-components.components.dialog.stories.js +0 -37
- package/components/dialog/stories/style.module.scss +0 -21
- package/components/donut-meter/stories/index.stories.tsx +0 -51
- package/components/donut-meter/test/component.tsx +0 -24
- package/components/gridicon/stories/index.stories.tsx +0 -18
- package/components/icon-tooltip/stories/index.stories.tsx +0 -108
- package/components/icon-tooltip/stories/style.scss +0 -36
- package/components/icon-tooltip/test/component.tsx +0 -15
- package/components/icons/stories/index.stories.tsx +0 -65
- package/components/icons/stories/style.module.scss +0 -33
- package/components/indeterminate-progress-bar/stories/index.stories.tsx +0 -14
- package/components/jetpack-footer/stories/index.stories.tsx +0 -39
- package/components/jetpack-footer/test/__snapshots__/component.tsx.snap +0 -186
- package/components/jetpack-footer/test/component.tsx +0 -159
- package/components/jetpack-logo/stories/index.stories.tsx +0 -20
- package/components/jetpack-logo/test/component.tsx +0 -16
- package/components/jetpack-search-logo/stories/index.stories.tsx +0 -21
- package/components/jetpack-search-logo/test/component.tsx +0 -16
- package/components/jetpack-vaultpress-backup-logo/stories/index.stories.tsx +0 -20
- package/components/jetpack-vaultpress-backup-logo/test/component.tsx +0 -18
- package/components/jetpack-videopress-logo/stories/index.stories.tsx +0 -20
- package/components/jetpack-videopress-logo/test/component.tsx +0 -16
- package/components/layout/stories/index.stories.tsx +0 -145
- package/components/layout/stories/styles.module.scss +0 -22
- package/components/loading-placeholder/stories/index.stories.tsx +0 -78
- package/components/notice/stories/index.stories.jsx +0 -90
- package/components/pricing-card/stories/index.stories.tsx +0 -55
- package/components/pricing-card/test/component.tsx +0 -89
- package/components/pricing-slider/stories/index.stories.tsx +0 -44
- package/components/pricing-slider/test/component.tsx +0 -14
- package/components/pricing-table/stories/index.stories.tsx +0 -74
- package/components/pricing-table/test/component.tsx +0 -41
- package/components/product-offer/stories/index.stories.tsx +0 -104
- package/components/product-price/stories/index.stories.tsx +0 -33
- package/components/product-price/test/component.tsx +0 -26
- package/components/qr-code/stories/index.stories.tsx +0 -32
- package/components/record-meter-bar/stories/index.stories.tsx +0 -39
- package/components/record-meter-bar/test/component.tsx +0 -52
- package/components/spinner/stories/index.stories.jsx +0 -20
- package/components/spinner/test/component.jsx +0 -17
- package/components/split-button/stories/index.stories.tsx +0 -35
- package/components/stat-card/stories/index.mdx +0 -35
- package/components/stat-card/stories/index.stories.tsx +0 -41
- package/components/stat-card/test/component.tsx +0 -24
- package/components/status/stories/index.stories.tsx +0 -44
- package/components/terms-of-service/test/index.test.jsx +0 -52
- package/components/testimonials/test/component.tsx +0 -103
- package/components/text/stories/headings.index.stories.tsx +0 -80
- package/components/text/stories/index.mdx +0 -195
- package/components/text/stories/index.stories.tsx +0 -69
- package/components/text/stories/style.module.scss +0 -64
- package/components/text/stories/title.index.stories.tsx +0 -34
- package/components/theme-provider/stories/index.stories.tsx +0 -164
- package/components/theme-provider/stories/style.module.scss +0 -79
- package/components/toggle-control/stories/index.stories.tsx +0 -83
- package/components/zendesk-chat/stories/index.stories.tsx +0 -15
- package/components/zendesk-chat/test/component.tsx +0 -16
- package/lib/locale/test/index.ts +0 -21
- package/tools/jp-redirect/test/index.tsx +0 -89
- package/tools/pricing-utils/test/index.ts +0 -39
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getScoreLetter } from '@automattic/jetpack-boost-score-api';
|
|
2
|
+
import { dateI18n } from '@wordpress/date';
|
|
3
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
4
|
+
import { type FunctionComponent } from 'react';
|
|
5
|
+
import './style-tooltip.scss';
|
|
6
|
+
import { Period } from '.';
|
|
7
|
+
|
|
8
|
+
export const Tooltip: FunctionComponent = ( { period }: { period: Period } ) => {
|
|
9
|
+
if ( ! period || ! period.dimensions || ! period.timestamp ) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const {
|
|
13
|
+
mobile_overall_score,
|
|
14
|
+
desktop_overall_score,
|
|
15
|
+
desktop_cls,
|
|
16
|
+
desktop_lcp,
|
|
17
|
+
desktop_tbt,
|
|
18
|
+
mobile_cls,
|
|
19
|
+
mobile_lcp,
|
|
20
|
+
mobile_tbt,
|
|
21
|
+
} = period.dimensions;
|
|
22
|
+
const scoreLetter = getScoreLetter( mobile_overall_score, desktop_overall_score );
|
|
23
|
+
const date = dateI18n( 'F j, Y', new Date( period.timestamp ), false );
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="jb-score-tooltip">
|
|
27
|
+
<div className="jb-score-tooltip__date">{ date }</div>
|
|
28
|
+
<div className="jb-score-tooltip__row">
|
|
29
|
+
<div className="jb-score-tooltip__column">{ __( 'Overall score', 'jetpack' ) }</div>
|
|
30
|
+
<div className="jb-score-tooltip__column">{ scoreLetter }</div>
|
|
31
|
+
</div>
|
|
32
|
+
<hr />
|
|
33
|
+
<div className="jb-score-tooltip__row">
|
|
34
|
+
<div className="jb-score-tooltip__column">{ __( 'Desktop score', 'jetpack' ) }</div>
|
|
35
|
+
<div className="jb-score-tooltip__column">
|
|
36
|
+
{
|
|
37
|
+
/* translators: %d is the score */
|
|
38
|
+
sprintf( __( '%d / 100', 'jetpack' ), desktop_overall_score )
|
|
39
|
+
}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
43
|
+
<div className="jb-score-tooltip__column">
|
|
44
|
+
{ __( 'Largest Contentful Paint', 'jetpack' ) }
|
|
45
|
+
</div>
|
|
46
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2fs', desktop_lcp ) }</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
49
|
+
<div className="jb-score-tooltip__column">{ __( 'Total Blocking Time', 'jetpack' ) }</div>
|
|
50
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2fs', desktop_tbt ) }</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
53
|
+
<div className="jb-score-tooltip__column">
|
|
54
|
+
{ __( 'Cumulative Layout Shift', 'jetpack' ) }
|
|
55
|
+
</div>
|
|
56
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2f', desktop_cls ) }</div>
|
|
57
|
+
</div>
|
|
58
|
+
<hr />
|
|
59
|
+
<div className="jb-score-tooltip__row">
|
|
60
|
+
<div className="jb-score-tooltip__column">{ __( 'Mobile score', 'jetpack' ) }</div>
|
|
61
|
+
<div className="jb-score-tooltip__column">
|
|
62
|
+
{
|
|
63
|
+
/* translators: %d is the score */
|
|
64
|
+
sprintf( __( '%d / 100', 'jetpack' ), mobile_overall_score )
|
|
65
|
+
}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
69
|
+
<div className="jb-score-tooltip__column">
|
|
70
|
+
{ __( 'Largest Contentful Paint', 'jetpack' ) }
|
|
71
|
+
</div>
|
|
72
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2fs', mobile_lcp ) }</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
75
|
+
<div className="jb-score-tooltip__column">{ __( 'Total Blocking Time', 'jetpack' ) }</div>
|
|
76
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2fs', mobile_tbt ) }</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="jb-score-tooltip__row jb-score-tooltip__row--secondary">
|
|
79
|
+
<div className="jb-score-tooltip__column">
|
|
80
|
+
{ __( 'Cumulative Layout Shift', 'jetpack' ) }
|
|
81
|
+
</div>
|
|
82
|
+
<div className="jb-score-tooltip__column">{ sprintf( '%0.2f', mobile_cls ) }</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="jb-score-tooltip__pointer"></div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom/client';
|
|
2
|
+
import uPlot from 'uplot';
|
|
3
|
+
import { Tooltip } from './tooltip';
|
|
4
|
+
import { Period } from '.';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Custom tooltips plugin for uPlot.
|
|
8
|
+
*
|
|
9
|
+
* @param {Period[]} periods - The periods to display in the tooltip.
|
|
10
|
+
* @returns {object} The uPlot plugin object with hooks.
|
|
11
|
+
*/
|
|
12
|
+
export function tooltipsPlugin( periods ) {
|
|
13
|
+
const reactRoot = document.createElement( 'div' );
|
|
14
|
+
let reactDom;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initializes the tooltips plugin.
|
|
18
|
+
*
|
|
19
|
+
* @param {uPlot} u - The uPlot instance.
|
|
20
|
+
* @param {object} _opts - Options for the uPlot instance.
|
|
21
|
+
*/
|
|
22
|
+
function init( u, _opts ) {
|
|
23
|
+
const over = u.over;
|
|
24
|
+
reactDom = ReactDOM.createRoot( reactRoot );
|
|
25
|
+
reactRoot.style.position = 'absolute';
|
|
26
|
+
reactRoot.style.bottom = -20 + 'px';
|
|
27
|
+
reactRoot.style.translate = '-50% calc( 100% - 20px )';
|
|
28
|
+
|
|
29
|
+
over.appendChild( reactRoot );
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hides all tooltips.
|
|
33
|
+
*/
|
|
34
|
+
function hideTips() {
|
|
35
|
+
reactRoot.style.display = 'none';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Shows all tooltips.
|
|
40
|
+
*/
|
|
41
|
+
function showTips() {
|
|
42
|
+
reactRoot.style.display = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
over.addEventListener( 'mouseleave', () => {
|
|
46
|
+
if ( ! u.cursor._lock ) {
|
|
47
|
+
hideTips();
|
|
48
|
+
}
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
over.addEventListener( 'mouseenter', () => {
|
|
52
|
+
showTips();
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
if ( u.cursor.left < 0 ) {
|
|
56
|
+
hideTips();
|
|
57
|
+
} else {
|
|
58
|
+
showTips();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Sets the cursor for tooltips.
|
|
64
|
+
*
|
|
65
|
+
* @param {uPlot} u - The uPlot instance.
|
|
66
|
+
*/
|
|
67
|
+
function setCursor( u ) {
|
|
68
|
+
const { idx } = u.cursor;
|
|
69
|
+
|
|
70
|
+
const period = periods[ idx ];
|
|
71
|
+
|
|
72
|
+
// Timestamp of the cursor position
|
|
73
|
+
const timestamp = u.data[ 0 ][ idx ];
|
|
74
|
+
|
|
75
|
+
// Find start and end of day for the cursor position
|
|
76
|
+
const startOfDay = timestamp - ( timestamp % 86400 );
|
|
77
|
+
const endOfDay = startOfDay + 86400;
|
|
78
|
+
|
|
79
|
+
// Find the left position, and width of the box, bounded by the range of the graph
|
|
80
|
+
const boxLeft = u.valToPos( Math.max( startOfDay, u.scales.x.min ), 'x' );
|
|
81
|
+
const boxWidth = u.valToPos( Math.min( endOfDay, u.scales.x.max ), 'x' ) - boxLeft;
|
|
82
|
+
const boxCenter = boxLeft + boxWidth / 2;
|
|
83
|
+
|
|
84
|
+
reactRoot.style.left = boxCenter + 'px';
|
|
85
|
+
reactDom.render(
|
|
86
|
+
Tooltip( {
|
|
87
|
+
period,
|
|
88
|
+
} )
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
hooks: {
|
|
94
|
+
init,
|
|
95
|
+
setCursor,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
import React, { useMemo, useRef, useCallback } from 'react';
|
|
3
|
+
import uPlot from 'uplot';
|
|
4
|
+
import UplotReact from 'uplot-react';
|
|
5
|
+
import { getUserLocale } from '../../lib/locale';
|
|
6
|
+
import numberFormat from '../number-format';
|
|
7
|
+
import { dayHighlightPlugin } from './day-highlight-plugin';
|
|
8
|
+
import getDateFormat from './get-date-format';
|
|
9
|
+
import { tooltipsPlugin } from './tooltips-plugin';
|
|
10
|
+
import { useBoostScoreTransform } from './use-boost-score-transform';
|
|
11
|
+
import useResize from './use-resize';
|
|
12
|
+
import { Period } from '.';
|
|
13
|
+
|
|
14
|
+
import './style-uplot.scss';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_DIMENSIONS = {
|
|
17
|
+
height: 300,
|
|
18
|
+
width: 600,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface UplotChartProps {
|
|
22
|
+
periods: Period[];
|
|
23
|
+
options?: Partial< uPlot.Options >;
|
|
24
|
+
legendContainer?: React.RefObject< HTMLDivElement >;
|
|
25
|
+
solidFill?: boolean;
|
|
26
|
+
period?: string;
|
|
27
|
+
range?: { startDate: number; endDate: number };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a series information object for uPlot based on the label and color.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} label - The label for the series.
|
|
34
|
+
* @param {number} score - The last score for the series.
|
|
35
|
+
* @returns {object} The series information object.
|
|
36
|
+
*/
|
|
37
|
+
function createSerieInfo( label: string, score ) {
|
|
38
|
+
const { spline } = uPlot.paths;
|
|
39
|
+
return {
|
|
40
|
+
label: label,
|
|
41
|
+
stroke: getColor( score ),
|
|
42
|
+
fill: u => {
|
|
43
|
+
const gradient = u.ctx.createLinearGradient( 0, 0, 0, DEFAULT_DIMENSIONS.height );
|
|
44
|
+
gradient.addColorStop( 0, getColor( score, '44' ) );
|
|
45
|
+
gradient.addColorStop( 1, getColor( score, '11' ) );
|
|
46
|
+
|
|
47
|
+
return gradient;
|
|
48
|
+
}, // use the gradient as fill for the series
|
|
49
|
+
width: 2,
|
|
50
|
+
paths: ( u, seriesIdx, idx0, idx1 ) => {
|
|
51
|
+
return spline?.()( u, seriesIdx, idx0, idx1 ) || null;
|
|
52
|
+
},
|
|
53
|
+
points: {
|
|
54
|
+
show: true,
|
|
55
|
+
},
|
|
56
|
+
value: ( self: uPlot, rawValue: number ) => {
|
|
57
|
+
if ( ! rawValue ) {
|
|
58
|
+
return '-';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return numberFormat( rawValue );
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the color value based on the score.
|
|
68
|
+
*
|
|
69
|
+
* @param {number} score - The score to get the color for.
|
|
70
|
+
* @param {string} opacity - Whether to return a transparent color.
|
|
71
|
+
* @returns {string} The color value.
|
|
72
|
+
*/
|
|
73
|
+
function getColor( score: number, opacity = 'FF' ) {
|
|
74
|
+
let color = '#D63638'; // bad
|
|
75
|
+
|
|
76
|
+
if ( score > 70 ) {
|
|
77
|
+
color = '#069e08'; // good
|
|
78
|
+
} else if ( score > 50 ) {
|
|
79
|
+
color = '#faa754'; //mediocre
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `${ color }${ opacity }`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* UplotLineChart component.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} props - The props object for the UplotLineChart component.
|
|
89
|
+
* @param {{ startDate: number, endDate: number }} props.range - The date range of the chart.
|
|
90
|
+
* @param {Period[]} props.periods - The periods to display in the chart.
|
|
91
|
+
* @returns {React.Element} The JSX element representing the UplotLineChart component.
|
|
92
|
+
*/
|
|
93
|
+
export default function UplotLineChart( { range, periods }: UplotChartProps ) {
|
|
94
|
+
const uplot = useRef< uPlot | null >( null );
|
|
95
|
+
const uplotContainer = useRef( null );
|
|
96
|
+
|
|
97
|
+
let lastDesktopScore = 0;
|
|
98
|
+
let lastMobileScore = 0;
|
|
99
|
+
|
|
100
|
+
if ( periods.length > 0 ) {
|
|
101
|
+
lastDesktopScore = periods[ periods.length - 1 ].dimensions.desktop_overall_score;
|
|
102
|
+
lastMobileScore = periods[ periods.length - 1 ].dimensions.mobile_overall_score;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const data = useBoostScoreTransform( periods );
|
|
106
|
+
|
|
107
|
+
const options: uPlot.Options = useMemo( () => {
|
|
108
|
+
const defaultOptions: uPlot.Options = {
|
|
109
|
+
class: 'boost-score-graph',
|
|
110
|
+
...DEFAULT_DIMENSIONS,
|
|
111
|
+
tzDate: ts => uPlot.tzDate( new Date( ts * 1e3 ), 'Etc/UTC' ),
|
|
112
|
+
fmtDate: ( chartDateStringTemplate: string ) => {
|
|
113
|
+
return date => getDateFormat( chartDateStringTemplate, date, getUserLocale() );
|
|
114
|
+
},
|
|
115
|
+
padding: [ 17, 0, 17, 0 ],
|
|
116
|
+
axes: [
|
|
117
|
+
{
|
|
118
|
+
// x-axis
|
|
119
|
+
grid: {
|
|
120
|
+
show: false,
|
|
121
|
+
},
|
|
122
|
+
ticks: {
|
|
123
|
+
stroke: '#50575E',
|
|
124
|
+
width: 1,
|
|
125
|
+
size: 3,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
// y-axis
|
|
130
|
+
side: 1,
|
|
131
|
+
gap: 8,
|
|
132
|
+
space: 100,
|
|
133
|
+
size: 30,
|
|
134
|
+
grid: {
|
|
135
|
+
stroke: 'rgba(220, 220, 222, 0.5)', // #DCDCDE with 0.5 opacity
|
|
136
|
+
width: 1,
|
|
137
|
+
},
|
|
138
|
+
ticks: {
|
|
139
|
+
show: false,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
cursor: {
|
|
144
|
+
x: false,
|
|
145
|
+
y: false,
|
|
146
|
+
},
|
|
147
|
+
series: [
|
|
148
|
+
{
|
|
149
|
+
label: __( 'Date', 'jetpack' ),
|
|
150
|
+
value: ( self: uPlot, rawValue: number ) => {
|
|
151
|
+
// outputs legend content - value available when mouse is hovering the chart
|
|
152
|
+
if ( ! rawValue ) {
|
|
153
|
+
return '-';
|
|
154
|
+
}
|
|
155
|
+
const date = new Date( rawValue );
|
|
156
|
+
return date.toLocaleDateString( getUserLocale() );
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
createSerieInfo( __( 'Desktop', 'jetpack' ), lastDesktopScore ),
|
|
160
|
+
createSerieInfo( __( 'Mobile', 'jetpack' ), lastMobileScore ),
|
|
161
|
+
],
|
|
162
|
+
scales: {
|
|
163
|
+
x: {
|
|
164
|
+
time: true,
|
|
165
|
+
auto: false,
|
|
166
|
+
range: [ range.startDate / 1000, range.endDate / 1000 ],
|
|
167
|
+
},
|
|
168
|
+
y: {
|
|
169
|
+
range: [ 0, 100 ],
|
|
170
|
+
auto: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
legend: {
|
|
174
|
+
show: false,
|
|
175
|
+
},
|
|
176
|
+
plugins: [ tooltipsPlugin( periods ), dayHighlightPlugin() ],
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
...defaultOptions,
|
|
180
|
+
};
|
|
181
|
+
}, [ lastDesktopScore, lastMobileScore, periods, range.endDate, range.startDate ] );
|
|
182
|
+
|
|
183
|
+
useResize( uplot, uplotContainer );
|
|
184
|
+
const onCreate = useCallback( chart => {
|
|
185
|
+
return ( uplot.current = chart );
|
|
186
|
+
}, [] );
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div ref={ uplotContainer }>
|
|
190
|
+
<UplotReact data={ data } onCreate={ onCreate } options={ options } />
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Period } from './index';
|
|
3
|
+
|
|
4
|
+
type ScoreGraphAlignedData = [
|
|
5
|
+
number[], // timestamps
|
|
6
|
+
number[], // desktop_overall_score
|
|
7
|
+
number[] // mobile_overall_score
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
// Extract the value of a dimension from an array of periods
|
|
11
|
+
const getPeriodDimension = function ( key: string, periods: Period[] ) {
|
|
12
|
+
return periods.map( ( { dimensions } ) => {
|
|
13
|
+
return dimensions[ key ];
|
|
14
|
+
} );
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transforms an array of periods into an array of arrays, where the first array is the timestamps, and the rest are the values for each key
|
|
19
|
+
*
|
|
20
|
+
* @param {Period[]} periods - Array of periods to transform
|
|
21
|
+
* @returns {ScoreGraphAlignedData | []} - Array of arrays, where the first array is the timestamps, and the rest are the values for each key
|
|
22
|
+
*/
|
|
23
|
+
export function useBoostScoreTransform( periods: Period[] ): ScoreGraphAlignedData | [] {
|
|
24
|
+
return useMemo( () => {
|
|
25
|
+
if ( ! periods?.length || ! periods[ 0 ].dimensions ) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const timestamps = periods.map( ( { timestamp } ) => timestamp / 1000 );
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
timestamps,
|
|
32
|
+
getPeriodDimension( 'desktop_overall_score', periods ),
|
|
33
|
+
getPeriodDimension( 'mobile_overall_score', periods ),
|
|
34
|
+
];
|
|
35
|
+
}, [ periods ] );
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { throttle } from '@wordpress/compose';
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import uPlot from 'uplot';
|
|
4
|
+
|
|
5
|
+
const THROTTLE_DURATION = 400; // in ms
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom hook to handle resizing of uPlot charts.
|
|
9
|
+
*
|
|
10
|
+
* @param {React.RefObject<uPlot>} uplotRef - The ref object for the uPlot instance.
|
|
11
|
+
* @param {React.RefObject<HTMLDivElement>} containerRef - The ref object for the container div.
|
|
12
|
+
*/
|
|
13
|
+
export default function useResize(
|
|
14
|
+
uplotRef: React.RefObject< uPlot >,
|
|
15
|
+
containerRef: React.RefObject< HTMLDivElement >
|
|
16
|
+
) {
|
|
17
|
+
useEffect( () => {
|
|
18
|
+
if ( ! uplotRef.current || ! containerRef.current ) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const resizeChart = throttle( () => {
|
|
23
|
+
// Repeat the check since resize can happen much later than event registration.
|
|
24
|
+
if ( ! uplotRef.current || ! containerRef.current ) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Only update width, not height.
|
|
29
|
+
uplotRef.current.setSize( {
|
|
30
|
+
height: uplotRef.current.height,
|
|
31
|
+
width: containerRef.current.clientWidth,
|
|
32
|
+
} );
|
|
33
|
+
}, THROTTLE_DURATION );
|
|
34
|
+
resizeChart();
|
|
35
|
+
window.addEventListener( 'resize', resizeChart );
|
|
36
|
+
|
|
37
|
+
// Cleanup on unmount.
|
|
38
|
+
return () => window.removeEventListener( 'resize', resizeChart );
|
|
39
|
+
}, [ uplotRef, containerRef ] );
|
|
40
|
+
}
|
|
@@ -259,6 +259,19 @@ class Gridicon extends Component< GridiconProps > {
|
|
|
259
259
|
<path d="M20 4v2h-2V4H6v2H4V4c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2v-2h2v2h12v-2h2v2c1.105 0 2-.895 2-2V6c0-1.105-.895-2-2-2zM6 16H4v-3h2v3zm0-5H4V8h2v3zm4 4V9l4.5 3-4.5 3zm10 1h-2v-3h2v3zm0-5h-2V8h2v3z" />
|
|
260
260
|
</g>
|
|
261
261
|
);
|
|
262
|
+
case 'gridicons-lock':
|
|
263
|
+
return (
|
|
264
|
+
<>
|
|
265
|
+
<g id="lock">
|
|
266
|
+
<path
|
|
267
|
+
d="M18,8h-1V7c0-2.757-2.243-5-5-5S7,4.243,7,7v1H6c-1.105,0-2,0.895-2,2v10c0,1.105,0.895,2,2,2h12c1.105,0,2-0.895,2-2V10
|
|
268
|
+
C20,8.895,19.105,8,18,8z M9,7c0-1.654,1.346-3,3-3s3,1.346,3,3v1H9V7z M13,15.723V18h-2v-2.277c-0.595-0.346-1-0.984-1-1.723
|
|
269
|
+
c0-1.105,0.895-2,2-2s2,0.895,2,2C14,14.738,13.595,15.376,13,15.723z"
|
|
270
|
+
/>
|
|
271
|
+
</g>
|
|
272
|
+
<g id="Layer_1"></g>
|
|
273
|
+
</>
|
|
274
|
+
);
|
|
262
275
|
}
|
|
263
276
|
}
|
|
264
277
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import ReactSlider from 'react-slider';
|
|
4
|
-
import {
|
|
4
|
+
import { NumberSliderProps } from './types';
|
|
5
5
|
import './style.scss';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Generate
|
|
8
|
+
* Generate Number Slider
|
|
9
9
|
* More support from the original ReactSlider component: https://zillow.github.io/react-slider/
|
|
10
10
|
*
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {NumberSliderProps} props - Props
|
|
12
12
|
* @returns {React.ReactElement} - JSX element
|
|
13
13
|
*/
|
|
14
|
-
const
|
|
14
|
+
const NumberSlider: React.FC< NumberSliderProps > = ( {
|
|
15
15
|
className,
|
|
16
16
|
maxValue = 100,
|
|
17
17
|
minValue = 0,
|
|
@@ -24,8 +24,8 @@ const PricingSlider: React.FC< PricingSliderProps > = ( {
|
|
|
24
24
|
} ) => {
|
|
25
25
|
const [ isThumbHolding, setIsThumbHolding ] = React.useState( false );
|
|
26
26
|
|
|
27
|
-
const componentClassName = classNames( 'jp-components-
|
|
28
|
-
'jp-components-
|
|
27
|
+
const componentClassName = classNames( 'jp-components-number-slider', className, {
|
|
28
|
+
'jp-components-number-slider--is-holding': isThumbHolding,
|
|
29
29
|
} );
|
|
30
30
|
|
|
31
31
|
const onBeforeChangeCallback = beforeValue => {
|
|
@@ -51,12 +51,12 @@ const PricingSlider: React.FC< PricingSliderProps > = ( {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
<div className={ componentClassName } data-testid="
|
|
54
|
+
<div className={ componentClassName } data-testid="number-slider">
|
|
55
55
|
<ReactSlider
|
|
56
|
-
className="jp-components-
|
|
57
|
-
thumbClassName="jp-components-
|
|
58
|
-
thumbActiveClassName="jp-components-
|
|
59
|
-
trackClassName="jp-components-
|
|
56
|
+
className="jp-components-number-slider__control"
|
|
57
|
+
thumbClassName="jp-components-number-slider__thumb"
|
|
58
|
+
thumbActiveClassName="jp-components-number-slider__thumb--is-active"
|
|
59
|
+
trackClassName="jp-components-number-slider__track"
|
|
60
60
|
value={ value }
|
|
61
61
|
max={ maxValue }
|
|
62
62
|
min={ minValue }
|
|
@@ -70,4 +70,4 @@ const PricingSlider: React.FC< PricingSliderProps > = ( {
|
|
|
70
70
|
);
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
export default
|
|
73
|
+
export default NumberSlider;
|
|
@@ -9,33 +9,33 @@ $track-height: 8px;
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// On holding thumb styling
|
|
12
|
-
.jp-components-
|
|
13
|
-
.jp-components-
|
|
12
|
+
.jp-components-number-slider--is-holding {
|
|
13
|
+
.jp-components-number-slider__thumb {
|
|
14
14
|
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04), 0 0 0 3px rgba(6, 158, 8, 0.25);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// Base styles
|
|
19
|
-
.jp-components-
|
|
19
|
+
.jp-components-number-slider__control {
|
|
20
20
|
width: 100%;
|
|
21
21
|
height: $thumb-height;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
.jp-components-
|
|
24
|
+
.jp-components-number-slider__track {
|
|
25
25
|
@include adjust-track-rail-styles;
|
|
26
26
|
top: calc(($thumb-height / 2) - ($track-height / 2));
|
|
27
27
|
background: var(--jp-gray);
|
|
28
28
|
|
|
29
|
-
&.jp-components-
|
|
29
|
+
&.jp-components-number-slider__track-0 {
|
|
30
30
|
background: var(--jp-green-40);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
&.jp-components-
|
|
33
|
+
&.jp-components-number-slider__track-1 {
|
|
34
34
|
background: var(--jp-gray);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
.jp-components-
|
|
38
|
+
.jp-components-number-slider__thumb {
|
|
39
39
|
display: flex;
|
|
40
40
|
align-items: center;
|
|
41
41
|
justify-content: center;
|
|
@@ -56,7 +56,7 @@ $track-height: 8px;
|
|
|
56
56
|
cursor: pointer;
|
|
57
57
|
|
|
58
58
|
// On focus thumb styling
|
|
59
|
-
&.jp-components-
|
|
59
|
+
&.jp-components-number-slider__thumb--is-active {
|
|
60
60
|
outline: none;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -4,9 +4,9 @@ interface HTMLPropsWithRefCallback< T > extends HTMLProps< T > {
|
|
|
4
4
|
ref: RefCallback< T >;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export type
|
|
7
|
+
export type NumberSliderProps = {
|
|
8
8
|
/**
|
|
9
|
-
* The wrapper class name of this
|
|
9
|
+
* The wrapper class name of this NumberSlider component.
|
|
10
10
|
*/
|
|
11
11
|
className?: string;
|
|
12
12
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import './style.scss';
|
|
2
|
+
|
|
3
|
+
type PopoverProps = {
|
|
4
|
+
action: React.ReactNode;
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const Popover: React.FC< PopoverProps > = ( { icon, children, action } ) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="jp-popover">
|
|
12
|
+
<div className="jp-popover__icon">{ icon }</div>
|
|
13
|
+
<div className="jp-popover__body">{ children }</div>
|
|
14
|
+
<div className="jp-popover__action">{ action }</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Popover;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.jp-popover {
|
|
2
|
+
width: 600px;
|
|
3
|
+
max-width: calc( 100% - 32px );
|
|
4
|
+
padding: 16px;
|
|
5
|
+
background-color: #fff;
|
|
6
|
+
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.06), 0px 4px 8px 0px rgba(0, 0, 0, 0.03);
|
|
7
|
+
border: 1px solid rgba(220, 220, 222, 1);
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
text-align: center;
|
|
10
|
+
position: absolute;
|
|
11
|
+
top: 50%;
|
|
12
|
+
left: 50%;
|
|
13
|
+
transform: translate(-50%, -50%);
|
|
14
|
+
|
|
15
|
+
> * {
|
|
16
|
+
margin: 16px;
|
|
17
|
+
clear: both;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__body p {
|
|
21
|
+
font-weight: 400;
|
|
22
|
+
line-height: 1.6em;
|
|
23
|
+
font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -63,6 +63,11 @@ const ProductPrice: React.FC< ProductPriceProps > = ( {
|
|
|
63
63
|
hidePriceFraction={ hidePriceFraction }
|
|
64
64
|
/>
|
|
65
65
|
) }
|
|
66
|
+
{ discountElt && (
|
|
67
|
+
<Text className={ classnames( styles[ 'promo-label' ], 'product-price_promo_label' ) }>
|
|
68
|
+
{ discountElt }
|
|
69
|
+
</Text>
|
|
70
|
+
) }
|
|
66
71
|
</div>
|
|
67
72
|
</div>
|
|
68
73
|
<div className={ styles.footer }>
|
|
@@ -76,11 +81,6 @@ const ProductPrice: React.FC< ProductPriceProps > = ( {
|
|
|
76
81
|
{ promoLabel }
|
|
77
82
|
</Text>
|
|
78
83
|
) }
|
|
79
|
-
{ discountElt && (
|
|
80
|
-
<Text className={ classnames( styles[ 'promo-label' ], 'product-price_promo_label' ) }>
|
|
81
|
-
{ discountElt }
|
|
82
|
-
</Text>
|
|
83
|
-
) }
|
|
84
84
|
</div>
|
|
85
85
|
</>
|
|
86
86
|
);
|