@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,84 @@
1
+ import type { Placement } from '../icon-tooltip/types';
2
+
3
+ export type PricingTableProps = {
4
+ /**
5
+ * Title of the pricing table.
6
+ */
7
+ title: string;
8
+
9
+ /**
10
+ * Array of items to display in the pricing table.
11
+ */
12
+ items: {
13
+ name: string;
14
+ tooltipInfo?: React.ReactNode;
15
+ tooltipTitle?: string;
16
+ tooltipPlacement?: Placement;
17
+ }[];
18
+
19
+ /**
20
+ * The columns to add to the table.
21
+ */
22
+ children: React.ReactNode;
23
+
24
+ /**
25
+ * Whether to show the intro offer disclaimer text with the ToS.
26
+ */
27
+ showIntroOfferDisclaimer?: boolean;
28
+ };
29
+
30
+ export type PricingTableColumnProps = {
31
+ /**
32
+ * Whether the column has the primary style.
33
+ */
34
+ primary?: boolean;
35
+
36
+ /**
37
+ * Items to show in a column.
38
+ */
39
+ children: React.ReactElement[];
40
+ };
41
+
42
+ export type PricingTableHeaderProps = {
43
+ /**
44
+ * Items to show in a header.
45
+ */
46
+ children: React.ReactNode;
47
+ };
48
+
49
+ export type PricingTableItemProps = {
50
+ /**
51
+ * Whether or not the item is included in the column.
52
+ */
53
+ isIncluded: boolean;
54
+
55
+ /**
56
+ * Whether the feature is coming soon. Takes precedence over isIncluded.
57
+ */
58
+ isComingSoon?: boolean;
59
+
60
+ /**
61
+ * A custom label to display instead of the default one.
62
+ */
63
+ label?: string | number | React.ReactElement;
64
+
65
+ /**
66
+ * Index of the item, relative to other PricingTableItem components. Internal use only.
67
+ */
68
+ index?: number;
69
+
70
+ /*
71
+ * If the item has more description a popover info can contain that.
72
+ */
73
+ tooltipInfo?: React.ReactNode;
74
+
75
+ /**
76
+ * Title for the popover, not required.
77
+ */
78
+ tooltipTitle?: string;
79
+
80
+ /**
81
+ * Class name for the popover, not required.
82
+ */
83
+ tooltipClassName?: string;
84
+ };
@@ -0,0 +1,47 @@
1
+ import { Icon, plus } from '@wordpress/icons';
2
+ import React, { Fragment } from 'react';
3
+ import { getIconBySlug } from '../icons/index';
4
+ import styles from './style.module.scss';
5
+ import { IconsCardProps } from './types';
6
+
7
+ /**
8
+ * Icons composition for a bundle product,
9
+ * based on the list of supported products.
10
+ *
11
+ * @param {IconsCardProps} props - Component props.
12
+ * @returns {React.ReactNode} Bundle product icons react component.
13
+ */
14
+ export const IconsCard: React.FC< IconsCardProps > = ( { products, icon, size = 24 } ) => {
15
+ if ( icon ) {
16
+ const CustomIcon = getIconBySlug( icon );
17
+ return (
18
+ <div className={ styles[ 'product-bundle-icons' ] }>
19
+ <CustomIcon size={ size } />
20
+ </div>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <div className={ styles[ 'product-bundle-icons' ] }>
26
+ { products.map( ( product, index ) => {
27
+ const ProductIcon = getIconBySlug( product );
28
+ const ProIcon = ProductIcon ? ProductIcon : () => null;
29
+
30
+ return (
31
+ <Fragment key={ index }>
32
+ <ProIcon size={ size } />
33
+
34
+ { index !== products.length - 1 && (
35
+ <Icon
36
+ className={ styles[ 'plus-icon' ] }
37
+ key={ `icon-plugs${ index * 2 + 1 }` }
38
+ icon={ plus }
39
+ size={ 16 }
40
+ />
41
+ ) }
42
+ </Fragment>
43
+ );
44
+ } ) }
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,115 @@
1
+ import { __, sprintf } from '@wordpress/i18n';
2
+ import { Icon, check } from '@wordpress/icons';
3
+ import classnames from 'classnames';
4
+ import Alert from '../alert';
5
+ import Button from '../button/index';
6
+ import { CheckmarkIcon } from '../icons/index';
7
+ import ProductPrice from '../product-price/index';
8
+ import Text, { H3, Title } from '../text/index';
9
+ import { IconsCard } from './icons-card';
10
+ import { ProductOfferHeader } from './product-offer-header';
11
+ import styles from './style.module.scss';
12
+ import { ProductOfferProps } from './types';
13
+ import type React from 'react';
14
+
15
+ /**
16
+ * Product Detail component.
17
+ *
18
+ * @param {ProductOfferProps} props - Component props.
19
+ * @returns {React.ReactNode} - ProductOffer react component.
20
+ */
21
+ const ProductOffer: React.FC< ProductOfferProps > = ( {
22
+ addProductUrl,
23
+ buttonDisclaimer,
24
+ buttonText = '',
25
+ className,
26
+ description,
27
+ error = '',
28
+ features,
29
+ hasRequiredPlan,
30
+ icon,
31
+ isBundle = false,
32
+ isCard,
33
+ isLoading,
34
+ onAdd,
35
+ pricing = {},
36
+ slug,
37
+ subTitle = '',
38
+ supportedProducts,
39
+ title = '',
40
+ } ) => {
41
+ const { isFree, price, currency, offPrice } = pricing;
42
+ const needsPurchase = ! isFree && ! hasRequiredPlan;
43
+
44
+ const defautlButtonText = sprintf(
45
+ /* translators: placeholder is product name. */
46
+ __( 'Add %s', 'jetpack' ),
47
+ title
48
+ );
49
+
50
+ return (
51
+ <div
52
+ className={ classnames( styles.wrapper, className, {
53
+ [ styles[ 'is-bundle-card' ] ]: isBundle,
54
+ [ styles[ 'is-card' ] ]: isCard || isBundle, // is card when is bundle.
55
+ } ) }
56
+ >
57
+ { isBundle && <ProductOfferHeader /> }
58
+
59
+ <div className={ styles[ 'card-container' ] }>
60
+ <IconsCard
61
+ icon={ icon }
62
+ products={ supportedProducts?.length ? supportedProducts : [ slug ] }
63
+ size={ 32 }
64
+ />
65
+ <H3>{ title }</H3>
66
+ { subTitle && <Title mb={ 3 }>{ subTitle }</Title> }
67
+ { description && <Text mb={ 3 }>{ description }</Text> }
68
+
69
+ <ul className={ styles.features }>
70
+ { features.map( ( feature, id ) => (
71
+ <Text component="li" key={ `feature-${ id }` } variant="body">
72
+ <Icon icon={ check } size={ 24 } className={ styles.check } />
73
+ { feature }
74
+ </Text>
75
+ ) ) }
76
+ </ul>
77
+
78
+ { needsPurchase && (
79
+ <ProductPrice price={ price } offPrice={ offPrice } currency={ currency } />
80
+ ) }
81
+
82
+ { isFree && <H3>{ __( 'Free', 'jetpack' ) }</H3> }
83
+
84
+ <Alert level="error" showIcon={ !! error }>
85
+ { error }
86
+ </Alert>
87
+
88
+ { buttonDisclaimer }
89
+
90
+ { ( ! isBundle || ( isBundle && ! hasRequiredPlan ) ) && (
91
+ <Button
92
+ onClick={ addProductUrl ? null : onAdd }
93
+ isLoading={ isLoading }
94
+ disabled={ isLoading }
95
+ variant={ isLoading || ! isBundle ? 'primary' : 'secondary' }
96
+ className={ styles[ 'add-button' ] }
97
+ { ...( addProductUrl ? { href: addProductUrl } : {} ) }
98
+ >
99
+ { buttonText || defautlButtonText }
100
+ </Button>
101
+ ) }
102
+
103
+ { isBundle && hasRequiredPlan && (
104
+ <div className={ styles[ 'product-has-required-plan' ] }>
105
+ <CheckmarkIcon size={ 36 } />
106
+ <Text>{ __( 'Active on your site', 'jetpack' ) }</Text>
107
+ </div>
108
+ ) }
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default ProductOffer;
115
+ export * from './icons-card';
@@ -0,0 +1,23 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ import { Icon, starFilled as star } from '@wordpress/icons';
3
+ import Text from '../text/index';
4
+ import styles from './style.module.scss';
5
+ import { ProductOfferHeaderProps } from './types';
6
+ import type React from 'react';
7
+
8
+ /**
9
+ * Product Detail Card Header component.
10
+ *
11
+ * @param {ProductOfferHeaderProps} props - Component props.
12
+ * @returns {React.ReactNode} ProductOfferHeader react component.
13
+ */
14
+ export const ProductOfferHeader: React.FC< ProductOfferHeaderProps > = ( {
15
+ title = __( 'Popular upgrade', 'jetpack' ),
16
+ } ) => {
17
+ return (
18
+ <div className={ styles[ 'card-header' ] }>
19
+ <Icon icon={ star } className={ styles[ 'product-bundle-icon' ] } size={ 24 } />
20
+ <Text variant="label">{ title }</Text>
21
+ </div>
22
+ );
23
+ };
@@ -0,0 +1,104 @@
1
+ import ProductOffer from '..';
2
+ import { IconsCard } from '../icons-card';
3
+ import type { ComponentStory, ComponentMeta } from '@storybook/react';
4
+
5
+ export default {
6
+ title: 'JS Packages/Components/Product Offer',
7
+ component: ProductOffer,
8
+ parameters: {
9
+ actions: { argTypesRegex: '^on.*' },
10
+ layout: 'centered',
11
+ },
12
+ } as ComponentMeta< typeof ProductOffer >;
13
+
14
+ const Template: ComponentStory< typeof ProductOffer > = args => <ProductOffer { ...args } />;
15
+
16
+ export const SecurityBundle = Template.bind( {} );
17
+ SecurityBundle.parameters = {};
18
+ SecurityBundle.args = {
19
+ slug: 'security',
20
+ icon: '',
21
+ name: 'Security',
22
+ title: 'Security',
23
+ subTitle: '',
24
+ description: 'Comprehensive site security, including Backup, Scan, and Anti-spam.',
25
+ isBundle: true,
26
+ isCard: true,
27
+ supportedProducts: [ 'backup', 'scan', 'anti-spam' ],
28
+ features: [
29
+ 'Real-time cloud backups with 10GB storage',
30
+ 'Automated real-time malware scan',
31
+ 'One-click fixes for most threats',
32
+ 'Comment & form spam protection',
33
+ ],
34
+ pricing: {
35
+ currency: 'USD',
36
+ price: 24.92,
37
+ offPrice: 12.42,
38
+ },
39
+ buttonText: '',
40
+ addProductUrl: '',
41
+ hasRequiredPlan: false,
42
+ isLoading: false,
43
+ error: '',
44
+ };
45
+
46
+ export const JetpackBackup = Template.bind( {} );
47
+ JetpackBackup.parameters = {};
48
+ JetpackBackup.args = {
49
+ slug: 'backup',
50
+ icon: '',
51
+ name: 'Backup',
52
+ title: 'Jepack Backup',
53
+ subTitle: '',
54
+ description:
55
+ 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.',
56
+ features: [
57
+ 'Real-time cloud backups',
58
+ '10GB of backup storage',
59
+ '30-day archive & activity log',
60
+ 'One-click restores',
61
+ ],
62
+ isBundle: false,
63
+ isCard: true,
64
+ pricing: {
65
+ currency: 'USD',
66
+ price: 9.66,
67
+ offPrice: 3.95,
68
+ },
69
+ addProductUrl: '',
70
+ hasRequiredPlan: false,
71
+ isLoading: false,
72
+ };
73
+
74
+ export const JetpackProtect = Template.bind( {} );
75
+ JetpackProtect.parameters = {};
76
+ JetpackProtect.args = {
77
+ slug: 'protect',
78
+ icon: 'jetpack',
79
+ title: 'Protect',
80
+ subTitle: 'Protect your site and scan for security vulnerabilities listed in our database.',
81
+ features: [
82
+ 'Over 20,000 listed vulnerabilities',
83
+ 'Daily automatic scans',
84
+ 'Check plugin and theme version status',
85
+ 'Easy to navigate and use',
86
+ ],
87
+ isBundle: false,
88
+ isCard: true,
89
+ pricing: {
90
+ isFree: true,
91
+ },
92
+ addProductUrl: '',
93
+ hasRequiredPlan: false,
94
+ isLoading: false,
95
+ };
96
+
97
+ const IconsCardTemplate: ComponentStory< typeof IconsCard > = args => <IconsCard { ...args } />;
98
+
99
+ export const IconsCardStory = IconsCardTemplate.bind( {} );
100
+ IconsCardStory.parameters = {};
101
+ IconsCardStory.args = {
102
+ products: [ 'backup', 'scan', 'anti-spam' ],
103
+ };
104
+ IconsCardStory.storyName = 'Icons Card';
@@ -0,0 +1,93 @@
1
+ .wrapper {
2
+ padding: calc( var( --spacing-base ) * 8 );
3
+ position: relative;
4
+ }
5
+
6
+ .is-card {
7
+ background-color: var( --jp-white );
8
+ border: 1px solid var( --jp-gray );
9
+ border-radius: var( --jp-border-radius );
10
+ box-shadow: 0 2px 6px var( --product-card-shadow ), 0 1px 2px var( --product-card-shadow );
11
+
12
+ .card-header {
13
+ border-radius: var( --jp-border-radius ) var( --jp-border-radius ) 0 0;
14
+ }
15
+ }
16
+
17
+ .card-header {
18
+ display: flex;
19
+ align-items: center;
20
+ background: var( --jp-black );
21
+ color: var( --jp-white );
22
+ padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base );
23
+ line-height: calc( var( --spacing-base ) * 3 );
24
+ position: absolute;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100%;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ .card-container {
32
+ height: 100%;
33
+ display: flex;
34
+ flex-direction: column;
35
+ }
36
+
37
+ .product-bundle-icon {
38
+ fill: var( --jp-white );
39
+ margin-right: var( --spacing-base );
40
+ }
41
+
42
+ .product-bundle-icons,
43
+ .product-icon {
44
+ display: flex;
45
+ align-items: center;
46
+ margin-bottom: calc( var( --spacing-base ) * 4 );
47
+ }
48
+
49
+ .plus-icon {
50
+ fill: rgb(140, 143, 148);
51
+ height: 24px;
52
+ line-height: 24px;
53
+ }
54
+
55
+ .features {
56
+ margin: 0;
57
+ padding: 0;
58
+ flex-grow: 1;
59
+ margin-bottom: calc( var( --spacing-base ) * 2 );
60
+
61
+ li {
62
+ list-style: none;
63
+ display: flex;
64
+ align-items: flex-start;
65
+ margin-bottom: var( --spacing-base );
66
+ }
67
+
68
+ .check {
69
+ fill: var( --jp-green-primary );
70
+ margin-right: var( --spacing-base );
71
+ flex-shrink: 0;
72
+ }
73
+ }
74
+
75
+ .product-has-required-plan {
76
+ display: flex;
77
+ justify-content: center;
78
+ align-items: center;
79
+
80
+ svg {
81
+ margin-right: var( --spacing-base );
82
+ }
83
+ }
84
+
85
+ .add-button {
86
+ width: 100%;
87
+ justify-content: center;
88
+
89
+ &:global(.is-primary):disabled {
90
+ opacity: 1;
91
+ background-color: var( --jp-black );
92
+ }
93
+ }
@@ -0,0 +1,125 @@
1
+ import { ProductPriceProps } from '../product-price/types';
2
+ import type { JetpackIconSlug } from '../icons';
3
+
4
+ export type ProductOfferProps = {
5
+ /**
6
+ * Product slug.
7
+ */
8
+ slug?: JetpackIconSlug;
9
+
10
+ /**
11
+ * Product name.
12
+ */
13
+ name?: string;
14
+
15
+ /**
16
+ * Custom Icon slug.
17
+ */
18
+ icon?: JetpackIconSlug;
19
+
20
+ /**
21
+ * Product title.
22
+ */
23
+ title?: string;
24
+
25
+ /**
26
+ * Product sub-title.
27
+ */
28
+ subTitle?: string;
29
+
30
+ /**
31
+ * Product description.
32
+ */
33
+ description?: string;
34
+
35
+ /**
36
+ * Features list of the product.
37
+ */
38
+ features?: Array< string >;
39
+
40
+ /**
41
+ * Add the styles to look like a card.
42
+ */
43
+ isCard?: boolean;
44
+
45
+ /**
46
+ * Whether or not the product is a bundle.
47
+ */
48
+ isBundle?: boolean;
49
+
50
+ /**
51
+ * List of supported products (for bundles).
52
+ */
53
+ supportedProducts?: Array< JetpackIconSlug >;
54
+
55
+ /**
56
+ * Product Pricing object.
57
+ */
58
+ pricing?: Pricing;
59
+
60
+ /**
61
+ * Whether or not the product has the required plan.
62
+ */
63
+ hasRequiredPlan?: boolean;
64
+
65
+ /**
66
+ * Applies the isLoading style to the component.
67
+ */
68
+ isLoading?: boolean;
69
+
70
+ /**
71
+ * A className to be concat with default ones.
72
+ */
73
+ className?: string;
74
+
75
+ /**
76
+ * Callback function to be executed on click on Add button.
77
+ */
78
+ onAdd?: VoidFunction;
79
+
80
+ /**
81
+ * The checkout URL to add/buy the product.
82
+ */
83
+ addProductUrl?: string;
84
+
85
+ /**
86
+ * The text to be displayed on the Add button.
87
+ */
88
+ buttonText?: string;
89
+
90
+ /**
91
+ * Content displayed below the Add button.
92
+ */
93
+ buttonDisclaimer?: React.ReactNode;
94
+
95
+ /**
96
+ * Error message.
97
+ */
98
+ error?: string;
99
+ };
100
+
101
+ export type Pricing = Pick< ProductPriceProps, 'currency' | 'offPrice' | 'price' > & {
102
+ /**
103
+ * Whether it is a free product
104
+ */
105
+ isFree?: boolean;
106
+ };
107
+
108
+ export type IconsCardProps = {
109
+ /**
110
+ * Custom icon slug.
111
+ */
112
+ icon: JetpackIconSlug;
113
+
114
+ /**
115
+ * List of supported products.
116
+ */
117
+ products: Array< JetpackIconSlug >;
118
+
119
+ /**
120
+ * Icon size.
121
+ */
122
+ size: number;
123
+ };
124
+
125
+ export type ProductOfferHeaderProps = Pick< ProductOfferProps, 'title' >;
@@ -0,0 +1,90 @@
1
+ /*
2
+ * External dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import classnames from 'classnames';
6
+ /*
7
+ * Internal dependencies
8
+ */
9
+ import Text from '../text';
10
+ import { Price } from './price';
11
+ import styles from './style.module.scss';
12
+ import type { ProductPriceProps } from './types';
13
+ import type React from 'react';
14
+
15
+ /**
16
+ * React component to render the price.
17
+ *
18
+ * @param {ProductPriceProps} props - Component props.
19
+ * @returns {React.ReactNode} Price react component.
20
+ */
21
+ const ProductPrice: React.FC< ProductPriceProps > = ( {
22
+ price,
23
+ offPrice,
24
+ currency = '',
25
+ showNotOffPrice = true,
26
+ hideDiscountLabel = true,
27
+ promoLabel = '',
28
+ legend = __( '/month, paid yearly', 'jetpack' ),
29
+ isNotConvenientPrice = false,
30
+ hidePriceFraction = false,
31
+ children,
32
+ } ) => {
33
+ if ( ( price == null && offPrice == null ) || ! currency ) {
34
+ return null;
35
+ }
36
+
37
+ showNotOffPrice = showNotOffPrice && offPrice != null;
38
+
39
+ const discount =
40
+ typeof price === 'number' && typeof offPrice === 'number'
41
+ ? Math.floor( ( ( price - offPrice ) / price ) * 100 )
42
+ : 0;
43
+
44
+ const showDiscountLabel = ! hideDiscountLabel && discount && discount > 0;
45
+
46
+ const discountElt = showDiscountLabel ? discount + __( '% off', 'jetpack' ) : null;
47
+
48
+ return (
49
+ <>
50
+ <div className={ styles.container }>
51
+ <div className={ classnames( styles[ 'price-container' ], 'product-price_container' ) }>
52
+ <Price
53
+ value={ offPrice ?? price }
54
+ currency={ currency }
55
+ isOff={ ! isNotConvenientPrice }
56
+ hidePriceFraction={ hidePriceFraction }
57
+ />
58
+ { showNotOffPrice && (
59
+ <Price
60
+ value={ price }
61
+ currency={ currency }
62
+ isOff={ false }
63
+ hidePriceFraction={ hidePriceFraction }
64
+ />
65
+ ) }
66
+ </div>
67
+ </div>
68
+ <div className={ styles.footer }>
69
+ { children ? (
70
+ children
71
+ ) : (
72
+ <Text className={ classnames( styles.legend, 'product-price_legend' ) }>{ legend }</Text>
73
+ ) }
74
+ { promoLabel && (
75
+ <Text className={ classnames( styles[ 'promo-label' ], 'product-price_promo_label' ) }>
76
+ { promoLabel }
77
+ </Text>
78
+ ) }
79
+ { discountElt && (
80
+ <Text className={ classnames( styles[ 'promo-label' ], 'product-price_promo_label' ) }>
81
+ { discountElt }
82
+ </Text>
83
+ ) }
84
+ </div>
85
+ </>
86
+ );
87
+ };
88
+
89
+ export default ProductPrice;
90
+ export * from './price';