@automattic/jetpack-components 1.7.0 → 1.8.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.
@@ -1,30 +1,22 @@
1
- import { __, _x } from '@wordpress/i18n';
2
- import { Icon, external } from '@wordpress/icons';
1
+ import { isWpcomPlatformSite, getAdminUrl } from '@automattic/jetpack-script-data';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Stack, Text, Link } from '@wordpress/ui';
3
4
  import clsx from 'clsx';
4
5
  import { getRedirectUrl } from '../../index.ts';
5
- import getSiteAdminUrl from '../../tools/get-site-admin-url/index.ts';
6
6
  import AutomatticBylineLogo from '../automattic-byline-logo/index.tsx';
7
7
  import './style.scss';
8
8
  import JetpackLogo from '../jetpack-logo/index.tsx';
9
- import useBreakpointMatch from '../layout/use-breakpoint-match/index.ts';
10
9
  import type { JetpackFooterProps, JetpackFooterMenuItem } from './types.ts';
11
- import type { FC, ReactNode } from 'react';
10
+ import type { FC } from 'react';
12
11
 
13
- const JetpackIcon: FC = () => (
14
- <JetpackLogo logoColor="#000" showText={ false } height={ 16 } aria-hidden="true" />
15
- );
16
-
17
- const ExternalIcon: FC = () => (
18
- <>
19
- <Icon icon={ external } size={ 16 } />
20
- <span className="jp-dashboard-footer__accessible-external-link">
21
- {
22
- /* translators: accessibility text */
23
- __( '(opens in a new tab)', 'jetpack-components' )
24
- }
25
- </span>
26
- </>
27
- );
12
+ declare global {
13
+ interface Window {
14
+ JetpackNetworkAdminData?: {
15
+ sitesUrl: string;
16
+ settingsUrl: string;
17
+ };
18
+ }
19
+ }
28
20
 
29
21
  /**
30
22
  * JetpackFooter component displays a tiny Jetpack logo with the product name on the left and the Automattic Airline "by line" on the right.
@@ -32,124 +24,93 @@ const ExternalIcon: FC = () => (
32
24
  * @param {JetpackFooterProps} props - Component properties.
33
25
  * @return {ReactNode} JetpackFooter component.
34
26
  */
