@automattic/jetpack-components 0.39.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/.gitattributes +8 -0
- package/CHANGELOG.md +747 -0
- package/LICENSE.txt +357 -0
- package/README.md +11 -0
- package/SECURITY.md +38 -0
- package/components/action-button/index.jsx +70 -0
- package/components/action-button/stories/index.stories.jsx +38 -0
- package/components/action-button/style.module.scss +11 -0
- package/components/action-button/test/component.jsx +16 -0
- package/components/action-popover/index.tsx +105 -0
- package/components/action-popover/stories/index.stories.jsx +115 -0
- package/components/action-popover/stories/styles.module.scss +7 -0
- package/components/action-popover/styles.module.scss +49 -0
- package/components/action-popover/types.ts +20 -0
- package/components/admin-page/index.tsx +58 -0
- package/components/admin-page/stories/index.stories.tsx +35 -0
- package/components/admin-page/stories/style.module.scss +13 -0
- package/components/admin-page/style.module.scss +11 -0
- package/components/admin-page/types.ts +41 -0
- package/components/admin-section/basic/index.tsx +15 -0
- package/components/admin-section/basic/style.module.scss +3 -0
- package/components/admin-section/hero/index.tsx +15 -0
- package/components/admin-section/hero/style.module.scss +3 -0
- package/components/admin-section/stories/index.stories.tsx +60 -0
- package/components/admin-section/types.ts +6 -0
- package/components/alert/index.tsx +61 -0
- package/components/alert/stories/index.stories.jsx +24 -0
- package/components/alert/style.module.scss +43 -0
- package/components/automattic-byline-logo/README.md +16 -0
- package/components/automattic-byline-logo/index.tsx +38 -0
- package/components/automattic-byline-logo/stories/index.stories.tsx +20 -0
- package/components/automattic-byline-logo/test/component.tsx +28 -0
- package/components/automattic-byline-logo/types.ts +18 -0
- package/components/boost-score-bar/index.tsx +103 -0
- package/components/boost-score-bar/stories/index.stories.tsx +39 -0
- package/components/boost-score-bar/style.scss +184 -0
- package/components/boost-score-bar/test/component.tsx +102 -0
- package/components/boost-score-bar/types.ts +9 -0
- package/components/button/index.tsx +84 -0
- package/components/button/stories/index.mdx +90 -0
- package/components/button/stories/index.stories.tsx +400 -0
- package/components/button/stories/style.module.scss +20 -0
- package/components/button/style.module.scss +212 -0
- package/components/button/types.ts +21 -0
- package/components/contextual-upgrade-trigger/README.md +81 -0
- package/components/contextual-upgrade-trigger/index.tsx +44 -0
- package/components/contextual-upgrade-trigger/stories/index.stories.tsx +25 -0
- package/components/contextual-upgrade-trigger/style.module.scss +75 -0
- package/components/contextual-upgrade-trigger/types.ts +9 -0
- package/components/decorative-card/index.tsx +44 -0
- package/components/decorative-card/stories/index.stories.tsx +18 -0
- package/components/decorative-card/style.scss +116 -0
- package/components/decorative-card/types.ts +16 -0
- package/components/dialog/index.tsx +69 -0
- package/components/dialog/stories/boost.png +0 -0
- package/components/dialog/stories/declaration.d.ts +1 -0
- package/components/dialog/stories/index.stories.tsx +118 -0
- package/components/dialog/stories/js-components.components.dialog.mdx +62 -0
- package/components/dialog/stories/js-components.components.dialog.stories.js +37 -0
- package/components/dialog/stories/style.module.scss +21 -0
- package/components/dialog/style.module.scss +30 -0
- package/components/donut-meter/index.tsx +147 -0
- package/components/donut-meter/stories/index.stories.tsx +51 -0
- package/components/donut-meter/style.scss +11 -0
- package/components/donut-meter/test/component.tsx +24 -0
- package/components/gridicon/index.tsx +298 -0
- package/components/gridicon/stories/index.stories.tsx +18 -0
- package/components/gridicon/style.scss +16 -0
- package/components/gridicon/types.ts +48 -0
- package/components/icon-tooltip/index.tsx +96 -0
- package/components/icon-tooltip/stories/index.stories.tsx +108 -0
- package/components/icon-tooltip/stories/style.scss +36 -0
- package/components/icon-tooltip/style.scss +121 -0
- package/components/icon-tooltip/test/component.tsx +15 -0
- package/components/icon-tooltip/types.ts +76 -0
- package/components/icons/README.md +98 -0
- package/components/icons/index.tsx +369 -0
- package/components/icons/stories/index.stories.tsx +65 -0
- package/components/icons/stories/style.module.scss +33 -0
- package/components/icons/style.module.scss +40 -0
- package/components/icons/types.ts +38 -0
- package/components/indeterminate-progress-bar/index.tsx +28 -0
- package/components/indeterminate-progress-bar/stories/index.stories.tsx +14 -0
- package/components/indeterminate-progress-bar/style.module.scss +34 -0
- package/components/indeterminate-progress-bar/types.ts +6 -0
- package/components/jetpack-footer/README.md +21 -0
- package/components/jetpack-footer/index.tsx +94 -0
- package/components/jetpack-footer/stories/index.stories.tsx +40 -0
- package/components/jetpack-footer/style.scss +116 -0
- package/components/jetpack-footer/test/component.tsx +45 -0
- package/components/jetpack-footer/types.ts +36 -0
- package/components/jetpack-logo/README.md +20 -0
- package/components/jetpack-logo/index.tsx +46 -0
- package/components/jetpack-logo/stories/index.stories.tsx +20 -0
- package/components/jetpack-logo/test/component.tsx +16 -0
- package/components/jetpack-logo/types.ts +7 -0
- package/components/jetpack-search-logo/README.md +20 -0
- package/components/jetpack-search-logo/index.tsx +93 -0
- package/components/jetpack-search-logo/stories/index.stories.tsx +21 -0
- package/components/jetpack-search-logo/test/component.tsx +16 -0
- package/components/jetpack-search-logo/types.ts +7 -0
- package/components/jetpack-vaultpress-backup-logo/README.md +19 -0
- package/components/jetpack-vaultpress-backup-logo/index.tsx +113 -0
- package/components/jetpack-vaultpress-backup-logo/stories/index.stories.tsx +20 -0
- package/components/jetpack-vaultpress-backup-logo/test/component.tsx +18 -0
- package/components/jetpack-vaultpress-backup-logo/types.ts +6 -0
- package/components/jetpack-videopress-logo/README.md +19 -0
- package/components/jetpack-videopress-logo/index.tsx +93 -0
- package/components/jetpack-videopress-logo/stories/index.stories.tsx +20 -0
- package/components/jetpack-videopress-logo/test/component.tsx +16 -0
- package/components/jetpack-videopress-logo/types.tsx +6 -0
- package/components/layout/README.md +9 -0
- package/components/layout/breakpoints.module.scss +20 -0
- package/components/layout/col/README.md +81 -0
- package/components/layout/col/index.tsx +49 -0
- package/components/layout/col/style.module.scss +28 -0
- package/components/layout/container/README.md +77 -0
- package/components/layout/container/index.tsx +42 -0
- package/components/layout/container/style.module.scss +35 -0
- package/components/layout/stories/index.stories.tsx +145 -0
- package/components/layout/stories/styles.module.scss +22 -0
- package/components/layout/types.ts +55 -0
- package/components/layout/use-breakpoint-match/README.md +43 -0
- package/components/layout/use-breakpoint-match/declaration.d.ts +9 -0
- package/components/layout/use-breakpoint-match/index.ts +79 -0
- package/components/loading-placeholder/README.md +22 -0
- package/components/loading-placeholder/index.tsx +23 -0
- package/components/loading-placeholder/stories/index.stories.tsx +78 -0
- package/components/loading-placeholder/style.module.scss +18 -0
- package/components/notice/index.tsx +98 -0
- package/components/notice/stories/index.stories.jsx +90 -0
- package/components/notice/style.module.scss +107 -0
- package/components/number-format/README.md +25 -0
- package/components/number-format/index.ts +16 -0
- package/components/pricing-card/index.tsx +127 -0
- package/components/pricing-card/stories/index.stories.tsx +55 -0
- package/components/pricing-card/style.scss +112 -0
- package/components/pricing-card/test/component.tsx +89 -0
- package/components/pricing-card/types.ts +52 -0
- package/components/pricing-slider/index.tsx +53 -0
- package/components/pricing-slider/stories/index.stories.tsx +44 -0
- package/components/pricing-slider/style.scss +55 -0
- package/components/pricing-slider/test/component.tsx +14 -0
- package/components/pricing-slider/types.ts +58 -0
- package/components/pricing-table/index.tsx +200 -0
- package/components/pricing-table/stories/index.stories.tsx +74 -0
- package/components/pricing-table/styles.module.scss +160 -0
- package/components/pricing-table/test/component.tsx +41 -0
- package/components/pricing-table/types.ts +84 -0
- package/components/product-offer/icons-card.tsx +47 -0
- package/components/product-offer/index.tsx +115 -0
- package/components/product-offer/product-offer-header.tsx +23 -0
- package/components/product-offer/stories/index.stories.tsx +104 -0
- package/components/product-offer/style.module.scss +93 -0
- package/components/product-offer/types.ts +125 -0
- package/components/product-price/index.tsx +90 -0
- package/components/product-price/price.tsx +35 -0
- package/components/product-price/stories/index.stories.tsx +33 -0
- package/components/product-price/style.module.scss +69 -0
- package/components/product-price/test/component.tsx +26 -0
- package/components/product-price/types.ts +73 -0
- package/components/qr-code/Readme.md +5 -0
- package/components/qr-code/index.tsx +78 -0
- package/components/qr-code/stories/index.stories.tsx +32 -0
- package/components/record-meter-bar/index.tsx +135 -0
- package/components/record-meter-bar/stories/index.stories.tsx +39 -0
- package/components/record-meter-bar/style.scss +38 -0
- package/components/record-meter-bar/test/component.tsx +52 -0
- package/components/spinner/README.md +28 -0
- package/components/spinner/index.jsx +45 -0
- package/components/spinner/stories/index.stories.jsx +20 -0
- package/components/spinner/style.scss +31 -0
- package/components/spinner/test/component.jsx +17 -0
- package/components/split-button/index.tsx +39 -0
- package/components/split-button/stories/index.stories.tsx +35 -0
- package/components/split-button/style.module.scss +21 -0
- package/components/split-button/types.ts +14 -0
- package/components/stat-card/index.tsx +49 -0
- package/components/stat-card/stories/index.mdx +35 -0
- package/components/stat-card/stories/index.stories.tsx +41 -0
- package/components/stat-card/style.module.scss +49 -0
- package/components/stat-card/test/component.tsx +24 -0
- package/components/stat-card/types.ts +28 -0
- package/components/status/README.md +42 -0
- package/components/status/index.tsx +40 -0
- package/components/status/stories/index.stories.tsx +44 -0
- package/components/status/style.module.scss +72 -0
- package/components/terms-of-service/index.tsx +63 -0
- package/components/terms-of-service/styles.scss +10 -0
- package/components/terms-of-service/test/index.test.jsx +52 -0
- package/components/terms-of-service/types.ts +30 -0
- package/components/testimonials/index.tsx +58 -0
- package/components/testimonials/style.scss +127 -0
- package/components/testimonials/test/component.tsx +103 -0
- package/components/testimonials/testimonial.tsx +47 -0
- package/components/testimonials/types.ts +17 -0
- package/components/text/constants.ts +30 -0
- package/components/text/index.tsx +91 -0
- package/components/text/stories/headings.index.stories.tsx +80 -0
- package/components/text/stories/index.mdx +195 -0
- package/components/text/stories/index.stories.tsx +69 -0
- package/components/text/stories/style.module.scss +64 -0
- package/components/text/stories/title.index.stories.tsx +34 -0
- package/components/text/style.module.scss +125 -0
- package/components/text/types.ts +53 -0
- package/components/theme-provider/Readme.md +49 -0
- package/components/theme-provider/globals.module.scss +6 -0
- package/components/theme-provider/index.tsx +151 -0
- package/components/theme-provider/stories/index.stories.tsx +164 -0
- package/components/theme-provider/stories/style.module.scss +79 -0
- package/components/theme-provider/types.ts +25 -0
- package/components/toggle-control/README.md +5 -0
- package/components/toggle-control/index.tsx +67 -0
- package/components/toggle-control/stories/index.stories.tsx +83 -0
- package/components/toggle-control/styles.module.scss +41 -0
- package/components/zendesk-chat/README.md +26 -0
- package/components/zendesk-chat/constants.ts +2 -0
- package/components/zendesk-chat/index.tsx +22 -0
- package/components/zendesk-chat/stories/index.stories.tsx +15 -0
- package/components/zendesk-chat/test/component.tsx +16 -0
- package/components/zendesk-chat/types.ts +3 -0
- package/index.ts +69 -0
- package/lib/locale/index.ts +48 -0
- package/lib/locale/test/index.ts +21 -0
- package/package.json +80 -0
- package/tools/get-product-checkout-url/Readme.md +32 -0
- package/tools/get-product-checkout-url/index.ts +31 -0
- package/tools/jp-redirect/README.md +61 -0
- package/tools/jp-redirect/index.ts +62 -0
- package/tools/jp-redirect/test/index.tsx +89 -0
- package/tools/jp-redirect/types.ts +49 -0
- package/tools/pricing-utils/README.md +16 -0
- package/tools/pricing-utils/index.ts +11 -0
- package/tools/pricing-utils/test/index.ts +39 -0
- package/tools/pricing-utils/types.ts +12 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getCurrencyObject } from '@automattic/format-currency';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import Text from '../text';
|
|
4
|
+
import styles from './style.module.scss';
|
|
5
|
+
import type { PriceProps } from './types';
|
|
6
|
+
import type React from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* React component to render a Price composition.
|
|
10
|
+
*
|
|
11
|
+
* @param {PriceProps} props - Component props.
|
|
12
|
+
* @returns {React.ReactNode} -Price react component.
|
|
13
|
+
*/
|
|
14
|
+
export const Price: React.FC< PriceProps > = ( { value, currency, isOff, hidePriceFraction } ) => {
|
|
15
|
+
const classNames = classnames( styles.price, 'product-price_price', {
|
|
16
|
+
[ styles[ 'is-not-off-price' ] ]: ! isOff,
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
const { symbol, integer, fraction } = getCurrencyObject( value, currency );
|
|
20
|
+
const showPriceFraction = ! hidePriceFraction || ! fraction.endsWith( '00' );
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Text className={ classNames } variant="headline-medium" component="p">
|
|
24
|
+
<Text className={ styles.symbol } component="sup" variant="title-medium">
|
|
25
|
+
{ symbol }
|
|
26
|
+
</Text>
|
|
27
|
+
{ integer }
|
|
28
|
+
{ showPriceFraction && (
|
|
29
|
+
<Text component="sup" variant="body-small" data-testid="PriceFraction">
|
|
30
|
+
<strong>{ fraction }</strong>
|
|
31
|
+
</Text>
|
|
32
|
+
) }
|
|
33
|
+
</Text>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CURRENCIES } from '@automattic/format-currency';
|
|
2
|
+
import ProductPrice from '../';
|
|
3
|
+
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'JS Packages/Components/Product Price',
|
|
7
|
+
component: ProductPrice,
|
|
8
|
+
argTypes: {
|
|
9
|
+
currency: {
|
|
10
|
+
control: { type: 'select' },
|
|
11
|
+
options: Object.keys( CURRENCIES ),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
} as ComponentMeta< typeof ProductPrice >;
|
|
15
|
+
|
|
16
|
+
// Export additional stories using pre-defined values
|
|
17
|
+
const Template: ComponentStory< typeof ProductPrice > = args => <ProductPrice { ...args } />;
|
|
18
|
+
|
|
19
|
+
const DefaultArgs = {
|
|
20
|
+
currency: 'USD',
|
|
21
|
+
price: 24.92,
|
|
22
|
+
offPrice: 12.42,
|
|
23
|
+
showNotOffPrice: true,
|
|
24
|
+
isNotConvenientPrice: false,
|
|
25
|
+
hidePriceFraction: false,
|
|
26
|
+
hideDiscountLabel: false,
|
|
27
|
+
promoLabel: 'NEW',
|
|
28
|
+
legend: '/month, paid yearly',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Export Default story
|
|
32
|
+
export const _default = Template.bind( {} );
|
|
33
|
+
_default.args = DefaultArgs;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
align-items: flex-start;
|
|
5
|
+
justify-content: flex-end;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.price-container {
|
|
9
|
+
flex-grow: 2;
|
|
10
|
+
flex-basis: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.price {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
position: relative;
|
|
17
|
+
|
|
18
|
+
&:first-child {
|
|
19
|
+
margin-right: calc( var( --spacing-base ) * 2 );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.is-not-off-price {
|
|
23
|
+
color: var( --jp-gray-20);
|
|
24
|
+
|
|
25
|
+
&:after {
|
|
26
|
+
content: " ";
|
|
27
|
+
display: block;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 3px;
|
|
30
|
+
background: var( --jp-red);
|
|
31
|
+
border-radius: var( --jp-border-radius );
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 50%;
|
|
34
|
+
margin-top: -2px;
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
.footer {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
align-items: flex-start;
|
|
43
|
+
justify-content: flex-start;
|
|
44
|
+
margin-bottom: calc( var( --spacing-base ) * 3 );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.legend {
|
|
48
|
+
color: var( --jp-gray-40 );
|
|
49
|
+
font-size: var(--font-body-small);
|
|
50
|
+
line-height: 20px;
|
|
51
|
+
|
|
52
|
+
&::after {
|
|
53
|
+
content: "\200B"; // Pseudo element to keep height
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.promo-label {
|
|
58
|
+
background-color: var( --jp-yellow-10);
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
padding-left: var( --spacing-base );
|
|
61
|
+
padding-right: var( --spacing-base );
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
margin-left: var( --spacing-base );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.symbol {
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import ProductPrice from '../index';
|
|
3
|
+
|
|
4
|
+
describe( 'ProductPrice', () => {
|
|
5
|
+
const testProps = {
|
|
6
|
+
currency: 'USD',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe( 'When not hiding the price fraction', () => {
|
|
10
|
+
describe( 'And the fraction is zero', () => {
|
|
11
|
+
it( 'does not render the price fraction', () => {
|
|
12
|
+
render( <ProductPrice { ...testProps } hidePriceFraction={ true } price={ 17.0 } /> );
|
|
13
|
+
|
|
14
|
+
expect( screen.queryByTestId( 'PriceFraction' ) ).not.toBeInTheDocument();
|
|
15
|
+
} );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
describe( 'And the fraction is not zero', () => {
|
|
19
|
+
it( 'renders the price fraction', () => {
|
|
20
|
+
render( <ProductPrice { ...testProps } hidePriceFraction={ false } price={ 17.1 } /> );
|
|
21
|
+
|
|
22
|
+
expect( screen.getByTestId( 'PriceFraction' ) ).toBeInTheDocument();
|
|
23
|
+
} );
|
|
24
|
+
} );
|
|
25
|
+
} );
|
|
26
|
+
} );
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type ProductPriceProps = {
|
|
2
|
+
/**
|
|
3
|
+
* Product price.
|
|
4
|
+
*/
|
|
5
|
+
price?: number;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Product price with discount.
|
|
9
|
+
*/
|
|
10
|
+
offPrice?: number;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Product currency code.
|
|
14
|
+
*/
|
|
15
|
+
currency?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Product legend.
|
|
19
|
+
*/
|
|
20
|
+
legend?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Show the not off price.
|
|
24
|
+
*/
|
|
25
|
+
showNotOffPrice?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Force the price as a not off price.
|
|
29
|
+
*/
|
|
30
|
+
isNotConvenientPrice?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Hides the price fraction if fraction is zero.
|
|
34
|
+
*/
|
|
35
|
+
hidePriceFraction?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hides discount label
|
|
39
|
+
*/
|
|
40
|
+
hideDiscountLabel?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Custom promo label
|
|
44
|
+
*/
|
|
45
|
+
promoLabel?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Alternative legend with HTML syntax
|
|
49
|
+
*/
|
|
50
|
+
children?: React.ReactNode;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type PriceProps = {
|
|
54
|
+
/**
|
|
55
|
+
* Price value.
|
|
56
|
+
*/
|
|
57
|
+
value: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Price currency code.
|
|
61
|
+
*/
|
|
62
|
+
currency: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* True when it is an off price.
|
|
66
|
+
*/
|
|
67
|
+
isOff: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hides the price fraction if fraction is zero.
|
|
71
|
+
*/
|
|
72
|
+
hidePriceFraction?: boolean;
|
|
73
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import QRCodeLib from 'qrcode.react';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
|
|
4
|
+
type QRCodeLibProps = React.ComponentProps< typeof QRCodeLib >;
|
|
5
|
+
|
|
6
|
+
export type QRCodeProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The value to encode.
|
|
9
|
+
*/
|
|
10
|
+
value?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Background color of the QR code.
|
|
14
|
+
*/
|
|
15
|
+
bgColor?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Foreground color of the QR code.
|
|
19
|
+
*/
|
|
20
|
+
fgColor?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Error correction level of the QR code.
|
|
24
|
+
*/
|
|
25
|
+
level?: QRCodeLibProps[ 'level' ];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether to include margin in the QR code.
|
|
29
|
+
*/
|
|
30
|
+
includeMargin?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render the QR code as a `canvas` or `svg`.
|
|
34
|
+
*/
|
|
35
|
+
renderAs?: QRCodeLibProps[ 'renderAs' ];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Size of the QR code.
|
|
39
|
+
*/
|
|
40
|
+
size?: number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Image settings for the QR code.
|
|
44
|
+
*/
|
|
45
|
+
imageSettings?: QRCodeLibProps[ 'imageSettings' ];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Renders a QR Code.
|
|
50
|
+
*
|
|
51
|
+
* @param {QRCodeProps} props - Component props
|
|
52
|
+
* @returns {React.ReactNode} - React component.
|
|
53
|
+
*/
|
|
54
|
+
const QRCode: React.FC< QRCodeProps > = ( {
|
|
55
|
+
value = 'https://jetpack.com',
|
|
56
|
+
bgColor,
|
|
57
|
+
fgColor,
|
|
58
|
+
level,
|
|
59
|
+
includeMargin,
|
|
60
|
+
imageSettings,
|
|
61
|
+
renderAs = 'canvas',
|
|
62
|
+
size = 248,
|
|
63
|
+
} ) => {
|
|
64
|
+
return (
|
|
65
|
+
<QRCodeLib
|
|
66
|
+
value={ value }
|
|
67
|
+
size={ size }
|
|
68
|
+
bgColor={ bgColor }
|
|
69
|
+
fgColor={ fgColor }
|
|
70
|
+
level={ level }
|
|
71
|
+
includeMargin={ includeMargin }
|
|
72
|
+
imageSettings={ imageSettings }
|
|
73
|
+
renderAs={ renderAs }
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default QRCode;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import QRCode from '../index';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'JS Packages/Components/QRCode',
|
|
5
|
+
component: QRCode,
|
|
6
|
+
argTypes: {
|
|
7
|
+
value: {
|
|
8
|
+
control: { type: 'text' },
|
|
9
|
+
},
|
|
10
|
+
size: {
|
|
11
|
+
control: { type: 'number' },
|
|
12
|
+
},
|
|
13
|
+
level: {
|
|
14
|
+
control: { type: 'select' },
|
|
15
|
+
options: [ 'L', 'M', 'Q', 'H' ],
|
|
16
|
+
},
|
|
17
|
+
fgColor: {
|
|
18
|
+
control: { type: 'color' },
|
|
19
|
+
},
|
|
20
|
+
bgColor: {
|
|
21
|
+
control: { type: 'color' },
|
|
22
|
+
},
|
|
23
|
+
renderAs: {
|
|
24
|
+
control: { type: 'select' },
|
|
25
|
+
options: [ 'canvas', 'svg' ],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Template = args => <QRCode { ...args } />;
|
|
31
|
+
|
|
32
|
+
export const _default = Template.bind( {} );
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import numberFormat from '../number-format';
|
|
4
|
+
|
|
5
|
+
import './style.scss';
|
|
6
|
+
|
|
7
|
+
type RecordMeterBarItem = {
|
|
8
|
+
/**
|
|
9
|
+
* Count for the given item
|
|
10
|
+
*/
|
|
11
|
+
count: number;
|
|
12
|
+
/**
|
|
13
|
+
* Label to be used for the given item
|
|
14
|
+
*/
|
|
15
|
+
label: string;
|
|
16
|
+
/**
|
|
17
|
+
* Color code for the background color for the item
|
|
18
|
+
*/
|
|
19
|
+
backgroundColor: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type RecordMeterBarProps = {
|
|
23
|
+
/**
|
|
24
|
+
* Total number of items for the record meter. If not provided, its is the sum of item.count of all items.
|
|
25
|
+
*/
|
|
26
|
+
totalCount?: number;
|
|
27
|
+
/**
|
|
28
|
+
* The items to display in Record meter.
|
|
29
|
+
*/
|
|
30
|
+
items: Array< RecordMeterBarItem >;
|
|
31
|
+
/**
|
|
32
|
+
* The formatting style for legend item display. If not provided, it defaults to showing legend label after count
|
|
33
|
+
*/
|
|
34
|
+
showLegendLabelBeforeCount?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The sort style for legend item. If not provided, it defaults to no sorting.
|
|
37
|
+
*/
|
|
38
|
+
sortByCount?: 'ascending' | 'descending';
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate Record Meter bar
|
|
43
|
+
*
|
|
44
|
+
* @param {RecordMeterBarProps} props - Props
|
|
45
|
+
* @returns {React.ReactElement} - JSX element
|
|
46
|
+
*/
|
|
47
|
+
const RecordMeterBar: React.FC< RecordMeterBarProps > = ( {
|
|
48
|
+
totalCount,
|
|
49
|
+
items = [],
|
|
50
|
+
showLegendLabelBeforeCount = false,
|
|
51
|
+
sortByCount,
|
|
52
|
+
} ) => {
|
|
53
|
+
const total = useMemo( () => {
|
|
54
|
+
// If total count is not given, then compute it from items' count
|
|
55
|
+
return (
|
|
56
|
+
totalCount ||
|
|
57
|
+
items.reduce( ( currentTotal, { count } ) => {
|
|
58
|
+
return currentTotal + count;
|
|
59
|
+
}, 0 )
|
|
60
|
+
);
|
|
61
|
+
}, [ items, totalCount ] );
|
|
62
|
+
|
|
63
|
+
const itemsToRender = useMemo( () => {
|
|
64
|
+
if ( sortByCount ) {
|
|
65
|
+
// create a new array because .sort() updates the array in place.
|
|
66
|
+
return [ ...items ].sort( ( a, z ) => {
|
|
67
|
+
return 'ascending' === sortByCount ? a.count - z.count : z.count - a.count;
|
|
68
|
+
} );
|
|
69
|
+
}
|
|
70
|
+
return items;
|
|
71
|
+
}, [ items, sortByCount ] );
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="record-meter-bar">
|
|
75
|
+
<div className="record-meter-bar__items" aria-hidden="true">
|
|
76
|
+
{ itemsToRender.map( ( { count, label, backgroundColor } ) => {
|
|
77
|
+
const widthPercent = ( ( count / total ) * 100 ).toPrecision( 2 );
|
|
78
|
+
return (
|
|
79
|
+
<div key={ label } style={ { backgroundColor, flexBasis: `${ widthPercent }%` } }></div>
|
|
80
|
+
);
|
|
81
|
+
} ) }
|
|
82
|
+
</div>
|
|
83
|
+
<div className="record-meter-bar__legend" aria-hidden="true">
|
|
84
|
+
<ul className="record-meter-bar__legend--items">
|
|
85
|
+
{ itemsToRender.map( ( { count, label, backgroundColor } ) => {
|
|
86
|
+
const formattedCount = numberFormat( count );
|
|
87
|
+
return (
|
|
88
|
+
<li key={ label } className="record-meter-bar__legend--item">
|
|
89
|
+
<div
|
|
90
|
+
className="record-meter-bar__legend--item-circle"
|
|
91
|
+
style={ { backgroundColor } }
|
|
92
|
+
/>
|
|
93
|
+
{ ! showLegendLabelBeforeCount && (
|
|
94
|
+
<span>
|
|
95
|
+
<span className="record-meter-bar__legend--item-count">{ formattedCount }</span>
|
|
96
|
+
<span className="record-meter-bar__legend--item-label">{ label }</span>
|
|
97
|
+
</span>
|
|
98
|
+
) }
|
|
99
|
+
{ showLegendLabelBeforeCount && (
|
|
100
|
+
<span>
|
|
101
|
+
<span className="record-meter-bar__legend--item-label record-meter-bar__legend--item-label-first">
|
|
102
|
+
{ label }
|
|
103
|
+
</span>
|
|
104
|
+
<span className="record-meter-bar__legend--item-count">
|
|
105
|
+
({ formattedCount })
|
|
106
|
+
</span>
|
|
107
|
+
</span>
|
|
108
|
+
) }
|
|
109
|
+
</li>
|
|
110
|
+
);
|
|
111
|
+
} ) }
|
|
112
|
+
</ul>
|
|
113
|
+
</div>
|
|
114
|
+
<table className="screen-reader-text">
|
|
115
|
+
<caption>{ __( 'Summary of the records', 'jetpack' ) }</caption>
|
|
116
|
+
<tbody>
|
|
117
|
+
<tr>
|
|
118
|
+
<th scope="col">{ __( 'Record type', 'jetpack' ) }</th>
|
|
119
|
+
<th scope="col">{ __( 'Record count', 'jetpack' ) }</th>
|
|
120
|
+
</tr>
|
|
121
|
+
{ itemsToRender.map( ( { label, count } ) => {
|
|
122
|
+
return (
|
|
123
|
+
<tr key={ label }>
|
|
124
|
+
<td>{ label }</td>
|
|
125
|
+
<td>{ count }</td>
|
|
126
|
+
</tr>
|
|
127
|
+
);
|
|
128
|
+
} ) }
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export default RecordMeterBar;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import RecordMeterBar, { RecordMeterBarProps } from '../index';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'JS Packages/Components/RecordMeterBar',
|
|
5
|
+
component: RecordMeterBar,
|
|
6
|
+
argTypes: {
|
|
7
|
+
sortByCount: {
|
|
8
|
+
control: { type: 'select' },
|
|
9
|
+
options: [ undefined, 'ascending', 'descending' ],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Template = args => <RecordMeterBar { ...args } />;
|
|
15
|
+
|
|
16
|
+
const DefaultArgs: RecordMeterBarProps = {
|
|
17
|
+
items: [
|
|
18
|
+
{ count: 18, label: 'Posts', backgroundColor: '#00BA37' },
|
|
19
|
+
{ count: 30, label: 'Plugins', backgroundColor: '#3895BA' },
|
|
20
|
+
{ count: 52, label: 'Comments', backgroundColor: '#E68B28' },
|
|
21
|
+
{ count: 24, label: 'Authors', backgroundColor: '#3859BA' },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Export Default story
|
|
26
|
+
export const _default = Template.bind( {} );
|
|
27
|
+
_default.args = DefaultArgs;
|
|
28
|
+
|
|
29
|
+
export const WithTotalCount = Template.bind( {} );
|
|
30
|
+
WithTotalCount.args = {
|
|
31
|
+
...DefaultArgs,
|
|
32
|
+
totalCount: 200,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const LabelBeforeCount = Template.bind( {} );
|
|
36
|
+
LabelBeforeCount.args = {
|
|
37
|
+
...DefaultArgs,
|
|
38
|
+
showLegendLabelBeforeCount: true,
|
|
39
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.record-meter-bar {
|
|
2
|
+
padding-block: 1em;
|
|
3
|
+
|
|
4
|
+
&__items {
|
|
5
|
+
height: 2rem;
|
|
6
|
+
display: flex;
|
|
7
|
+
border-radius: 1rem;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
margin-bottom: 2em;
|
|
10
|
+
background-color: var(--jp-gray-off);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__legend {
|
|
14
|
+
&--items {
|
|
15
|
+
display: flex;
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
&--item {
|
|
19
|
+
display: flex;
|
|
20
|
+
margin: 0;
|
|
21
|
+
margin-inline-end: 1em;
|
|
22
|
+
align-items: center;
|
|
23
|
+
}
|
|
24
|
+
&--item-circle {
|
|
25
|
+
width: 1rem;
|
|
26
|
+
height: 1rem;
|
|
27
|
+
display: inline-block;
|
|
28
|
+
margin-inline-end: 0.4em;
|
|
29
|
+
border-radius: 100%;
|
|
30
|
+
}
|
|
31
|
+
&--item-count {
|
|
32
|
+
margin-inline-end: 0.4em;
|
|
33
|
+
}
|
|
34
|
+
&--item-label-first {
|
|
35
|
+
margin-inline-end: 0.4em;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { render, queryByAttribute } from '@testing-library/react';
|
|
2
|
+
import RecordMeterBar, { RecordMeterBarProps } from '../index';
|
|
3
|
+
|
|
4
|
+
const getRecordBarItems = ( container: HTMLElement ) => {
|
|
5
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
6
|
+
return queryByAttribute( 'class', container, 'record-meter-bar__items' ).children;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const getRecordBarLegendItems = ( container: HTMLElement ) => {
|
|
10
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
11
|
+
return queryByAttribute( 'class', container, 'record-meter-bar__legend--items' ).children;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe( 'RecordMeterBar', () => {
|
|
15
|
+
const testProps: RecordMeterBarProps = {
|
|
16
|
+
items: [
|
|
17
|
+
{ count: 18, label: 'Posts', backgroundColor: '#00BA37' },
|
|
18
|
+
{ count: 30, label: 'Plugins', backgroundColor: '#3895BA' },
|
|
19
|
+
{ count: 52, label: 'Comments', backgroundColor: '#E68B28' },
|
|
20
|
+
{ count: 24, label: 'Authors', backgroundColor: '#3859BA' },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
it( 'renders nothing when no items are passed', () => {
|
|
24
|
+
const { container } = render( <RecordMeterBar items={ [] } /> );
|
|
25
|
+
|
|
26
|
+
expect( getRecordBarItems( container ) ).toHaveLength( 0 );
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
it( 'renders the bar when NO totalCount is passed', () => {
|
|
30
|
+
const { container } = render( <RecordMeterBar { ...testProps } /> );
|
|
31
|
+
|
|
32
|
+
expect( getRecordBarItems( container ) ).toHaveLength( 4 );
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
it( 'renders the bar when totalCount IS passed', () => {
|
|
36
|
+
const { container } = render( <RecordMeterBar { ...testProps } totalCount={ 200 } /> );
|
|
37
|
+
|
|
38
|
+
expect( getRecordBarItems( container ) ).toHaveLength( 4 );
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
it( 'renders the legend when NO totalCount is passed', () => {
|
|
42
|
+
const { container } = render( <RecordMeterBar { ...testProps } /> );
|
|
43
|
+
|
|
44
|
+
expect( getRecordBarLegendItems( container ) ).toHaveLength( 4 );
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
it( 'renders the legend when totalCount IS passed', () => {
|
|
48
|
+
const { container } = render( <RecordMeterBar { ...testProps } totalCount={ 200 } /> );
|
|
49
|
+
|
|
50
|
+
expect( getRecordBarLegendItems( container ) ).toHaveLength( 4 );
|
|
51
|
+
} );
|
|
52
|
+
} );
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Spinner (DEPRECATED)
|
|
2
|
+
|
|
3
|
+
## We encourage to use [core Spinner](https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/spinner#readme) component instead
|
|
4
|
+
---------
|
|
5
|
+
|
|
6
|
+
Spinner is a React component for rendering a loading indicator.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```jsx
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import Spinner from '@automattic/jetpack-components';
|
|
13
|
+
|
|
14
|
+
export default class extends React.Component {
|
|
15
|
+
render() {
|
|
16
|
+
return <Spinner />;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
The following props can be passed to the Spinner component:
|
|
24
|
+
|
|
25
|
+
| PROPERTY | TYPE | REQUIRED | DEFAULT | DESCRIPTION |
|
|
26
|
+
| --------- | -------- | -------- | --------- | ----------------------------------------------- |
|
|
27
|
+
| **size** | _number_ | no | `20` | The width and height of the spinner, in pixels. |
|
|
28
|
+
| **color** | _strong_ | no | `#000000` | The color of the spinner, in hex. |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import './style.scss';
|
|
5
|
+
|
|
6
|
+
const Spinner = props => {
|
|
7
|
+
const className = props.className + ' jp-components-spinner';
|
|
8
|
+
|
|
9
|
+
const styleOuter = {
|
|
10
|
+
width: props.size,
|
|
11
|
+
height: props.size,
|
|
12
|
+
fontSize: props.size, // allows border-width to be specified in em units
|
|
13
|
+
borderTopColor: props.color,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const styleInner = {
|
|
17
|
+
borderTopColor: props.color,
|
|
18
|
+
borderRightColor: props.color,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className={ className }>
|
|
23
|
+
<div className="jp-components-spinner__outer" style={ styleOuter }>
|
|
24
|
+
<div className="jp-components-spinner__inner" style={ styleInner } />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Spinner.propTypes = {
|
|
31
|
+
/** The spinner color. */
|
|
32
|
+
color: PropTypes.string,
|
|
33
|
+
/** CSS class names. */
|
|
34
|
+
className: PropTypes.string,
|
|
35
|
+
/** The spinner size. */
|
|
36
|
+
size: PropTypes.number,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
Spinner.defaultProps = {
|
|
40
|
+
color: '#FFFFFF',
|
|
41
|
+
className: '',
|
|
42
|
+
size: 20,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Spinner;
|