@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.
Files changed (235) hide show
  1. package/.gitattributes +8 -0
  2. package/CHANGELOG.md +747 -0
  3. package/LICENSE.txt +357 -0
  4. package/README.md +11 -0
  5. package/SECURITY.md +38 -0
  6. package/components/action-button/index.jsx +70 -0
  7. package/components/action-button/stories/index.stories.jsx +38 -0
  8. package/components/action-button/style.module.scss +11 -0
  9. package/components/action-button/test/component.jsx +16 -0
  10. package/components/action-popover/index.tsx +105 -0
  11. package/components/action-popover/stories/index.stories.jsx +115 -0
  12. package/components/action-popover/stories/styles.module.scss +7 -0
  13. package/components/action-popover/styles.module.scss +49 -0
  14. package/components/action-popover/types.ts +20 -0
  15. package/components/admin-page/index.tsx +58 -0
  16. package/components/admin-page/stories/index.stories.tsx +35 -0
  17. package/components/admin-page/stories/style.module.scss +13 -0
  18. package/components/admin-page/style.module.scss +11 -0
  19. package/components/admin-page/types.ts +41 -0
  20. package/components/admin-section/basic/index.tsx +15 -0
  21. package/components/admin-section/basic/style.module.scss +3 -0
  22. package/components/admin-section/hero/index.tsx +15 -0
  23. package/components/admin-section/hero/style.module.scss +3 -0
  24. package/components/admin-section/stories/index.stories.tsx +60 -0
  25. package/components/admin-section/types.ts +6 -0
  26. package/components/alert/index.tsx +61 -0
  27. package/components/alert/stories/index.stories.jsx +24 -0
  28. package/components/alert/style.module.scss +43 -0
  29. package/components/automattic-byline-logo/README.md +16 -0
  30. package/components/automattic-byline-logo/index.tsx +38 -0
  31. package/components/automattic-byline-logo/stories/index.stories.tsx +20 -0
  32. package/components/automattic-byline-logo/test/component.tsx +28 -0
  33. package/components/automattic-byline-logo/types.ts +18 -0
  34. package/components/boost-score-bar/index.tsx +103 -0
  35. package/components/boost-score-bar/stories/index.stories.tsx +39 -0
  36. package/components/boost-score-bar/style.scss +184 -0
  37. package/components/boost-score-bar/test/component.tsx +102 -0
  38. package/components/boost-score-bar/types.ts +9 -0
  39. package/components/button/index.tsx +84 -0
  40. package/components/button/stories/index.mdx +90 -0
  41. package/components/button/stories/index.stories.tsx +400 -0
  42. package/components/button/stories/style.module.scss +20 -0
  43. package/components/button/style.module.scss +212 -0
  44. package/components/button/types.ts +21 -0
  45. package/components/contextual-upgrade-trigger/README.md +81 -0
  46. package/components/contextual-upgrade-trigger/index.tsx +44 -0
  47. package/components/contextual-upgrade-trigger/stories/index.stories.tsx +25 -0
  48. package/components/contextual-upgrade-trigger/style.module.scss +75 -0
  49. package/components/contextual-upgrade-trigger/types.ts +9 -0
  50. package/components/decorative-card/index.tsx +44 -0
  51. package/components/decorative-card/stories/index.stories.tsx +18 -0
  52. package/components/decorative-card/style.scss +116 -0
  53. package/components/decorative-card/types.ts +16 -0
  54. package/components/dialog/index.tsx +69 -0
  55. package/components/dialog/stories/boost.png +0 -0
  56. package/components/dialog/stories/declaration.d.ts +1 -0
  57. package/components/dialog/stories/index.stories.tsx +118 -0
  58. package/components/dialog/stories/js-components.components.dialog.mdx +62 -0
  59. package/components/dialog/stories/js-components.components.dialog.stories.js +37 -0
  60. package/components/dialog/stories/style.module.scss +21 -0
  61. package/components/dialog/style.module.scss +30 -0
  62. package/components/donut-meter/index.tsx +147 -0
  63. package/components/donut-meter/stories/index.stories.tsx +51 -0
  64. package/components/donut-meter/style.scss +11 -0
  65. package/components/donut-meter/test/component.tsx +24 -0
  66. package/components/gridicon/index.tsx +298 -0
  67. package/components/gridicon/stories/index.stories.tsx +18 -0
  68. package/components/gridicon/style.scss +16 -0
  69. package/components/gridicon/types.ts +48 -0
  70. package/components/icon-tooltip/index.tsx +96 -0
  71. package/components/icon-tooltip/stories/index.stories.tsx +108 -0
  72. package/components/icon-tooltip/stories/style.scss +36 -0
  73. package/components/icon-tooltip/style.scss +121 -0
  74. package/components/icon-tooltip/test/component.tsx +15 -0
  75. package/components/icon-tooltip/types.ts +76 -0
  76. package/components/icons/README.md +98 -0
  77. package/components/icons/index.tsx +369 -0
  78. package/components/icons/stories/index.stories.tsx +65 -0
  79. package/components/icons/stories/style.module.scss +33 -0
  80. package/components/icons/style.module.scss +40 -0
  81. package/components/icons/types.ts +38 -0
  82. package/components/indeterminate-progress-bar/index.tsx +28 -0
  83. package/components/indeterminate-progress-bar/stories/index.stories.tsx +14 -0
  84. package/components/indeterminate-progress-bar/style.module.scss +34 -0
  85. package/components/indeterminate-progress-bar/types.ts +6 -0
  86. package/components/jetpack-footer/README.md +21 -0
  87. package/components/jetpack-footer/index.tsx +94 -0
  88. package/components/jetpack-footer/stories/index.stories.tsx +40 -0
  89. package/components/jetpack-footer/style.scss +116 -0
  90. package/components/jetpack-footer/test/component.tsx +45 -0
  91. package/components/jetpack-footer/types.ts +36 -0
  92. package/components/jetpack-logo/README.md +20 -0
  93. package/components/jetpack-logo/index.tsx +46 -0
  94. package/components/jetpack-logo/stories/index.stories.tsx +20 -0
  95. package/components/jetpack-logo/test/component.tsx +16 -0
  96. package/components/jetpack-logo/types.ts +7 -0
  97. package/components/jetpack-search-logo/README.md +20 -0
  98. package/components/jetpack-search-logo/index.tsx +93 -0
  99. package/components/jetpack-search-logo/stories/index.stories.tsx +21 -0
  100. package/components/jetpack-search-logo/test/component.tsx +16 -0
  101. package/components/jetpack-search-logo/types.ts +7 -0
  102. package/components/jetpack-vaultpress-backup-logo/README.md +19 -0
  103. package/components/jetpack-vaultpress-backup-logo/index.tsx +113 -0
  104. package/components/jetpack-vaultpress-backup-logo/stories/index.stories.tsx +20 -0
  105. package/components/jetpack-vaultpress-backup-logo/test/component.tsx +18 -0
  106. package/components/jetpack-vaultpress-backup-logo/types.ts +6 -0
  107. package/components/jetpack-videopress-logo/README.md +19 -0
  108. package/components/jetpack-videopress-logo/index.tsx +93 -0
  109. package/components/jetpack-videopress-logo/stories/index.stories.tsx +20 -0
  110. package/components/jetpack-videopress-logo/test/component.tsx +16 -0
  111. package/components/jetpack-videopress-logo/types.tsx +6 -0
  112. package/components/layout/README.md +9 -0
  113. package/components/layout/breakpoints.module.scss +20 -0
  114. package/components/layout/col/README.md +81 -0
  115. package/components/layout/col/index.tsx +49 -0
  116. package/components/layout/col/style.module.scss +28 -0
  117. package/components/layout/container/README.md +77 -0
  118. package/components/layout/container/index.tsx +42 -0
  119. package/components/layout/container/style.module.scss +35 -0
  120. package/components/layout/stories/index.stories.tsx +145 -0
  121. package/components/layout/stories/styles.module.scss +22 -0
  122. package/components/layout/types.ts +55 -0
  123. package/components/layout/use-breakpoint-match/README.md +43 -0
  124. package/components/layout/use-breakpoint-match/declaration.d.ts +9 -0
  125. package/components/layout/use-breakpoint-match/index.ts +79 -0
  126. package/components/loading-placeholder/README.md +22 -0
  127. package/components/loading-placeholder/index.tsx +23 -0
  128. package/components/loading-placeholder/stories/index.stories.tsx +78 -0
  129. package/components/loading-placeholder/style.module.scss +18 -0
  130. package/components/notice/index.tsx +98 -0
  131. package/components/notice/stories/index.stories.jsx +90 -0
  132. package/components/notice/style.module.scss +107 -0
  133. package/components/number-format/README.md +25 -0
  134. package/components/number-format/index.ts +16 -0
  135. package/components/pricing-card/index.tsx +127 -0
  136. package/components/pricing-card/stories/index.stories.tsx +55 -0
  137. package/components/pricing-card/style.scss +112 -0
  138. package/components/pricing-card/test/component.tsx +89 -0
  139. package/components/pricing-card/types.ts +52 -0
  140. package/components/pricing-slider/index.tsx +53 -0
  141. package/components/pricing-slider/stories/index.stories.tsx +44 -0
  142. package/components/pricing-slider/style.scss +55 -0
  143. package/components/pricing-slider/test/component.tsx +14 -0
  144. package/components/pricing-slider/types.ts +58 -0
  145. package/components/pricing-table/index.tsx +200 -0
  146. package/components/pricing-table/stories/index.stories.tsx +74 -0
  147. package/components/pricing-table/styles.module.scss +160 -0
  148. package/components/pricing-table/test/component.tsx +41 -0
  149. package/components/pricing-table/types.ts +84 -0
  150. package/components/product-offer/icons-card.tsx +47 -0
  151. package/components/product-offer/index.tsx +115 -0
  152. package/components/product-offer/product-offer-header.tsx +23 -0
  153. package/components/product-offer/stories/index.stories.tsx +104 -0
  154. package/components/product-offer/style.module.scss +93 -0
  155. package/components/product-offer/types.ts +125 -0
  156. package/components/product-price/index.tsx +90 -0
  157. package/components/product-price/price.tsx +35 -0
  158. package/components/product-price/stories/index.stories.tsx +33 -0
  159. package/components/product-price/style.module.scss +69 -0
  160. package/components/product-price/test/component.tsx +26 -0
  161. package/components/product-price/types.ts +73 -0
  162. package/components/qr-code/Readme.md +5 -0
  163. package/components/qr-code/index.tsx +78 -0
  164. package/components/qr-code/stories/index.stories.tsx +32 -0
  165. package/components/record-meter-bar/index.tsx +135 -0
  166. package/components/record-meter-bar/stories/index.stories.tsx +39 -0
  167. package/components/record-meter-bar/style.scss +38 -0
  168. package/components/record-meter-bar/test/component.tsx +52 -0
  169. package/components/spinner/README.md +28 -0
  170. package/components/spinner/index.jsx +45 -0
  171. package/components/spinner/stories/index.stories.jsx +20 -0
  172. package/components/spinner/style.scss +31 -0
  173. package/components/spinner/test/component.jsx +17 -0
  174. package/components/split-button/index.tsx +39 -0
  175. package/components/split-button/stories/index.stories.tsx +35 -0
  176. package/components/split-button/style.module.scss +21 -0
  177. package/components/split-button/types.ts +14 -0
  178. package/components/stat-card/index.tsx +49 -0
  179. package/components/stat-card/stories/index.mdx +35 -0
  180. package/components/stat-card/stories/index.stories.tsx +41 -0
  181. package/components/stat-card/style.module.scss +49 -0
  182. package/components/stat-card/test/component.tsx +24 -0
  183. package/components/stat-card/types.ts +28 -0
  184. package/components/status/README.md +42 -0
  185. package/components/status/index.tsx +40 -0
  186. package/components/status/stories/index.stories.tsx +44 -0
  187. package/components/status/style.module.scss +72 -0
  188. package/components/terms-of-service/index.tsx +63 -0
  189. package/components/terms-of-service/styles.scss +10 -0
  190. package/components/terms-of-service/test/index.test.jsx +52 -0
  191. package/components/terms-of-service/types.ts +30 -0
  192. package/components/testimonials/index.tsx +58 -0
  193. package/components/testimonials/style.scss +127 -0
  194. package/components/testimonials/test/component.tsx +103 -0
  195. package/components/testimonials/testimonial.tsx +47 -0
  196. package/components/testimonials/types.ts +17 -0
  197. package/components/text/constants.ts +30 -0
  198. package/components/text/index.tsx +91 -0
  199. package/components/text/stories/headings.index.stories.tsx +80 -0
  200. package/components/text/stories/index.mdx +195 -0
  201. package/components/text/stories/index.stories.tsx +69 -0
  202. package/components/text/stories/style.module.scss +64 -0
  203. package/components/text/stories/title.index.stories.tsx +34 -0
  204. package/components/text/style.module.scss +125 -0
  205. package/components/text/types.ts +53 -0
  206. package/components/theme-provider/Readme.md +49 -0
  207. package/components/theme-provider/globals.module.scss +6 -0
  208. package/components/theme-provider/index.tsx +151 -0
  209. package/components/theme-provider/stories/index.stories.tsx +164 -0
  210. package/components/theme-provider/stories/style.module.scss +79 -0
  211. package/components/theme-provider/types.ts +25 -0
  212. package/components/toggle-control/README.md +5 -0
  213. package/components/toggle-control/index.tsx +67 -0
  214. package/components/toggle-control/stories/index.stories.tsx +83 -0
  215. package/components/toggle-control/styles.module.scss +41 -0
  216. package/components/zendesk-chat/README.md +26 -0
  217. package/components/zendesk-chat/constants.ts +2 -0
  218. package/components/zendesk-chat/index.tsx +22 -0
  219. package/components/zendesk-chat/stories/index.stories.tsx +15 -0
  220. package/components/zendesk-chat/test/component.tsx +16 -0
  221. package/components/zendesk-chat/types.ts +3 -0
  222. package/index.ts +69 -0
  223. package/lib/locale/index.ts +48 -0
  224. package/lib/locale/test/index.ts +21 -0
  225. package/package.json +80 -0
  226. package/tools/get-product-checkout-url/Readme.md +32 -0
  227. package/tools/get-product-checkout-url/index.ts +31 -0
  228. package/tools/jp-redirect/README.md +61 -0
  229. package/tools/jp-redirect/index.ts +62 -0
  230. package/tools/jp-redirect/test/index.tsx +89 -0
  231. package/tools/jp-redirect/types.ts +49 -0
  232. package/tools/pricing-utils/README.md +16 -0
  233. package/tools/pricing-utils/index.ts +11 -0
  234. package/tools/pricing-utils/test/index.ts +39 -0
  235. 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,5 @@
1
+ # <QRCode>
2
+
3
+ Simple React component to render a QR Code.
4
+
5
+ It acts as an abstraction layer of [react.qrcode](https://github.com/zpao/qrcode.react) component, allowing to change it on the fly if it becomes needed.
@@ -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;