35
- const JetpackFooter: FC< JetpackFooterProps > = ( {
36
- moduleName = 'Jetpack' /** "Jetpack" is a product name, do not translate. */,
37
- className,
38
- moduleNameHref = 'https://jetpack.com',
39
- menu,
40
- useInternalLinks,
41
- onAboutClick,
42
- onPrivacyClick,
43
- onTermsClick,
44
- ...otherProps
45
- } ) => {
46
- const [ isSm ] = useBreakpointMatch( 'sm', '<=' );
47
- const [ isMd ] = useBreakpointMatch( 'md', '<=' );
48
- const [ isLg ] = useBreakpointMatch( 'lg', '>' );
27
+ const JetpackFooter: FC< JetpackFooterProps > = ( { className, menu, ...otherProps } ) => {
28
+ let items: JetpackFooterMenuItem[] = [];
49
29
 
50
- const siteAdminUrl = getSiteAdminUrl();
51
-
52
- let items: JetpackFooterMenuItem[] = [
53
- {
54
- label: _x( 'About', 'Link to learn more about Jetpack.', 'jetpack-components' ),
55
- title: __( 'About Jetpack', 'jetpack-components' ),
56
- href: useInternalLinks
57
- ? new URL( 'admin.php?page=jetpack_about', siteAdminUrl ).href
58
- : getRedirectUrl( 'jetpack-about' ),
59
- target: useInternalLinks ? '_self' : '_blank',
60
- onClick: onAboutClick,
61
- },
62
- {
63
- label: _x( 'Privacy', 'Shorthand for Privacy Policy.', 'jetpack-components' ),
64
- title: __( "Automattic's Privacy Policy", 'jetpack-components' ),
65
- href: useInternalLinks
66
- ? new URL( 'admin.php?page=jetpack#/privacy', siteAdminUrl ).href
67
- : getRedirectUrl( 'a8c-privacy' ),
68
- target: useInternalLinks ? '_self' : '_blank',
69
- onClick: onPrivacyClick,
70
- },
71
- {
72
- label: _x( 'Terms', 'Shorthand for Terms of Service.', 'jetpack-components' ),
73
- title: __( 'WordPress.com Terms of Service', 'jetpack-components' ),
74
- href: getRedirectUrl( 'wpcom-tos' ),
75
- target: '_blank',
76
- onClick: onTermsClick,
77
- },
78
- ];
30
+ if ( ! isWpcomPlatformSite() && ! window?.JetpackNetworkAdminData ) {
31
+ items = [
32
+ {
33
+ label: __( 'Products', 'jetpack-components' ),
34
+ href: getAdminUrl( 'admin.php?page=my-jetpack#/products' ),
35
+ },
36
+ {
37
+ label: __( 'Help', 'jetpack-components' ),
38
+ href: getAdminUrl( 'admin.php?page=my-jetpack#/help' ),
39
+ },
40
+ ...items,
41
+ ];
42
+ }
79
43
 
80
44
  if ( menu ) {
81
45
  items = [ ...items, ...menu ];
82
46
  }
83
47
 
84
- const jetpackItemContent = (
85
- <>
86
- <JetpackIcon />
87
- { moduleName }
88
- </>
89
- );
90
-
91
48
  return (
92
- <footer
93
- className={ clsx(
94
- 'jp-dashboard-footer',
95
- {
96
- 'is-sm': isSm,
97
- 'is-md': isMd,
98
- 'is-lg': isLg,
99
- },
100
- className
101
- ) }
49
+ <Stack
50
+ render={ <footer /> }
51
+ className={ clsx( 'jetpack-footer', className ) }
102
52
  aria-label={ __( 'Jetpack', 'jetpack-components' ) }
103
53
  role="contentinfo"
54
+ direction="row"
55
+ justify="flex-start"
56
+ align="center"
57
+ wrap="wrap"
58
+ gap="xl"
104
59
  { ...otherProps }
105
60
  >
106
- <ul>
107
- <li className="jp-dashboard-footer__jp-item">
108
- { moduleNameHref ? (
109
- <a href={ moduleNameHref }>{ jetpackItemContent }</a>
110
- ) : (
111
- jetpackItemContent
112
- ) }
113
- </li>
61
+ <Stack className="jetpack-footer__logo" direction="row" gap="sm" align="center">
62
+ <JetpackLogo showText={ false } height={ 16 } aria-hidden="true" />
63
+ <Text variant="body-md">Jetpack</Text>
64
+ </Stack>
65
+ <Stack render={ <ul /> } direction="row" gap="lg" wrap="wrap">
114
66
  { items.map( item => {
115
67
  const isButton = item.role === 'button';
116
- const isExternalLink = ! isButton && item.target === '_blank';
117
68
 
118
69
  return (
119
70
  <li key={ item.label }>
120
- <a
121
- href={ item.href }
122
- title={ item.title }
123
- target={ item.target }
124
- onClick={ item.onClick }
125
- onKeyDown={ item.onKeyDown }
126
- className={ clsx( 'jp-dashboard-footer__menu-item', {
127
- 'is-external': isExternalLink,
128
- } ) }
129
- role={ item.role }
130
- rel={ isExternalLink ? 'noopener noreferrer' : undefined }
131
- tabIndex={ isButton ? 0 : undefined }
71
+ <Text
72
+ variant="body-sm"
73
+ className="jetpack-footer__menu-item"
74
+ render={
75
+ isButton ? (
76
+ <Link
77
+ render={ <Text variant="body-md" render={ <span /> } /> }
78
+ tone="neutral"
79
+ variant="default"
80
+ role={ item.role }
81
+ tabIndex={ 0 }
82
+ onClick={ item.onClick || undefined }
83
+ onKeyDown={ item.onKeyDown || undefined }
84
+ />
85
+ ) : (
86
+ <Link
87
+ render={ <Text variant="body-md" render={ <a /> } /> }
88
+ tone="neutral"
89
+ variant="default"
90
+ href={ item.href || '' }
91
+ title={ item.title || '' }
92
+ role={ item.role }
93
+ onClick={ item.onClick || undefined }
94
+ onKeyDown={ item.onKeyDown || undefined }
95
+ />
96
+ )
97
+ }
132
98
  >
133
99
  { item.label }
134
- { isExternalLink && <ExternalIcon /> }
135
- </a>
100
+ </Text>
136
101
  </li>
137
102
  );
138
103
  } ) }
139
- <li className="jp-dashboard-footer__a8c-item">
140
- <a
141
- href={
142
- useInternalLinks
143
- ? new URL( 'admin.php?page=jetpack_about', siteAdminUrl ).href
144
- : getRedirectUrl( 'a8c-about' )
145
- }
146
- aria-label={ __( 'An Automattic Airline', 'jetpack-components' ) }
147
- >
148
- <AutomatticBylineLogo aria-hidden="true" />
149
- </a>
150
- </li>
151
- </ul>
152
- </footer>
104
+ </Stack>
105
+ <a
106
+ className="jetpack-footer__a8c"
107
+ href={ getRedirectUrl( 'a8c-about' ) }
108
+ rel="noopener noreferrer"
109
+ target="_blank"
110
+ >
111
+ <AutomatticBylineLogo height={ 8 } />
112
+ </a>
113
+ </Stack>
153
114
  );
154
115
  };
155
116
 
@@ -1,119 +1,49 @@
1
- .jp-dashboard-footer {
2
- display: flex;
3
- flex-wrap: wrap;
4
- align-items: center;
5
-
6
- max-width: 1128px;
1
+ @use "sass:meta";
2
+ @include meta.load-css("pkg:@wordpress/theme/design-tokens.css");
3
+
4
+ .jetpack-footer {
5
+ border-top: var(--wpds-border-width-xs, 1px) solid var(--wpds-color-stroke-surface-neutral-weak, #e0e0e0);
6
+ box-sizing: border-box;
7
+ font-size: var(--wpds-font-size-md, 13px);
8
+ padding: var(--wpds-dimension-padding-xl) var(--wpds-dimension-padding-2xl);
7
9
  width: 100%;
8
10
 
9
- color: var(--jp-black);
10
-
11
- font-size: var(--font-body-extra-small);
12
- line-height: 1.333;
13
-
14
- a {
15
- text-decoration: none;
11
+ .jetpack-footer__menu-item {
16
12
 
17
13
  &:any-link,
18
14
  &[role="button"] {
19
- color: inherit;
15
+ color: var(--wpds-color-fg-interactive-neutral-weak);
16
+ cursor: pointer;
17
+ text-decoration: none;
20
18
  }
21
19
 
22
20
  &:hover {
23
21
  text-decoration: underline;
24
- text-decoration-thickness: 1.5px;
25
- }
26
-
27
- &:focus {
28
- box-shadow: none;
29
- outline-width: 0;
30
- }
31
-
32
- &:focus-visible {
33
- border-radius: 2px;
34
- box-shadow: none;
35
- outline: 1.5px solid currentColor;
36
- outline-offset: 3px;
37
22
  }
38
23
  }
39
24
 
40
- &.is-sm {
41
-
42
- > ul {
43
- flex-direction: column;
44
- align-items: flex-start;
45
- gap: 0.125rem;
46
- }
47
- }
48
-
49
- &.is-md {
50
- flex-direction: column;
51
- align-items: flex-start;
52
- }
53
-
54
25
  > ul {
55
- display: flex;
56
- flex-wrap: wrap;
57
- align-items: center;
58
- gap: 1rem;
59
-
60
- width: 100%;
26
+ list-style: none;
61
27
  margin: 0;
62
28
  padding: 0;
63
29
 
64
- list-style: none;
65
-
66
30
  > li {
67
- margin-bottom: 0;
68
-
69
- > a {
70
- display: flex;
71
- align-items: center;
72
- gap: 0.25rem;
73
-
74
- min-height: 44px; // Minimum target area size
75
- }
31
+ margin: 0;
76
32
  }
77
33
  }
78
34
  }
79
35
 
80
- .jp-dashboard-footer__accessible-external-link {
81
- border: 0;
82
- clip-path: inset(50%);
83
- height: 1px;
84
- margin: -1px;
85
- overflow: hidden;
86
- padding: 0;
87
- position: absolute;
88
- width: 1px;
89
- overflow-wrap: normal;
90
- }
91
-
92
- .jp-dashboard-footer__jp-item {
93
- padding-inline-end: 1rem;
94
-
95
- font-weight: 600;
96
-
97
- .jp-dashboard-footer.is-sm & {
98
- padding-bottom: 1rem;
99
- }
36
+ .jetpack-footer__logo {
37
+ flex-shrink: 0;
100
38
  }
101
39
 
102
- .jp-dashboard-footer__a8c-item {
40
+ a.jetpack-footer__a8c {
103
41
 
104
- .jp-dashboard-footer.is-lg & {
42
+ @media (min-width: 480px) {
105
43
  margin-inline-start: auto;
106
44
  }
107
45
 
108
- .jp-dashboard-footer.is-sm & {
109
- padding-top: 1rem;
110
- }
111
- }
112
-
113
- .jp-dashboard-footer__jp-item,
114
- .jp-dashboard-footer__a8c-item {
115
-
116
- > a {
117
- text-decoration: none; // Logos typically have no hover effect
46
+ svg {
47
+ fill: var(--wpds-color-fg-interactive-neutral-weak);
118
48
  }
119
49
  }
@@ -1,56 +1,20 @@
1
1
  export type JetpackFooterMenuItem = {
2
- href: string;
2
+ href?: string;
3
3
  label: string;
4
4
  onClick?: () => void;
5
5
  onKeyDown?: () => void;
6
- target?: string;
7
6
  title?: string;
8
7
  role?: string;
9
8
  };
10
9
 
11
10
  export type JetpackFooterProps = {
12
11
  /**
13
- * Name of the module, e.g. 'Jetpack Search'.
14
- */
15
- moduleName?: string;
16
-
17
- /**
18
- * additional className of the wrapper, `jp-dashboard-footer` always included.
12
+ * Additional className of the wrapper, `jetpack-footer` always included.
19
13
  */
20
14
  className?: string;
21
15
 
22
16
  /**
23
- * Link that the Module name will link to (optional).
24
- */
25
- moduleNameHref?: string;
26
-
27
- /**
28
- * Navigation menu to display in the footer.
17
+ * Additional links to display in the footer.
29
18
  */
30
19
  menu?: JetpackFooterMenuItem[];
31
-
32
- /**
33
- * Whether to enable Jetpack admin links.
34
- */
35
- useInternalLinks?: boolean;
36
-
37
- /**
38
- * URL of the site WP Admin.
39
- */
40
- siteAdminUrl?: string;
41
-
42
- /**
43
- * Function called when the About link is clicked.
44
- */
45
- onAboutClick?: () => void;
46
-
47
- /**
48
- * Function called when the Privacy link is clicked.
49
- */
50
- onPrivacyClick?: () => void;
51
-
52
- /**
53
- * Function called when the Terms link is clicked.
54
- */
55
- onTermsClick?: () => void;
56
20
  };
@@ -17,6 +17,14 @@ export type HeaderProps = {
17
17
  * Optional icon to display in the header.
18
18
  */
19
19
  icon?: React.ReactNode;
20
+ /**
21
+ * Optional callback to run before navigating back.
22
+ */
23
+ onGoBack?: VoidFunction;
24
+ /**
25
+ * Optional callback to run before closing the modal.
26
+ */
27
+ onClose?: VoidFunction;
20
28
  };
21
29
 
22
30
  /**
@@ -25,13 +33,25 @@ export type HeaderProps = {
25
33
  *
26
34
  * @return component
27
35
  */
28
- export function Header( { icon, title, isScreenLocked }: HeaderProps ) {
36
+ export function Header( {
37
+ icon,
38
+ title,
39
+ isScreenLocked,
40
+ onGoBack: onGoBackProp,
41
+ onClose: onCloseProp,
42
+ }: HeaderProps ) {
29
43
  const context = useContext( NavigatorModalContext );
30
44
  const navigator = useNavigator();
31
45
 
32
46
  const onGoBack = useCallback( () => {
47
+ onGoBackProp?.();
33
48
  navigator.goBack();
34
- }, [ navigator ] );
49
+ }, [ navigator, onGoBackProp ] );
50
+
51
+ const onCloseModal = useCallback( () => {
52
+ onCloseProp?.();
53
+ context.onClose?.();
54
+ }, [ onCloseProp, context ] );
35
55
 
36
56
  return (
37
57
  <div className="jp-navigator-modal__header">
@@ -51,7 +71,7 @@ export function Header( { icon, title, isScreenLocked }: HeaderProps ) {
51
71
  { context.isDismissible ? (
52
72
  <Button
53
73
  size="compact"
54
- onClick={ context.onClose }
74
+ onClick={ onCloseModal }
55
75
  icon={ close }
56
76
  label={ __( 'Close', 'jetpack-components' ) }
57
77
  variant="tertiary"
@@ -1,61 +1,51 @@
1
1
  import { Flex, Navigator } from '@wordpress/components';
2
2
  import clsx from 'clsx';
3
3
  import { Footer, FooterProps } from './footer.tsx';
4
- import { Header } from './header.tsx';
4
+ import { Header, HeaderProps } from './header.tsx';
5
5
 
6
6
  export type ScreenProps = Omit<
7
7
  React.ComponentProps< typeof Navigator.Screen >,
8
8
  'content' | 'children'
9
- > & {
10
- /**
11
- * The title of the screen.
12
- */
13
- title?: string;
9
+ > &
10
+ Omit< HeaderProps, 'icon' > & {
11
+ /**
12
+ * Optional icon to display in the header.
13
+ */
14
+ headerIcon?: React.ReactNode;
14
15
 
15
- /**
16
- * Optional icon to display in the header.
17
- */
18
- headerIcon?: React.ReactNode;
16
+ /**
17
+ * The path of the screen.
18
+ */
19
+ path: string;
20
+ /**
21
+ * The sidebar content
22
+ */
23
+ sidebar?: React.ReactNode;
24
+ /**
25
+ * The footer content
26
+ */
27
+ footerContent?: React.ReactNode;
19
28
 
20
- /**
21
- * The path of the screen.
22
- */
23
- path: string;
24
- /**
25
- * The sidebar content
26
- */
27
- sidebar?: React.ReactNode;
28
- /**
29
- * Whether the screen is locked or has a parent screen.
30
- *
31
- * When it's locked, it means there will be no navigation back to a previous screen.
32
- */
33
- isScreenLocked?: boolean;
34
- /**
35
- * The footer content
36
- */
37
- footerContent?: React.ReactNode;
29
+ /**
30
+ * The footer actions
31
+ */
32
+ footerActions?: FooterProps[ 'actions' ];
38
33
 
39
- /**
40
- * The footer actions
41
- */
42
- footerActions?: FooterProps[ 'actions' ];
34
+ /**
35
+ * className to be applied to the modal.
36
+ */
37
+ className?: string;
43
38
 
44
- /**
45
- * className to be applied to the modal.
46
- */
47
- className?: string;
39
+ /**
40
+ * The content of the screen.
41
+ */
42
+ content?: React.ReactNode;
48
43
 
49
- /**
50
- * The content of the screen.
51
- */
52
- content?: React.ReactNode;
53
-
54
- /**
55
- * The children of the screen. Alternative to `content`.
56
- */
57
- children?: React.ReactNode;
58
- };
44
+ /**
45
+ * The children of the screen. Alternative to `content`.
46
+ */
47
+ children?: React.ReactNode;
48
+ };
59
49
 
60
50
  /**
61
51
  * Renders a screen.
@@ -71,6 +61,8 @@ export function Screen( {
71
61
  sidebar,
72
62
  headerIcon,
73
63
  isScreenLocked,
64
+ onGoBack,
65
+ onClose,
74
66
  footerContent,
75
67
  footerActions,
76
68
  children,
@@ -86,7 +78,13 @@ export function Screen( {
86
78
  { ...props }
87
79
  >
88
80
  <Flex direction="column" gap={ 0 }>
89
- <Header title={ title } isScreenLocked={ isScreenLocked } icon={ headerIcon } />
81
+ <Header
82
+ title={ title }
83
+ isScreenLocked={ isScreenLocked }
84
+ icon={ headerIcon }
85
+ onGoBack={ onGoBack }
86
+ onClose={ onClose }
87
+ />
90
88
 
91
89
  <Flex gap={ 0 } align="start" className="jp-navigator-modal__body">
92
90
  { sidebar ? <div className="jp-navigator-modal__sidebar">{ sidebar }</div> : null }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Jetpack Components Package",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/components/#readme",
6
6
  "bugs": {
@@ -46,9 +46,9 @@
46
46
  "dependencies": {
47
47
  "@automattic/format-currency": "1.0.1",
48
48
  "@automattic/jetpack-api": "^1.0.21",
49
- "@automattic/jetpack-boost-score-api": "^1.0.35",
49
+ "@automattic/jetpack-boost-score-api": "^1.0.36",
50
50
  "@automattic/jetpack-script-data": "^0.6.1",
51
- "@automattic/number-formatters": "^1.1.3",
51
+ "@automattic/number-formatters": "^1.1.4",
52
52
  "@babel/runtime": "^7",
53
53
  "@wordpress/admin-ui": "1.10.0",
54
54
  "@wordpress/browserslist-config": "6.42.0",
@@ -60,6 +60,8 @@
60
60
  "@wordpress/i18n": "6.15.0",
61
61
  "@wordpress/icons": "12.0.0",
62
62
  "@wordpress/notices": "5.42.0",
63
+ "@wordpress/theme": "0.9.0",
64
+ "@wordpress/ui": "0.9.0",
63
65
  "clsx": "2.1.1",
64
66
  "prop-types": "^15.7.2",
65
67
  "qrcode.react": "4.2.0",
@@ -73,8 +75,8 @@
73
75
  "@babel/core": "7.29.0",
74
76
  "@babel/preset-react": "7.28.5",
75
77
  "@jest/globals": "30.3.0",
76
- "@storybook/addon-docs": "10.3.1",
77
- "@storybook/react": "10.3.1",
78
+ "@storybook/addon-docs": "10.3.3",
79
+ "@storybook/react": "10.3.3",
78
80
  "@testing-library/dom": "10.4.1",
79
81
  "@testing-library/react": "16.3.2",
80
82
  "@testing-library/user-event": "14.6.1",
@@ -87,7 +89,7 @@
87
89
  "react": "18.3.1",
88
90
  "react-dom": "18.3.1",
89
91
  "require-from-string": "2.0.2",
90
- "storybook": "10.3.1",
92
+ "storybook": "10.3.3",
91
93
  "ts-dedent": "2.2.0",
92
94
  "typescript": "5.9.3",
93
95
  "webpack": "5.105.2",
@@ -1,8 +0,0 @@
1
- /**
2
- * Returns the site admin URL.
3
- *
4
- * @deprecated use `getAdminUrl` from `@automattic/jetpack-script-data` instead.
5
- *
6
- * @return {?string} The site admin URL or null if not available.
7
- */
8
- export default function getSiteAdminUrl(): any;
@@ -1,17 +0,0 @@
1
- import { getScriptData } from '@automattic/jetpack-script-data';
2
- /**
3
- * Returns the site admin URL.
4
- *
5
- * @deprecated use `getAdminUrl` from `@automattic/jetpack-script-data` instead.
6
- *
7
- * @return {?string} The site admin URL or null if not available.
8
- */
9
- export default function getSiteAdminUrl() {
10
- return (getScriptData()?.site?.admin_url ||
11
- window.Initial_State?.adminUrl ||
12
- window.Jetpack_Editor_Initial_State?.adminUrl ||
13
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Not using @ts-expect-error because myJetpackInitialState is typed in "my-jetpack" package and that doesn't expect this error
14
- // @ts-ignore The usage of myJetpackInitialState is not typed inside this generic package. We should get rid of it in the future.
15
- window?.myJetpackInitialState?.adminUrl ||
16
- null);
17
- }