@automattic/jetpack-components 1.9.0 → 1.11.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 (42) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/build/components/admin-page/index.d.ts +0 -1
  3. package/build/components/admin-page/index.js +3 -10
  4. package/build/components/admin-page/style.module.scss +16 -3
  5. package/build/components/admin-page/types.d.ts +8 -0
  6. package/build/components/boost-score-bar/index.js +3 -3
  7. package/build/components/chip/style.module.scss +0 -4
  8. package/build/components/gravatar/index.d.ts +58 -0
  9. package/build/components/gravatar/index.js +68 -0
  10. package/build/components/gravatar/style.scss +7 -0
  11. package/build/components/icon-tooltip/index.js +3 -3
  12. package/build/components/icon-tooltip/types.d.ts +4 -3
  13. package/build/components/jetpack-footer/index.js +1 -1
  14. package/build/components/terms-of-service/index.js +10 -10
  15. package/build/components/testimonials/index.js +2 -2
  16. package/build/components/toggle-control/index.d.ts +2 -0
  17. package/build/components/toggle-control/index.js +2 -2
  18. package/build/index.d.ts +3 -1
  19. package/build/index.js +2 -1
  20. package/components/admin-page/index.tsx +12 -19
  21. package/components/admin-page/style.module.scss +16 -3
  22. package/components/admin-page/types.ts +9 -0
  23. package/components/boost-score-bar/index.tsx +3 -3
  24. package/components/chip/style.module.scss +0 -4
  25. package/components/gravatar/index.tsx +138 -0
  26. package/components/gravatar/style.scss +7 -0
  27. package/components/icon-tooltip/index.tsx +3 -3
  28. package/components/icon-tooltip/types.ts +4 -3
  29. package/components/jetpack-footer/index.tsx +2 -3
  30. package/components/terms-of-service/index.tsx +12 -12
  31. package/components/testimonials/index.tsx +3 -3
  32. package/components/toggle-control/index.tsx +5 -0
  33. package/index.ts +3 -1
  34. package/package.json +26 -8
  35. package/build/components/gridicon/index.d.ts +0 -14
  36. package/build/components/gridicon/index.js +0 -173
  37. package/build/components/gridicon/style.scss +0 -16
  38. package/build/components/gridicon/types.d.ts +0 -39
  39. package/build/components/gridicon/types.js +0 -1
  40. package/components/gridicon/index.tsx +0 -334
  41. package/components/gridicon/style.scss +0 -16
  42. package/components/gridicon/types.ts +0 -48
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [1.11.0] - 2026-05-11
6
+ ### Added
7
+ - AdminPage: Add admin-page and jetpack-footer sub-path exports for use in wp-build dashboards. [#48510]
8
+ - Gravatar: Add a component with hovercard support at the `./gravatar` subpath, shared across products that render subscriber or response avatars. [#48581]
9
+ - Global Notices: Add global-notices sub-path export for use in wp-build dashboards. [#48586]
10
+
11
+ ### Changed
12
+ - AdminPage: Update to @wordpress/admin-ui 2.0.0 and use the new `visual` prop to render the Jetpack logo alongside the page title. [#48410]
13
+ - Components: Use Link from `@wordpress/ui` instead of ExternalLink. [#48529]
14
+
15
+ ### Removed
16
+ - Components: Remove Gridicon component. Use Icon and named icon exports from `@wordpress/icons` instead. [#48537]
17
+
18
+ ### Fixed
19
+ - AdminPage: Pass a stable `jp-admin-page__page` className to admin-ui's Page so layout overrides survive admin-ui 2.0.0's switch to CSS Modules; pin the header heading level to `<h2>` and center the new `visual` slot to keep the Jetpack logo aligned with the title. [#48410]
20
+
21
+ ## [1.10.0] - 2026-05-04
22
+ ### Added
23
+ - AdminPage: Add `unwrapped` prop to render children directly inside the admin-ui Page, skipping the default Container/Col grid wrap. Use for full-bleed pages (DataViews-based admin surfaces) that own their own scroll/layout model. [#48244]
24
+ - ToggleControl: Forward the `aria-label` prop to the underlying checkbox so consumers can label toggles that have no visible label. [#48277]
25
+
26
+ ### Changed
27
+ - Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
28
+ - Remove "new" style variant from the Chip component. [#48174]
29
+
30
+ ### Deprecated
31
+ - Mark the Spinner component as deprecated in favor of the WordPress Core Spinner from @wordpress/components. [#47451]
32
+
5
33
  ## [1.9.0] - 2026-04-27
6
34
  ### Deprecated
7
35
  - Componentry: Deprecate the Chip component. Use Badge from @wordpress/ui instead — map type="new" to intent="stable". [#48162]
@@ -1752,6 +1780,8 @@
1752
1780
  ### Changed
1753
1781
  - Update node version requirement to 14.16.1
1754
1782
 
1783
+ [1.11.0]: https://github.com/Automattic/jetpack-components/compare/1.10.0...1.11.0
1784
+ [1.10.0]: https://github.com/Automattic/jetpack-components/compare/1.9.0...1.10.0
1755
1785
  [1.9.0]: https://github.com/Automattic/jetpack-components/compare/1.8.3...1.9.0
1756
1786
  [1.8.3]: https://github.com/Automattic/jetpack-components/compare/1.8.2...1.8.3
1757
1787
  [1.8.2]: https://github.com/Automattic/jetpack-components/compare/1.8.1...1.8.2
@@ -1,4 +1,3 @@
1
- import '@wordpress/admin-ui/build-style/style.css';
2
1
  import type { AdminPageProps } from './types.ts';
3
2
  import type { FC } from 'react';
4
3
  /**
@@ -1,9 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import restApi from '@automattic/jetpack-api';
3
3
  import { Page } from '@wordpress/admin-ui';
4
- import '@wordpress/admin-ui/build-style/style.css';
5
- import { __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
6
- } from '@wordpress/components';
7
4
  import { __, sprintf } from '@wordpress/i18n';
8
5
  import clsx from 'clsx';
9
6
  import { useEffect, useCallback } from 'react';
@@ -20,7 +17,7 @@ import styles from './style.module.scss';
20
17
  * @param {AdminPageProps} props - Component properties.
21
18
  * @return {ReactNode} AdminPage component.
22
19
  */
23
- const AdminPage = ({ children, className, showHeader = true, showFooter = true, showBackground = true, sandboxedDomain = '', apiRoot = '', apiNonce = '', optionalMenuItems, header, title, subTitle, logo, actions, breadcrumbs, tabs, showBottomBorder = true, }) => {
20
+ const AdminPage = ({ children, className, showHeader = true, showFooter = true, showBackground = true, sandboxedDomain = '', apiRoot = '', apiNonce = '', optionalMenuItems, header, title, subTitle, logo, actions, breadcrumbs, tabs, showBottomBorder = true, unwrapped = false, }) => {
24
21
  useEffect(() => {
25
22
  restApi.setApiRoot(apiRoot);
26
23
  restApi.setApiNonce(apiNonce);
@@ -45,13 +42,9 @@ const AdminPage = ({ children, className, showHeader = true, showFooter = true,
45
42
  __('There was an error testing Jetpack. Error: %s', 'jetpack-components'), error.message));
46
43
  }
47
44
  }, []);
48
- // Compose the title with logo for the admin-ui Page header.
49
- // Page's Header wraps this in an <h2> tag, so we just pass the content directly.
50
- const composedTitle = title ? (_jsxs(HStack, { spacing: 2, justify: "left", children: [logo || _jsx(JetpackLogo, { showText: false, height: 20 }), _jsx("span", { children: title })
51
- ] })) : undefined;
52
45
  // When title or breadcrumbs are provided, use admin-ui Page for the full page layout.
53
- if (showHeader && (composedTitle || breadcrumbs)) {
54
- return (_jsx("div", { className: rootClassName, children: _jsxs(Page, { ariaLabel: title, breadcrumbs: breadcrumbs, title: composedTitle, subTitle: subTitle, actions: actions, showSidebarToggle: false, children: [tabs, _jsx(Container, { fluid: true, horizontalSpacing: 0, children: _jsx(Col, { children: children }) }), showFooter && _jsx(JetpackFooter, { menu: optionalMenuItems })] }) }));
46
+ if (showHeader && (title || breadcrumbs)) {
47
+ return (_jsx("div", { className: rootClassName, children: _jsxs(Page, { className: "jp-admin-page__page", visual: logo || _jsx(JetpackLogo, { showText: false, height: 20 }), breadcrumbs: breadcrumbs, title: title, subTitle: subTitle, actions: actions, showSidebarToggle: false, children: [tabs, unwrapped ? (children) : (_jsx(Container, { fluid: true, horizontalSpacing: 0, children: _jsx(Col, { children: children }) })), showFooter && _jsx(JetpackFooter, { menu: optionalMenuItems })] }) }));
55
48
  }
56
49
  // Legacy path: no title provided, render the classic header.
57
50
  return (_jsxs("div", { className: rootClassName, children: [showHeader && (_jsx(Container, { horizontalSpacing: 5, children: _jsxs(Col, { className: clsx(styles['admin-page-header'], 'jp-admin-page-header'), children: [header ? header : _jsx(JetpackLogo, {}), sandboxedDomain && (_jsx("code", { className: styles['sandbox-domain-badge'], onClick: testConnection, onKeyDown: testConnection,
@@ -13,23 +13,36 @@
13
13
  // or when showBottomBorder is false.
14
14
  // Ideally admin-ui would expose a prop or CSS custom property for this:
15
15
  // https://github.com/WordPress/gutenberg/issues/75428
16
- &.without-bottom-border :global(.admin-ui-page__header) {
16
+
17
+ // Anchor: `.jp-admin-page__page` is the className we pass to admin-ui's
18
+ // <Page>; admin-ui 2.0.0 renders the header as a stable <header> element
19
+ // directly inside that page node — no class hooks anymore.
20
+ &.without-bottom-border :global(.jp-admin-page__page > header) {
17
21
  border-bottom: none;
18
22
  }
19
23
 
20
24
  // Disable sticky header until we make it work better across all pages.
21
25
  // JETPACK-1386
22
- :global(.admin-ui-page__header) {
26
+ :global(.jp-admin-page__page > header) {
23
27
  position: relative;
24
28
  z-index: 1;
25
29
  }
26
30
 
27
31
  // Normalize admin headers: implementation of admin-ui needs to
28
32
  // comprehend old wp-admin floating containers, such as Hello Dolly.
29
- :global(.admin-ui-page) {
33
+ :global(.jp-admin-page__page) {
30
34
  clear: both;
31
35
  }
32
36
 
37
+ // admin-ui 2.0.0's header `visual` slot is a 24px grid box but does not
38
+ // center its contents. Our JetpackLogo is 20px tall, so without this it
39
+ // pins to the top-left of the cell and looks misaligned vs. the title.
40
+ // admin-ui ships the slot as `<div aria-hidden="true">` in the header.
41
+ // TODO: remove once upstream centers contents in `.header-visual`.
42
+ :global(.jp-admin-page__page > header [aria-hidden="true"]) {
43
+ place-items: center;
44
+ }
45
+
33
46
  .admin-page-header {
34
47
  display: flex;
35
48
  align-items: center;
@@ -76,4 +76,12 @@ export type AdminPageProps = {
76
76
  * Hidden when `tabs` is used.
77
77
  */
78
78
  showBottomBorder?: boolean;
79
+ /**
80
+ * Render `children` directly inside the admin-ui Page, skipping the
81
+ * default `<Container fluid horizontalSpacing={0}><Col>{children}</Col></Container>`
82
+ * wrap. Use for full-bleed pages (DataViews-based admin surfaces, full-app
83
+ * dashboards) that own their own scroll/layout model and don't want the
84
+ * outer Container's grid to break their flex chain. Defaults to `false`.
85
+ */
86
+ unwrapped?: boolean;
79
87
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { __ } from '@wordpress/i18n';
3
+ import { Icon, desktop, mobile } from '@wordpress/icons';
3
4
  import clsx from 'clsx';
4
- import Gridicon from '../gridicon/index.js';
5
5
  import './style.scss';
6
6
  export const BoostScoreBar = ({ score, isLoading, showPrevScores, active, prevScore = 0, scoreBarType, noBoostScoreTooltip, }) => {
7
7
  if (!active) {
@@ -12,9 +12,9 @@ export const BoostScoreBar = ({ score, isLoading, showPrevScores, active, prevSc
12
12
  const mobileIconLabel = __('Mobile score', 'jetpack-components');
13
13
  const getIcon = () => {
14
14
  if (scoreBarType === 'desktop') {
15
- return _jsx(Gridicon, { icon: "computer", size: 24 });
15
+ return _jsx(Icon, { icon: desktop, size: 24 });
16
16
  }
17
- return _jsx(Gridicon, { icon: "phone", size: 24 });
17
+ return _jsx(Icon, { icon: mobile, size: 24 });
18
18
  };
19
19
  const getFillColor = () => {
20
20
  // This shows the loading color unless a score already exists.
@@ -11,8 +11,4 @@
11
11
  line-height: 20px;
12
12
  font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13
13
 
14
- &.is-new {
15
- background-color: var(--jp-green-5);
16
- color: var(--jp-green-50);
17
- }
18
14
  }
@@ -0,0 +1,58 @@
1
+ import '@gravatar-com/hovercards/dist/style.css';
2
+ import './style.scss';
3
+ /**
4
+ * Gravatar `defaultImage` styles, mirroring https://docs.gravatar.com/sdk/images/#default-image
5
+ */
6
+ type DefaultImage = 'blank' | 'color' | 'identicon' | 'initials' | 'monsterid' | 'mp' | 'retro' | 'robohash' | 'wavatar';
7
+ export type GravatarProps = {
8
+ /**
9
+ * Style of the placeholder image when the email has no Gravatar profile.
10
+ * @default 'initials'
11
+ */
12
+ defaultImage?: DefaultImage;
13
+ /**
14
+ * Display name for accessibility (used as `alt` text and the hovercard label).
15
+ */
16
+ displayName?: string;
17
+ /**
18
+ * Email address to look up on Gravatar.
19
+ */
20
+ email: string;
21
+ /**
22
+ * Rendered avatar size in pixels.
23
+ * @default 48
24
+ */
25
+ size?: number;
26
+ /**
27
+ * Optional class name forwarded to the underlying `<img>` element. The
28
+ * default `jetpack-components-gravatar` class is always applied so consumers
29
+ * can target it directly.
30
+ */
31
+ className?: string;
32
+ /**
33
+ * Whether to attach a Gravatar profile hovercard to the avatar.
34
+ * @default true
35
+ */
36
+ useHovercard?: boolean;
37
+ };
38
+ /**
39
+ * Renders a Gravatar profile image with an optional Gravatar profile hovercard.
40
+ *
41
+ * If the email has no Gravatar profile, the configured `defaultImage` style is
42
+ * used (initials by default).
43
+ *
44
+ * Long-term, this is the seam for switching to a core or Gravatar-shipped
45
+ * component (see https://github.com/WordPress/gutenberg/issues/76836); until
46
+ * then, Forms and Newsletter share this implementation.
47
+ *
48
+ * @param props - The component props.
49
+ * @param props.defaultImage - Style of the placeholder image when the email has no Gravatar.
50
+ * @param props.displayName - Display name used for `alt` text + hovercard label.
51
+ * @param props.email - Email address to look up on Gravatar.
52
+ * @param props.size - Rendered avatar size in pixels.
53
+ * @param props.className - Optional class name forwarded to the underlying `<img>`.
54
+ * @param props.useHovercard - Whether to attach the Gravatar profile hovercard.
55
+ * @return The Gravatar avatar `<img>`, or null when no email is available.
56
+ */
57
+ export default function Gravatar({ defaultImage, displayName, email, size, className, useHovercard }: GravatarProps): JSX.Element | null;
58
+ export {};
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * External dependencies
4
+ */
5
+ import { Hovercards } from '@gravatar-com/hovercards';
6
+ import '@gravatar-com/hovercards/dist/style.css';
7
+ import { useEffect, useRef } from '@wordpress/element';
8
+ import { __ } from '@wordpress/i18n';
9
+ import clsx from 'clsx';
10
+ import { sha256 } from 'js-sha256';
11
+ import './style.scss';
12
+ /**
13
+ * Renders a Gravatar profile image with an optional Gravatar profile hovercard.
14
+ *
15
+ * If the email has no Gravatar profile, the configured `defaultImage` style is
16
+ * used (initials by default).
17
+ *
18
+ * Long-term, this is the seam for switching to a core or Gravatar-shipped
19
+ * component (see https://github.com/WordPress/gutenberg/issues/76836); until
20
+ * then, Forms and Newsletter share this implementation.
21
+ *
22
+ * @param props - The component props.
23
+ * @param props.defaultImage - Style of the placeholder image when the email has no Gravatar.
24
+ * @param props.displayName - Display name used for `alt` text + hovercard label.
25
+ * @param props.email - Email address to look up on Gravatar.
26
+ * @param props.size - Rendered avatar size in pixels.
27
+ * @param props.className - Optional class name forwarded to the underlying `<img>`.
28
+ * @param props.useHovercard - Whether to attach the Gravatar profile hovercard.
29
+ * @return The Gravatar avatar `<img>`, or null when no email is available.
30
+ */
31
+ export default function Gravatar({ defaultImage = 'initials', displayName, email, size = 48, className, useHovercard = true, }) {
32
+ const profileImageRef = useRef(null);
33
+ const hovercardRef = useRef(null);
34
+ useEffect(() => {
35
+ if (!useHovercard || !profileImageRef.current) {
36
+ return;
37
+ }
38
+ hovercardRef.current = new Hovercards({
39
+ // See https://github.com/Automattic/gravatar/tree/trunk/web/packages/hovercards#translations
40
+ i18n: {
41
+ 'Edit your profile →': __('Edit your profile →', 'jetpack-components'),
42
+ 'View profile →': __('View profile →', 'jetpack-components'),
43
+ Contact: __('Contact', 'jetpack-components'),
44
+ 'Send money': __('Send money', 'jetpack-components'),
45
+ 'Sorry, we are unable to load this Gravatar profile.': __('Sorry, we are unable to load this Gravatar profile.', 'jetpack-components'),
46
+ 'Gravatar not found.': __('Gravatar not found.', 'jetpack-components'),
47
+ 'This profile is private.': __('This profile is private.', 'jetpack-components'),
48
+ 'Too Many Requests.': __('Too many requests.', 'jetpack-components'),
49
+ 'Internal Server Error.': __('Internal server error.', 'jetpack-components'),
50
+ 'Is this you?': __('Is this you?', 'jetpack-components'),
51
+ 'Claim your free profile.': __('Claim your free profile.', 'jetpack-components'),
52
+ Email: __('Email', 'jetpack-components'),
53
+ 'Home Phone': __('Home phone', 'jetpack-components'),
54
+ 'Work Phone': __('Work phone', 'jetpack-components'),
55
+ 'Cell Phone': __('Cell phone', 'jetpack-components'),
56
+ 'Contact Form': __('Contact form', 'jetpack-components'),
57
+ Calendar: __('Calendar', 'jetpack-components'),
58
+ },
59
+ });
60
+ hovercardRef.current.attach(profileImageRef.current);
61
+ }, [useHovercard]);
62
+ if (!email) {
63
+ return null;
64
+ }
65
+ const hashedEmail = sha256(email);
66
+ const hovercardName = displayName ? `&name=${encodeURIComponent(displayName)}` : '';
67
+ return (_jsx("img", { ref: profileImageRef, className: clsx('jetpack-components-gravatar', className), alt: displayName || '', src: `https://secure.gravatar.com/avatar/${hashedEmail}?d=${defaultImage}${hovercardName}`, width: size, height: size, loading: "lazy" }));
68
+ }
@@ -0,0 +1,7 @@
1
+ .jetpack-components-gravatar {
2
+ background-color: var(--jp-gray-5, #dcdcde);
3
+ border-radius: 50%;
4
+ display: block;
5
+ flex-shrink: 0; // Prevent shrinking when Gravatar is inside flex containers.
6
+ overflow: hidden;
7
+ }
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Popover } from '@wordpress/components';
3
+ import { Icon, info } from '@wordpress/icons';
3
4
  import clsx from 'clsx';
4
5
  import { useCallback, useState } from 'react';
5
6
  import Button from '../button/index.js';
6
- import Gridicon from '../gridicon/index.js';
7
7
  import './style.scss';
8
8
  const placementsToPositions = (placement) => {
9
9
  const mapping = {
@@ -22,7 +22,7 @@ const placementsToPositions = (placement) => {
22
22
  * @param {IconTooltipProps} props - Props
23
23
  * @return {ReactElement} - JSX element
24
24
  */
25
- const IconTooltip = ({ className = '', iconClassName = '', placement = 'bottom-end', animate = true, iconCode = 'info-outline', iconSize = 18, offset = 10, title, children, popoverAnchorStyle = 'icon', forceShow = false, hoverShow = false, wide = false, inline = true, shift = false, }) => {
25
+ const IconTooltip = ({ className = '', iconClassName = '', placement = 'bottom-end', animate = true, iconCode = info, iconSize = 18, offset = 10, title, children, popoverAnchorStyle = 'icon', forceShow = false, hoverShow = false, wide = false, inline = true, shift = false, }) => {
26
26
  const POPOVER_HELPER_WIDTH = 124;
27
27
  const [isVisible, setIsVisible] = useState(false);
28
28
  const [hoverTimeout, setHoverTimeout] = useState(null);
@@ -70,7 +70,7 @@ const IconTooltip = ({ className = '', iconClassName = '', placement = 'bottom-e
70
70
  setHoverTimeout(id);
71
71
  }
72
72
  }, [hoverShow]);
73
- return (_jsxs("div", { className: wrapperClassNames, "data-testid": "icon-tooltip_wrapper", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [!isAnchorWrapper && (_jsx(Button, { variant: "link", onMouseDown: toggleTooltip, children: _jsx(Gridicon, { className: iconClassName, icon: iconCode, size: iconSize }) })), _jsx("div", { className: clsx('icon-tooltip-helper', { 'is-wide': wide }), style: iconShiftBySize, children: (isForcedToShow || isVisible) && (_jsx(Popover, { ...args, children: _jsxs("div", { children: [title && _jsx("div", { className: "icon-tooltip-title", children: title }), _jsx("div", { className: "icon-tooltip-content", children: children })
73
+ return (_jsxs("div", { className: wrapperClassNames, "data-testid": "icon-tooltip_wrapper", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [!isAnchorWrapper && (_jsx(Button, { variant: "link", onMouseDown: toggleTooltip, children: _jsx(Icon, { className: iconClassName, icon: iconCode, size: iconSize }) })), _jsx("div", { className: clsx('icon-tooltip-helper', { 'is-wide': wide }), style: iconShiftBySize, children: (isForcedToShow || isVisible) && (_jsx(Popover, { ...args, children: _jsxs("div", { children: [title && _jsx("div", { className: "icon-tooltip-title", children: title }), _jsx("div", { className: "icon-tooltip-content", children: children })
74
74
  ] }) })) })
75
75
  ] }));
76
76
  };
@@ -1,3 +1,4 @@
1
+ import type { IconType } from '@wordpress/components';
1
2
  import type { ReactNode } from 'react';
2
3
  export type Placement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
3
4
  export type Position = 'top left' | 'top center' | 'top right' | 'bottom left' | 'bottom center' | 'bottom right';
@@ -7,7 +8,7 @@ export type IconTooltipProps = {
7
8
  */
8
9
  className?: string;
9
10
  /**
10
- * The class name applied to Gridicon.
11
+ * The class name applied to the icon.
11
12
  */
12
13
  iconClassName?: string;
13
14
  /**
@@ -23,9 +24,9 @@ export type IconTooltipProps = {
23
24
  */
24
25
  animate?: boolean;
25
26
  /**
26
- * The icon code for Gridicon.
27
+ * The icon to display. Accepts icon components from `@wordpress/icons`.
27
28
  */
28
- iconCode?: string;
29
+ iconCode?: IconType;
29
30
  /**
30
31
  * The title of Popover.
31
32
  */
@@ -36,7 +36,7 @@ const JetpackFooter = ({ className, menu, ...otherProps }) => {
36
36
  _jsx(JetpackLogo, { showText: false, height: 16, "aria-hidden": "true" }), _jsx(Text, { variant: "body-md", children: "Jetpack" })
37
37
  ] }), _jsx(Stack, { render: _jsx("ul", {}), direction: "row", gap: "lg", wrap: "wrap", children: items.map(item => {
38
38
  const isButton = item.role === 'button';
39
- return (_jsx("li", { children: _jsx(Text, { variant: "body-sm", className: "jetpack-footer__menu-item", render: isButton ? (_jsx(Link, { render: _jsx(Text, { variant: "body-md", render: _jsx("span", {}) }), tone: "neutral", variant: "default", role: item.role, tabIndex: 0, onClick: item.onClick || undefined, onKeyDown: item.onKeyDown || undefined })) : (_jsx(Link, { render: _jsx(Text, { variant: "body-md", render: _jsx("a", {}) }), tone: "neutral", variant: "default", href: item.href || '', title: item.title || '', role: item.role, onClick: item.onClick || undefined, onKeyDown: item.onKeyDown || undefined })), children: item.label }) }, item.label));
39
+ return (_jsx("li", { children: _jsx(Text, { variant: "body-md", className: "jetpack-footer__menu-item", render: isButton ? (_jsx(Link, { render: _jsx("span", {}), tone: "neutral", variant: "default", role: item.role, tabIndex: 0, onClick: item.onClick || undefined, onKeyDown: item.onKeyDown || undefined })) : (_jsx(Link, { tone: "neutral", variant: "default", href: item.href || '', title: item.title || '', role: item.role, onClick: item.onClick || undefined, onKeyDown: item.onKeyDown || undefined })), children: item.label }) }, item.label));
40
40
  }) }), _jsx("a", { className: "jetpack-footer__a8c", href: getRedirectUrl('a8c-about'), rel: "noopener noreferrer", target: "_blank", children: _jsx(AutomatticBylineLogo, { height: 8 }) })
41
41
  ] }));
42
42
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ExternalLink } from '@wordpress/components';
3
2
  import { createInterpolateElement } from '@wordpress/element';
4
3
  import { __, sprintf } from '@wordpress/i18n';
4
+ import { Link } from '@wordpress/ui';
5
5
  import clsx from 'clsx';
6
6
  import { getRedirectUrl } from '../../index.js';
7
7
  import Text from '../text/index.js';
@@ -24,25 +24,25 @@ const MultipleButtonsText = ({ multipleButtonsLabels }) => {
24
24
  /* translators: %1$s is button label 1 and %2$s is button label 2 */
25
25
  __('By clicking <strong>%1$s</strong> or <strong>%2$s</strong>, you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your site‘s data</shareDetailsLink> with us.', 'jetpack-components'), multipleButtonsLabels[0], multipleButtonsLabels[1]), {
26
26
  strong: _jsx("strong", {}),
27
- tosLink: _jsx(Link, { slug: "wpcom-tos" }),
28
- shareDetailsLink: _jsx(Link, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
27
+ tosLink: _jsx(TosLink, { slug: "wpcom-tos" }),
28
+ shareDetailsLink: _jsx(TosLink, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
29
29
  });
30
30
  }
31
31
  return createInterpolateElement(__('By clicking the buttons above, you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your site‘s data</shareDetailsLink> with us.', 'jetpack-components'), {
32
- tosLink: _jsx(Link, { slug: "wpcom-tos" }),
33
- shareDetailsLink: _jsx(Link, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
32
+ tosLink: _jsx(TosLink, { slug: "wpcom-tos" }),
33
+ shareDetailsLink: _jsx(TosLink, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
34
34
  });
35
35
  };
36
36
  const SingleButtonText = ({ agreeButtonLabel }) => createInterpolateElement(sprintf(
37
37
  /* translators: %s is a button label */
38
38
  __('By clicking <strong>%s</strong>, you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your site‘s data</shareDetailsLink> with us.', 'jetpack-components'), agreeButtonLabel), {
39
39
  strong: _jsx("strong", {}),
40
- tosLink: _jsx(Link, { slug: "wpcom-tos" }),
41
- shareDetailsLink: _jsx(Link, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
40
+ tosLink: _jsx(TosLink, { slug: "wpcom-tos" }),
41
+ shareDetailsLink: _jsx(TosLink, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
42
42
  });
43
43
  const TermsOfServiceTextOnly = () => createInterpolateElement(__('By continuing you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your site’s data</shareDetailsLink> with us. We’ll check if that email is linked to an existing WordPress.com account or create a new one instantly.', 'jetpack-components'), {
44
- tosLink: _jsx(Link, { slug: "wpcom-tos" }),
45
- shareDetailsLink: _jsx(Link, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
44
+ tosLink: _jsx(TosLink, { slug: "wpcom-tos" }),
45
+ shareDetailsLink: _jsx(TosLink, { slug: "jetpack-support-what-data-does-jetpack-sync" }),
46
46
  });
47
- const Link = ({ slug, children }) => (_jsx(ExternalLink, { className: "terms-of-service__link", href: getRedirectUrl(slug), children: children }));
47
+ const TosLink = ({ slug, children }) => (_jsx(Link, { openInNewTab: true, className: "terms-of-service__link", href: getRedirectUrl(slug), children: children }));
48
48
  export default TermsOfService;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useCallback } from '@wordpress/element';
3
- import Gridicon from '../gridicon/index.js';
3
+ import { Icon, chevronLeft, chevronRight } from '@wordpress/icons';
4
4
  import { Testimonial } from './testimonial.js';
5
5
  const Testimonials = ({ testimonials }) => {
6
6
  const [currentTestimonialIndex, setcurrentTestimonialIndex] = useState(0);
@@ -13,6 +13,6 @@ const Testimonials = ({ testimonials }) => {
13
13
  setcurrentTestimonialIndex(newIndex);
14
14
  }, [currentTestimonialIndex, testimonials]);
15
15
  const shouldShowArrows = testimonials.length > 1;
16
- return (_jsxs("div", { className: "testimonials", children: [shouldShowArrows && (_jsx("button", { "data-testid": "left-arrow", className: "testimonials__left-arrow", onClick: decrementTestimonial, children: _jsx(Gridicon, { icon: "chevron-left", size: 48 }) })), testimonials.map((testimonial, index) => (_jsx(Testimonial, { ...testimonial, hidden: currentTestimonialIndex !== index }, index))), shouldShowArrows && (_jsx("button", { "data-testid": "right-arrow", className: "testimonials__right-arrow", onClick: incrementTestimonial, children: _jsx(Gridicon, { icon: "chevron-right", size: 48 }) }))] }));
16
+ return (_jsxs("div", { className: "testimonials", children: [shouldShowArrows && (_jsx("button", { "data-testid": "left-arrow", className: "testimonials__left-arrow", onClick: decrementTestimonial, children: _jsx(Icon, { icon: chevronLeft, size: 48 }) })), testimonials.map((testimonial, index) => (_jsx(Testimonial, { ...testimonial, hidden: currentTestimonialIndex !== index }, index))), shouldShowArrows && (_jsx("button", { "data-testid": "right-arrow", className: "testimonials__right-arrow", onClick: incrementTestimonial, children: _jsx(Icon, { icon: chevronRight, size: 48 }) }))] }));
17
17
  };
18
18
  export default Testimonials;
@@ -12,6 +12,8 @@ export interface ToggleControlProps {
12
12
  toggling?: boolean;
13
13
  /** The label for the toggle. */
14
14
  label?: ReactNode;
15
+ /** Accessible name for the underlying checkbox. Use when no visible `label` is rendered. */
16
+ 'aria-label'?: string;
15
17
  /** The size of the toggle. */
16
18
  size?: 'small' | 'normal';
17
19
  /** A callback function invoked when the toggle is clicked. */
@@ -3,7 +3,7 @@ import { ToggleControl as WPToggleControl } from '@wordpress/components';
3
3
  import clsx from 'clsx';
4
4
  import { useCallback } from 'react';
5
5
  import styles from './styles.module.scss';
6
- const ToggleControl = ({ checked, className, disabled, help, toggling, label, size = 'normal', onChange, }) => {
6
+ const ToggleControl = ({ checked, className, disabled, help, toggling, label, 'aria-label': ariaLabel, size = 'normal', onChange, }) => {
7
7
  const showChecked = toggling !== undefined ? (checked && !toggling) || (!checked && toggling) : checked;
8
8
  const handleOnChange = useCallback((value) => {
9
9
  // Don't toggle if the toggle is already toggling.
@@ -16,6 +16,6 @@ const ToggleControl = ({ checked, className, disabled, help, toggling, label, si
16
16
  [styles['is-toggling']]: toggling,
17
17
  [styles['is-small']]: size === 'small',
18
18
  [styles['no-label']]: !label,
19
- }), disabled: disabled, help: help, label: label, onChange: handleOnChange }));
19
+ }), disabled: disabled, help: help, label: label, "aria-label": ariaLabel, onChange: handleOnChange }));
20
20
  };
21
21
  export default ToggleControl;
package/build/index.d.ts CHANGED
@@ -10,8 +10,10 @@ export { default as AutomatticBylineLogo } from './components/automattic-byline-
10
10
  export { default as AutomatticIconLogo } from './components/automattic-icon-logo/index.tsx';
11
11
  export { default as AutomatticForAgenciesLogo } from './components/automattic-for-agencies-logo/index.tsx';
12
12
  export { default as JetpackFooter } from './components/jetpack-footer/index.tsx';
13
+ /** @deprecated Use `Spinner` from `@wordpress/components` instead. */
13
14
  export { default as Spinner } from './components/spinner/index.tsx';
14
- export { default as Gridicon } from './components/gridicon/index.tsx';
15
+ export { default as Gravatar } from './components/gravatar/index.tsx';
16
+ export type { GravatarProps } from './components/gravatar/index.tsx';
15
17
  export { default as IconTooltip } from './components/icon-tooltip/index.tsx';
16
18
  export { default as ActionButton } from './components/action-button/index.tsx';
17
19
  export { default as PricingCard } from './components/pricing-card/index.tsx';
package/build/index.js CHANGED
@@ -24,8 +24,9 @@ export { default as AutomatticBylineLogo } from './components/automattic-byline-
24
24
  export { default as AutomatticIconLogo } from './components/automattic-icon-logo/index.js';
25
25
  export { default as AutomatticForAgenciesLogo } from './components/automattic-for-agencies-logo/index.js';
26
26
  export { default as JetpackFooter } from './components/jetpack-footer/index.js';
27
+ /** @deprecated Use `Spinner` from `@wordpress/components` instead. */
27
28
  export { default as Spinner } from './components/spinner/index.js';
28
- export { default as Gridicon } from './components/gridicon/index.js';
29
+ export { default as Gravatar } from './components/gravatar/index.js';
29
30
  export { default as IconTooltip } from './components/icon-tooltip/index.js';
30
31
  export { default as ActionButton } from './components/action-button/index.js';
31
32
  export { default as PricingCard } from './components/pricing-card/index.js';
@@ -1,9 +1,5 @@
1
1
  import restApi from '@automattic/jetpack-api';
2
2
  import { Page } from '@wordpress/admin-ui';
3
- import '@wordpress/admin-ui/build-style/style.css';
4
- import {
5
- __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
6
- } from '@wordpress/components';
7
3
  import { __, sprintf } from '@wordpress/i18n';
8
4
  import clsx from 'clsx';
9
5
  import { useEffect, useCallback } from 'react';
@@ -41,6 +37,7 @@ const AdminPage: FC< AdminPageProps > = ( {
41
37
  breadcrumbs,
42
38
  tabs,
43
39
  showBottomBorder = true,
40
+ unwrapped = false,
44
41
  } ) => {
45
42
  useEffect( () => {
46
43
  restApi.setApiRoot( apiRoot );
@@ -73,31 +70,27 @@ const AdminPage: FC< AdminPageProps > = ( {
73
70
  }
74
71
  }, [] );
75
72
 
76
- // Compose the title with logo for the admin-ui Page header.
77
- // Page's Header wraps this in an <h2> tag, so we just pass the content directly.
78
- const composedTitle = title ? (
79
- <HStack spacing={ 2 } justify="left">
80
- { logo || <JetpackLogo showText={ false } height={ 20 } /> }
81
- <span>{ title }</span>
82
- </HStack>
83
- ) : undefined;
84
-
85
73
  // When title or breadcrumbs are provided, use admin-ui Page for the full page layout.
86
- if ( showHeader && ( composedTitle || breadcrumbs ) ) {
74
+ if ( showHeader && ( title || breadcrumbs ) ) {
87
75
  return (
88
76
  <div className={ rootClassName }>
89
77
  <Page
90
- ariaLabel={ title }
78
+ className="jp-admin-page__page"
79
+ visual={ logo || <JetpackLogo showText={ false } height={ 20 } /> }
91
80
  breadcrumbs={ breadcrumbs }
92
- title={ composedTitle }
81
+ title={ title }
93
82
  subTitle={ subTitle }
94
83
  actions={ actions }
95
84
  showSidebarToggle={ false }
96
85
  >
97
86
  { tabs }
98
- <Container fluid horizontalSpacing={ 0 }>
99
- <Col>{ children }</Col>
100
- </Container>
87
+ { unwrapped ? (
88
+ children
89
+ ) : (
90
+ <Container fluid horizontalSpacing={ 0 }>
91
+ <Col>{ children }</Col>
92
+ </Container>
93
+ ) }
101
94
  { showFooter && <JetpackFooter menu={ optionalMenuItems } /> }
102
95
  </Page>
103
96
  </div>
@@ -13,23 +13,36 @@
13
13
  // or when showBottomBorder is false.
14
14
  // Ideally admin-ui would expose a prop or CSS custom property for this:
15
15
  // https://github.com/WordPress/gutenberg/issues/75428
16
- &.without-bottom-border :global(.admin-ui-page__header) {
16
+
17
+ // Anchor: `.jp-admin-page__page` is the className we pass to admin-ui's
18
+ // <Page>; admin-ui 2.0.0 renders the header as a stable <header> element
19
+ // directly inside that page node — no class hooks anymore.
20
+ &.without-bottom-border :global(.jp-admin-page__page > header) {
17
21
  border-bottom: none;
18
22
  }
19
23
 
20
24
  // Disable sticky header until we make it work better across all pages.
21
25
  // JETPACK-1386
22
- :global(.admin-ui-page__header) {
26
+ :global(.jp-admin-page__page > header) {
23
27
  position: relative;
24
28
  z-index: 1;
25
29
  }
26
30
 
27
31
  // Normalize admin headers: implementation of admin-ui needs to
28
32
  // comprehend old wp-admin floating containers, such as Hello Dolly.
29
- :global(.admin-ui-page) {
33
+ :global(.jp-admin-page__page) {
30
34
  clear: both;
31
35
  }
32
36
 
37
+ // admin-ui 2.0.0's header `visual` slot is a 24px grid box but does not
38
+ // center its contents. Our JetpackLogo is 20px tall, so without this it
39
+ // pins to the top-left of the cell and looks misaligned vs. the title.
40
+ // admin-ui ships the slot as `<div aria-hidden="true">` in the header.
41
+ // TODO: remove once upstream centers contents in `.header-visual`.
42
+ :global(.jp-admin-page__page > header [aria-hidden="true"]) {
43
+ place-items: center;
44
+ }
45
+
33
46
  .admin-page-header {
34
47
  display: flex;
35
48
  align-items: center;
@@ -94,4 +94,13 @@ export type AdminPageProps = {
94
94
  * Hidden when `tabs` is used.
95
95
  */
96
96
  showBottomBorder?: boolean;
97
+
98
+ /**
99
+ * Render `children` directly inside the admin-ui Page, skipping the
100
+ * default `<Container fluid horizontalSpacing={0}><Col>{children}</Col></Container>`
101
+ * wrap. Use for full-bleed pages (DataViews-based admin surfaces, full-app
102
+ * dashboards) that own their own scroll/layout model and don't want the
103
+ * outer Container's grid to break their flex chain. Defaults to `false`.
104
+ */
105
+ unwrapped?: boolean;
97
106
  };
@@ -1,6 +1,6 @@
1
1
  import { __ } from '@wordpress/i18n';
2
+ import { Icon, desktop, mobile } from '@wordpress/icons';
2
3
  import clsx from 'clsx';
3
- import Gridicon from '../gridicon/index.tsx';
4
4
  import type { BoostScoreBarProps } from './types.ts';
5
5
  import type { FunctionComponent } from 'react';
6
6
 
@@ -25,10 +25,10 @@ export const BoostScoreBar: FunctionComponent< BoostScoreBarProps > = ( {
25
25
 
26
26
  const getIcon = () => {
27
27
  if ( scoreBarType === 'desktop' ) {
28
- return <Gridicon icon="computer" size={ 24 } />;
28
+ return <Icon icon={ desktop } size={ 24 } />;
29
29
  }
30
30
 
31
- return <Gridicon icon="phone" size={ 24 } />;
31
+ return <Icon icon={ mobile } size={ 24 } />;
32
32
  };
33
33
 
34
34
  const getFillColor = () => {