@automattic/jetpack-components 0.40.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [0.40.1] - 2023-07-05
6
+ ### Changed
7
+ - Adjust component pricing slider border and box-shadow styling. [#31593]
8
+ - Updated package dependencies. [#31659]
9
+ - Updated package dependencies. [#31661]
10
+ - Update storybook mdx to use `@storybook/blocks` directly rather than `@storybook/addon-docs`. [#31607]
11
+
12
+ ### Fixed
13
+ - Add tagName prop to Col and Container [#31606]
14
+ - Fix Jetpack footer accessibility issues [#31417]
15
+
5
16
  ## [0.40.0] - 2023-06-26
6
17
  ### Added
7
18
  - Add authentication to Zendesk chat. [#31339]
@@ -748,5 +759,6 @@
748
759
  ### Changed
749
760
  - Update node version requirement to 14.16.1
750
761
 
762
+ [0.40.1]: https://github.com/Automattic/jetpack-components/compare/0.40.0...0.40.1
751
763
  [0.40.0]: https://github.com/Automattic/jetpack-components/compare/0.39.0...0.40.0
752
764
  [0.39.0]: https://github.com/Automattic/jetpack-components/compare/0.38.1...0.39.0
@@ -1,4 +1,4 @@
1
- import { Meta, Story, Canvas } from '@storybook/addon-docs';
1
+ import { Meta, Story, Canvas } from '@storybook/blocks';
2
2
  import Button from '../index';
3
3
  import * as ButtonStories from './index.stories';
4
4
 
@@ -8,6 +8,10 @@ import JetpackLogo from '../jetpack-logo';
8
8
  import useBreakpointMatch from '../layout/use-breakpoint-match';
9
9
  import type { JetpackFooterProps } from './types';
10
10
 
11
+ const JetpackIcon: React.FC = () => (
12
+ <JetpackLogo logoColor="#000" showText={ false } height={ 16 } aria-hidden="true" />
13
+ );
14
+
11
15
  /**
12
16
  * JetpackFooter component displays a tiny Jetpack logo with the product name on the left and the Automattic Airline "by line" on the right.
13
17
  *
@@ -24,70 +28,69 @@ const JetpackFooter: React.FC< JetpackFooterProps > = ( {
24
28
  } ) => {
25
29
  const [ isSm ] = useBreakpointMatch( 'sm', '<=' );
26
30
  const [ isMd ] = useBreakpointMatch( 'md', '<=' );
31
+ const [ isLg ] = useBreakpointMatch( 'lg', '>' );
32
+
33
+ const jetpackItemContent = (
34
+ <>
35
+ <JetpackIcon />
36
+ { moduleName }
37
+ </>
38
+ );
27
39
 
28
40
  return (
29
- <div
41
+ <footer
30
42
  className={ classnames(
31
43
  'jp-dashboard-footer',
32
44
  {
33
45
  'is-sm': isSm,
34
46
  'is-md': isMd,
47
+ 'is-lg': isLg,
35
48
  },
36
49
  className
37
50
  ) }
51
+ aria-label={ __( 'Jetpack', 'jetpack' ) }
38
52
  { ...otherProps }
39
53
  >
40
- <div className="jp-dashboard-footer__logo">
41
- <JetpackLogo
42
- logoColor="#000"
43
- showText={ false }
44
- height={ 16 }
45
- className="jp-dashboard-footer__jetpack-symbol"
46
- aria-label={ __( 'Jetpack logo', 'jetpack' ) }
47
- />
48
- <span className="jp-dashboard-footer__module-name">
54
+ <ul>
55
+ <li className="jp-dashboard-footer__jp-item">
49
56
  { moduleNameHref ? (
50
- <a href={ moduleNameHref } aria-label={ moduleName }>
51
- { moduleName }
52
- </a>
57
+ <a href={ moduleNameHref }>{ jetpackItemContent }</a>
53
58
  ) : (
54
- moduleName
59
+ jetpackItemContent
55
60
  ) }
56
- </span>
57
- </div>
58
- { menu && (
59
- <div className="jp-dashboard-footer__menu">
60
- { menu.map( item => (
61
- <a
62
- key={ item.label }
63
- href={ item.href }
64
- title={ item.title }
65
- target={ item.target }
66
- onClick={ item.onClick }
67
- onKeyDown={ item.onKeyDown }
68
- className={ classnames( 'jp-dashboard-footer__menu-item', {
69
- 'is-external': item.target === '_blank',
70
- } ) }
71
- role={ item.role }
72
- rel="noopener noreferrer"
73
- tabIndex={ item.role === 'button' ? 0 : undefined }
74
- >
75
- { item.label }
76
- { item.target === '_blank' && (
77
- <Icon className="jp-dashboard-footer__menu-item__icon" icon={ external } />
78
- ) }
79
- </a>
80
- ) ) }
81
- </div>
82
- ) }
83
- <a
84
- className="jp-dashboard-footer__a8c-logo"
85
- href={ a8cLogoHref }
86
- aria-label={ __( 'An Automattic Airline', 'jetpack' ) }
87
- >
88
- <AutomatticBylineLogo />
89
- </a>
90
- </div>
61
+ </li>
62
+ { menu?.map( item => {
63
+ const isButton = item.role === 'button';
64
+ const isExternalLink = ! isButton && item.target === '_blank';
65
+
66
+ return (
67
+ <li key={ item.label }>
68
+ <a
69
+ href={ item.href }
70
+ title={ item.title }
71
+ target={ item.target }
72
+ onClick={ item.onClick }
73
+ onKeyDown={ item.onKeyDown }
74
+ className={ classnames( 'jp-dashboard-footer__menu-item', {
75
+ 'is-external': isExternalLink,
76
+ } ) }
77
+ role={ item.role }
78
+ rel={ isExternalLink ? 'noopener noreferrer' : undefined }
79
+ tabIndex={ isButton ? 0 : undefined }
80
+ >
81
+ { item.label }
82
+ { isExternalLink && <Icon icon={ external } size={ 16 } /> }
83
+ </a>
84
+ </li>
85
+ );
86
+ } ) }
87
+ <li className="jp-dashboard-footer__a8c-item">
88
+ <a href={ a8cLogoHref } aria-label={ __( 'An Automattic Airline', 'jetpack' ) }>
89
+ <AutomatticBylineLogo aria-hidden="true" />
90
+ </a>
91
+ </li>
92
+ </ul>
93
+ </footer>
91
94
  );
92
95
  };
93
96
 
@@ -1,21 +1,42 @@
1
1
  .jp-dashboard-footer {
2
- align-items: center;
3
- color: var( --jp-black );
4
2
  display: flex;
5
3
  flex-wrap: wrap;
6
- font-size: var( --font-body-extra-small );
7
- line-height: 1.333;
4
+ align-items: center;
5
+
8
6
  max-width: 1128px;
9
7
  width: 100%;
10
8
 
9
+ color: var( --jp-black );
10
+
11
+ font-size: var( --font-body-extra-small );
12
+ line-height: 1.333;
13
+
11
14
  a {
12
- &,
13
- &:link,
14
- &:hover,
15
- &:visited,
16
- &:active {
17
- color: var( --jp-black );
18
- text-decoration: none;
15
+ text-decoration: none;
16
+
17
+ &:any-link,
18
+ &[role=button] {
19
+ color: inherit;
20
+ }
21
+
22
+ &:hover {
23
+ text-decoration: underline;
24
+ text-decoration-thickness: 1.5px;
25
+ }
26
+
27
+ &:focus {
28
+ border-radius: 2px;
29
+ box-shadow: none;
30
+ outline: 1.5px solid currentColor;
31
+ outline-offset: 3px;
32
+ }
33
+ }
34
+
35
+ &.is-sm {
36
+ > ul {
37
+ flex-direction: column;
38
+ align-items: flex-start;
39
+ gap: 0.125rem;
19
40
  }
20
41
  }
21
42
 
@@ -23,94 +44,56 @@
23
44
  flex-direction: column;
24
45
  align-items: flex-start;
25
46
  }
26
- }
27
47
 
28
- .jp-dashboard-footer__logo {
29
- margin-right: 32px;
48
+ > ul {
49
+ display: flex;
50
+ flex-wrap: wrap;
51
+ align-items: center;
52
+ gap: 1rem;
30
53
 
31
- .jp-dashboard-footer.is-md & {
32
- margin-right: 0;
33
- margin-bottom: 24px;
34
- }
54
+ width: 100%;
55
+ margin: 0;
56
+ padding: 0;
57
+
58
+ list-style: none;
35
59
 
36
- .jp-dashboard-footer.is-sm & {
37
- margin-bottom: 48px;
38
- }
39
- }
60
+ > li {
61
+ margin-bottom: 0;
40
62
 
41
- .jp-dashboard-footer__module-name,
42
- .jp-dashboard-footer__jetpack-symbol {
43
- vertical-align: middle;
44
- display: inline-block;
45
- }
63
+ > a {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.25rem;
46
67
 
47
- .jp-dashboard-footer__module-name {
48
- margin-left: 5px;
49
- font-weight: 600;
68
+ min-height: 44px; // Minimum target area size
69
+ }
70
+ }
71
+ }
50
72
  }
51
73
 
52
- .jp-dashboard-footer__menu {
53
- display: flex;
74
+ .jp-dashboard-footer__jp-item {
75
+ padding-inline-end: 1rem;
54
76
 
55
- .jp-dashboard-footer.is-md & {
56
- margin-bottom: 24px;
57
- }
77
+ font-weight: 600;
58
78
 
59
79
  .jp-dashboard-footer.is-sm & {
60
- flex-direction: column;
61
- align-items: flex-start;
62
- margin-bottom: 48px;
63
-
64
- .jp-dashboard-footer__menu-item {
65
- margin-right: 0;
66
- margin-bottom: 16px;
67
-
68
- &:last-child {
69
- margin-bottom: 0;
70
- }
71
- }
80
+ padding-bottom: 1rem;
72
81
  }
82
+ }
73
83
 
74
- .jp-dashboard-footer__menu-item {
75
- position: relative;
76
- color: var( --jp-black );
77
- margin-right: 16px;
78
- text-decoration: none;
79
-
80
- &.is-external {
81
- padding-right: 20px;
82
- }
83
-
84
- &:focus {
85
- border-radius: 2px;
86
- outline-offset: 3px;
87
- outline: 1.5px solid var( --jp-black );
88
- box-shadow: none;
89
- }
90
-
91
- &:hover {
92
- text-decoration: underline;
93
- text-decoration-thickness: 1.5px;
94
- }
95
-
96
- &:hover:focus {
97
- text-decoration: none;
98
- }
84
+ .jp-dashboard-footer__a8c-item {
85
+ .jp-dashboard-footer.is-lg & {
86
+ margin-inline-start: auto;
99
87
  }
100
88
 
101
- .jp-dashboard-footer__menu-item__icon {
102
- width: 16px;
103
- height: 16px;
104
- margin-left: 4px;
105
- position: absolute;
106
- right: 0;
89
+ .jp-dashboard-footer.is-sm & {
90
+ padding-top: 1rem;
107
91
  }
108
92
  }
109
93
 
110
- .jp-dashboard-footer__a8c-logo {
111
- margin-left: auto;
112
-
113
- .jp-dashboard-footer.is-md & {
114
- margin-left: 0;
94
+ .jp-dashboard-footer__jp-item,
95
+ .jp-dashboard-footer__a8c-item {
96
+ > a {
97
+ text-decoration: none; // Logos typically have no hover effect
115
98
  }
116
- }
99
+ }
@@ -0,0 +1,117 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`JetpackFooter Render the component should match the snapshot: all props 1`] = `
4
+ <div>
5
+ <footer
6
+ aria-label="Jetpack"
7
+ class="jp-dashboard-footer sample-classname"
8
+ >
9
+ <ul>
10
+ <li
11
+ class="jp-dashboard-footer__jp-item"
12
+ >
13
+ <a
14
+ href="https://jetpack.com/path/to-some-page"
15
+ >
16
+ <svg
17
+ aria-hidden="true"
18
+ aria-labelledby="jetpack-logo-title"
19
+ class="jetpack-logo"
20
+ height="16"
21
+ viewBox="0 0 32 32"
22
+ x="0px"
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ y="0px"
25
+ >
26
+ <desc
27
+ id="jetpack-logo-title"
28
+ >
29
+ Jetpack Logo
30
+ </desc>
31
+ <path
32
+ d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M15,19H7l8-16V19z M17,29V13h8L17,29z"
33
+ fill="#000"
34
+ />
35
+ </svg>
36
+ Test module
37
+ </a>
38
+ </li>
39
+ <li>
40
+ <a
41
+ class="jp-dashboard-footer__menu-item"
42
+ href="/"
43
+ >
44
+ Link
45
+ </a>
46
+ </li>
47
+ <li>
48
+ <a
49
+ class="jp-dashboard-footer__menu-item is-external"
50
+ href="/"
51
+ rel="noopener noreferrer"
52
+ target="_blank"
53
+ >
54
+ External link
55
+ <svg
56
+ aria-hidden="true"
57
+ focusable="false"
58
+ height="16"
59
+ viewBox="0 0 24 24"
60
+ width="16"
61
+ xmlns="http://www.w3.org/2000/svg"
62
+ >
63
+ <path
64
+ d="M19.5 4.5h-7V6h4.44l-5.97 5.97 1.06 1.06L18 7.06v4.44h1.5v-7Zm-13 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3H17v3a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h3V5.5h-3Z"
65
+ />
66
+ </svg>
67
+ </a>
68
+ </li>
69
+ <li>
70
+ <a
71
+ class="jp-dashboard-footer__menu-item"
72
+ href="/"
73
+ role="button"
74
+ tabindex="0"
75
+ >
76
+ Button link
77
+ </a>
78
+ </li>
79
+ <li
80
+ class="jp-dashboard-footer__a8c-item"
81
+ >
82
+ <a
83
+ aria-label="An Automattic Airline"
84
+ href="https://automattic.com"
85
+ >
86
+ <svg
87
+ aria-hidden="true"
88
+ aria-labelledby="jp-automattic-byline-logo-title"
89
+ class="jp-automattic-byline-logo"
90
+ enable-background="new 0 0 935 38.2"
91
+ height="7"
92
+ role="img"
93
+ viewBox="0 0 935 38.2"
94
+ x="0"
95
+ y="0"
96
+ >
97
+ <desc
98
+ id="jp-automattic-byline-logo-title"
99
+ >
100
+ An Automattic Airline
101
+ </desc>
102
+ <path
103
+ d="M317.1 38.2c-12.6 0-20.7-9.1-20.7-18.5v-1.2c0-9.6 8.2-18.5 20.7-18.5 12.6 0 20.8 8.9 20.8 18.5v1.2C337.9 29.1 329.7 38.2 317.1 38.2zM331.2 18.6c0-6.9-5-13-14.1-13s-14 6.1-14 13v0.9c0 6.9 5 13.1 14 13.1s14.1-6.2 14.1-13.1V18.6zM175 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7L157 1.3h5.5L182 36.8H175zM159.7 8.2L152 23.1h15.7L159.7 8.2zM212.4 38.2c-12.7 0-18.7-6.9-18.7-16.2V1.3h6.6v20.9c0 6.6 4.3 10.5 12.5 10.5 8.4 0 11.9-3.9 11.9-10.5V1.3h6.7V22C231.4 30.8 225.8 38.2 212.4 38.2zM268.6 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H268.6zM397.3 36.8V8.7l-1.8 3.1 -14.9 25h-3.3l-14.7-25 -1.8-3.1v28.1h-6.5V1.3h9.2l14 24.4 1.7 3 1.7-3 13.9-24.4h9.1v35.5H397.3zM454.4 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7l19.2-35.5h5.5l19.5 35.5H454.4zM439.1 8.2l-7.7 14.9h15.7L439.1 8.2zM488.4 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H488.4zM537.3 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H537.3zM569.3 36.8V4.6c2.7 0 3.7-1.4 3.7-3.4h2.8v35.5L569.3 36.8 569.3 36.8zM628 11.3c-3.2-2.9-7.9-5.7-14.2-5.7 -9.5 0-14.8 6.5-14.8 13.3v0.7c0 6.7 5.4 13 15.3 13 5.9 0 10.8-2.8 13.9-5.7l4 4.2c-3.9 3.8-10.5 7.1-18.3 7.1 -13.4 0-21.6-8.7-21.6-18.3v-1.2c0-9.6 8.9-18.7 21.9-18.7 7.5 0 14.3 3.1 18 7.1L628 11.3zM321.5 12.4c1.2 0.8 1.5 2.4 0.8 3.6l-6.1 9.4c-0.8 1.2-2.4 1.6-3.6 0.8l0 0c-1.2-0.8-1.5-2.4-0.8-3.6l6.1-9.4C318.7 11.9 320.3 11.6 321.5 12.4L321.5 12.4z"
104
+ />
105
+ <path
106
+ d="M37.5 36.7l-4.7-8.9H11.7l-4.6 8.9H0L19.4 0.8H25l19.7 35.9H37.5zM22 7.8l-7.8 15.1h15.9L22 7.8zM82.8 36.7l-23.3-24 -2.3-2.5v26.6h-6.7v-36H57l22.6 24 2.3 2.6V0.8h6.7v35.9H82.8z"
107
+ />
108
+ <path
109
+ d="M719.9 37l-4.8-8.9H694l-4.6 8.9h-7.1l19.5-36h5.6l19.8 36H719.9zM704.4 8l-7.8 15.1h15.9L704.4 8zM733 37V1h6.8v36H733zM781 37c-1.8 0-2.6-2.5-2.9-5.8l-0.2-3.7c-0.2-3.6-1.7-5.1-8.4-5.1h-12.8V37H750V1h19.6c10.8 0 15.7 4.3 15.7 9.9 0 3.9-2 7.7-9 9 7 0.5 8.5 3.7 8.6 7.9l0.1 3c0.1 2.5 0.5 4.3 2.2 6.1V37H781zM778.5 11.8c0-2.6-2.1-5.1-7.9-5.1h-13.8v10.8h14.4c5 0 7.3-2.4 7.3-5.2V11.8zM794.8 37V1h6.8v30.4h28.2V37H794.8zM836.7 37V1h6.8v36H836.7zM886.2 37l-23.4-24.1 -2.3-2.5V37h-6.8V1h6.5l22.7 24.1 2.3 2.6V1h6.8v36H886.2zM902.3 37V1H935v5.6h-26v9.2h20v5.5h-20v10.1h26V37H902.3z"
110
+ />
111
+ </svg>
112
+ </a>
113
+ </li>
114
+ </ul>
115
+ </footer>
116
+ </div>
117
+ `;
@@ -1,45 +1,162 @@
1
+ import { jest } from '@jest/globals';
1
2
  import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
2
4
  import JetpackFooter from '../index';
3
5
 
4
6
  describe( 'JetpackFooter', () => {
5
- const testProps = {
6
- className: 'sample-classname',
7
- };
7
+ const className = 'sample-classname';
8
+ const moduleName = 'Test module';
9
+ const moduleNameHref = 'https://jetpack.com/path/to-some-page';
10
+ const a8cLogoHref = 'https://automattic.com';
8
11
 
9
- describe( 'Render the JetpackFooter component', () => {
10
- it( 'validate the class name', () => {
11
- const { container } = render( <JetpackFooter { ...testProps } /> );
12
- // eslint-disable-next-line testing-library/no-node-access
13
- expect( container.firstChild ).toHaveClass( 'sample-classname' );
12
+ describe( 'Render the component', () => {
13
+ const menu = [
14
+ {
15
+ label: 'Link',
16
+ href: '/',
17
+ },
18
+ {
19
+ label: 'External link',
20
+ href: '/',
21
+ target: '_blank',
22
+ },
23
+ {
24
+ label: 'Button link',
25
+ href: '/',
26
+ role: 'button',
27
+ },
28
+ ];
29
+
30
+ it( 'should include a footer tag', () => {
31
+ render( <JetpackFooter /> );
32
+
33
+ const element = screen.getByRole( 'contentinfo', { name: 'Jetpack' } );
34
+
35
+ expect( element ).toBeInTheDocument();
14
36
  } );
15
37
 
16
- it( 'validates Jetpack logo', () => {
38
+ it( 'should apply the class name', () => {
39
+ render( <JetpackFooter className={ className } /> );
40
+
41
+ const element = screen.getByRole( 'contentinfo' );
42
+
43
+ expect( element ).toHaveClass( className );
44
+ } );
45
+
46
+ it( 'should render the Jetpack logo', () => {
17
47
  render( <JetpackFooter /> );
18
48
 
19
- expect( screen.getByLabelText( 'Jetpack logo' ) ).toBeInTheDocument();
49
+ const element = screen.getByLabelText( 'Jetpack' );
50
+
51
+ expect( element ).toBeInTheDocument();
52
+ } );
53
+
54
+ it( 'should render the module name as a link', () => {
55
+ render( <JetpackFooter moduleName={ moduleName } moduleNameHref={ moduleNameHref } /> );
56
+
57
+ const element = screen.getByText( moduleName );
58
+
59
+ expect( element ).toBeInTheDocument();
60
+ expect( element ).toBeInstanceOf( HTMLAnchorElement );
61
+ expect( element ).toHaveAttribute( 'href', moduleNameHref );
62
+ } );
63
+
64
+ it( 'should render the module name as regular text', () => {
65
+ render( <JetpackFooter moduleName={ moduleName } moduleNameHref={ null } /> );
66
+
67
+ const element = screen.getByText( moduleName );
68
+
69
+ expect( element ).toBeInTheDocument();
70
+ expect( element ).not.toBeInstanceOf( HTMLAnchorElement );
71
+ } );
72
+
73
+ it( 'should render the Automattic logo', () => {
74
+ render( <JetpackFooter a8cLogoHref={ a8cLogoHref } /> );
75
+
76
+ const element = screen.getByLabelText( 'An Automattic Airline', { selector: 'a' } );
77
+
78
+ expect( element ).toBeInTheDocument();
79
+ expect( element ).toHaveAttribute( 'href', a8cLogoHref );
80
+ } );
81
+
82
+ it( 'should render a list', () => {
83
+ render( <JetpackFooter menu={ menu } /> );
84
+
85
+ const element = screen.getByRole( 'list' );
86
+
87
+ expect( element ).toBeInTheDocument();
88
+ // eslint-disable-next-line testing-library/no-node-access
89
+ expect( element.children ).toHaveLength( 2 + menu.length );
90
+ } );
91
+
92
+ it( 'should render the links', () => {
93
+ render( <JetpackFooter menu={ menu } /> );
94
+
95
+ const link = screen.getByRole( 'link', { name: menu[ 0 ].label } );
96
+ const externalLink = screen.getByRole( 'link', { name: menu[ 1 ].label } );
97
+ const button = screen.getByRole( 'button', { name: menu[ 2 ].label } );
98
+
99
+ expect( link ).toBeInTheDocument();
100
+
101
+ expect( externalLink ).toBeInTheDocument();
102
+ expect( externalLink ).toHaveAttribute( 'target', '_blank' );
103
+ expect( externalLink ).toHaveAttribute( 'rel', 'noopener noreferrer' );
104
+ expect( externalLink ).toContainHTML( 'svg' );
105
+
106
+ expect( button ).toBeInTheDocument();
107
+ expect( button ).toHaveAttribute( 'tabindex', '0' );
20
108
  } );
21
109
 
22
- it( 'tests for module name and link', () => {
23
- render(
110
+ it( 'should match the snapshot', () => {
111
+ const { container } = render(
24
112
  <JetpackFooter
25
- moduleName="Test module"
26
- moduleNameHref="https://jetpack.com/path/to-some-page"
113
+ className={ className }
114
+ moduleName={ moduleName }
115
+ moduleNameHref={ moduleNameHref }
116
+ a8cLogoHref={ a8cLogoHref }
117
+ menu={ menu }
27
118
  />
28
119
  );
120
+ expect( container ).toMatchSnapshot( 'all props' );
121
+ } );
122
+ } );
29
123
 
30
- const element = screen.getByLabelText( 'Test module' );
124
+ describe( 'Fire events', () => {
125
+ const onClick = jest.fn();
126
+ const onKeyDown = jest.fn();
127
+ const menu = [
128
+ {
129
+ label: 'Link',
130
+ href: '/',
131
+ onClick,
132
+ onKeyDown,
133
+ },
134
+ ];
31
135
 
32
- expect( element ).toBeInTheDocument();
33
- expect( element ).toBeInstanceOf( HTMLAnchorElement );
34
- expect( element ).toHaveAttribute( 'href', 'https://jetpack.com/path/to-some-page' );
136
+ it( 'should call the menu item onClick function', async () => {
137
+ const user = userEvent.setup();
138
+
139
+ render( <JetpackFooter menu={ menu } /> );
140
+
141
+ const element = screen.getByRole( 'link', { name: menu[ 0 ].label } );
142
+
143
+ await user.click( element );
144
+
145
+ expect( onClick ).toHaveBeenCalled();
35
146
  } );
36
147
 
37
- it( 'validates the a8c label', () => {
38
- render( <JetpackFooter /> );
148
+ it( 'should call the menu item onKeyDown function', async () => {
149
+ const user = userEvent.setup();
150
+
151
+ render( <JetpackFooter menu={ menu } /> );
152
+
153
+ const element = screen.getByRole( 'link', { name: menu[ 0 ].label } );
154
+
155
+ // Need to focus on element first
156
+ await user.click( element );
157
+ await user.keyboard( '[Enter]' );
39
158
 
40
- for ( const element of screen.getAllByLabelText( 'An Automattic Airline' ) ) {
41
- expect( element ).toBeInTheDocument();
42
- }
159
+ expect( onKeyDown ).toHaveBeenCalled();
43
160
  } );
44
161
  } );
45
162
  } );
@@ -5,7 +5,7 @@ type JetpackFooterMenuItem = {
5
5
  onKeyDown?: () => void;
6
6
  target?: string;
7
7
  title?: string;
8
- role?: 'button';
8
+ role?: string;
9
9
  };
10
10
 
11
11
  export type JetpackFooterProps = {
@@ -21,6 +21,14 @@ import { Container, Col, Text } from '@automattic/jetpack-components';
21
21
 
22
22
  ## Props
23
23
 
24
+ ### tagName
25
+
26
+ The HTML tag of the column element.
27
+
28
+ - Type: `String`
29
+ - Default: `div`
30
+ - Required: `false`
31
+
24
32
  ### className
25
33
 
26
34
  A custom class to append with the default ones.
@@ -1,4 +1,5 @@
1
1
  import classnames from 'classnames';
2
+ import { createElement } from 'react';
2
3
  import { ColProps } from '../types';
3
4
  import styles from './style.module.scss';
4
5
  import type React from 'react';
@@ -14,7 +15,7 @@ const lgCols = Number( styles.lgCols );
14
15
  * @returns {React.ReactElement} Col component.
15
16
  */
16
17
  const Col: React.FC< ColProps > = props => {
17
- const { children, className } = props;
18
+ const { children, tagName = 'div', className } = props;
18
19
 
19
20
  const sm = Math.min( smCols, typeof props.sm === 'number' ? props.sm : smCols ); // max of 4, if undefined = 4
20
21
  const smStart = Math.min( smCols, typeof props.sm === 'object' ? props.sm.start : 0 ); // max of 4, if undefined = 0
@@ -43,7 +44,13 @@ const Col: React.FC< ColProps > = props => {
43
44
  [ styles[ `col-lg-${ lgEnd }-end` ] ]: lgEnd > 0,
44
45
  } );
45
46
 
46
- return <div className={ colClassName }>{ children }</div>;
47
+ return createElement(
48
+ tagName,
49
+ {
50
+ className: colClassName,
51
+ },
52
+ children
53
+ );
47
54
  };
48
55
 
49
56
  export default Col;
@@ -20,6 +20,14 @@ import { Container, Col, Button } from '@automattic/jetpack-components';
20
20
 
21
21
  ## Props
22
22
 
23
+ ### tagName
24
+
25
+ The HTML tag of the container.
26
+
27
+ - Type: `String`
28
+ - Default: `div`
29
+ - Required: `false`
30
+
23
31
  ### className
24
32
 
25
33
  A custom class to append with the default ones.
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import { useMemo } from 'react';
2
+ import { createElement, useMemo } from 'react';
3
3
  import { ContainerProps } from '../types';
4
4
  import styles from './style.module.scss';
5
5
  import type React from 'react';
@@ -13,6 +13,7 @@ import type React from 'react';
13
13
  const Container: React.FC< ContainerProps > = ( {
14
14
  children,
15
15
  fluid = false,
16
+ tagName = 'div',
16
17
  className,
17
18
  horizontalGap = 1,
18
19
  horizontalSpacing = 1,
@@ -32,10 +33,13 @@ const Container: React.FC< ContainerProps > = ( {
32
33
  [ styles.fluid ]: fluid,
33
34
  } );
34
35
 
35
- return (
36
- <div className={ containerClassName } style={ containerStyle }>
37
- { children }
38
- </div>
36
+ return createElement(
37
+ tagName,
38
+ {
39
+ className: containerClassName,
40
+ style: containerStyle,
41
+ },
42
+ children
39
43
  );
40
44
  };
41
45
 
@@ -1,6 +1,11 @@
1
1
  type ColSpan = number | { start: number; end: number };
2
2
 
3
3
  export type ColProps = {
4
+ /**
5
+ * Tag name of the column element.
6
+ */
7
+ tagName?: string;
8
+
4
9
  /**
5
10
  * Custom className to be inserted.
6
11
  */
@@ -28,6 +33,11 @@ export type ColProps = {
28
33
  };
29
34
 
30
35
  export type ContainerProps = {
36
+ /**
37
+ * Tag name of the container.
38
+ */
39
+ tagName?: string;
40
+
31
41
  /**
32
42
  * Make container not having a max width.
33
43
  */
@@ -22,7 +22,27 @@ const PricingSlider: React.FC< PricingSliderProps > = ( {
22
22
  onAfterChange,
23
23
  renderThumb,
24
24
  } ) => {
25
- const componentClassName = classNames( 'jp-components-pricing-slider', className );
25
+ const [ isThumbHolding, setIsThumbHolding ] = React.useState( false );
26
+
27
+ const componentClassName = classNames( 'jp-components-pricing-slider', className, {
28
+ 'jp-components-pricing-slider--is-holding': isThumbHolding,
29
+ } );
30
+
31
+ const onBeforeChangeCallback = beforeValue => {
32
+ setIsThumbHolding( true );
33
+
34
+ if ( typeof onBeforeChange === 'function' ) {
35
+ onBeforeChange( beforeValue );
36
+ }
37
+ };
38
+
39
+ const onAfterChangeCallback = afterValue => {
40
+ setIsThumbHolding( false );
41
+
42
+ if ( typeof onAfterChange === 'function' ) {
43
+ onAfterChange( afterValue );
44
+ }
45
+ };
26
46
 
27
47
  const renderThumbCallback = renderThumb
28
48
  ? renderThumb
@@ -43,8 +63,8 @@ const PricingSlider: React.FC< PricingSliderProps > = ( {
43
63
  step={ step }
44
64
  renderThumb={ renderThumbCallback } // eslint-disable-line react/jsx-no-bind
45
65
  onChange={ onChange } // eslint-disable-line react/jsx-no-bind
46
- onBeforeChange={ onBeforeChange } // eslint-disable-line react/jsx-no-bind
47
- onAfterChange={ onAfterChange } // eslint-disable-line react/jsx-no-bind
66
+ onBeforeChange={ onBeforeChangeCallback } // eslint-disable-line react/jsx-no-bind
67
+ onAfterChange={ onAfterChangeCallback } // eslint-disable-line react/jsx-no-bind
48
68
  />
49
69
  </div>
50
70
  );
@@ -8,6 +8,13 @@ $track-height: 8px;
8
8
  border-radius: 8px; /* stylelint-disable-line scales/radii */
9
9
  }
10
10
 
11
+ // On holding thumb styling
12
+ .jp-components-pricing-slider--is-holding {
13
+ .jp-components-pricing-slider__thumb {
14
+ box-shadow: 0 6px 8px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04), 0 0 0 3px rgba(6, 158, 8, 0.25);
15
+ }
16
+ }
17
+
11
18
  // Base styles
12
19
  .jp-components-pricing-slider__control {
13
20
  width: 100%;
@@ -35,8 +42,8 @@ $track-height: 8px;
35
42
  padding: 8px 16px;
36
43
  height: $thumb-height;
37
44
  background-color: var(--jp-white);
38
- border: 1px solid var(--jp-gray);
39
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 6px 8px rgba(0, 0, 0, 0.08);
45
+ border: 1.5px solid var(--jp-green-50);
46
+ box-shadow: 0 6px 8px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
40
47
  border-radius: 4px;
41
48
  color: var(--jp-black);
42
49
  font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif;
@@ -48,8 +55,12 @@ $track-height: 8px;
48
55
  white-space: nowrap;
49
56
  cursor: pointer;
50
57
 
58
+ // On focus thumb styling
51
59
  &.jp-components-pricing-slider__thumb--is-active {
52
- // On focus styling
53
60
  outline: none;
54
61
  }
62
+
63
+ &[aria-valuenow="0"] {
64
+ border-color: var(--jp-gray);
65
+ }
55
66
  }
@@ -1,4 +1,4 @@
1
- import { Meta, Story, Canvas } from '@storybook/addon-docs';
1
+ import { Meta, Story, Canvas } from '@storybook/blocks';
2
2
  import StatCard from '../index';
3
3
  import * as StatCardStories from './index.stories';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Meta, Source, Story } from '@storybook/addon-docs';
1
+ import { Meta, Source, Story } from '@storybook/blocks';
2
2
  import dedent from 'ts-dedent';
3
3
  import Text, { BOX_MODEL_VALUES, H2, H3, Title } from '../index';
4
4
  import * as Stories from './index.stories';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Jetpack Components Package",
5
5
  "author": "Automattic",
6
6
  "license": "GPL-2.0-or-later",
@@ -16,20 +16,20 @@
16
16
  "dependencies": {
17
17
  "@automattic/format-currency": "1.0.1",
18
18
  "@babel/runtime": "^7",
19
- "@wordpress/browserslist-config": "5.18.0",
20
- "@wordpress/components": "25.1.0",
21
- "@wordpress/compose": "6.12.0",
22
- "@wordpress/date": "4.35.0",
23
- "@wordpress/element": "5.12.0",
24
- "@wordpress/i18n": "4.35.0",
25
- "@wordpress/icons": "9.26.0",
19
+ "@wordpress/browserslist-config": "5.19.0",
20
+ "@wordpress/components": "25.2.0",
21
+ "@wordpress/compose": "6.13.0",
22
+ "@wordpress/date": "4.36.0",
23
+ "@wordpress/element": "5.13.0",
24
+ "@wordpress/i18n": "4.36.0",
25
+ "@wordpress/icons": "9.27.0",
26
26
  "classnames": "2.3.2",
27
27
  "prop-types": "^15.7.2",
28
28
  "qrcode.react": "3.1.0",
29
29
  "react-slider": "2.0.5"
30
30
  },
31
31
  "devDependencies": {
32
- "@automattic/jetpack-base-styles": "^0.6.1",
32
+ "@automattic/jetpack-base-styles": "^0.6.2",
33
33
  "@babel/core": "7.21.5",
34
34
  "@babel/preset-react": "7.18.6",
35
35
  "@jest/globals": "29.4.3",
@@ -41,11 +41,11 @@
41
41
  "@testing-library/user-event": "14.4.3",
42
42
  "@types/jest": "29.2.5",
43
43
  "@types/qrcode.react": "1.0.2",
44
- "@types/react": "18.0.27",
45
- "@types/react-dom": "18.0.10",
44
+ "@types/react": "18.2.14",
45
+ "@types/react-dom": "18.2.6",
46
46
  "@types/react-slider": "1.3.1",
47
47
  "@types/react-test-renderer": "18.0.0",
48
- "@types/testing-library__jest-dom": "5.14.1",
48
+ "@types/testing-library__jest-dom": "5.14.6",
49
49
  "jest": "29.4.3",
50
50
  "jest-environment-jsdom": "29.4.3",
51
51
  "react": "18.2.0",
@@ -53,6 +53,7 @@
53
53
  "react-test-renderer": "18.2.0",
54
54
  "require-from-string": "2.0.2",
55
55
  "resize-observer-polyfill": "1.5.1",
56
+ "ts-dedent": "2.2.0",
56
57
  "typescript": "5.0.4",
57
58
  "webpack": "5.76.0",
58
59
  "webpack-cli": "4.9.1"