@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 +16 -0
- package/build/components/jetpack-logo/index.js +3 -2
- package/build/components/jetpack-logo/types.d.ts +1 -0
- package/build/components/navigator-modal/context.d.ts +2 -0
- package/build/components/navigator-modal/context.js +6 -0
- package/build/components/navigator-modal/footer.d.ts +14 -0
- package/build/components/navigator-modal/footer.js +29 -0
- package/build/components/navigator-modal/header.d.ts +17 -0
- package/build/components/navigator-modal/header.js +21 -0
- package/build/components/navigator-modal/index.d.ts +16 -0
- package/build/components/navigator-modal/index.js +33 -0
- package/build/components/navigator-modal/screen.d.ts +51 -0
- package/build/components/navigator-modal/screen.js +17 -0
- package/build/components/navigator-modal/types.d.ts +24 -0
- package/build/components/navigator-modal/types.js +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/components/jetpack-logo/index.tsx +3 -1
- package/components/jetpack-logo/types.ts +1 -0
- package/components/navigator-modal/context.ts +10 -0
- package/components/navigator-modal/footer.tsx +50 -0
- package/components/navigator-modal/header.tsx +58 -0
- package/components/navigator-modal/index.tsx +57 -0
- package/components/navigator-modal/screen.tsx +94 -0
- package/components/navigator-modal/styles.module.scss +100 -0
- package/components/navigator-modal/types.ts +27 -0
- package/index.ts +1 -0
- package/package.json +14 -14
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:
|
|
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;
|
|
@@ -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">{
|
|
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"
|
|
@@ -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
|
+
"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.
|
|
45
|
-
"@automattic/jetpack-boost-score-api": "^1.0.
|
|
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.
|
|
50
|
-
"@wordpress/components": "30.
|
|
51
|
-
"@wordpress/compose": "7.
|
|
52
|
-
"@wordpress/data": "10.
|
|
53
|
-
"@wordpress/date": "5.
|
|
54
|
-
"@wordpress/element": "6.
|
|
55
|
-
"@wordpress/i18n": "6.
|
|
56
|
-
"@wordpress/icons": "11.
|
|
57
|
-
"@wordpress/notices": "5.
|
|
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.
|
|
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.
|
|
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",
|