@automattic/jetpack-components 1.6.1 → 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,20 +1,33 @@
1
1
  # Jetpack Admin Footer
2
2
 
3
3
  Component that renders Jetpack Admin Footer.
4
- It takes moduleName and URL to show in the footer.
5
4
 
6
- ## How to use:
5
+ ## How to use
6
+
7
+ Note that most of the time you would just use `admin-page` component, which includes the footer. If you must use footer independently, basic usage is:
8
+
9
+ ```js
10
+ <JetpackFooter />
11
+ ```
12
+
13
+ In special occasions you might want to use custom className or additional menu links:
7
14
 
8
15
  ```js
9
- <JetpackFooter moduleName="Jetpack Search" className="jp-dashboard-footer" />
16
+ const menu = [
17
+ {
18
+ label: "Support",
19
+ href="https://wordpress.com/support/",
20
+ },
21
+ {
22
+ label: "Chat",
23
+ onClick: () => {},
24
+ }
25
+ ];
26
+
27
+ <JetpackFooter menu={ menu } className="my-footer" />
10
28
  ```
11
29
 
12
30
  ## Props
13
31
 
14
- - `className`: String - (default: `jp-dashboard-footer`) the additional class name set on the element.
15
- - `moduleName`: String - (default: `Jetpack`) set the name of the Module, e.g. `Jetpack Search`.
16
- - `moduleNameHref`: String - (default: `https://jetpack.com`) link that the Module name will link to.
17
- - `menu`: JetpackFooterMenuItem[] - (default: `undefined`) set the menu items to be rendered in the footer.
18
- - `onAboutClick`: () => void - (default: `undefined`) function called when the About link is clicked.
19
- - `onPrivacyClick`: () => void - (default: `undefined`) function called when the Privacy link is clicked.
20
- - `onTermsClick`: () => void - (default: `undefined`) function called when the Terms link is clicked.
32
+ - `className`: String - (default: `jetpack-footer`) the additional class name set on the element.
33
+ - `menu`: JetpackFooterMenuItem[] - (default: `undefined`) set additional menu items to be rendered in the footer.
@@ -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 }