@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,28 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import AutomatticBylineLogo from '../index';
3
+
4
+ describe( 'AutomatticBylineLogo', () => {
5
+ const testProps = {
6
+ className: 'sample-classname',
7
+ };
8
+
9
+ describe( 'Render the AutomatticBylineLogo component', () => {
10
+ it( 'validate the class name', () => {
11
+ const { container } = render( <AutomatticBylineLogo { ...testProps } /> );
12
+ // eslint-disable-next-line testing-library/no-node-access
13
+ expect( container.firstChild ).toHaveClass( 'sample-classname' );
14
+ } );
15
+
16
+ it( 'renders the default title', () => {
17
+ render( <AutomatticBylineLogo /> );
18
+
19
+ expect( screen.getByText( 'An Automattic Airline' ) ).toBeInTheDocument();
20
+ } );
21
+
22
+ it( 'renders the given title', () => {
23
+ render( <AutomatticBylineLogo title="An A8C Airline" /> );
24
+
25
+ expect( screen.getByText( 'An A8C Airline' ) ).toBeInTheDocument();
26
+ } );
27
+ } );
28
+ } );
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+
3
+ export interface AutomatticBylineLogoProps extends React.SVGProps< SVGSVGElement > {
4
+ /**
5
+ * Title for SVG.
6
+ */
7
+ title?: string;
8
+
9
+ /**
10
+ * Height for SVG.
11
+ */
12
+ height?: number;
13
+
14
+ /**
15
+ * Additional className for the a wrapper. `jp-automattic-byline-logo` always included
16
+ */
17
+ className?: string;
18
+ }
@@ -0,0 +1,103 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ import classNames from 'classnames';
3
+ import Gridicon from '../gridicon';
4
+ import type { BoostScoreBarProps } from './types';
5
+ import type { FunctionComponent } from 'react';
6
+
7
+ import './style.scss';
8
+
9
+ export const BoostScoreBar: FunctionComponent< BoostScoreBarProps > = ( {
10
+ score,
11
+ isLoading,
12
+ showPrevScores,
13
+ active,
14
+ prevScore = 0,
15
+ scoreBarType,
16
+ noBoostScoreTooltip,
17
+ } ) => {
18
+ if ( ! active ) {
19
+ return null;
20
+ }
21
+
22
+ const prevScoreOffset = ( prevScore / score ) * 100;
23
+ const desktopIconLabel = __( 'Desktop score', 'jetpack' );
24
+ const mobileIconLabel = __( 'Mobile score', 'jetpack' );
25
+
26
+ const getIcon = () => {
27
+ if ( scoreBarType === 'desktop' ) {
28
+ return <Gridicon icon="computer" size={ 24 } />;
29
+ }
30
+
31
+ return <Gridicon icon="phone" size={ 24 } />;
32
+ };
33
+
34
+ const getFillColor = () => {
35
+ // This shows the loading color unless a score already exists.
36
+ // In that case, shows the color based on the current score while refreshing.
37
+ if ( isLoading && ! score ) {
38
+ return 'fill-loading';
39
+ }
40
+
41
+ if ( score > 70 ) {
42
+ return 'fill-good';
43
+ }
44
+
45
+ if ( score > 50 ) {
46
+ return 'fill-mediocre';
47
+ }
48
+
49
+ return 'fill-bad';
50
+ };
51
+
52
+ return (
53
+ <div className={ classNames( 'jb-score-bar', `jb-score-bar--${ scoreBarType }` ) }>
54
+ <div className="jb-score-bar__label">
55
+ { getIcon() }
56
+ <div>{ scoreBarType === 'desktop' ? desktopIconLabel : mobileIconLabel }</div>
57
+ </div>
58
+
59
+ <div className="jb-score-bar__bounds">
60
+ <div
61
+ className={ classNames( 'jb-score-bar__filler', getFillColor() ) }
62
+ style={ { width: `${ score }%` } }
63
+ >
64
+ { isLoading ? (
65
+ <div className="jb-score-bar__loading">
66
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 128 128">
67
+ <g>
68
+ <path d="M64 0a7 7 0 11-7 7 7 7 0 017-7zm29.86 12.2a2.8 2.8 0 11-3.83 1.02 2.8 2.8 0 013.83-1.02zm22.16 21.68a3.15 3.15 0 11-4.3-1.15 3.15 3.15 0 014.3 1.15zm.87 60.53a4.2 4.2 0 11-1.57-5.7 4.2 4.2 0 011.54 5.73zm7.8-30.5a3.85 3.85 0 11-3.85-3.85 3.85 3.85 0 013.85 3.84zm-30 53.2a4.55 4.55 0 111.66-6.23 4.55 4.55 0 01-1.67 6.22zM64 125.9a4.9 4.9 0 114.9-4.9 4.9 4.9 0 01-4.9 4.9zm-31.06-8.22a5.25 5.25 0 117.17-1.93 5.25 5.25 0 01-7.14 1.93zM9.9 95.1a5.6 5.6 0 117.65 2.06A5.6 5.6 0 019.9 95.1zM1.18 63.9a5.95 5.95 0 115.95 5.94 5.95 5.95 0 01-5.96-5.94zm8.1-31.6a6.3 6.3 0 112.32 8.6 6.3 6.3 0 01-2.3-8.6zM32.25 8.87a6.65 6.65 0 11-2.44 9.1 6.65 6.65 0 012.46-9.1z" />
69
+ <animateTransform
70
+ attributeName="transform"
71
+ type="rotate"
72
+ values="0 64 64;30 64 64;60 64 64;90 64 64;120 64 64;150 64 64;180 64 64;210 64 64;240 64 64;270 64 64;300 64 64;330 64 64"
73
+ calcMode="discrete"
74
+ dur="1080ms"
75
+ repeatCount="indefinite"
76
+ />
77
+ </g>
78
+ </svg>
79
+ </div>
80
+ ) : (
81
+ <div className="jb-score-bar__score">{ score }</div>
82
+ ) }
83
+
84
+ { showPrevScores && prevScore && prevScore < score && (
85
+ <div
86
+ className="jb-score-bar__no_boost_score"
87
+ style={ { left: `min(${ prevScoreOffset }%, calc( 100% - var(--clearance-space))` } }
88
+ >
89
+ { prevScore }
90
+ { noBoostScoreTooltip && (
91
+ <div role="tooltip" className="jb-score-bar__no_boost_score_tooltip">
92
+ { noBoostScoreTooltip }
93
+ </div>
94
+ ) }
95
+ </div>
96
+ ) }
97
+ </div>
98
+ </div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export default BoostScoreBar;
@@ -0,0 +1,39 @@
1
+ import BoostScoreBar from '../index';
2
+ import type { Meta } from '@storybook/react';
3
+
4
+ const meta: Meta< typeof BoostScoreBar > = {
5
+ title: 'JS Packages/Components/Boost Score Bar',
6
+ component: BoostScoreBar,
7
+ argTypes: {
8
+ score: { control: 'range', min: 0, max: 100, defaultValue: 80 },
9
+ prevScore: { control: 'range', min: 0, max: 100 },
10
+ isLoading: { control: 'boolean' },
11
+ showPrevScores: { control: 'boolean' },
12
+ active: { control: 'boolean' },
13
+ scoreBarType: { control: 'radio', options: [ 'desktop', 'mobile' ] },
14
+ noBoostScoreTooltip: { control: 'text' },
15
+ },
16
+ decorators: [
17
+ Story => (
18
+ <div style={ { width: '80%', maxWidth: '1320px', margin: '200px auto', fontSize: '16px' } }>
19
+ <Story />
20
+ </div>
21
+ ),
22
+ ],
23
+ };
24
+
25
+ const defaultValues = {
26
+ score: 80,
27
+ prevScore: 70,
28
+ isLoading: false,
29
+ showPrevScores: true,
30
+ active: true,
31
+ scoreBarType: 'desktop',
32
+ noBoostScoreTooltip: 'No boost score',
33
+ };
34
+
35
+ export default meta;
36
+
37
+ const Template = args => <BoostScoreBar { ...args } />;
38
+ export const _default = Template.bind( {} );
39
+ _default.args = defaultValues;
@@ -0,0 +1,184 @@
1
+ @use "sass:math";
2
+
3
+ $bar_height: 42px;
4
+ $label_z_index: 50;
5
+ $score_pointer_size: 6px;
6
+ $no_boost_score_size: 28px;
7
+
8
+ %score-pill {
9
+ background-color: #fff;
10
+ border-radius: $bar_height;
11
+ height: $bar_height;
12
+ display: flex;
13
+ align-items: center;
14
+ border: 2px solid transparent;
15
+ }
16
+
17
+ .jb-score-bar {
18
+ width: 100%;
19
+ display: flex;
20
+ color: var(--jp-gray-70);
21
+
22
+ &--mobile { // .jb-score-bar--mobile
23
+ margin-bottom: 1.5rem;
24
+ }
25
+
26
+ @media (max-width: 767px) {
27
+ flex-direction: column;
28
+ &__label {
29
+ background-color: transparent;
30
+ }
31
+ }
32
+
33
+
34
+ &__loading {
35
+ @extend %score-pill;
36
+ width: $bar_height;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ }
41
+
42
+ &__label {
43
+ @extend %score-pill;
44
+ display: grid;
45
+ grid-template-columns: 24px 1fr;
46
+ grid-column-gap: 10px;
47
+ justify-content: center;
48
+ position: relative;
49
+ font-size: 14px;
50
+ position: relative;
51
+
52
+ @media (min-width: 768px) {
53
+ padding-left: 15px;
54
+ padding-right: 15px;
55
+ width: 200px;
56
+ z-index: $label_z_index;
57
+ }
58
+ }
59
+
60
+ &__score {
61
+ @extend %score-pill;
62
+ border-radius: 100%;
63
+ font-weight: 700;
64
+ width: $bar_height;
65
+ justify-content: center;
66
+ position: absolute;
67
+ right: -1px;
68
+ height: $bar_height;
69
+ }
70
+
71
+ &__bounds {
72
+ background-color: #f1f1f1;
73
+ border-radius: math.div($bar-height, 2);
74
+ display: flex;
75
+ height: $bar_height;
76
+ width: 100%;
77
+ max-width: 100%;
78
+ position: relative;
79
+ z-index: $label_z_index - 10;
80
+ @media (min-width: 768px) {
81
+ width: calc(100% + #{math.div($bar-height, 2)});
82
+ margin-left: math.div(-$bar_height, 2) ;
83
+ }
84
+ }
85
+
86
+ &__filler {
87
+ display: flex;
88
+ min-width: 1px + $bar_height * 2;
89
+ justify-content: flex-end;
90
+ border-radius: 0 $bar_height $bar_height 0;
91
+ transition: width 300ms ease-in-out;
92
+ will-change: width;
93
+ width: 0;
94
+ position: relative;
95
+ @media (max-width: 767px) {
96
+ min-width: $bar_height + 1px;
97
+ border-radius: inherit;
98
+ }
99
+ }
100
+
101
+ &__no_boost_score {
102
+
103
+ --clearance-space: #{$bar_height + $no_boost_score_size + 4px};
104
+
105
+ position: absolute;
106
+ background: #fff;
107
+ border-radius: 50%;
108
+ padding: 3px;
109
+ top: $score_pointer_size;
110
+ width: $no_boost_score_size;
111
+ height: $no_boost_score_size;
112
+ line-height: 1.625;
113
+ font-size: 0.7em;
114
+ text-align: center;
115
+ cursor: pointer;
116
+ z-index: 2;
117
+ border: 2px solid transparent;
118
+ background-clip: padding-box;
119
+
120
+ &:hover {
121
+ border: 2px solid rgba(255, 255, 255, 0.5);
122
+ }
123
+
124
+ &::after {
125
+ content: " ";
126
+ width: 0;
127
+ height: 0;
128
+ border-left: $score_pointer_size solid transparent;
129
+ border-right: $score_pointer_size solid transparent;
130
+ border-bottom: $score_pointer_size solid #fff;
131
+
132
+ position: absolute;
133
+ top: $bar_height - $score_pointer_size * 2 - 2px;
134
+ left: $score_pointer_size;
135
+ }
136
+ }
137
+
138
+ &__no_boost_score_tooltip {
139
+ box-shadow: 0 0 20px 0 rgb(170 170 170 / 28%);
140
+ position: absolute;
141
+ top: -62px;
142
+ left: -10em;
143
+ width: 20em;
144
+ background: #FFF;
145
+ padding: 16px;
146
+ border-radius: 4px;
147
+ margin: 0 16px;
148
+ display: none;
149
+
150
+ &::after {
151
+ content: " ";
152
+ width: 0;
153
+ height: 0;
154
+ border-left: $score_pointer_size solid transparent;
155
+ border-right: $score_pointer_size solid transparent;
156
+ border-top: $score_pointer_size solid #fff;
157
+
158
+ position: absolute;
159
+ bottom: -$score_pointer_size;
160
+ left: 10em;
161
+ margin: 0 -10px
162
+ }
163
+ }
164
+
165
+ &__no_boost_score:hover &__no_boost_score_tooltip {
166
+ display: block;
167
+ }
168
+
169
+ .fill-loading {
170
+ background-color: var(--jp-white);
171
+ }
172
+
173
+ .fill-good {
174
+ background-color: var(--jp-green);
175
+ }
176
+
177
+ .fill-mediocre {
178
+ background-color: var(--jp-orange-20);
179
+ }
180
+
181
+ .fill-bad {
182
+ background-color: var(--jp-red-50);
183
+ }
184
+ }
@@ -0,0 +1,102 @@
1
+ /* eslint-disable testing-library/no-container */
2
+ /* eslint-disable testing-library/no-node-access */
3
+ import { render, screen } from '@testing-library/react';
4
+ import BoostScoreBar from '..';
5
+ import type { BoostScoreBarProps } from '../types';
6
+
7
+ describe( 'BoostScrollBar', () => {
8
+ const defaultProps: BoostScoreBarProps = {
9
+ score: 80,
10
+ prevScore: 60,
11
+ scoreBarType: 'desktop',
12
+ active: true,
13
+ showPrevScores: true,
14
+ isLoading: false,
15
+ noBoostScoreTooltip: 'Test tooltip text',
16
+ };
17
+
18
+ it( 'renders the boost scroll bar when active is true', () => {
19
+ const { container } = render( <BoostScoreBar { ...defaultProps } /> );
20
+
21
+ // eslint-disable-next-line testing-library/no-node-access
22
+ expect( container.firstChild ).toHaveAttribute( 'class', 'jb-score-bar jb-score-bar--desktop' );
23
+ } );
24
+
25
+ it( 'does not render the boost scroll bar when active is false', () => {
26
+ const { container } = render( <BoostScoreBar { ...defaultProps } active={ false } /> );
27
+
28
+ // eslint-disable-next-line testing-library/no-node-access
29
+ expect( container ).toBeEmptyDOMElement();
30
+ } );
31
+
32
+ it( 'does not render the tooltip when noBoostScoreTooltip is not provided', () => {
33
+ render( <BoostScoreBar { ...defaultProps } noBoostScoreTooltip={ undefined } /> );
34
+
35
+ expect( screen.queryByRole( 'tooltip' ) ).not.toBeInTheDocument();
36
+ } );
37
+
38
+ it( 'renders the tooltip with the correct text when noBoostScoreTooltip is provided', () => {
39
+ render( <BoostScoreBar { ...defaultProps } /> );
40
+
41
+ expect( screen.getByRole( 'tooltip' ) ).toHaveTextContent( 'Test tooltip text' );
42
+ } );
43
+
44
+ it( 'displays loading spinner when isLoading is true', () => {
45
+ const { container } = render( <BoostScoreBar { ...defaultProps } isLoading={ true } /> );
46
+
47
+ expect( container.querySelector( '.jb-score-bar__loading' ) ).toBeInTheDocument();
48
+ } );
49
+
50
+ it( 'does not display loading spinner when isLoading is false', () => {
51
+ const { container } = render( <BoostScoreBar { ...defaultProps } /> );
52
+
53
+ expect( container.querySelector( '.jb-score-bar__loading' ) ).not.toBeInTheDocument();
54
+ } );
55
+
56
+ it( 'renders mobile icon and text when scoreBarType is mobile', () => {
57
+ const { container } = render( <BoostScoreBar { ...defaultProps } scoreBarType="mobile" /> );
58
+
59
+ expect( screen.getByText( 'Mobile score' ) ).toBeInTheDocument();
60
+ expect( container.querySelector( '.gridicons-phone' ) ).toBeInTheDocument();
61
+ } );
62
+
63
+ it( 'renders desktop icon and text when scoreBarType is desktop', () => {
64
+ const { container } = render( <BoostScoreBar { ...defaultProps } /> );
65
+
66
+ expect( screen.getByText( 'Desktop score' ) ).toBeInTheDocument();
67
+ expect( container.querySelector( '.gridicons-computer' ) ).toBeInTheDocument();
68
+ } );
69
+
70
+ it( 'renders previous scores when showPrevScores is true', () => {
71
+ const { container } = render( <BoostScoreBar { ...defaultProps } /> );
72
+
73
+ const previousScoreContainer = container.querySelector( '.jb-score-bar__no_boost_score' );
74
+
75
+ expect( previousScoreContainer ).toBeInTheDocument();
76
+ expect( previousScoreContainer ).toHaveTextContent( '60' );
77
+ } );
78
+
79
+ it( 'does not render previous scores when showPrevScores is false', () => {
80
+ const { container } = render( <BoostScoreBar { ...defaultProps } showPrevScores={ false } /> );
81
+
82
+ expect( container.querySelector( '.jb-score-bar__no_boost_score' ) ).not.toBeInTheDocument();
83
+ } );
84
+
85
+ it( 'renders the correct score bar class when score is < 50', () => {
86
+ const { container } = render( <BoostScoreBar { ...defaultProps } score={ 40 } /> );
87
+
88
+ expect( container.querySelector( '.fill-bad' ) ).toBeInTheDocument();
89
+ } );
90
+
91
+ it( 'renders the correct score bar class when score is >= 50 and < 71', () => {
92
+ const { container } = render( <BoostScoreBar { ...defaultProps } score={ 60 } /> );
93
+
94
+ expect( container.querySelector( '.fill-mediocre' ) ).toBeInTheDocument();
95
+ } );
96
+
97
+ it( 'renders the correct score bar class when score is > 70', () => {
98
+ const { container } = render( <BoostScoreBar { ...defaultProps } /> );
99
+
100
+ expect( container.querySelector( '.fill-good' ) ).toBeInTheDocument();
101
+ } );
102
+ } );
@@ -0,0 +1,9 @@
1
+ export interface BoostScoreBarProps {
2
+ score: number;
3
+ prevScore: number;
4
+ isLoading: boolean;
5
+ showPrevScores: boolean;
6
+ active: boolean;
7
+ scoreBarType: 'mobile' | 'desktop';
8
+ noBoostScoreTooltip: string | null;
9
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { Button as WPButton, Spinner } from '@wordpress/components';
5
+ import { Icon, external } from '@wordpress/icons';
6
+ import classNames from 'classnames';
7
+ import React, { forwardRef } from 'react';
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import styles from './style.module.scss';
12
+ import type { ButtonProps } from './types';
13
+
14
+ /**
15
+ * Button component
16
+ *
17
+ * @param {ButtonProps} props - Component Props
18
+ * @returns {React.ReactNode} Rendered button
19
+ */
20
+ const Button = forwardRef< HTMLInputElement, ButtonProps >( ( props, ref ) => {
21
+ const {
22
+ children,
23
+ variant = 'primary',
24
+ size = 'normal',
25
+ weight = 'bold',
26
+ icon,
27
+ iconSize,
28
+ disabled,
29
+ isDestructive,
30
+ isLoading,
31
+ isExternalLink,
32
+ className: propsClassName,
33
+ text,
34
+ fullWidth,
35
+ ...componentProps
36
+ } = props;
37
+
38
+ const className = classNames( styles.button, propsClassName, {
39
+ [ styles.normal ]: size === 'normal',
40
+ [ styles.small ]: size === 'small',
41
+ [ styles.icon ]: Boolean( icon ),
42
+ [ styles.loading ]: isLoading,
43
+ [ styles.regular ]: weight === 'regular',
44
+ [ styles[ 'full-width' ] ]: fullWidth,
45
+ [ styles[ 'is-icon-button' ] ]: Boolean( icon ) && ! children,
46
+ } );
47
+
48
+ componentProps.ref = ref;
49
+
50
+ const externalIconSize = size === 'normal' ? 20 : 16;
51
+ const externalIcon = isExternalLink && (
52
+ <Icon size={ externalIconSize } icon={ external } className={ styles[ 'external-icon' ] } />
53
+ );
54
+ const externalTarget = isExternalLink ? '_blank' : undefined;
55
+
56
+ // ref https://github.com/WordPress/gutenberg/pull/44198
57
+ const hasChildren =
58
+ children?.[ 0 ] &&
59
+ children[ 0 ] !== null &&
60
+ // Tooltip should not considered as a child
61
+ children?.[ 0 ]?.props?.className !== 'components-tooltip';
62
+
63
+ return (
64
+ <WPButton
65
+ target={ externalTarget }
66
+ variant={ variant }
67
+ className={ classNames( className, { 'has-text': !! icon && hasChildren } ) }
68
+ icon={ ! isExternalLink ? icon : undefined }
69
+ iconSize={ iconSize }
70
+ disabled={ disabled }
71
+ aria-disabled={ disabled }
72
+ isDestructive={ isDestructive }
73
+ text={ text }
74
+ { ...componentProps }
75
+ >
76
+ { isLoading && <Spinner /> }
77
+ <span>{ children }</span>
78
+ { externalIcon }
79
+ </WPButton>
80
+ );
81
+ } );
82
+ Button.displayName = 'Button';
83
+
84
+ export default Button;
@@ -0,0 +1,90 @@
1
+ import { Meta, Story, Canvas } from '@storybook/addon-docs';
2
+ import Button from '../index';
3
+ import * as ButtonStories from './index.stories';
4
+
5
+ <Meta of={ButtonStories} />
6
+
7
+ # Button
8
+ This is a component to create buttons and links across Jetpack.
9
+
10
+ ## Props
11
+
12
+ Below is the current available props for `Button`.
13
+
14
+ ### variant
15
+
16
+ It accepts 4 variant types: `primary`, `secondary`, `link`, `external-link`.
17
+
18
+ <Canvas withSource="open">
19
+ <Story id="js-packages-components-button--button-primary" />
20
+ <Story id="js-packages-components-button--button-secondary" />
21
+ <Story id="js-packages-components-button--button-link" />
22
+ <Story id="js-packages-components-button--button-external-link" />
23
+ </Canvas>
24
+
25
+ ### size
26
+
27
+ It accepts 2 size types: `normal` and `small`.
28
+
29
+ <Canvas>
30
+ <Story id="js-packages-components-button--button-primary" />
31
+ <Button size="small">Jetpack Button</Button>
32
+ </Canvas>
33
+
34
+ ### weight
35
+
36
+ It accepts 2 weight types: `bold` and `regular`. `bold` as default.
37
+
38
+ ### icon
39
+
40
+ It accepts an SVG, Component or string.
41
+
42
+ You could use the ones from [@wordpress/icons](https://github.com/WordPress/gutenberg/blob/trunk/packages/icons/src/index.js).
43
+
44
+ <Canvas withSource="open">
45
+ <Story id="js-packages-components-button--icon" />
46
+ </Canvas>
47
+
48
+ ### iconSize
49
+
50
+ It defines the size of the icon, it needs to be a number.
51
+
52
+ ### disabled
53
+
54
+ Disables the button removing cursor-pointer effect.
55
+
56
+ <Canvas withSource="open">
57
+ <Story id="js-packages-components-button--disabled" />
58
+ </Canvas>
59
+
60
+ ### isDestructive
61
+
62
+ Set button on destructive state, which changes the color to red.
63
+
64
+ <Canvas withSource="open">
65
+ <Story id="js-packages-components-button--destructive" />
66
+ </Canvas>
67
+
68
+ ### isLoading
69
+
70
+ Set button on loading state, which applies a centered spinner.
71
+
72
+ <Canvas withSource="open">
73
+ <Story id="js-packages-components-button--loading" />
74
+ </Canvas>
75
+
76
+ ### text
77
+
78
+ It applies a text before children, it could be used together or on its own.
79
+
80
+ <Canvas>
81
+ <Button text="My Text">My Children</Button>
82
+ </Canvas>
83
+
84
+ ### fullWidth
85
+
86
+ Stretches the button to take up the full width of the container.
87
+
88
+ <Canvas withSource="open">
89
+ <Story id="js-packages-components-button--full-width" />
90
+ </Canvas>