@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,90 @@
1
+ import { ExternalLink } from '@wordpress/components';
2
+ import Button from '../../button';
3
+ import Notice from '../index';
4
+
5
+ export default {
6
+ title: 'JS Packages/Components/Notice',
7
+ component: Notice,
8
+ argTypes: {
9
+ level: {
10
+ control: {
11
+ type: 'select',
12
+ },
13
+ options: [ 'info', 'success', 'warning', 'error' ],
14
+ },
15
+ hideCloseButton: {
16
+ control: {
17
+ type: 'boolean',
18
+ },
19
+ },
20
+ },
21
+ };
22
+
23
+ const Template = args => <Notice { ...args } />;
24
+
25
+ export const _default = Template.bind( {} );
26
+ _default.args = {
27
+ level: 'info',
28
+ title: 'Improve your hovercraft',
29
+ children:
30
+ 'Make your hovercraft better with HoverPack; the best hovercraft upgrade kit on the market.',
31
+ onClose: () => alert( 'Close clicked' ),
32
+ actions: [
33
+ <Button key="install" isPrimary>
34
+ Install now
35
+ </Button>,
36
+ <ExternalLink key="learn-more" href="https://en.wikipedia.org/wiki/Hovercraft">
37
+ Learn more
38
+ </ExternalLink>,
39
+ ],
40
+ hideCloseButton: false,
41
+ };
42
+
43
+ export const warning = Template.bind( {} );
44
+ warning.args = {
45
+ level: 'warning',
46
+ title: 'Your hovercraft is full of eels.',
47
+ children: (
48
+ <div>
49
+ Either your vehicle needs to be cleared, or some kind of translation error has occurred.
50
+ </div>
51
+ ),
52
+ actions: [
53
+ <Button key="bail" isPrimary>
54
+ Start Bailing
55
+ </Button>,
56
+ <ExternalLink key="learn-more" href="https://en.wikipedia.org/wiki/Hovercraft">
57
+ Learn more
58
+ </ExternalLink>,
59
+ ],
60
+ hideCloseButton: false,
61
+ };
62
+
63
+ export const success = Template.bind( {} );
64
+ success.args = {
65
+ level: 'success',
66
+ title: 'Your hovercraft has been upgraded.',
67
+ children: 'Please enjoy your newer, cooler hovercraft.',
68
+ onClose: () => alert( 'Close clicked' ),
69
+ actions: [
70
+ <Button key="start" isPrimary>
71
+ Start crafting
72
+ </Button>,
73
+ ],
74
+ hideCloseButton: false,
75
+ };
76
+
77
+ export const error = Template.bind( {} );
78
+ error.args = {
79
+ level: 'error',
80
+ title: 'The eels have stolen your hovercraft.',
81
+ children:
82
+ 'We were unable to remove the eels from your hovercraft. Please contact the authorities, as the eels are armed and dangerous.',
83
+ onClose: () => alert( 'Close clicked' ),
84
+ actions: [
85
+ <ExternalLink key="learn-more" href="https://en.wikipedia.org/wiki/Eel">
86
+ Learn more
87
+ </ExternalLink>,
88
+ ],
89
+ hideCloseButton: false,
90
+ };
@@ -0,0 +1,107 @@
1
+ .container {
2
+ display: flex;
3
+ align-items: flex-start;
4
+
5
+ font-size: var( --font-body );
6
+
7
+ border: 1px solid var( --jp-black );
8
+ border-radius: 4px;
9
+ border-color: var( --jp-gray-5 );
10
+ border-left-width: 6px;
11
+
12
+ padding: 24px 31px 27px 18px;
13
+ }
14
+
15
+ .icon-wrapper {
16
+ margin-right: 20px;
17
+ width: calc( var( --spacing-base ) * 3 );
18
+ height: calc( var( --spacing-base ) * 3 );
19
+ }
20
+
21
+ .close-button {
22
+ width: calc( var( --spacing-base ) * 3 );
23
+ height: calc( var( --spacing-base ) * 3 );
24
+
25
+ background-color: transparent;
26
+ border: none;
27
+ cursor: pointer;
28
+ outline: none;
29
+ }
30
+
31
+ // Mobile layout differences.
32
+ @media screen and ( max-width: 600px ) {
33
+ .container {
34
+ position: relative;
35
+ padding-top: 68px;
36
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.03), 0px 1px 2px rgba(0, 0, 0, 0.06);
37
+ }
38
+
39
+ .icon-wrapper {
40
+ position: absolute;
41
+ top: 24px;
42
+ left: 24px;
43
+ }
44
+
45
+ .close-button {
46
+ position: absolute;
47
+ top: 24px;
48
+ right: 24px;
49
+ }
50
+ }
51
+
52
+ .main-content {
53
+ flex-grow: 1;
54
+ }
55
+
56
+ .title {
57
+ font-weight: 600;
58
+ margin-bottom: 8px;
59
+ }
60
+
61
+ .action-bar {
62
+ display: flex;
63
+ align-items: center;
64
+ margin-top: 20px;
65
+
66
+ a {
67
+ &, &:hover, &:active, &:focus {
68
+ color: var( --jp-black );
69
+ }
70
+ }
71
+
72
+ > * {
73
+ margin-right: 24px;
74
+ }
75
+ }
76
+
77
+ .is-error {
78
+ border-left-color: var( --jp-red );
79
+
80
+ .icon {
81
+ fill: var( --jp-red );
82
+ }
83
+ }
84
+
85
+ .is-warning {
86
+ border-left-color: var( --jp-yellow-20 );
87
+
88
+ .icon {
89
+ fill: var( --jp-yellow-20 );
90
+ }
91
+ }
92
+
93
+ .is-info {
94
+ border-left-color: var( --black );
95
+
96
+ .icon {
97
+ fill: var( --black );
98
+ }
99
+ }
100
+
101
+ .is-success {
102
+ border-left-color: var( --jp-green );
103
+
104
+ .icon {
105
+ fill: var( --jp-green );
106
+ }
107
+ }
@@ -0,0 +1,25 @@
1
+ numberFormat
2
+ =========
3
+
4
+ This utility function can be used to format numbers so they are displayed in the format expected for a specific locale.
5
+
6
+ For example, a large number such as `123456` would be displayed as `123,456` in US English, and as `123 456` in FR French.
7
+
8
+ The function relies on `Intl.NumberFormat` to format the numbers, based on the locale information available in WordPress, or based on the browser locale as a fallback.
9
+
10
+ ## General Usage:
11
+
12
+ ```js
13
+ import { numberFormat } from '@automattic/jetpack-components';
14
+
15
+ render() {
16
+ const number = '123456';
17
+ const options = {
18
+ notation: 'compact',
19
+ compactDisplay: 'short',
20
+ };
21
+ return (
22
+ <>{ numberFormat( number ) }</> // 123K
23
+ );
24
+ }
25
+ ```
@@ -0,0 +1,16 @@
1
+ import { getUserLocale } from '../../lib/locale';
2
+
3
+ /**
4
+ * Format a number using the locale in use by the user viewing the page.
5
+ *
6
+ * @param {number} number - The number to format.
7
+ * @param {Intl.NumberFormatOptions} options - The format options
8
+ * @returns {string} Formatted number.
9
+ */
10
+ const numberFormat = ( number: number, options: Intl.NumberFormatOptions = {} ): string => {
11
+ const locale = getUserLocale();
12
+
13
+ return new Intl.NumberFormat( locale, options ).format( number );
14
+ };
15
+
16
+ export default numberFormat;
@@ -0,0 +1,127 @@
1
+ import { getCurrencyObject } from '@automattic/format-currency';
2
+ import { Button } from '@wordpress/components';
3
+ import { sprintf, __ } from '@wordpress/i18n';
4
+ import TermsOfService from '../terms-of-service';
5
+ import type { PricingCardProps } from './types';
6
+ import type { CurrencyObject } from '@automattic/format-currency/src/types';
7
+ import type React from 'react';
8
+
9
+ import './style.scss';
10
+
11
+ /**
12
+ * Whether or not to display a price's decimal part in the UI.
13
+ * Needed as `getCurrencyObject` will always return the decimal part populated even if it
14
+ * doesn't exist.
15
+ *
16
+ * @param {CurrencyObject} currencyObject -- A currency object returned from `getCurrencyObject`.
17
+ * @returns {boolean} Whether or not to display the price decimal part.
18
+ */
19
+ const showPriceDecimals = ( currencyObject: CurrencyObject ): boolean => {
20
+ return currencyObject.fraction.indexOf( '00' ) === -1;
21
+ };
22
+
23
+ /**
24
+ * The Pricing card component.
25
+ *
26
+ * @param {PricingCardProps} props -- The component props.
27
+ * @returns {React.ReactNode} The rendered component.
28
+ */
29
+ const PricingCard: React.FC< PricingCardProps > = ( {
30
+ currencyCode = 'USD',
31
+ priceDetails = __( '/month, paid yearly', 'jetpack' ),
32
+ ...props
33
+ } ) => {
34
+ const currencyObjectBefore = getCurrencyObject( props.priceBefore, currencyCode );
35
+ const currencyObjectAfter = getCurrencyObject( props.priceAfter, currencyCode );
36
+
37
+ return (
38
+ <div className="jp-components__pricing-card">
39
+ { props.icon && (
40
+ <div className="jp-components__pricing-card__icon">
41
+ { 'string' === typeof props.icon ? (
42
+ <img
43
+ src={ props.icon }
44
+ alt={ sprintf(
45
+ /* translators: placeholder is a product name */
46
+ __( 'Icon for the product %s', 'jetpack' ),
47
+ props.title
48
+ ) }
49
+ />
50
+ ) : (
51
+ props.icon
52
+ ) }
53
+ </div>
54
+ ) }
55
+ <h1 className="jp-components__pricing-card__title">{ props.title }</h1>
56
+ <div className="jp-components__pricing-card__pricing">
57
+ { props.priceBefore !== props.priceAfter && (
58
+ <div className="jp-components__pricing-card__price-before">
59
+ <span className="jp-components__pricing-card__currency">
60
+ { currencyObjectBefore.symbol }
61
+ </span>
62
+ <span className="jp-components__pricing-card__price">
63
+ { currencyObjectBefore.integer }
64
+ </span>
65
+ { showPriceDecimals( currencyObjectBefore ) && (
66
+ <span className="jp-components__pricing-card__price-decimal">
67
+ { ' ' }
68
+ { currencyObjectBefore.fraction }
69
+ </span>
70
+ ) }
71
+ <div className="jp-components__pricing-card__price-strikethrough"></div>
72
+ </div>
73
+ ) }
74
+ <div className="jp-components__pricing-card__price-after">
75
+ <span className="jp-components__pricing-card__currency">
76
+ { currencyObjectAfter.symbol }
77
+ </span>
78
+ <span className="jp-components__pricing-card__price">
79
+ { currencyObjectAfter.integer }
80
+ </span>
81
+ { showPriceDecimals( currencyObjectAfter ) && (
82
+ <span className="jp-components__pricing-card__price-decimal">
83
+ { currencyObjectAfter.fraction }
84
+ </span>
85
+ ) }
86
+ </div>
87
+ <span className="jp-components__pricing-card__price-details">{ priceDetails }</span>
88
+ </div>
89
+
90
+ { props.children && (
91
+ <div className="jp-components__pricing-card__extra-content-wrapper">{ props.children }</div>
92
+ ) }
93
+
94
+ { /*
95
+ * Keep the option to pass custom terms of service text,
96
+ * but by default, use the shared `TermsOfService` component
97
+ * and pass the CTA button's text to it
98
+ */ }
99
+ { props.tosText && <div className="jp-components__pricing-card__tos">{ props.tosText }</div> }
100
+
101
+ { props.ctaText && (
102
+ <>
103
+ { ! props.tosText && (
104
+ <div className="jp-components__pricing-card__tos">
105
+ <TermsOfService agreeButtonLabel={ props.ctaText } />
106
+ </div>
107
+ ) }
108
+ <div className="jp-components__pricing-card__cta">
109
+ <Button
110
+ className="jp-components__pricing-card__button"
111
+ label={ props.ctaText }
112
+ onClick={ props.onCtaClick }
113
+ >
114
+ { props.ctaText }
115
+ </Button>
116
+ </div>
117
+ </>
118
+ ) }
119
+
120
+ { props.infoText && (
121
+ <div className="jp-components__pricing-card__info">{ props.infoText }</div>
122
+ ) }
123
+ </div>
124
+ );
125
+ };
126
+
127
+ export default PricingCard;
@@ -0,0 +1,55 @@
1
+ import { action } from '@storybook/addon-actions';
2
+ import PricingCard from '../index';
3
+ import type { ComponentStory, ComponentMeta } from '@storybook/react';
4
+
5
+ export default {
6
+ title: 'JS Packages/Components/Pricing Card',
7
+ component: PricingCard,
8
+ // TODO: Storybook Actions are not working. See https://github.com/storybookjs/storybook/issues/7215
9
+ argTypes: {
10
+ onCtaClick: { action: 'clicked' },
11
+ },
12
+ } as ComponentMeta< typeof PricingCard >;
13
+
14
+ // Export additional stories using pre-defined values
15
+ const Template: ComponentStory< typeof PricingCard > = args => <PricingCard { ...args } />;
16
+
17
+ const DefaultArgs = {
18
+ title: 'Jetpack Backup',
19
+ icon: "data:image/svg+xml,%3Csvg width='32' height='32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m21.092 15.164.019-1.703v-.039c0-1.975-1.803-3.866-4.4-3.866-2.17 0-3.828 1.351-4.274 2.943l-.426 1.524-1.581-.065a2.92 2.92 0 0 0-.12-.002c-1.586 0-2.977 1.344-2.977 3.133 0 1.787 1.388 3.13 2.973 3.133H22.399c1.194 0 2.267-1.016 2.267-2.4 0-1.235-.865-2.19-1.897-2.368l-1.677-.29Zm-10.58-3.204a4.944 4.944 0 0 0-.201-.004c-2.75 0-4.978 2.298-4.978 5.133s2.229 5.133 4.978 5.133h12.088c2.357 0 4.267-1.97 4.267-4.4 0-2.18-1.538-3.99-3.556-4.339v-.06c0-3.24-2.865-5.867-6.4-5.867-2.983 0-5.49 1.871-6.199 4.404Z' fill='%23000'/%3E%3C/svg%3E",
20
+ priceBefore: 9,
21
+ priceAfter: 4.5,
22
+ ctaText: 'Get Jetpack Backup',
23
+ infoText:
24
+ 'Special introductory pricing, all renewals are at full price. 14 day money back guarantee.',
25
+ onCtaClick: action( 'onCtaClick' ),
26
+ };
27
+
28
+ // Export Default story
29
+ export const _default = Template.bind( {} );
30
+ _default.args = DefaultArgs;
31
+
32
+ export const Minimal = Template.bind( {} );
33
+ Minimal.args = {
34
+ ...DefaultArgs,
35
+ icon: null,
36
+ ctaText: null,
37
+ onCtaClick: null,
38
+ infoText: null,
39
+ };
40
+
41
+ const TemplateWithChildren = args => (
42
+ <PricingCard { ...args }>
43
+ <ul>
44
+ <li>Automated real-time backups</li>
45
+ <li>Easy one-click restores</li>
46
+ <li>Complete list of all site changes</li>
47
+ <li>Global server infrastructure</li>
48
+ <li>Best-in-class support</li>
49
+ </ul>
50
+ </PricingCard>
51
+ );
52
+ export const WithChildren = TemplateWithChildren.bind( {} );
53
+ WithChildren.args = {
54
+ ...DefaultArgs,
55
+ };
@@ -0,0 +1,112 @@
1
+ @import '@automattic/jetpack-base-styles/style';
2
+
3
+ .jp-components__pricing-card {
4
+ width: -moz-fit-content;
5
+ width: fit-content;
6
+ max-width: 384px;
7
+ padding: 24px 24px 32px;
8
+ background:var( --jp-white );
9
+ box-shadow: 0px 10px 40px rgba( 0, 0, 0, 0.08 );
10
+ border-radius: var( --jp-border-radius );
11
+
12
+ @media screen and ( min-width: 600px ) {
13
+ padding: 32px 32px 44px;
14
+ }
15
+
16
+ &__icon {
17
+ img {
18
+ height: 32px;
19
+ width: 32px;
20
+ }
21
+ }
22
+
23
+ &__title {
24
+ margin: 16px 0 24px;
25
+ color: #101517;
26
+ font-size: 32px;
27
+ line-height: 38px;
28
+ }
29
+
30
+ &__pricing {
31
+ display: flex;
32
+ flex-wrap: wrap;
33
+ }
34
+
35
+ &__price-before,
36
+ &__price-after {
37
+ display: inline-block;
38
+ margin-bottom: 8px;
39
+ padding: 0 2px;
40
+ font-size: 54px;
41
+ font-weight: 700;
42
+ line-height: 40px;
43
+ }
44
+
45
+ &__price-before {
46
+ position: relative;
47
+ margin-right: 16px;
48
+ color: var( --jp-gray-20 );
49
+ }
50
+
51
+ &__price-strikethrough {
52
+ position: absolute;
53
+ width: 100%;
54
+ height: 3px;
55
+ left: 0px;
56
+ top: 20px;
57
+ background: var( --jp-pink );
58
+ border-radius: 1.5px;
59
+ }
60
+
61
+ &__price-after {
62
+ color: var( --jp-black );
63
+ }
64
+
65
+ &__currency {
66
+ vertical-align: super;
67
+ font-size: var( --font-title-small );
68
+ font-weight: 400;
69
+ line-height: 20px;
70
+ }
71
+
72
+ &__price-details {
73
+ align-self: flex-end;
74
+ margin-bottom: 8px;
75
+ font-size: 14px;
76
+ font-weight: 400;
77
+ line-height: 17px;
78
+ color: var( --jp-gray-50 );
79
+ letter-spacing: -0.02em;
80
+ }
81
+
82
+ &__price-decimal {
83
+ font-size: var( --font-label );
84
+ line-height: 14px;
85
+ vertical-align: top;
86
+ }
87
+
88
+ &__button {
89
+ width: 100%;
90
+ height: auto;
91
+ font-size: 18px;
92
+ background: var( --jp-black );
93
+ color: var( --jp-white ) !important;
94
+ border-radius: var( --jp-border-radius );
95
+ padding: 14px 24px;
96
+ margin: 24px 0px 32px;
97
+ justify-content: center;
98
+ align-items: center;
99
+ }
100
+
101
+ &__info,
102
+ &__tos {
103
+ font-size: var( --font-label );
104
+ line-height: 20px;
105
+ letter-spacing: -0.02em;
106
+ color: var( --jp-gray-60 );
107
+ }
108
+
109
+ &__tos {
110
+ margin-top: 24px;
111
+ }
112
+ }
@@ -0,0 +1,89 @@
1
+ import { jest } from '@jest/globals';
2
+ import { render, screen, cleanup } from '@testing-library/react/pure';
3
+ import PricingCard from '../index';
4
+
5
+ // Note we're using @testing-library/react/pure here to disable the automatic cleanup.
6
+ // So be sure to call `cleanup()` for each `render()`.
7
+ /* eslint-disable testing-library/no-render-in-setup */
8
+
9
+ describe( 'PricingCard', () => {
10
+ const testProps = {
11
+ title: 'Dummy Pricing Card',
12
+ icon: 'dummy_icon',
13
+ priceBefore: 9,
14
+ priceAfter: 4.5,
15
+ ctaText: 'Get Dummy Offer',
16
+ infoText: 'Dummy Info Text',
17
+ onCtaClick: jest.fn(),
18
+ };
19
+
20
+ describe( 'Initially', () => {
21
+ let container;
22
+
23
+ beforeAll( () => {
24
+ ( { container } = render( <PricingCard { ...testProps } /> ) );
25
+ } );
26
+ afterAll( () => {
27
+ cleanup();
28
+ } );
29
+
30
+ it( 'renders the title', () => {
31
+ expect( screen.getByRole( 'heading' ) ).toHaveTextContent( 'Dummy Pricing Card' );
32
+ } );
33
+
34
+ it( 'renders the icon', () => {
35
+ const img = screen.getByAltText( 'Icon for the product Dummy Pricing Card' );
36
+ expect( img ).toBeInTheDocument();
37
+ expect( img ).toHaveAttribute( 'src', 'dummy_icon' );
38
+ } );
39
+
40
+ it( 'renders the price before', () => {
41
+ // eslint-disable-next-line testing-library/no-node-access
42
+ const node = container.querySelector( '.jp-components__pricing-card__price-before' );
43
+ expect( node ).toBeInTheDocument();
44
+ expect( node ).toHaveTextContent( '$9' );
45
+ } );
46
+
47
+ it( 'renders the price after', () => {
48
+ // eslint-disable-next-line testing-library/no-node-access
49
+ const node = container.querySelector( '.jp-components__pricing-card__price-after' );
50
+ expect( node ).toBeInTheDocument();
51
+ expect( node ).toHaveTextContent( '$4.50' );
52
+ } );
53
+
54
+ it( 'renders the CTA button', () => {
55
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Get Dummy Offer' );
56
+ } );
57
+
58
+ it( 'renders the info text', () => {
59
+ // eslint-disable-next-line testing-library/no-node-access
60
+ const node = container.querySelector( '.jp-components__pricing-card__info' );
61
+ expect( node ).toBeInTheDocument();
62
+ expect( node ).toHaveTextContent( 'Dummy Info Text' );
63
+ } );
64
+ } );
65
+
66
+ describe( 'When price before and price after match', () => {
67
+ let container;
68
+
69
+ beforeAll( () => {
70
+ ( { container } = render( <PricingCard { ...testProps } priceAfter={ 9 } /> ) );
71
+ } );
72
+ afterAll( () => {
73
+ cleanup();
74
+ } );
75
+
76
+ it( "doesn't render the price before", () => {
77
+ // eslint-disable-next-line testing-library/no-node-access
78
+ const node = container.querySelector( '.jp-components__pricing-card__price-before' );
79
+ expect( node ).not.toBeInTheDocument();
80
+ } );
81
+
82
+ it( 'renders the price after', () => {
83
+ // eslint-disable-next-line testing-library/no-node-access
84
+ const node = container.querySelector( '.jp-components__pricing-card__price-after' );
85
+ expect( node ).toBeInTheDocument();
86
+ expect( node ).toHaveTextContent( '$9' );
87
+ } );
88
+ } );
89
+ } );
@@ -0,0 +1,52 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { Button } from '@wordpress/components';
5
+ import type React from 'react';
6
+
7
+ export type PricingCardProps = {
8
+ /**
9
+ * The Title.
10
+ */
11
+ title: string;
12
+ /**
13
+ * The Icon.
14
+ */
15
+ icon?: string | React.ReactNode;
16
+ /**
17
+ * Price before discount.
18
+ */
19
+ priceBefore: number;
20
+ /**
21
+ * Price after discount.
22
+ */
23
+ priceAfter: number;
24
+ /**
25
+ * Price details.
26
+ */
27
+ priceDetails?: string;
28
+ /**
29
+ * The Currency code, eg 'USD'.
30
+ */
31
+ currencyCode?: string;
32
+ /**
33
+ * The CTA copy.
34
+ */
35
+ ctaText?: string;
36
+ /**
37
+ * The CTA callback to be called on click.
38
+ */
39
+ onCtaClick?: React.ComponentProps< typeof Button >[ 'onClick' ];
40
+ /**
41
+ * Optional informative text.
42
+ */
43
+ infoText?: React.ReactNode;
44
+ /**
45
+ * The TOS copy.
46
+ */
47
+ tosText?: React.ReactNode;
48
+ /**
49
+ * Optional Child nodes
50
+ */
51
+ children?: React.ReactNode;
52
+ };