@automattic/jetpack-components 1.4.8 → 1.4.9

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 (30) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/build/components/button/index.js +15 -2
  3. package/build/components/button/types.d.ts +6 -3
  4. package/build/components/icon-tooltip/index.js +1 -1
  5. package/build/components/navigator-modal/footer.d.ts +2 -3
  6. package/build/components/navigator-modal/footer.js +7 -7
  7. package/build/components/navigator-modal/header.js +1 -2
  8. package/build/components/navigator-modal/index.d.ts +7 -3
  9. package/build/components/navigator-modal/index.js +7 -7
  10. package/build/components/navigator-modal/screen.d.ts +7 -8
  11. package/build/components/navigator-modal/screen.js +2 -3
  12. package/build/components/navigator-modal/types.d.ts +0 -10
  13. package/build/components/number-control/index.d.ts +3 -2
  14. package/build/components/number-control/index.js +2 -1
  15. package/build/components/pricing-card/types.d.ts +2 -3
  16. package/build/components/split-button/index.js +7 -1
  17. package/components/button/index.tsx +16 -15
  18. package/components/button/types.ts +11 -3
  19. package/components/copy-to-clipboard/index.tsx +3 -3
  20. package/components/icon-tooltip/index.tsx +2 -2
  21. package/components/navigator-modal/footer.tsx +9 -10
  22. package/components/navigator-modal/header.tsx +2 -3
  23. package/components/navigator-modal/index.tsx +22 -9
  24. package/components/navigator-modal/screen.tsx +25 -22
  25. package/components/navigator-modal/{styles.module.scss → styles.scss} +31 -42
  26. package/components/navigator-modal/types.ts +0 -12
  27. package/components/number-control/index.jsx +2 -1
  28. package/components/pricing-card/types.ts +2 -3
  29. package/components/split-button/index.tsx +8 -1
  30. package/package.json +13 -13
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [1.4.9] - 2026-02-02
6
+ ### Added
7
+ - NavigatorModal: Allow passing all the props supported by underlying components. [#46856]
8
+
9
+ ### Changed
10
+ - NavigatorModal: Allow customizing the modal via stable BEM class names. [#46818]
11
+ - Update package dependencies. [#46854]
12
+
5
13
  ## [1.4.8] - 2026-01-26
6
14
  ### Changed
7
15
  - Update dependencies.
@@ -1624,6 +1632,7 @@
1624
1632
  ### Changed
1625
1633
  - Update node version requirement to 14.16.1
1626
1634
 
1635
+ [1.4.9]: https://github.com/Automattic/jetpack-components/compare/1.4.8...1.4.9
1627
1636
  [1.4.8]: https://github.com/Automattic/jetpack-components/compare/1.4.7...1.4.8
1628
1637
  [1.4.7]: https://github.com/Automattic/jetpack-components/compare/1.4.6...1.4.7
1629
1638
  [1.4.6]: https://github.com/Automattic/jetpack-components/compare/1.4.5...1.4.6
@@ -22,7 +22,6 @@ const Button = forwardRef((props, ref) => {
22
22
  [styles['full-width']]: fullWidth,
23
23
  [styles['is-icon-button']]: Boolean(icon) && !children,
24
24
  });
25
- componentProps.ref = ref;
26
25
  const externalIconSize = size === 'normal' ? 20 : 16;
27
26
  const externalIcon = isExternalLink && (_jsxs(_Fragment, { children: [_jsx(Icon, { size: externalIconSize, icon: external, className: styles['external-icon'] }), _jsx(VisuallyHidden, { as: "span", children:
28
27
  /* translators: accessibility text */
@@ -33,7 +32,21 @@ const Button = forwardRef((props, ref) => {
33
32
  children[0] !== null &&
34
33
  // Tooltip should not considered as a child
35
34
  children?.[0]?.props?.className !== 'components-tooltip';
36
- return (_jsxs(WPButton, { target: externalTarget, variant: variant, className: clsx(className, { 'has-text': !!icon && hasChildren }), icon: !isExternalLink ? icon : undefined, iconSize: iconSize, disabled: disabled, "aria-disabled": disabled, isDestructive: isDestructive, text: text, ...componentProps, children: [isLoading && _jsx(Spinner, {}), _jsx("span", { children: children }), externalIcon] }));
35
+ // Cast to work around WPButton's strict union type that can't be satisfied when spreading props
36
+ const wpButtonProps = {
37
+ ref,
38
+ target: externalTarget,
39
+ variant,
40
+ className: clsx(className, { 'has-text': !!icon && hasChildren }),
41
+ icon: !isExternalLink ? icon : undefined,
42
+ iconSize,
43
+ disabled,
44
+ 'aria-disabled': disabled,
45
+ isDestructive,
46
+ text,
47
+ ...componentProps,
48
+ };
49
+ return (_jsxs(WPButton, { ...wpButtonProps, children: [isLoading && _jsx(Spinner, {}), _jsx("span", { children: children }), externalIcon] }));
37
50
  });
38
51
  Button.displayName = 'Button';
39
52
  export default Button;
@@ -1,5 +1,5 @@
1
1
  import { Button } from '@wordpress/components';
2
- import type { ComponentProps, ForwardedRef, ReactNode } from 'react';
2
+ import type { ComponentProps, ForwardedRef, MouseEventHandler, ReactNode } from 'react';
3
3
  type JetpackButtonBaseProps = {
4
4
  className?: string;
5
5
  children?: ReactNode;
@@ -13,7 +13,10 @@ type JetpackButtonBaseProps = {
13
13
  weight?: 'bold' | 'regular';
14
14
  fullWidth?: boolean;
15
15
  ref?: ForwardedRef<unknown>;
16
+ href?: string;
17
+ target?: string;
18
+ onClick?: MouseEventHandler<HTMLButtonElement> | MouseEventHandler<HTMLAnchorElement>;
16
19
  };
17
- type WPButtonProps = Omit<ComponentProps<typeof Button>, 'size' | 'variant'>;
18
- export type ButtonProps = JetpackButtonBaseProps & WPButtonProps;
20
+ type WPButtonBaseProps = Omit<ComponentProps<typeof Button>, 'size' | 'variant' | 'disabled' | 'href' | 'target' | 'onClick'>;
21
+ export type ButtonProps = JetpackButtonBaseProps & WPButtonBaseProps;
19
22
  export {};
@@ -40,7 +40,7 @@ const IconTooltip = ({ className = '', iconClassName = '', placement = 'bottom-e
40
40
  resize: false,
41
41
  flip: false,
42
42
  offset, // The distance (in px) between the anchor and the popover.
43
- focusOnMount: 'container',
43
+ focusOnMount: 'firstElement',
44
44
  onClose: hideTooltip,
45
45
  className: 'icon-tooltip-container',
46
46
  inline,
@@ -1,6 +1,5 @@
1
1
  import { Button } from '@wordpress/components';
2
- import { SharedProps } from './types.ts';
3
- export type FooterProps = SharedProps & {
2
+ export type FooterProps = React.HTMLAttributes<HTMLDivElement> & {
4
3
  actions?: Array<((props: {
5
4
  navigate: VoidFunction;
6
5
  }) => React.ReactElement) | React.ComponentProps<typeof Button>>;
@@ -13,4 +12,4 @@ export type FooterProps = SharedProps & {
13
12
  *
14
13
  * @return The rendered footer.
15
14
  */
16
- export declare function Footer({ children, actions, isScreenLocked }: FooterProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function Footer({ children, actions, isScreenLocked, className, ...props }: FooterProps): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Flex, FlexBlock, FlexItem, useNavigator } from '@wordpress/components';
3
+ import clsx from 'clsx';
3
4
  import { useCallback, useContext } from 'react';
4
5
  import { NavigatorModalContext } from "./context.js";
5
- import styles from './styles.module.scss';
6
6
  /**
7
7
  * Renders a footer.
8
8
  *
@@ -10,7 +10,7 @@ import styles from './styles.module.scss';
10
10
  *
11
11
  * @return The rendered footer.
12
12
  */
13
- export function Footer({ children, actions, isScreenLocked }) {
13
+ export function Footer({ children, actions, isScreenLocked, className, ...props }) {
14
14
  const navigator = useNavigator();
15
15
  const context = useContext(NavigatorModalContext);
16
16
  const navigate = useCallback(() => {
@@ -21,14 +21,14 @@ export function Footer({ children, actions, isScreenLocked }) {
21
21
  context.onClose?.();
22
22
  }
23
23
  }, [isScreenLocked, navigator, context]);
24
- return (_jsxs(Flex, { className: styles.footer, children: [_jsx(FlexBlock, { children: children }), actions ? (_jsx(FlexItem, { children: _jsx(Flex, { children: actions.map((props, index) => {
25
- if (typeof props === 'function') {
26
- return props({ navigate });
24
+ return (_jsxs(Flex, { className: clsx('jp-navigator-modal__footer', className), ...props, children: [_jsx(FlexBlock, { children: children }), actions ? (_jsx(FlexItem, { children: _jsx(Flex, { children: actions.map((action, index) => {
25
+ if (typeof action === 'function') {
26
+ return action({ navigate });
27
27
  }
28
- return (_jsx(Button, { ...props,
28
+ return (_jsx(Button, { ...action,
29
29
  // eslint-disable-next-line react/jsx-no-bind
30
30
  onClick: event => {
31
- props.onClick?.(event);
31
+ action.onClick?.(event);
32
32
  navigate();
33
33
  } }, index));
34
34
  }) }) })) : null] }));
@@ -4,7 +4,6 @@ import { __, isRTL } from '@wordpress/i18n';
4
4
  import { chevronLeft, chevronRight, close } from '@wordpress/icons';
5
5
  import { useCallback, useContext } from 'react';
6
6
  import { NavigatorModalContext } from "./context.js";
7
- import styles from './styles.module.scss';
8
7
  /**
9
8
  * Renders a header for the NavigatorModal.
10
9
  * @param {HeaderProps} props - Props
@@ -17,5 +16,5 @@ export function Header({ icon, title, isScreenLocked }) {
17
16
  const onGoBack = useCallback(() => {
18
17
  navigator.goBack();
19
18
  }, [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, icon, _jsx("h1", { children: title })] }), context.isDismissible ? (_jsx(Button, { size: "compact", onClick: context.onClose, icon: close, label: __('Close', 'jetpack-components'), variant: "tertiary" })) : null] }));
19
+ return (_jsxs("div", { className: "jp-navigator-modal__header", children: [_jsxs("div", { className: "jp-navigator-modal__title-wrap", children: [!isScreenLocked ? (_jsx(Button, { label: __('Go back', 'jetpack-components'), icon: isRTL() ? chevronRight : chevronLeft, onClick: onGoBack, variant: "tertiary", size: "compact" })) : null, icon, _jsx("h1", { children: title })] }), context.isDismissible ? (_jsx(Button, { size: "compact", onClick: context.onClose, icon: close, label: __('Close', 'jetpack-components'), variant: "tertiary" })) : null] }));
21
20
  }
@@ -1,13 +1,17 @@
1
+ import { Modal } from '@wordpress/components';
1
2
  import { Screen } from './screen.tsx';
2
- import { TNavigatorModalContext, SharedProps } from './types.ts';
3
+ import './styles.scss';
4
+ import { TNavigatorModalContext } from './types.ts';
5
+ type ModalProps = React.ComponentProps<typeof Modal>;
6
+ type NavigatorModalProps = Omit<ModalProps, 'onRequestClose'> & TNavigatorModalContext;
3
7
  /**
4
8
  * Renders a modal with navigator capabilities.
5
9
  *
6
- * @param {SharedProps & TNavigatorModalContext} props - Props
10
+ * @param {NavigatorModalProps} props - Props
7
11
  *
8
12
  * @return Component
9
13
  */
10
- declare function NavigatorModalMain({ children, className, initialPath, onClose, isDismissible, }: SharedProps & TNavigatorModalContext): import("react/jsx-runtime").JSX.Element;
14
+ declare function NavigatorModalMain({ children, className, initialPath, onClose, isDismissible, ...props }: NavigatorModalProps): import("react/jsx-runtime").JSX.Element;
11
15
  export declare const NavigatorModal: typeof NavigatorModalMain & {
12
16
  Screen: typeof Screen & {
13
17
  displayName: string;
@@ -4,27 +4,27 @@ import clsx from 'clsx';
4
4
  import { useContext } from 'react';
5
5
  import { NavigatorModalContext } from "./context.js";
6
6
  import { Screen } from "./screen.js";
7
- import styles from './styles.module.scss';
7
+ import './styles.scss';
8
8
  /**
9
9
  * Renders the internal NavigatorModal component.
10
10
  *
11
- * @param { SharedProps } props - Props
11
+ * @param { ModalProps } props - Props
12
12
  *
13
13
  * @return Component
14
14
  */
15
- function InternalNavigatorModal({ children, className }) {
15
+ function InternalNavigatorModal({ children, className, ...props }) {
16
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 }) }));
17
+ return (_jsx(Modal, { __experimentalHideHeader: true, onRequestClose: context.onClose, className: clsx('jp-navigator-modal', className), ...props, children: _jsx(Navigator, { initialPath: context.initialPath, className: "jp-navigator-modal__navigator", children: children }) }));
18
18
  }
19
19
  /**
20
20
  * Renders a modal with navigator capabilities.
21
21
  *
22
- * @param {SharedProps & TNavigatorModalContext} props - Props
22
+ * @param {NavigatorModalProps} props - Props
23
23
  *
24
24
  * @return Component
25
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 }) }));
26
+ function NavigatorModalMain({ children, className, initialPath = '/', onClose, isDismissible = true, ...props }) {
27
+ return (_jsx(NavigatorModalContext.Provider, { value: { onClose, initialPath, isDismissible }, children: _jsx(InternalNavigatorModal, { className: className, ...props, children: children }) }));
28
28
  }
29
29
  export const NavigatorModal = Object.assign(NavigatorModalMain, {
30
30
  Screen: Object.assign(Screen, {
@@ -1,5 +1,6 @@
1
+ import { Navigator } from '@wordpress/components';
1
2
  import { FooterProps } from './footer.tsx';
2
- export type ScreenProps = {
3
+ export type ScreenProps = Omit<React.ComponentProps<typeof Navigator.Screen>, 'content' | 'children'> & {
3
4
  /**
4
5
  * The title of the screen.
5
6
  */
@@ -34,17 +35,15 @@ export type ScreenProps = {
34
35
  * className to be applied to the modal.
35
36
  */
36
37
  className?: string;
37
- } & ({
38
38
  /**
39
39
  * The content of the screen.
40
40
  */
41
- children: React.ReactNode;
42
- } | {
41
+ content?: React.ReactNode;
43
42
  /**
44
- * The content of the screen.
43
+ * The children of the screen. Alternative to `content`.
45
44
  */
46
- content: React.ReactNode;
47
- });
45
+ children?: React.ReactNode;
46
+ };
48
47
  /**
49
48
  * Renders a screen.
50
49
  *
@@ -52,4 +51,4 @@ export type ScreenProps = {
52
51
  *
53
52
  * @return The rendered screen.
54
53
  */
55
- export declare function Screen({ path, className, title, sidebar, headerIcon, isScreenLocked, footerContent, footerActions, ...props }: ScreenProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function Screen({ path, className, title, sidebar, headerIcon, isScreenLocked, footerContent, footerActions, children, content, ...props }: ScreenProps): import("react/jsx-runtime").JSX.Element;
@@ -3,7 +3,6 @@ import { Flex, Navigator } from '@wordpress/components';
3
3
  import clsx from 'clsx';
4
4
  import { Footer } from "./footer.js";
5
5
  import { Header } from "./header.js";
6
- import styles from './styles.module.scss';
7
6
  /**
8
7
  * Renders a screen.
9
8
  *
@@ -11,7 +10,7 @@ import styles from './styles.module.scss';
11
10
  *
12
11
  * @return The rendered screen.
13
12
  */
14
- export function Screen({ path, className, title, sidebar, headerIcon, isScreenLocked, footerContent, footerActions, ...props }) {
13
+ export function Screen({ path, className, title, sidebar, headerIcon, isScreenLocked, footerContent, footerActions, children, content, ...props }) {
15
14
  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, icon: headerIcon }), _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] }) }));
15
+ return (_jsx(Navigator.Screen, { path: path, className: clsx('jp-navigator-modal__screen', className), ...props, children: _jsxs(Flex, { direction: "column", gap: 0, children: [_jsx(Header, { title: title, isScreenLocked: isScreenLocked, icon: headerIcon }), _jsxs(Flex, { gap: 0, align: "start", className: "jp-navigator-modal__body", children: [sidebar ? _jsx("div", { className: "jp-navigator-modal__sidebar", children: sidebar }) : null, _jsx("div", { className: "jp-navigator-modal__content", children: content ?? children })] }), hasFooter ? (_jsx(Footer, { actions: footerActions, isScreenLocked: isScreenLocked, children: footerContent })) : null] }) }));
17
16
  }
@@ -12,13 +12,3 @@ export type TNavigatorModalContext = {
12
12
  */
13
13
  onClose?: VoidFunction;
14
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
- };
@@ -3,8 +3,9 @@ export default NumberControl;
3
3
  * This uses the experimental NumberControl from the block
4
4
  * editor where available, otherwise it falls back to a
5
5
  * standard TextControl, limited to numbers.
6
+ * @type {import('react').ComponentType<any>}
6
7
  *
7
8
  * @param {any} props - the NumberControl component props
8
- * @return {object} - NumberControl component
9
+ * @return {import('react').ReactNode} - NumberControl component
9
10
  */
10
- declare const NumberControl: any;
11
+ declare const NumberControl: import("react").ComponentType<any>;
@@ -5,9 +5,10 @@ TextControl, } from '@wordpress/components';
5
5
  * This uses the experimental NumberControl from the block
6
6
  * editor where available, otherwise it falls back to a
7
7
  * standard TextControl, limited to numbers.
8
+ * @type {import('react').ComponentType<any>}
8
9
  *
9
10
  * @param {any} props - the NumberControl component props
10
- * @return {object} - NumberControl component
11
+ * @return {import('react').ReactNode} - NumberControl component
11
12
  */
12
13
  const NumberControl = ExperimentalNumberControl ||
13
14
  function CustomNumberControl(props) {
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import type { Button } from '@wordpress/components';
5
- import type { ComponentProps, ReactNode } from 'react';
4
+ import type { ReactNode } from 'react';
6
5
  export type PricingCardProps = {
7
6
  /**
8
7
  * The Title.
@@ -35,7 +34,7 @@ export type PricingCardProps = {
35
34
  /**
36
35
  * The CTA callback to be called on click.
37
36
  */
38
- onCtaClick?: ComponentProps<typeof Button>['onClick'];
37
+ onCtaClick?: React.MouseEventHandler<HTMLButtonElement>;
39
38
  /**
40
39
  * Optional informative text.
41
40
  */
@@ -3,6 +3,12 @@ import { Button, Flex, DropdownMenu } from '@wordpress/components';
3
3
  import styles from './style.module.scss';
4
4
  const DownIcon = () => (_jsx("svg", { width: "15", height: "9", fill: "none", xmlns: "http://www.w3.org/2000/svg", viewBox: "10 9 4 7", children: _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "m18.004 10.555-6.005 5.459-6.004-5.459 1.009-1.11 4.995 4.542 4.996-4.542 1.009 1.11Z" }) }));
5
5
  const SplitButton = ({ variant, controls, popoverProps, toggleProps, label, ...buttonProps }) => {
6
- return (_jsx(Flex, { className: styles['split-button'], children: _jsxs("div", { role: "group", className: "components-button-group", children: [_jsx(Button, { variant: variant, ...buttonProps, className: styles.button }), _jsx(DropdownMenu, { toggleProps: { variant, className: styles.button, ...toggleProps }, popoverProps: { noArrow: false, ...popoverProps }, icon: _jsx(DownIcon, {}), disableOpenOnArrowDown: true, controls: controls, label: label })] }) }));
6
+ // Cast to work around WPButton's strict union type
7
+ const wpButtonProps = {
8
+ variant,
9
+ className: styles.button,
10
+ ...buttonProps,
11
+ };
12
+ return (_jsx(Flex, { className: styles['split-button'], children: _jsxs("div", { role: "group", className: "components-button-group", children: [_jsx(Button, { ...wpButtonProps }), _jsx(DropdownMenu, { toggleProps: { variant, className: styles.button, ...toggleProps }, popoverProps: { noArrow: false, ...popoverProps }, icon: _jsx(DownIcon, {}), disableOpenOnArrowDown: true, controls: controls, label: label })] }) }));
7
13
  };
8
14
  export default SplitButton;
@@ -5,7 +5,6 @@ import clsx from 'clsx';
5
5
  import { forwardRef } from 'react';
6
6
  import styles from './style.module.scss';
7
7
  import type { ButtonProps } from './types.ts';
8
- import type { ReactNode } from 'react';
9
8
 
10
9
  /**
11
10
  * Button component
@@ -41,8 +40,6 @@ const Button = forwardRef< HTMLInputElement, ButtonProps >( ( props, ref ) => {
41
40
  [ styles[ 'is-icon-button' ] ]: Boolean( icon ) && ! children,
42
41
  } );
43
42
 
44
- componentProps.ref = ref;
45
-
46
43
  const externalIconSize = size === 'normal' ? 20 : 16;
47
44
  const externalIcon = isExternalLink && (
48
45
  <>
@@ -64,19 +61,23 @@ const Button = forwardRef< HTMLInputElement, ButtonProps >( ( props, ref ) => {
64
61
  // Tooltip should not considered as a child
65
62
  children?.[ 0 ]?.props?.className !== 'components-tooltip';
66
63
 
64
+ // Cast to work around WPButton's strict union type that can't be satisfied when spreading props
65
+ const wpButtonProps = {
66
+ ref,
67
+ target: externalTarget,
68
+ variant,
69
+ className: clsx( className, { 'has-text': !! icon && hasChildren } ),
70
+ icon: ! isExternalLink ? icon : undefined,
71
+ iconSize,
72
+ disabled,
73
+ 'aria-disabled': disabled,
74
+ isDestructive,
75
+ text,
76
+ ...componentProps,
77
+ } as React.ComponentProps< typeof WPButton >;
78
+
67
79
  return (
68
- <WPButton
69
- target={ externalTarget }
70
- variant={ variant }
71
- className={ clsx( className, { 'has-text': !! icon && hasChildren } ) }
72
- icon={ ! isExternalLink ? icon : undefined }
73
- iconSize={ iconSize }
74
- disabled={ disabled }
75
- aria-disabled={ disabled }
76
- isDestructive={ isDestructive }
77
- text={ text }
78
- { ...componentProps }
79
- >
80
+ <WPButton { ...wpButtonProps }>
80
81
  { isLoading && <Spinner /> }
81
82
  <span>{ children }</span>
82
83
  { externalIcon }
@@ -1,5 +1,5 @@
1
1
  import { Button } from '@wordpress/components';
2
- import type { ComponentProps, ForwardedRef, ReactNode } from 'react';
2
+ import type { ComponentProps, ForwardedRef, MouseEventHandler, ReactNode } from 'react';
3
3
 
4
4
  type JetpackButtonBaseProps = {
5
5
  className?: string;
@@ -14,8 +14,16 @@ type JetpackButtonBaseProps = {
14
14
  weight?: 'bold' | 'regular';
15
15
  fullWidth?: boolean;
16
16
  ref?: ForwardedRef< unknown >;
17
+ href?: string;
18
+ target?: string;
19
+ onClick?: MouseEventHandler< HTMLButtonElement > | MouseEventHandler< HTMLAnchorElement >;
17
20
  };
18
21
 
19
- type WPButtonProps = Omit< ComponentProps< typeof Button >, 'size' | 'variant' >;
22
+ // Extract base props from WordPress Button, omitting the union-discriminated properties
23
+ // that cause type conflicts (size, variant, disabled, href)
24
+ type WPButtonBaseProps = Omit<
25
+ ComponentProps< typeof Button >,
26
+ 'size' | 'variant' | 'disabled' | 'href' | 'target' | 'onClick'
27
+ >;
20
28
 
21
- export type ButtonProps = JetpackButtonBaseProps & WPButtonProps;
29
+ export type ButtonProps = JetpackButtonBaseProps & WPButtonBaseProps;
@@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n';
4
4
  import Button from '../button/index.tsx';
5
5
  import { ClipboardIcon, CheckmarkIcon } from '../icons/index.tsx';
6
6
  import { CopyToClipboardProps } from './types.ts';
7
- import type { FC, ReactNode } from 'react';
7
+ import type { FC } from 'react';
8
8
 
9
9
  export const CopyToClipboard: FC< CopyToClipboardProps > = ( {
10
10
  buttonStyle = 'icon',
@@ -40,8 +40,8 @@ export const CopyToClipboard: FC< CopyToClipboardProps > = ( {
40
40
  };
41
41
  }, [] );
42
42
 
43
- let icon: ReactNode = null;
44
- let label: ReactNode = null;
43
+ let icon: JSX.Element = null;
44
+ let label: string = null;
45
45
 
46
46
  if ( 'text' !== buttonStyle ) {
47
47
  icon = hasCopied ? <CheckmarkIcon /> : <ClipboardIcon />;
@@ -64,12 +64,12 @@ const IconTooltip: FC< IconTooltipProps > = ( {
64
64
  resize: false,
65
65
  flip: false,
66
66
  offset, // The distance (in px) between the anchor and the popover.
67
- focusOnMount: 'container' as const,
67
+ focusOnMount: 'firstElement',
68
68
  onClose: hideTooltip,
69
69
  className: 'icon-tooltip-container',
70
70
  inline,
71
71
  shift,
72
- };
72
+ } satisfies Omit< React.ComponentProps< typeof Popover >, 'children' >;
73
73
 
74
74
  const isAnchorWrapper = popoverAnchorStyle === 'wrapper';
75
75
 
@@ -1,10 +1,9 @@
1
1
  import { Button, Flex, FlexBlock, FlexItem, useNavigator } from '@wordpress/components';
2
+ import clsx from 'clsx';
2
3
  import { useCallback, useContext } from 'react';
3
4
  import { NavigatorModalContext } from './context.ts';
4
- import styles from './styles.module.scss';
5
- import { SharedProps } from './types.ts';
6
5
 
7
- export type FooterProps = SharedProps & {
6
+ export type FooterProps = React.HTMLAttributes< HTMLDivElement > & {
8
7
  actions?: Array<
9
8
  | ( ( props: { navigate: VoidFunction } ) => React.ReactElement )
10
9
  | React.ComponentProps< typeof Button >
@@ -19,7 +18,7 @@ export type FooterProps = SharedProps & {
19
18
  *
20
19
  * @return The rendered footer.
21
20
  */
22
- export function Footer( { children, actions, isScreenLocked }: FooterProps ) {
21
+ export function Footer( { children, actions, isScreenLocked, className, ...props }: FooterProps ) {
23
22
  const navigator = useNavigator();
24
23
  const context = useContext( NavigatorModalContext );
25
24
 
@@ -32,23 +31,23 @@ export function Footer( { children, actions, isScreenLocked }: FooterProps ) {
32
31
  }, [ isScreenLocked, navigator, context ] );
33
32
 
34
33
  return (
35
- <Flex className={ styles.footer }>
34
+ <Flex className={ clsx( 'jp-navigator-modal__footer', className ) } { ...props }>
36
35
  <FlexBlock>{ children }</FlexBlock>
37
36
  { actions ? (
38
37
  <FlexItem>
39
38
  <Flex>
40
- { actions.map( ( props, index ) => {
41
- if ( typeof props === 'function' ) {
42
- return props( { navigate } );
39
+ { actions.map( ( action, index ) => {
40
+ if ( typeof action === 'function' ) {
41
+ return action( { navigate } );
43
42
  }
44
43
 
45
44
  return (
46
45
  <Button
47
46
  key={ index }
48
- { ...props }
47
+ { ...action }
49
48
  // eslint-disable-next-line react/jsx-no-bind
50
49
  onClick={ event => {
51
- props.onClick?.( event );
50
+ action.onClick?.( event );
52
51
  navigate();
53
52
  } }
54
53
  />
@@ -3,7 +3,6 @@ import { __, isRTL } from '@wordpress/i18n';
3
3
  import { chevronLeft, chevronRight, close } from '@wordpress/icons';
4
4
  import { useCallback, useContext } from 'react';
5
5
  import { NavigatorModalContext } from './context.ts';
6
- import styles from './styles.module.scss';
7
6
 
8
7
  export type HeaderProps = {
9
8
  /**
@@ -35,8 +34,8 @@ export function Header( { icon, title, isScreenLocked }: HeaderProps ) {
35
34
  }, [ navigator ] );
36
35
 
37
36
  return (
38
- <div className={ styles.header }>
39
- <div className={ styles[ 'title-wrap' ] }>
37
+ <div className="jp-navigator-modal__header">
38
+ <div className="jp-navigator-modal__title-wrap">
40
39
  { ! isScreenLocked ? (
41
40
  <Button
42
41
  label={ __( 'Go back', 'jetpack-components' ) }
@@ -3,26 +3,36 @@ import clsx from 'clsx';
3
3
  import { useContext } from 'react';
4
4
  import { NavigatorModalContext } from './context.ts';
5
5
  import { Screen } from './screen.tsx';
6
- import styles from './styles.module.scss';
7
- import { TNavigatorModalContext, SharedProps } from './types.ts';
6
+ import './styles.scss';
7
+ import { TNavigatorModalContext } from './types.ts';
8
+
9
+ type ModalProps = React.ComponentProps< typeof Modal >;
10
+
11
+ // Omit onRequestClose since NavigatorModal uses onClose from TNavigatorModalContext instead
12
+ type NavigatorModalProps = Omit< ModalProps, 'onRequestClose' > & TNavigatorModalContext;
8
13
 
9
14
  /**
10
15
  * Renders the internal NavigatorModal component.
11
16
  *
12
- * @param { SharedProps } props - Props
17
+ * @param { ModalProps } props - Props
13
18
  *
14
19
  * @return Component
15
20
  */
16
- function InternalNavigatorModal( { children, className }: SharedProps ) {
21
+ function InternalNavigatorModal( {
22
+ children,
23
+ className,
24
+ ...props
25
+ }: Omit< ModalProps, 'onRequestClose' > ) {
17
26
  const context = useContext( NavigatorModalContext );
18
27
 
19
28
  return (
20
29
  <Modal
21
30
  __experimentalHideHeader
22
31
  onRequestClose={ context.onClose }
23
- className={ clsx( styles.modal, className ) }
32
+ className={ clsx( 'jp-navigator-modal', className ) }
33
+ { ...props }
24
34
  >
25
- <Navigator initialPath={ context.initialPath } className={ styles.navigator }>
35
+ <Navigator initialPath={ context.initialPath } className="jp-navigator-modal__navigator">
26
36
  { children }
27
37
  </Navigator>
28
38
  </Modal>
@@ -32,7 +42,7 @@ function InternalNavigatorModal( { children, className }: SharedProps ) {
32
42
  /**
33
43
  * Renders a modal with navigator capabilities.
34
44
  *
35
- * @param {SharedProps & TNavigatorModalContext} props - Props
45
+ * @param {NavigatorModalProps} props - Props
36
46
  *
37
47
  * @return Component
38
48
  */
@@ -42,10 +52,13 @@ function NavigatorModalMain( {
42
52
  initialPath = '/',
43
53
  onClose,
44
54
  isDismissible = true,
45
- }: SharedProps & TNavigatorModalContext ) {
55
+ ...props
56
+ }: NavigatorModalProps ) {
46
57
  return (
47
58
  <NavigatorModalContext.Provider value={ { onClose, initialPath, isDismissible } }>
48
- <InternalNavigatorModal className={ className }>{ children }</InternalNavigatorModal>
59
+ <InternalNavigatorModal className={ className } { ...props }>
60
+ { children }
61
+ </InternalNavigatorModal>
49
62
  </NavigatorModalContext.Provider>
50
63
  );
51
64
  }
@@ -2,9 +2,11 @@ import { Flex, Navigator } from '@wordpress/components';
2
2
  import clsx from 'clsx';
3
3
  import { Footer, FooterProps } from './footer.tsx';
4
4
  import { Header } from './header.tsx';
5
- import styles from './styles.module.scss';
6
5
 
7
- export type ScreenProps = {
6
+ export type ScreenProps = Omit<
7
+ React.ComponentProps< typeof Navigator.Screen >,
8
+ 'content' | 'children'
9
+ > & {
8
10
  /**
9
11
  * The title of the screen.
10
12
  */
@@ -43,20 +45,17 @@ export type ScreenProps = {
43
45
  * className to be applied to the modal.
44
46
  */
45
47
  className?: string;
46
- } & (
47
- | {
48
- /**
49
- * The content of the screen.
50
- */
51
- children: React.ReactNode;
52
- }
53
- | {
54
- /**
55
- * The content of the screen.
56
- */
57
- content: React.ReactNode;
58
- }
59
- );
48
+
49
+ /**
50
+ * The content of the screen.
51
+ */
52
+ content?: React.ReactNode;
53
+
54
+ /**
55
+ * The children of the screen. Alternative to `content`.
56
+ */
57
+ children?: React.ReactNode;
58
+ };
60
59
 
61
60
  /**
62
61
  * Renders a screen.
@@ -74,20 +73,24 @@ export function Screen( {
74
73
  isScreenLocked,
75
74
  footerContent,
76
75
  footerActions,
76
+ children,
77
+ content,
77
78
  ...props
78
79
  }: ScreenProps ) {
79
80
  const hasFooter = Boolean( footerContent || ( footerActions && footerActions.length ) );
80
81
 
81
82
  return (
82
- <Navigator.Screen path={ path } className={ clsx( styles.screen, className ) }>
83
+ <Navigator.Screen
84
+ path={ path }
85
+ className={ clsx( 'jp-navigator-modal__screen', className ) }
86
+ { ...props }
87
+ >
83
88
  <Flex direction="column" gap={ 0 }>
84
89
  <Header title={ title } isScreenLocked={ isScreenLocked } icon={ headerIcon } />
85
90
 
86
- <Flex gap={ 0 } align="start" className={ styles.body }>
87
- { sidebar ? <div className={ styles.sidebar }>{ sidebar }</div> : null }
88
- <div className={ styles.content }>
89
- { ( 'children' in props && props.children ) || ( 'content' in props && props.content ) }
90
- </div>
91
+ <Flex gap={ 0 } align="start" className="jp-navigator-modal__body">
92
+ { sidebar ? <div className="jp-navigator-modal__sidebar">{ sidebar }</div> : null }
93
+ <div className="jp-navigator-modal__content">{ content ?? children }</div>
91
94
  </Flex>
92
95
  { hasFooter ? (
93
96
  <Footer actions={ footerActions } isScreenLocked={ isScreenLocked }>
@@ -1,37 +1,36 @@
1
1
  @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
2
2
 
3
- .modal {
3
+ .components-modal__screen-overlay:has(.jp-navigator-modal) {
4
+ z-index: 99999; // Slightly lower than snackbar z-index (100001) so snackbars remain visible above the modal
5
+ }
6
+
7
+ .jp-navigator-modal {
4
8
  container-type: inline-size;
5
9
  container-name: navigator-modal;
6
-
7
- :global(.components-modal__screen-overlay):has(&) {
8
- z-index: 99999; // Slightly lower than snackbar z-index (100001) so snackbars remain visible above the modal
9
- }
10
-
11
10
  width: 100%;
12
11
 
13
12
  @include gb.break-medium() {
14
13
  max-height: 90%;
15
14
  }
16
15
 
17
- :global(.components-modal__content) {
16
+ .components-modal__content {
18
17
  padding: 0;
19
18
 
20
19
  > div {
21
20
  height: 100%;
22
21
  }
23
22
  }
24
- }
25
23
 
26
- .navigator {
27
- height: 100%;
28
- }
24
+ &__navigator {
25
+ height: 100%;
26
+ }
29
27
 
30
- .screen {
31
- height: 100%;
32
- padding: 0;
28
+ &__screen {
29
+ height: 100%;
30
+ padding: 0;
31
+ }
33
32
 
34
- .header {
33
+ &__header {
35
34
  display: flex;
36
35
  align-items: center;
37
36
  justify-content: space-between;
@@ -40,12 +39,6 @@
40
39
  border-bottom: 1px solid gb.$gray-200;
41
40
  flex-shrink: 0;
42
41
 
43
- .title-wrap {
44
- display: flex;
45
- align-items: center;
46
- gap: 1rem;
47
- }
48
-
49
42
  h1 {
50
43
  margin: 0;
51
44
  font-size: 15px;
@@ -55,55 +48,51 @@
55
48
  }
56
49
  }
57
50
 
58
- .body {
51
+ &__title-wrap {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 1rem;
55
+ }
56
+
57
+ &__body {
59
58
  height: 100%;
60
59
  overflow: auto;
61
60
  flex-direction: column;
62
61
 
63
62
  @container navigator-modal (width >= #{gb.$break-small}) {
64
-
65
- & {
66
- flex-direction: row;
67
- }
63
+ flex-direction: row;
68
64
  }
69
65
  }
70
66
 
71
- .sidebar {
67
+ &__sidebar {
72
68
  width: 100%;
73
69
  display: flex;
74
70
  flex-direction: column;
75
71
  gap: 1.5rem;
76
72
 
77
73
  @container navigator-modal (width >= #{gb.$break-small}) {
78
-
79
- & {
80
- flex-basis: 30%;
81
- height: 100%;
82
- flex-shrink: 0;
83
- border-inline-end: 1px solid gb.$gray-300;
84
- overflow: auto;
85
- }
74
+ flex-basis: 30%;
75
+ height: 100%;
76
+ flex-shrink: 0;
77
+ border-inline-end: 1px solid gb.$gray-300;
78
+ overflow: auto;
86
79
  }
87
80
  }
88
81
 
89
- .content {
82
+ &__content {
90
83
  width: 100%;
91
84
  height: 100%;
92
85
  flex: 1;
93
86
 
94
87
  @container navigator-modal (width >= #{gb.$break-small}) {
95
-
96
- & {
97
- overflow: auto;
98
- }
88
+ overflow: auto;
99
89
  }
100
90
  }
101
91
 
102
- .footer {
92
+ &__footer {
103
93
  padding: 1rem 1.5rem;
104
94
  border-top: 1px solid gb.$gray-300;
105
95
  height: 4.5rem;
106
96
  flex-shrink: 0;
107
97
  }
108
-
109
98
  }
@@ -13,15 +13,3 @@ export type TNavigatorModalContext = {
13
13
  */
14
14
  onClose?: VoidFunction;
15
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
- };
@@ -7,9 +7,10 @@ import {
7
7
  * This uses the experimental NumberControl from the block
8
8
  * editor where available, otherwise it falls back to a
9
9
  * standard TextControl, limited to numbers.
10
+ * @type {import('react').ComponentType<any>}
10
11
  *
11
12
  * @param {any} props - the NumberControl component props
12
- * @return {object} - NumberControl component
13
+ * @return {import('react').ReactNode} - NumberControl component
13
14
  */
14
15
  const NumberControl =
15
16
  ExperimentalNumberControl ||
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import type { Button } from '@wordpress/components';
5
- import type { ComponentProps, ReactNode } from 'react';
4
+ import type { ReactNode } from 'react';
6
5
 
7
6
  export type PricingCardProps = {
8
7
  /**
@@ -36,7 +35,7 @@ export type PricingCardProps = {
36
35
  /**
37
36
  * The CTA callback to be called on click.
38
37
  */
39
- onCtaClick?: ComponentProps< typeof Button >[ 'onClick' ];
38
+ onCtaClick?: React.MouseEventHandler< HTMLButtonElement >;
40
39
  /**
41
40
  * Optional informative text.
42
41
  */
@@ -21,10 +21,17 @@ const SplitButton: FC< SplitButtonProps > = ( {
21
21
  label,
22
22
  ...buttonProps
23
23
  } ) => {
24
+ // Cast to work around WPButton's strict union type
25
+ const wpButtonProps = {
26
+ variant,
27
+ className: styles.button,
28
+ ...buttonProps,
29
+ } as React.ComponentProps< typeof Button >;
30
+
24
31
  return (
25
32
  <Flex className={ styles[ 'split-button' ] }>
26
33
  <div role="group" className="components-button-group">
27
- <Button variant={ variant } { ...buttonProps } className={ styles.button } />
34
+ <Button { ...wpButtonProps } />
28
35
  <DropdownMenu
29
36
  toggleProps={ { variant, className: styles.button, ...toggleProps } }
30
37
  popoverProps={ { noArrow: false, ...popoverProps } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "1.4.8",
3
+ "version": "1.4.9",
4
4
  "description": "Jetpack Components Package",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/components/#readme",
6
6
  "bugs": {
@@ -45,20 +45,20 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "@automattic/format-currency": "1.0.1",
48
- "@automattic/jetpack-api": "^1.0.15",
49
- "@automattic/jetpack-boost-score-api": "^1.0.26",
48
+ "@automattic/jetpack-api": "^1.0.16",
49
+ "@automattic/jetpack-boost-score-api": "^1.0.27",
50
50
  "@automattic/jetpack-script-data": "^0.5.4",
51
51
  "@automattic/number-formatters": "^1.0.16",
52
52
  "@babel/runtime": "^7",
53
- "@wordpress/browserslist-config": "6.38.0",
54
- "@wordpress/components": "32.0.0",
55
- "@wordpress/compose": "7.38.0",
56
- "@wordpress/data": "10.38.0",
57
- "@wordpress/date": "5.38.0",
58
- "@wordpress/element": "6.38.0",
59
- "@wordpress/i18n": "6.11.0",
60
- "@wordpress/icons": "11.5.0",
61
- "@wordpress/notices": "5.38.0",
53
+ "@wordpress/browserslist-config": "6.39.0",
54
+ "@wordpress/components": "32.1.0",
55
+ "@wordpress/compose": "7.39.0",
56
+ "@wordpress/data": "10.39.0",
57
+ "@wordpress/date": "5.39.0",
58
+ "@wordpress/element": "6.39.0",
59
+ "@wordpress/i18n": "6.12.0",
60
+ "@wordpress/icons": "11.6.0",
61
+ "@wordpress/notices": "5.39.0",
62
62
  "clsx": "2.1.1",
63
63
  "prop-types": "^15.7.2",
64
64
  "qrcode.react": "4.2.0",
@@ -68,7 +68,7 @@
68
68
  "uplot-react": "1.1.4"
69
69
  },
70
70
  "devDependencies": {
71
- "@automattic/jetpack-base-styles": "^1.0.14",
71
+ "@automattic/jetpack-base-styles": "^1.0.15",
72
72
  "@babel/core": "7.28.4",
73
73
  "@babel/preset-react": "7.27.1",
74
74
  "@jest/globals": "30.2.0",