@automattic/jetpack-components 1.3.12 → 1.4.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [1.4.0] - 2025-12-08
6
+ ### Added
7
+ - Jetpack Logo: Add support for customized title in shared component. [#46162]
8
+
9
+ ### Changed
10
+ - Make `NavigatorModal` responsive using container queries. [#46102]
11
+
12
+ ## [1.3.13] - 2025-12-01
13
+ ### Added
14
+ - Add NavigatorModal component. [#46101]
15
+
16
+ ### Changed
17
+ - Update package dependencies. [#46143]
18
+
5
19
  ## [1.3.12] - 2025-11-20
6
20
  ### Changed
7
21
  - Update package dependencies. [#46022]
@@ -1569,6 +1583,8 @@
1569
1583
  ### Changed
1570
1584
  - Update node version requirement to 14.16.1
1571
1585
 
1586
+ [1.4.0]: https://github.com/Automattic/jetpack-components/compare/1.3.13...1.4.0
1587
+ [1.3.13]: https://github.com/Automattic/jetpack-components/compare/1.3.12...1.3.13
1572
1588
  [1.3.12]: https://github.com/Automattic/jetpack-components/compare/1.3.11...1.3.12
1573
1589
  [1.3.11]: https://github.com/Automattic/jetpack-components/compare/1.3.10...1.3.11
1574
1590
  [1.3.10]: https://github.com/Automattic/jetpack-components/compare/1.3.9...1.3.10
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { __ } from '@wordpress/i18n';
3
3
  import clsx from 'clsx';
4
- const JetpackLogo = ({ logoColor = '#069e08', showText = true, className, height = 32, ...otherProps }) => {
4
+ const JetpackLogo = ({ logoColor = '#069e08', showText = true, className, height = 32, title, ...otherProps }) => {
5
5
  const viewBox = showText ? '0 0 118 32' : '0 0 32 32';
6
+ const logoTitle = title ?? __('Jetpack Logo', 'jetpack-components');
6
7
  return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", x: "0px", y: "0px", viewBox: viewBox, className: clsx('jetpack-logo', className), "aria-labelledby": "jetpack-logo-title", height: height, ...otherProps,
7
8
  // role="img" is required to prevent VoiceOver on Safari reading the content of the SVG
8
- role: "img", children: [_jsx("title", { id: "jetpack-logo-title", children: __('Jetpack Logo', 'jetpack-components') }), _jsx("path", { fill: logoColor, 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" }), showText && (_jsxs(_Fragment, { children: [_jsx("path", { d: "M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z" }), _jsx("path", { d: "M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z" }), _jsx("path", { d: "M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z" }), _jsx("path", { d: "M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z" }), _jsx("path", { d: "M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z" }), _jsx("path", { d: "M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z" }), _jsx("path", { d: "M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z" })] }))] }));
9
+ role: "img", children: [_jsx("title", { id: "jetpack-logo-title", children: logoTitle }), _jsx("path", { fill: logoColor, 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" }), showText && (_jsxs(_Fragment, { children: [_jsx("path", { d: "M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z" }), _jsx("path", { d: "M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z" }), _jsx("path", { d: "M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z" }), _jsx("path", { d: "M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z" }), _jsx("path", { d: "M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z" }), _jsx("path", { d: "M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z" }), _jsx("path", { d: "M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z" })] }))] }));
9
10
  };
10
11
  export default JetpackLogo;
@@ -4,4 +4,5 @@ export type JetpackLogoProps = {
4
4
  height?: number;
5
5
  showText?: boolean;
6
6
  logoColor?: string;
7
+ title?: string;
7
8
  };
@@ -0,0 +1,2 @@
1
+ import type { TNavigatorModalContext } from './types.ts';
2
+ export declare const NavigatorModalContext: import("react").Context<TNavigatorModalContext>;
@@ -0,0 +1,6 @@
1
+ import { createContext } from '@wordpress/element';
2
+ const initialContextValue = {
3
+ onClose: () => { },
4
+ };
5
+ export const NavigatorModalContext = createContext(initialContextValue);
6
+ NavigatorModalContext.displayName = 'NavigatorModalContext';
@@ -0,0 +1,14 @@
1
+ import { Button } from '@wordpress/components';
2
+ import { SharedProps } from './types.ts';
3
+ export type FooterProps = SharedProps & {
4
+ actions?: Array<React.ComponentProps<typeof Button>>;
5
+ isScreenLocked?: boolean;
6
+ };
7
+ /**
8
+ * Renders a footer.
9
+ *
10
+ * @param {FooterProps} props - Props
11
+ *
12
+ * @return The rendered footer.
13
+ */
14
+ export declare function Footer({ children, actions, isScreenLocked }: FooterProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Flex, FlexBlock, FlexItem, useNavigator } from '@wordpress/components';
3
+ import { useContext } from 'react';
4
+ import { NavigatorModalContext } from "./context.js";
5
+ import styles from './styles.module.scss';
6
+ /**
7
+ * Renders a footer.
8
+ *
9
+ * @param {FooterProps} props - Props
10
+ *
11
+ * @return The rendered footer.
12
+ */
13
+ export function Footer({ children, actions, isScreenLocked }) {
14
+ const navigator = useNavigator();
15
+ const context = useContext(NavigatorModalContext);
16
+ return (_jsxs(Flex, { className: styles.footer, children: [_jsx(FlexBlock, { children: children }), actions ? (_jsx(FlexItem, { children: _jsx(Flex, { children: actions.map(({ onClick, ...actionProps }, index) => (_jsx(Button
17
+ // eslint-disable-next-line react/jsx-no-bind
18
+ , {
19
+ // eslint-disable-next-line react/jsx-no-bind
20
+ onClick: event => {
21
+ onClick?.(event);
22
+ if (!isScreenLocked) {
23
+ navigator.goBack();
24
+ }
25
+ else {
26
+ context.onClose?.();
27
+ }
28
+ }, ...actionProps }, index))) }) })) : null] }));
29
+ }
@@ -0,0 +1,17 @@
1
+ export type HeaderProps = {
2
+ /**
3
+ * The title of the header.
4
+ */
5
+ title: string;
6
+ /**
7
+ * Whether the screen is locked, in which case to hide the back button.
8
+ */
9
+ isScreenLocked?: boolean;
10
+ };
11
+ /**
12
+ * Renders a header for the NavigatorModal.
13
+ * @param {HeaderProps} props - Props
14
+ *
15
+ * @return component
16
+ */
17
+ export declare function Header({ title, isScreenLocked }: HeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, useNavigator } from '@wordpress/components';
3
+ import { __, isRTL } from '@wordpress/i18n';
4
+ import { chevronLeft, chevronRight, close } from '@wordpress/icons';
5
+ import { useCallback, useContext } from 'react';
6
+ import { NavigatorModalContext } from "./context.js";
7
+ import styles from './styles.module.scss';
8
+ /**
9
+ * Renders a header for the NavigatorModal.
10
+ * @param {HeaderProps} props - Props
11
+ *
12
+ * @return component
13
+ */
14
+ export function Header({ title, isScreenLocked }) {
15
+ const context = useContext(NavigatorModalContext);
16
+ const navigator = useNavigator();
17
+ const onGoBack = useCallback(() => {
18
+ navigator.goBack();
19
+ }, [navigator]);
20
+ return (_jsxs("div", { className: styles.header, children: [_jsxs("div", { className: styles['title-wrap'], children: [!isScreenLocked ? (_jsx(Button, { label: __('Go back', 'jetpack-components'), icon: isRTL() ? chevronRight : chevronLeft, onClick: onGoBack, variant: "tertiary", size: "compact" })) : null, _jsx("h1", { children: title })] }), context.isDismissible ? (_jsx(Button, { size: "compact", onClick: context.onClose, icon: close, label: __('Close', 'jetpack-components'), variant: "tertiary" })) : null] }));
21
+ }
@@ -0,0 +1,16 @@
1
+ import { Screen } from './screen.tsx';
2
+ import { TNavigatorModalContext, SharedProps } from './types.ts';
3
+ /**
4
+ * Renders a modal with navigator capabilities.
5
+ *
6
+ * @param {SharedProps & TNavigatorModalContext} props - Props
7
+ *
8
+ * @return Component
9
+ */
10
+ declare function NavigatorModalMain({ children, className, initialPath, onClose, isDismissible, }: SharedProps & TNavigatorModalContext): import("react/jsx-runtime").JSX.Element;
11
+ export declare const NavigatorModal: typeof NavigatorModalMain & {
12
+ Screen: typeof Screen & {
13
+ displayName: string;
14
+ };
15
+ };
16
+ export {};
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Modal, Navigator } from '@wordpress/components';
3
+ import clsx from 'clsx';
4
+ import { useContext } from 'react';
5
+ import { NavigatorModalContext } from "./context.js";
6
+ import { Screen } from "./screen.js";
7
+ import styles from './styles.module.scss';
8
+ /**
9
+ * Renders the internal NavigatorModal component.
10
+ *
11
+ * @param { SharedProps } props - Props
12
+ *
13
+ * @return Component
14
+ */
15
+ function InternalNavigatorModal({ children, className }) {
16
+ const context = useContext(NavigatorModalContext);
17
+ return (_jsx(Modal, { __experimentalHideHeader: true, onRequestClose: context.onClose, className: clsx(styles.modal, className), children: _jsx(Navigator, { initialPath: context.initialPath, className: styles.navigator, children: children }) }));
18
+ }
19
+ /**
20
+ * Renders a modal with navigator capabilities.
21
+ *
22
+ * @param {SharedProps & TNavigatorModalContext} props - Props
23
+ *
24
+ * @return Component
25
+ */
26
+ function NavigatorModalMain({ children, className, initialPath = '/', onClose, isDismissible = true, }) {
27
+ return (_jsx(NavigatorModalContext.Provider, { value: { onClose, initialPath, isDismissible }, children: _jsx(InternalNavigatorModal, { className: className, children: children }) }));
28
+ }
29
+ export const NavigatorModal = Object.assign(NavigatorModalMain, {
30
+ Screen: Object.assign(Screen, {
31
+ displayName: 'NavigatorModal.Screen',
32
+ }),
33
+ });
@@ -0,0 +1,51 @@
1
+ import { FooterProps } from './footer.tsx';
2
+ export type ScreenProps = {
3
+ /**
4
+ * The title of the screen.
5
+ */
6
+ title?: string;
7
+ /**
8
+ * The path of the screen.
9
+ */
10
+ path: string;
11
+ /**
12
+ * The sidebar content
13
+ */
14
+ sidebar?: React.ReactNode;
15
+ /**
16
+ * Whether the screen is locked or has a parent screen.
17
+ *
18
+ * When it's locked, it means there will be no navigation back to a previous screen.
19
+ */
20
+ isScreenLocked?: boolean;
21
+ /**
22
+ * The footer content
23
+ */
24
+ footerContent?: React.ReactNode;
25
+ /**
26
+ * The footer actions
27
+ */
28
+ footerActions?: FooterProps['actions'];
29
+ /**
30
+ * className to be applied to the modal.
31
+ */
32
+ className?: string;
33
+ } & ({
34
+ /**
35
+ * The content of the screen.
36
+ */
37
+ children: React.ReactNode;
38
+ } | {
39
+ /**
40
+ * The content of the screen.
41
+ */
42
+ content: React.ReactNode;
43
+ });
44
+ /**
45
+ * Renders a screen.
46
+ *
47
+ * @param {ScreenProps} props - Props
48
+ *
49
+ * @return The rendered screen.
50
+ */
51
+ export declare function Screen({ path, className, title, sidebar, isScreenLocked, footerContent, footerActions, ...props }: ScreenProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Flex, Navigator } from '@wordpress/components';
3
+ import clsx from 'clsx';
4
+ import { Footer } from "./footer.js";
5
+ import { Header } from "./header.js";
6
+ import styles from './styles.module.scss';
7
+ /**
8
+ * Renders a screen.
9
+ *
10
+ * @param {ScreenProps} props - Props
11
+ *
12
+ * @return The rendered screen.
13
+ */
14
+ export function Screen({ path, className, title, sidebar, isScreenLocked, footerContent, footerActions, ...props }) {
15
+ const hasFooter = Boolean(footerContent || (footerActions && footerActions.length));
16
+ return (_jsx(Navigator.Screen, { path: path, className: clsx(styles.screen, className), children: _jsxs(Flex, { direction: "column", gap: 0, children: [_jsx(Header, { title: title, isScreenLocked: isScreenLocked }), _jsxs(Flex, { gap: 0, align: "start", className: styles.body, children: [sidebar ? _jsx("div", { className: styles.sidebar, children: sidebar }) : null, _jsx("div", { className: styles.content, children: ('children' in props && props.children) || ('content' in props && props.content) })] }), hasFooter ? (_jsx(Footer, { actions: footerActions, isScreenLocked: isScreenLocked, children: footerContent })) : null] }) }));
17
+ }
@@ -0,0 +1,24 @@
1
+ export type TNavigatorModalContext = {
2
+ /**
3
+ * Whether the modal is dismissible.
4
+ */
5
+ isDismissible?: boolean;
6
+ /**
7
+ * The initial active path.
8
+ */
9
+ initialPath?: string;
10
+ /**
11
+ * Callback fired when the close button is clicked.
12
+ */
13
+ onClose?: VoidFunction;
14
+ };
15
+ export type SharedProps = {
16
+ /**
17
+ * The content of the component.
18
+ */
19
+ children: React.ReactNode;
20
+ /**
21
+ * className to be applied to the modal.
22
+ */
23
+ className?: string;
24
+ };
@@ -0,0 +1 @@
1
+ export {};
package/build/index.d.ts CHANGED
@@ -64,3 +64,4 @@ export { default as DiffViewer } from './components/diff-viewer/index.tsx';
64
64
  export { default as MarkedLines } from './components/marked-lines/index.tsx';
65
65
  export { default as DetailsViewer } from './components/details-viewer/index.tsx';
66
66
  export * from './components/global-notices/index.ts';
67
+ export * from './components/navigator-modal/index.tsx';
package/build/index.js CHANGED
@@ -78,3 +78,4 @@ export { default as DiffViewer } from "./components/diff-viewer/index.js";
78
78
  export { default as MarkedLines } from "./components/marked-lines/index.js";
79
79
  export { default as DetailsViewer } from "./components/details-viewer/index.js";
80
80
  export * from "./components/global-notices/index.js";
81
+ export * from "./components/navigator-modal/index.js";
@@ -8,9 +8,11 @@ const JetpackLogo: FC< JetpackLogoProps > = ( {
8
8
  showText = true,
9
9
  className,
10
10
  height = 32,
11
+ title,
11
12
  ...otherProps
12
13
  } ) => {
13
14
  const viewBox = showText ? '0 0 118 32' : '0 0 32 32';
15
+ const logoTitle = title ?? __( 'Jetpack Logo', 'jetpack-components' );
14
16
 
15
17
  return (
16
18
  <svg
@@ -25,7 +27,7 @@ const JetpackLogo: FC< JetpackLogoProps > = ( {
25
27
  // role="img" is required to prevent VoiceOver on Safari reading the content of the SVG
26
28
  role="img"
27
29
  >
28
- <title id="jetpack-logo-title">{ __( 'Jetpack Logo', 'jetpack-components' ) }</title>
30
+ <title id="jetpack-logo-title">{ logoTitle }</title>
29
31
  <path
30
32
  fill={ logoColor }
31
33
  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"
@@ -4,4 +4,5 @@ export type JetpackLogoProps = {
4
4
  height?: number;
5
5
  showText?: boolean;
6
6
  logoColor?: string;
7
+ title?: string;
7
8
  };
@@ -0,0 +1,10 @@
1
+ import { createContext } from '@wordpress/element';
2
+ import type { TNavigatorModalContext } from './types.ts';
3
+
4
+ const initialContextValue: TNavigatorModalContext = {
5
+ onClose: () => {},
6
+ };
7
+
8
+ export const NavigatorModalContext = createContext( initialContextValue );
9
+
10
+ NavigatorModalContext.displayName = 'NavigatorModalContext';
@@ -0,0 +1,50 @@
1
+ import { Button, Flex, FlexBlock, FlexItem, useNavigator } from '@wordpress/components';
2
+ import { useContext } from 'react';
3
+ import { NavigatorModalContext } from './context.ts';
4
+ import styles from './styles.module.scss';
5
+ import { SharedProps } from './types.ts';
6
+
7
+ export type FooterProps = SharedProps & {
8
+ actions?: Array< React.ComponentProps< typeof Button > >;
9
+ isScreenLocked?: boolean;
10
+ };
11
+
12
+ /**
13
+ * Renders a footer.
14
+ *
15
+ * @param {FooterProps} props - Props
16
+ *
17
+ * @return The rendered footer.
18
+ */
19
+ export function Footer( { children, actions, isScreenLocked }: FooterProps ) {
20
+ const navigator = useNavigator();
21
+ const context = useContext( NavigatorModalContext );
22
+
23
+ return (
24
+ <Flex className={ styles.footer }>
25
+ <FlexBlock>{ children }</FlexBlock>
26
+ { actions ? (
27
+ <FlexItem>
28
+ <Flex>
29
+ { actions.map( ( { onClick, ...actionProps }, index ) => (
30
+ <Button
31
+ // eslint-disable-next-line react/jsx-no-bind
32
+ onClick={ event => {
33
+ onClick?.( event );
34
+
35
+ if ( ! isScreenLocked ) {
36
+ navigator.goBack();
37
+ } else {
38
+ context.onClose?.();
39
+ }
40
+ } }
41
+ key={ index }
42
+ { ...actionProps }
43
+ />
44
+ ) ) }
45
+ </Flex>
46
+ </FlexItem>
47
+ ) : null }
48
+ </Flex>
49
+ );
50
+ }
@@ -0,0 +1,58 @@
1
+ import { Button, useNavigator } from '@wordpress/components';
2
+ import { __, isRTL } from '@wordpress/i18n';
3
+ import { chevronLeft, chevronRight, close } from '@wordpress/icons';
4
+ import { useCallback, useContext } from 'react';
5
+ import { NavigatorModalContext } from './context.ts';
6
+ import styles from './styles.module.scss';
7
+
8
+ export type HeaderProps = {
9
+ /**
10
+ * The title of the header.
11
+ */
12
+ title: string;
13
+ /**
14
+ * Whether the screen is locked, in which case to hide the back button.
15
+ */
16
+ isScreenLocked?: boolean;
17
+ };
18
+
19
+ /**
20
+ * Renders a header for the NavigatorModal.
21
+ * @param {HeaderProps} props - Props
22
+ *
23
+ * @return component
24
+ */
25
+ export function Header( { title, isScreenLocked }: HeaderProps ) {
26
+ const context = useContext( NavigatorModalContext );
27
+ const navigator = useNavigator();
28
+
29
+ const onGoBack = useCallback( () => {
30
+ navigator.goBack();
31
+ }, [ navigator ] );
32
+
33
+ return (
34
+ <div className={ styles.header }>
35
+ <div className={ styles[ 'title-wrap' ] }>
36
+ { ! isScreenLocked ? (
37
+ <Button
38
+ label={ __( 'Go back', 'jetpack-components' ) }
39
+ icon={ isRTL() ? chevronRight : chevronLeft }
40
+ onClick={ onGoBack }
41
+ variant="tertiary"
42
+ size="compact"
43
+ />
44
+ ) : null }
45
+ <h1>{ title }</h1>
46
+ </div>
47
+ { context.isDismissible ? (
48
+ <Button
49
+ size="compact"
50
+ onClick={ context.onClose }
51
+ icon={ close }
52
+ label={ __( 'Close', 'jetpack-components' ) }
53
+ variant="tertiary"
54
+ />
55
+ ) : null }
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,57 @@
1
+ import { Modal, Navigator } from '@wordpress/components';
2
+ import clsx from 'clsx';
3
+ import { useContext } from 'react';
4
+ import { NavigatorModalContext } from './context.ts';
5
+ import { Screen } from './screen.tsx';
6
+ import styles from './styles.module.scss';
7
+ import { TNavigatorModalContext, SharedProps } from './types.ts';
8
+
9
+ /**
10
+ * Renders the internal NavigatorModal component.
11
+ *
12
+ * @param { SharedProps } props - Props
13
+ *
14
+ * @return Component
15
+ */
16
+ function InternalNavigatorModal( { children, className }: SharedProps ) {
17
+ const context = useContext( NavigatorModalContext );
18
+
19
+ return (
20
+ <Modal
21
+ __experimentalHideHeader
22
+ onRequestClose={ context.onClose }
23
+ className={ clsx( styles.modal, className ) }
24
+ >
25
+ <Navigator initialPath={ context.initialPath } className={ styles.navigator }>
26
+ { children }
27
+ </Navigator>
28
+ </Modal>
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Renders a modal with navigator capabilities.
34
+ *
35
+ * @param {SharedProps & TNavigatorModalContext} props - Props
36
+ *
37
+ * @return Component
38
+ */
39
+ function NavigatorModalMain( {
40
+ children,
41
+ className,
42
+ initialPath = '/',
43
+ onClose,
44
+ isDismissible = true,
45
+ }: SharedProps & TNavigatorModalContext ) {
46
+ return (
47
+ <NavigatorModalContext.Provider value={ { onClose, initialPath, isDismissible } }>
48
+ <InternalNavigatorModal className={ className }>{ children }</InternalNavigatorModal>
49
+ </NavigatorModalContext.Provider>
50
+ );
51
+ }
52
+
53
+ export const NavigatorModal = Object.assign( NavigatorModalMain, {
54
+ Screen: Object.assign( Screen, {
55
+ displayName: 'NavigatorModal.Screen',
56
+ } ),
57
+ } );
@@ -0,0 +1,94 @@
1
+ import { Flex, Navigator } from '@wordpress/components';
2
+ import clsx from 'clsx';
3
+ import { Footer, FooterProps } from './footer.tsx';
4
+ import { Header } from './header.tsx';
5
+ import styles from './styles.module.scss';
6
+
7
+ export type ScreenProps = {
8
+ /**
9
+ * The title of the screen.
10
+ */
11
+ title?: string;
12
+
13
+ /**
14
+ * The path of the screen.
15
+ */
16
+ path: string;
17
+ /**
18
+ * The sidebar content
19
+ */
20
+ sidebar?: React.ReactNode;
21
+ /**
22
+ * Whether the screen is locked or has a parent screen.
23
+ *
24
+ * When it's locked, it means there will be no navigation back to a previous screen.
25
+ */
26
+ isScreenLocked?: boolean;
27
+ /**
28
+ * The footer content
29
+ */
30
+ footerContent?: React.ReactNode;
31
+
32
+ /**
33
+ * The footer actions
34
+ */
35
+ footerActions?: FooterProps[ 'actions' ];
36
+
37
+ /**
38
+ * className to be applied to the modal.
39
+ */
40
+ className?: string;
41
+ } & (
42
+ | {
43
+ /**
44
+ * The content of the screen.
45
+ */
46
+ children: React.ReactNode;
47
+ }
48
+ | {
49
+ /**
50
+ * The content of the screen.
51
+ */
52
+ content: React.ReactNode;
53
+ }
54
+ );
55
+
56
+ /**
57
+ * Renders a screen.
58
+ *
59
+ * @param {ScreenProps} props - Props
60
+ *
61
+ * @return The rendered screen.
62
+ */
63
+ export function Screen( {
64
+ path,
65
+ className,
66
+ title,
67
+ sidebar,
68
+ isScreenLocked,
69
+ footerContent,
70
+ footerActions,
71
+ ...props
72
+ }: ScreenProps ) {
73
+ const hasFooter = Boolean( footerContent || ( footerActions && footerActions.length ) );
74
+
75
+ return (
76
+ <Navigator.Screen path={ path } className={ clsx( styles.screen, className ) }>
77
+ <Flex direction="column" gap={ 0 }>
78
+ <Header title={ title } isScreenLocked={ isScreenLocked } />
79
+
80
+ <Flex gap={ 0 } align="start" className={ styles.body }>
81
+ { sidebar ? <div className={ styles.sidebar }>{ sidebar }</div> : null }
82
+ <div className={ styles.content }>
83
+ { ( 'children' in props && props.children ) || ( 'content' in props && props.content ) }
84
+ </div>
85
+ </Flex>
86
+ { hasFooter ? (
87
+ <Footer actions={ footerActions } isScreenLocked={ isScreenLocked }>
88
+ { footerContent }
89
+ </Footer>
90
+ ) : null }
91
+ </Flex>
92
+ </Navigator.Screen>
93
+ );
94
+ }
@@ -0,0 +1,100 @@
1
+ @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
2
+
3
+ .modal {
4
+ container-type: inline-size;
5
+ container-name: navigator-modal;
6
+
7
+ width: 80rem;
8
+
9
+ @include gb.break-medium() {
10
+ height: 40rem;
11
+ max-height: 90%;
12
+ }
13
+
14
+ :global(.components-modal__content) {
15
+ padding: 0;
16
+
17
+ > div {
18
+ height: 100%;
19
+ }
20
+ }
21
+ }
22
+
23
+ .navigator {
24
+ height: 100%;
25
+ }
26
+
27
+ .screen {
28
+ height: 100%;
29
+
30
+ .header {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ padding: 1.5rem;
35
+ height: 4.5rem;
36
+ border-bottom: 1px solid gb.$gray-200;
37
+ flex-shrink: 0;
38
+
39
+ .title-wrap {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 1rem;
43
+ }
44
+
45
+ h1 {
46
+ margin: 0;
47
+ font-size: 15px;
48
+ font-weight: 500;
49
+ line-height: 24px;
50
+ color: gb.$gray-900;
51
+ }
52
+ }
53
+
54
+ .body {
55
+ height: 100%;
56
+ overflow: auto;
57
+ flex-direction: column;
58
+
59
+ @container navigator-modal (width >= #{gb.$break-small}) {
60
+
61
+ & {
62
+ flex-direction: row;
63
+ }
64
+ }
65
+ }
66
+
67
+ .sidebar {
68
+ width: 100%;
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: 1.5rem;
72
+ flex-basis: 30%;
73
+
74
+ @container navigator-modal (width >= #{gb.$break-small}) {
75
+
76
+ & {
77
+ height: 100%;
78
+ max-width: 22rem;
79
+ min-width: 18rem;
80
+ flex-shrink: 0;
81
+ border-inline-end: 1px solid gb.$gray-300;
82
+ }
83
+ }
84
+ }
85
+
86
+ .content {
87
+ width: 100%;
88
+ height: 100%;
89
+ flex: 1;
90
+ overflow: auto;
91
+ }
92
+
93
+ .footer {
94
+ padding: 1rem 1.5rem;
95
+ border-top: 1px solid gb.$gray-300;
96
+ height: 4.5rem;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ }
@@ -0,0 +1,27 @@
1
+ export type TNavigatorModalContext = {
2
+ /**
3
+ * Whether the modal is dismissible.
4
+ */
5
+ isDismissible?: boolean;
6
+ /**
7
+ * The initial active path.
8
+ */
9
+ initialPath?: string;
10
+
11
+ /**
12
+ * Callback fired when the close button is clicked.
13
+ */
14
+ onClose?: VoidFunction;
15
+ };
16
+
17
+ export type SharedProps = {
18
+ /**
19
+ * The content of the component.
20
+ */
21
+ children: React.ReactNode;
22
+
23
+ /**
24
+ * className to be applied to the modal.
25
+ */
26
+ className?: string;
27
+ };
package/index.ts CHANGED
@@ -85,3 +85,4 @@ export { default as DiffViewer } from './components/diff-viewer/index.tsx';
85
85
  export { default as MarkedLines } from './components/marked-lines/index.tsx';
86
86
  export { default as DetailsViewer } from './components/details-viewer/index.tsx';
87
87
  export * from './components/global-notices/index.ts';
88
+ export * from './components/navigator-modal/index.tsx';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "1.3.12",
3
+ "version": "1.4.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": {
@@ -41,30 +41,30 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@automattic/format-currency": "1.0.1",
44
- "@automattic/jetpack-api": "^1.0.11",
45
- "@automattic/jetpack-boost-score-api": "^1.0.18",
44
+ "@automattic/jetpack-api": "^1.0.12",
45
+ "@automattic/jetpack-boost-score-api": "^1.0.20",
46
46
  "@automattic/jetpack-script-data": "^0.5.4",
47
47
  "@automattic/number-formatters": "^1.0.15",
48
48
  "@babel/runtime": "^7",
49
- "@wordpress/browserslist-config": "6.35.0",
50
- "@wordpress/components": "30.8.0",
51
- "@wordpress/compose": "7.35.0",
52
- "@wordpress/data": "10.35.0",
53
- "@wordpress/date": "5.35.0",
54
- "@wordpress/element": "6.35.0",
55
- "@wordpress/i18n": "6.8.0",
56
- "@wordpress/icons": "11.2.0",
57
- "@wordpress/notices": "5.35.0",
49
+ "@wordpress/browserslist-config": "6.36.0",
50
+ "@wordpress/components": "30.9.0",
51
+ "@wordpress/compose": "7.36.0",
52
+ "@wordpress/data": "10.36.0",
53
+ "@wordpress/date": "5.36.0",
54
+ "@wordpress/element": "6.36.0",
55
+ "@wordpress/i18n": "6.9.0",
56
+ "@wordpress/icons": "11.3.0",
57
+ "@wordpress/notices": "5.36.0",
58
58
  "clsx": "2.1.1",
59
59
  "prop-types": "^15.7.2",
60
60
  "qrcode.react": "4.2.0",
61
61
  "react-slider": "2.0.5",
62
- "social-logos": "^3.3.4",
62
+ "social-logos": "^3.3.5",
63
63
  "uplot": "1.6.31",
64
64
  "uplot-react": "1.1.4"
65
65
  },
66
66
  "devDependencies": {
67
- "@automattic/jetpack-base-styles": "^1.0.11",
67
+ "@automattic/jetpack-base-styles": "^1.0.12",
68
68
  "@babel/core": "7.28.4",
69
69
  "@babel/preset-react": "7.27.1",
70
70
  "@jest/globals": "30.2.0",