@automattic/jetpack-components 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/build/components/admin-page/style.module.scss +2 -2
  3. package/build/components/copy-to-clipboard/index.js +1 -1
  4. package/build/components/decorative-card/style.scss +4 -1
  5. package/build/components/diff-viewer/index.d.ts +2 -1
  6. package/build/components/global-notices/global-notices.d.ts +4 -0
  7. package/build/components/global-notices/global-notices.js +4 -0
  8. package/build/components/global-notices/use-global-notices.d.ts +3 -0
  9. package/build/components/global-notices/use-global-notices.js +3 -0
  10. package/build/components/gravatar/index.d.ts +1 -0
  11. package/build/components/icon-tooltip/index.js +37 -15
  12. package/build/components/icon-tooltip/style.scss +0 -8
  13. package/build/components/icon-tooltip/types.d.ts +4 -0
  14. package/build/components/jetpack-footer/style.scss +2 -5
  15. package/build/components/jitm-slot/index.d.ts +15 -0
  16. package/build/components/jitm-slot/index.js +52 -0
  17. package/build/components/jitm-slot/style.scss +16 -0
  18. package/build/components/jitm-slot/types.d.ts +10 -0
  19. package/build/components/text/style.module.scss +4 -4
  20. package/build/components/text/types.d.ts +4 -4
  21. package/build/components/theme-provider/index.js +1 -1
  22. package/build/index.d.ts +1 -2
  23. package/build/index.js +1 -2
  24. package/components/admin-page/style.module.scss +2 -2
  25. package/components/copy-to-clipboard/index.tsx +2 -2
  26. package/components/decorative-card/style.scss +4 -1
  27. package/components/diff-viewer/index.tsx +1 -0
  28. package/components/global-notices/README.md +25 -0
  29. package/components/global-notices/global-notices.tsx +4 -0
  30. package/components/global-notices/use-global-notices.ts +3 -0
  31. package/components/gravatar/index.tsx +1 -0
  32. package/components/icon-tooltip/index.tsx +49 -18
  33. package/components/icon-tooltip/style.scss +0 -8
  34. package/components/icon-tooltip/types.ts +5 -0
  35. package/components/jetpack-footer/style.scss +2 -5
  36. package/components/jitm-slot/index.tsx +67 -0
  37. package/components/jitm-slot/style.scss +16 -0
  38. package/components/jitm-slot/types.ts +10 -0
  39. package/components/text/style.module.scss +4 -4
  40. package/components/text/types.ts +4 -4
  41. package/components/theme-provider/index.tsx +1 -1
  42. package/index.ts +1 -2
  43. package/package.json +12 -7
  44. package/build/components/notice/index.d.ts +0 -29
  45. package/build/components/notice/index.js +0 -35
  46. package/build/components/notice/style.module.scss +0 -120
  47. package/build/components/zendesk-chat/constants.d.ts +0 -1
  48. package/build/components/zendesk-chat/constants.js +0 -2
  49. package/build/components/zendesk-chat/index.d.ts +0 -3
  50. package/build/components/zendesk-chat/index.js +0 -27
  51. package/build/components/zendesk-chat/types.d.ts +0 -11
  52. package/components/notice/index.tsx +0 -93
  53. package/components/notice/style.module.scss +0 -120
  54. package/components/zendesk-chat/README.md +0 -25
  55. package/components/zendesk-chat/constants.ts +0 -2
  56. package/components/zendesk-chat/index.tsx +0 -34
  57. package/components/zendesk-chat/types.ts +0 -14
  58. /package/build/components/{zendesk-chat → jitm-slot}/types.js +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [4.0.0] - 2026-10-05
6
+ ### Changed
7
+ - Update package dependencies. [#52999]
8
+
9
+ ### Removed
10
+ - Remove the Notice component; use Notice from @wordpress/ui instead. [#52932]
11
+ - Remove the unused ZendeskChat component. [#52074]
12
+
13
+ ## [3.3.0] - 2026-09-28
14
+ ### Added
15
+ - Add a `JitmSlot` component that renders the Jetpack in-dashboard message slot and keeps the message across route changes. [#52641]
16
+ - IconTooltip: Add a `label` prop to give the icon trigger an accessible name. [#52800]
17
+
18
+ ### Changed
19
+ - IconTooltip: Keep focus on the trigger when a tooltip opens, and announce its content to screen readers. [#52736]
20
+ - IconTooltip: Show the trigger's focus ring the way other WordPress buttons do. [#52736]
21
+ - Use logical CSS properties so layouts mirror in right-to-left languages. [#52749]
22
+
23
+ ### Deprecated
24
+ - Deprecate `GlobalNotices` and `useGlobalNotices`. Use `SnackbarNotices` from `@wordpress/notices` instead. [#52193]
25
+
26
+ ### Fixed
27
+ - IconTooltip: Close a tooltip opened on hover when Escape is pressed, without moving focus. [#52799]
28
+
5
29
  ## [3.2.0] - 2026-09-23
6
30
  ### Added
7
31
  - IconTooltip: Add a `trigger` prop that renders a text trigger in place of the icon, and a `closeOnClickOutside` prop to keep a tooltip open while the visitor works elsewhere on the page. [#52704]
@@ -1963,6 +1987,8 @@
1963
1987
  ### Changed
1964
1988
  - Update node version requirement to 14.16.1
1965
1989
 
1990
+ [4.0.0]: https://github.com/Automattic/jetpack-components/compare/3.3.0...4.0.0
1991
+ [3.3.0]: https://github.com/Automattic/jetpack-components/compare/3.2.0...3.3.0
1966
1992
  [3.2.0]: https://github.com/Automattic/jetpack-components/compare/3.1.2...3.2.0
1967
1993
  [3.1.2]: https://github.com/Automattic/jetpack-components/compare/3.1.1...3.1.2
1968
1994
  [3.1.1]: https://github.com/Automattic/jetpack-components/compare/3.1.0...3.1.1
@@ -105,9 +105,9 @@
105
105
  // We override only the bits that need to differ from those defaults.
106
106
  float: none;
107
107
  text-align: end;
108
- background: var(--wpds-color-background-surface-neutral-strong, #fff);
108
+ background: var(--wpds-color-background-surface-neutral-strong);
109
109
  font-style: italic;
110
- color: var(--wpds-color-foreground-content-neutral-weak, #87a6bc);
110
+ color: var(--wpds-color-foreground-content-neutral-weak);
111
111
  border-bottom: none;
112
112
 
113
113
  @media (max-width: 659px) {
@@ -6,7 +6,7 @@ import Button from '../button/index.js';
6
6
  import { ClipboardIcon, CheckmarkIcon } from '../icons/index.js';
7
7
  export const CopyToClipboard = ({ buttonStyle = 'icon', textToCopy, onCopy, ...buttonProps }) => {
8
8
  const [hasCopied, setHasCopied] = useState(false);
9
- const copyTimer = useRef();
9
+ const copyTimer = useRef(undefined);
10
10
  const copyRef = useCopyToClipboard(textToCopy, () => {
11
11
  if (copyTimer.current) {
12
12
  clearTimeout(copyTimer.current);
@@ -29,7 +29,7 @@
29
29
  display: block;
30
30
  position: absolute;
31
31
  top: 24px;
32
- left: 24px;
32
+ inset-inline-start: 24px;
33
33
  width: 38px;
34
34
  height: 8px;
35
35
  background-image: url('data:image/svg+xml,<svg width="38" height="8" viewBox="0 0 38 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 7C1 7 2.37087 1 6.89831 1C11.4257 1 14.3709 7 18.8983 7C23.4257 7 26.7777 1 31.3051 1C35.912 1 37 7 37 7" stroke="white" stroke-width="1.5" stroke-linejoin="round"/></svg>');
@@ -43,6 +43,9 @@
43
43
  &__icon-container {
44
44
  position: absolute;
45
45
  top: 50%;
46
+ // Kept physical: translate(-50%) shifts by a fixed direction, not a
47
+ // logical one, so left must stay paired with it or the box goes
48
+ // off-center by its own width under RTL without rtlcss.
46
49
  left: 50%;
47
50
  transform: translate(-50%, -50%);
48
51
  width: 80px;
@@ -1,4 +1,5 @@
1
+ import type { JSX } from 'react';
1
2
  export declare const DiffViewer: ({ diff }: {
2
3
  diff: any;
3
- }) => import("react").JSX.Element;
4
+ }) => JSX.Element;
4
5
  export default DiffViewer;
@@ -4,6 +4,10 @@ export type GlobalNoticesProps = {
4
4
  /**
5
5
  * Renders the global notices.
6
6
  *
7
+ * @deprecated Use `SnackbarNotices` from `@wordpress/notices` instead. Pages
8
+ * rendered by `@wordpress/boot` already mount one, so mounting this as well
9
+ * shows every notice twice.
10
+ *
7
11
  * @param {GlobalNoticesProps} props - Component props.
8
12
  *
9
13
  * @return {import('react').ReactNode} The rendered notices list.
@@ -5,6 +5,10 @@ import { useGlobalNotices } from './use-global-notices.js';
5
5
  /**
6
6
  * Renders the global notices.
7
7
  *
8
+ * @deprecated Use `SnackbarNotices` from `@wordpress/notices` instead. Pages
9
+ * rendered by `@wordpress/boot` already mount one, so mounting this as well
10
+ * shows every notice twice.
11
+ *
8
12
  * @param {GlobalNoticesProps} props - Component props.
9
13
  *
10
14
  * @return {import('react').ReactNode} The rendered notices list.
@@ -4,6 +4,9 @@ export type TGlobalNotices = ReturnType<NoticesStore['getActions']> & ReturnType
4
4
  /**
5
5
  * The global notices hook.
6
6
  *
7
+ * @deprecated Dispatch to `@wordpress/notices` directly, passing
8
+ * `{ type: 'snackbar' }` — this hook only defaulted that option.
9
+ *
7
10
  * @return {TGlobalNotices} The global notices selectors and actions.
8
11
  */
9
12
  export declare function useGlobalNotices(): TGlobalNotices;
@@ -3,6 +3,9 @@ import { store as noticesStore } from '@wordpress/notices';
3
3
  /**
4
4
  * The global notices hook.
5
5
  *
6
+ * @deprecated Dispatch to `@wordpress/notices` directly, passing
7
+ * `{ type: 'snackbar' }` — this hook only defaulted that option.
8
+ *
6
9
  * @return {TGlobalNotices} The global notices selectors and actions.
7
10
  */
8
11
  export function useGlobalNotices() {
@@ -1,5 +1,6 @@
1
1
  import '@gravatar-com/hovercards/dist/style.css';
2
2
  import './style.scss';
3
+ import type { JSX } from 'react';
3
4
  /**
4
5
  * Gravatar `defaultImage` styles, mirroring https://docs.gravatar.com/sdk/images/#default-image
5
6
  */
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { speak } from '@wordpress/a11y';
2
3
  import { Popover } from '@wordpress/components';
3
4
  import { focus } from '@wordpress/dom';
4
5
  import { Icon, info } from '@wordpress/icons';
@@ -23,7 +24,7 @@ const placementsToPositions = (placement) => {
23
24
  * @param {IconTooltipProps} props - Props
24
25
  * @return {ReactElement} - JSX element
25
26
  */
26
- const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', placement = 'bottom-end', animate = true, iconCode = info, iconSize = 18, offset = 10, title, children, popoverAnchorStyle = 'icon', trigger, onTriggerClick, closeOnClickOutside = true, forceShow = false, hoverShow = false, wide = false, inline = true, shift = false, }) => {
27
+ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', placement = 'bottom-end', animate = true, iconCode = info, label, iconSize = 18, offset = 10, title, children, popoverAnchorStyle = 'icon', trigger, onTriggerClick, closeOnClickOutside = true, forceShow = false, hoverShow = false, wide = false, inline = true, shift = false, }) => {
27
28
  const POPOVER_HELPER_WIDTH = 124;
28
29
  const [isVisible, setIsVisible] = useState(false);
29
30
  const [hoverTimeout, setHoverTimeout] = useState(null);
@@ -43,13 +44,16 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
43
44
  openedByHover.current = false;
44
45
  setIsVisible(!isVisible);
45
46
  }, [isVisible, setIsVisible, onTriggerClick]);
46
- // Focus can stay on a text trigger while its tooltip is open, so it handles the dialog keys too.
47
+ // Focus can stay on the trigger while its tooltip is open, so it handles the dialog keys too.
47
48
  const handleTriggerKeyDown = useCallback((event) => {
49
+ // A held key repeats activation, so only its first press toggles.
50
+ if (event.repeat && (event.key === 'Enter' || event.key === ' ')) {
51
+ event.preventDefault();
52
+ return;
53
+ }
48
54
  // A link only activates on Enter; a button also activates on Space.
49
- if (event.key === ' ') {
50
- if (!event.repeat) {
51
- toggleTooltip(event);
52
- }
55
+ if (hasTextTrigger && event.key === ' ') {
56
+ toggleTooltip(event);
53
57
  return;
54
58
  }
55
59
  if (!isVisible) {
@@ -70,7 +74,7 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
70
74
  hideTooltip();
71
75
  }
72
76
  }
73
- }, [isVisible, hideTooltip, toggleTooltip]);
77
+ }, [hasTextTrigger, isVisible, hideTooltip, toggleTooltip]);
74
78
  const isAnchorWrapper = popoverAnchorStyle === 'wrapper';
75
79
  const isForcedToShow = isAnchorWrapper && forceShow;
76
80
  const handlePopoverKeyDown = useCallback((event) => {
@@ -104,9 +108,6 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
104
108
  focusAfterClose.current = destination ?? null;
105
109
  hideTooltip();
106
110
  }, [hideTooltip]);
107
- const focusOnOpen = isForcedToShow || (hasTextTrigger && !openedByHover.current)
108
- ? 'firstElement'
109
- : !openedByHover.current;
110
111
  const args = {
111
112
  // To be compatible with deprecating prop `position`.
112
113
  position: placementsToPositions(placement),
@@ -116,9 +117,9 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
116
117
  resize: false,
117
118
  flip: false,
118
119
  offset, // The distance (in px) between the anchor and the popover.
119
- // Focusing the popover itself puts Escape in reach even with nothing tabbable inside. A text
120
- // trigger or caller-controlled popover moves focus to its first link, if it has one.
121
- focusOnMount: focusOnOpen,
120
+ // Focus stays on the trigger, which handles the dialog keys; a caller-controlled popover
121
+ // has no trigger, so it focuses its first button instead.
122
+ focusOnMount: isForcedToShow ? 'firstElement' : false,
122
123
  // Tab moves through the popover in document order rather than cycling inside it, and
123
124
  // handlePopoverKeyDown decides where it lands on the way out.
124
125
  constrainTabbing: false,
@@ -126,7 +127,7 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
126
127
  ref: popoverRef,
127
128
  onClose: () => {
128
129
  const popover = popoverRef.current;
129
- if (hasTextTrigger && popover?.contains(popover.ownerDocument.activeElement)) {
130
+ if (popover?.contains(popover.ownerDocument.activeElement)) {
130
131
  focusAfterClose.current = triggerRef.current;
131
132
  }
132
133
  hideTooltip();
@@ -160,6 +161,20 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
160
161
  doc?.addEventListener('pointerdown', handlePointerDown);
161
162
  return () => doc?.removeEventListener('pointerdown', handlePointerDown);
162
163
  }, [isVisible, isForcedToShow, closeOnClickOutside, hideTooltip]);
164
+ // Hover leaves focus elsewhere, so Escape is watched directly to keep the content dismissible.
165
+ useEffect(() => {
166
+ if (!isVisible || !openedByHover.current) {
167
+ return;
168
+ }
169
+ const doc = wrapperRef.current?.ownerDocument;
170
+ const handleKeyDown = (event) => {
171
+ if (event.key === 'Escape') {
172
+ hideTooltip();
173
+ }
174
+ };
175
+ doc?.addEventListener('keydown', handleKeyDown);
176
+ return () => doc?.removeEventListener('keydown', handleKeyDown);
177
+ }, [isVisible, hideTooltip]);
163
178
  useEffect(() => {
164
179
  if (isForcedToShow || isVisible) {
165
180
  return;
@@ -168,6 +183,13 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
168
183
  focusAfterClose.current = null;
169
184
  destination?.focus();
170
185
  }, [isForcedToShow, isVisible]);
186
+ // Focus stays on the trigger, so a screen reader hears the popover through an announcement.
187
+ useEffect(() => {
188
+ const popover = popoverRef.current;
189
+ if (isVisible && !openedByHover.current && popover) {
190
+ speak(popover.innerText ?? popover.textContent, 'polite');
191
+ }
192
+ }, [isVisible]);
171
193
  const handleMouseEnter = useCallback(() => {
172
194
  if (hoverShow) {
173
195
  if (hoverTimeout) {
@@ -188,6 +210,6 @@ const IconTooltip = ({ className = '', popoverClassName, iconClassName = '', pla
188
210
  }
189
211
  }, [hoverShow]);
190
212
  const helper = (_jsx("div", { className: clsx('icon-tooltip-helper', { 'is-wide': wide }), style: iconShiftBySize, children: (isForcedToShow || isVisible) && (_jsx(Popover, { ...args, children: _jsxs("div", { children: [title && _jsx("div", { className: "icon-tooltip-title", children: title }), _jsx("div", { className: "icon-tooltip-content", children: children })] }) })) }));
191
- return (_jsxs("div", { ref: wrapperRef, className: wrapperClassNames, "data-testid": "icon-tooltip_wrapper", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [hasTextTrigger && (_jsxs(_Fragment, { children: [_jsx("a", { ref: triggerRef, href: "#", role: "button", className: "icon-tooltip-trigger", "aria-expanded": isVisible, onClick: toggleTooltip, onKeyDown: handleTriggerKeyDown, children: trigger }), _jsx("span", { className: "icon-tooltip-anchor", children: _jsx("span", { children: helper }) })] })), !hasTextTrigger && !isAnchorWrapper && (_jsx(Button, { variant: "link", "aria-expanded": isVisible, onClick: toggleTooltip, children: _jsx(Icon, { className: iconClassName, icon: iconCode, size: iconSize }) })), !hasTextTrigger && helper] }));
213
+ return (_jsxs("div", { ref: wrapperRef, className: wrapperClassNames, "data-testid": "icon-tooltip_wrapper", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [hasTextTrigger && (_jsxs(_Fragment, { children: [_jsx("a", { ref: triggerRef, href: "#", role: "button", className: "icon-tooltip-trigger", "aria-expanded": isVisible, onClick: toggleTooltip, onKeyDown: handleTriggerKeyDown, children: trigger }), _jsx("span", { className: "icon-tooltip-anchor", children: _jsx("span", { children: helper }) })] })), !hasTextTrigger && !isAnchorWrapper && (_jsx(Button, { ref: triggerRef, variant: "link", "aria-label": label, "aria-expanded": isVisible, onClick: toggleTooltip, onKeyDown: handleTriggerKeyDown, children: _jsx(Icon, { className: iconClassName, icon: iconCode, size: iconSize }) })), !hasTextTrigger && helper] }));
192
214
  };
193
215
  export default IconTooltip;
@@ -107,14 +107,6 @@ $arrow-color: var(--jp-gray);
107
107
  .icon-tooltip-container {
108
108
  // Recover events
109
109
  pointer-events: all;
110
- // Takes focus on open, but its box is wider than the visible content,
111
- // so the ring goes on the content instead.
112
- outline: none;
113
-
114
- &:focus-visible .components-popover__content {
115
- outline: var(--wp-admin-border-width-focus, 2px) solid var(--wp-admin-theme-color, #3858e9);
116
- }
117
-
118
110
  // __content styles here covers both the inline and portal modes
119
111
  .components-popover__content {
120
112
  padding: 24px;
@@ -31,6 +31,10 @@ export type IconTooltipProps = {
31
31
  * The icon to display. Accepts icon components from `@wordpress/icons`.
32
32
  */
33
33
  iconCode?: ComponentProps<typeof Icon>['icon'];
34
+ /**
35
+ * The accessible name of the icon button. Ignored when `trigger` is set, since its text names it.
36
+ */
37
+ label?: string;
34
38
  /**
35
39
  * The title of Popover.
36
40
  */
@@ -1,10 +1,7 @@
1
- @use "sass:meta";
2
- @include meta.load-css("pkg:@wordpress/theme/design-tokens.css");
3
-
4
1
  .jetpack-footer {
5
- border-top: var(--wpds-border-width-xs, 1px) solid var(--wpds-color-stroke-surface-neutral-weak, #e0e0e0);
2
+ border-top: var(--wpds-border-width-xs) solid var(--wpds-color-stroke-surface-neutral-weak);
6
3
  box-sizing: border-box;
7
- font-size: var(--wpds-typography-font-size-md, 13px);
4
+ font-size: var(--wpds-typography-font-size-md);
8
5
  padding: var(--wpds-dimension-padding-xl) var(--wpds-dimension-padding-2xl);
9
6
  width: 100%;
10
7
  display: flex;
@@ -0,0 +1,15 @@
1
+ import './style.scss';
2
+ import type { JitmSlotProps } from './types.ts';
3
+ import type { FC } from 'react';
4
+ /**
5
+ * Hosts the element the JITM script re-parents its card into.
6
+ *
7
+ * The JITM script places its card once per page load, so the element outlives any
8
+ * one slot: an unmounted slot parks it, hidden, and the next slot takes it back.
9
+ * `className` is for width and alignment; `style.scss` owns spacing.
10
+ *
11
+ * @param {JitmSlotProps} props - Component props.
12
+ * @return {JSX.Element} The slot.
13
+ */
14
+ declare const JitmSlot: FC<JitmSlotProps>;
15
+ export default JitmSlot;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useLayoutEffect, useRef } from 'react';
4
+ import './style.scss';
5
+ const NOTICES_ID = 'jp-admin-notices';
6
+ /**
7
+ * Returns the page's one `#jp-admin-notices`, creating it on first use.
8
+ *
9
+ * Found through the DOM, not a module variable: each wp-build route bundles its own copy
10
+ * of this module.
11
+ *
12
+ * @return {HTMLElement} The element.
13
+ */
14
+ const getNotices = () => {
15
+ let element = document.getElementById(NOTICES_ID);
16
+ if (!element) {
17
+ element = document.createElement('div');
18
+ element.id = NOTICES_ID;
19
+ }
20
+ return element;
21
+ };
22
+ /**
23
+ * Hosts the element the JITM script re-parents its card into.
24
+ *
25
+ * The JITM script places its card once per page load, so the element outlives any
26
+ * one slot: an unmounted slot parks it, hidden, and the next slot takes it back.
27
+ * `className` is for width and alignment; `style.scss` owns spacing.
28
+ *
29
+ * @param {JitmSlotProps} props - Component props.
30
+ * @return {JSX.Element} The slot.
31
+ */
32
+ const JitmSlot = ({ className, inset }) => {
33
+ const ref = useRef(null);
34
+ useLayoutEffect(() => {
35
+ const host = ref.current;
36
+ if (!host) {
37
+ return;
38
+ }
39
+ const element = getNotices();
40
+ element.hidden = false;
41
+ host.appendChild(element);
42
+ return () => {
43
+ // A slot that mounted before this one unmounted may already hold it.
44
+ if (element.parentNode === host) {
45
+ element.hidden = true;
46
+ document.body.appendChild(element);
47
+ }
48
+ };
49
+ }, []);
50
+ return (_jsx("div", { ref: ref, className: clsx('jp-jitm-slot', { 'jp-jitm-slot--inset': inset }, className), "data-testid": "jp-jitm-slot" }));
51
+ };
52
+ export default JitmSlot;
@@ -0,0 +1,16 @@
1
+ // The slot is always rendered and usually empty, so it takes no space
2
+ // of its own; spacing sits on the card so a dismissed one leaves no gap.
3
+ .jp-jitm-slot {
4
+ margin: 0;
5
+ padding: 0;
6
+
7
+ // `jitm-card` comes from packages/jitm/src/js/jetpack-jitm.js.
8
+ .jitm-card {
9
+ margin: var(--wpds-dimension-gap-xl) 0;
10
+ }
11
+ }
12
+
13
+ // Same specificity as the rule above, so it has to come after it.
14
+ .jp-jitm-slot--inset .jitm-card {
15
+ margin-inline: var(--wpds-dimension-padding-2xl);
16
+ }
@@ -0,0 +1,10 @@
1
+ export type JitmSlotProps = {
2
+ /**
3
+ * Layout the dashboard owns: width, column span, centering. Not spacing.
4
+ */
5
+ className?: string;
6
+ /**
7
+ * The slot sits outside the page's padded container, so the card takes the page gutter.
8
+ */
9
+ inset?: boolean;
10
+ };
@@ -89,9 +89,9 @@ $props: (
89
89
 
90
90
  $sides: (
91
91
  "top": "t",
92
- "right": "r",
92
+ "inline-end": "r",
93
93
  "bottom": "b",
94
- "left": "l",
94
+ "inline-start": "l",
95
95
  );
96
96
 
97
97
  @each $prop, $p in $props {
@@ -106,8 +106,8 @@ $sides: (
106
106
 
107
107
  /* mx, px */
108
108
  .#{$p}x-#{$size} {
109
- #{$prop}-left: calc(var(--spacing-base) * #{$size});
110
- #{$prop}-right: calc(var(--spacing-base) * #{$size});
109
+ #{$prop}-inline-start: calc(var(--spacing-base) * #{$size});
110
+ #{$prop}-inline-end: calc(var(--spacing-base) * #{$size});
111
111
  }
112
112
 
113
113
  .#{$p}y-#{$size} {
@@ -7,11 +7,11 @@ export type TextProps = {
7
7
  m?: SpacingValue;
8
8
  /** margin-top */
9
9
  mt?: SpacingValue;
10
- /** margin-right */
10
+ /** margin-inline-end (right in LTR) */
11
11
  mr?: SpacingValue;
12
12
  /** margin-bottom */
13
13
  mb?: SpacingValue;
14
- /** margin-left */
14
+ /** margin-inline-start (left in LTR) */
15
15
  ml?: SpacingValue;
16
16
  /** margin left and right */
17
17
  mx?: SpacingValue;
@@ -21,11 +21,11 @@ export type TextProps = {
21
21
  p?: SpacingValue;
22
22
  /** padding-top */
23
23
  pt?: SpacingValue;
24
- /** padding-right */
24
+ /** padding-inline-end (right in LTR) */
25
25
  pr?: SpacingValue;
26
26
  /** padding-bottom */
27
27
  pb?: SpacingValue;
28
- /** padding-left */
28
+ /** padding-inline-start (left in LTR) */
29
29
  pl?: SpacingValue;
30
30
  /** padding left and right */
31
31
  px?: SpacingValue;
@@ -105,7 +105,7 @@ const setup = (root, id, withGlobalStyles) => {
105
105
  * @return {ReactNode} ThemeProvider component.
106
106
  */
107
107
  const ThemeProvider = ({ children = null, targetDom, id, withGlobalStyles = true, }) => {
108
- const themeWrapperRef = useRef();
108
+ const themeWrapperRef = useRef(undefined);
109
109
  // Check whether the theme provider instance is already registered.
110
110
  const isAlreadyProvided = globalThemeInstances?.[id]?.provided;
111
111
  useLayoutEffect(() => {
package/build/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { default as Col } from './components/layout/col/index.tsx';
20
20
  export { default as Testimonials } from './components/testimonials/index.tsx';
21
21
  export { default as Container } from './components/layout/container/index.tsx';
22
22
  export { default as CopyToClipboard } from './components/copy-to-clipboard/index.tsx';
23
+ export { default as JitmSlot } from './components/jitm-slot/index.tsx';
23
24
  export * from './components/icons/index.tsx';
24
25
  export { default as ThemeProvider } from './components/theme-provider/index.tsx';
25
26
  export { default as Text, H2, H3, Title } from './components/text/index.tsx';
@@ -36,11 +37,9 @@ export { default as ProductPrice } from './components/product-price/index.tsx';
36
37
  export { default as DotPager } from './components/dot-pager/index.tsx';
37
38
  export { default as DonutMeter } from './components/donut-meter/index.tsx';
38
39
  export { default as RecordMeterBar } from './components/record-meter-bar/index.tsx';
39
- export { default as Notice } from './components/notice/index.tsx';
40
40
  export { default as Popover } from './components/popover/index.tsx';
41
41
  export { default as IndeterminateProgressBar } from './components/indeterminate-progress-bar/index.tsx';
42
42
  export { default as ActionPopover } from './components/action-popover/index.tsx';
43
- export { default as ZendeskChat } from './components/zendesk-chat/index.tsx';
44
43
  export { default as ProgressBar } from './components/progress-bar/index.tsx';
45
44
  export { default as UpsellBanner } from './components/upsell-banner/index.tsx';
46
45
  export { getUserLocale, cleanLocale } from './lib/locale/index.ts';
package/build/index.js CHANGED
@@ -33,6 +33,7 @@ export { default as Col } from './components/layout/col/index.js';
33
33
  export { default as Testimonials } from './components/testimonials/index.js';
34
34
  export { default as Container } from './components/layout/container/index.js';
35
35
  export { default as CopyToClipboard } from './components/copy-to-clipboard/index.js';
36
+ export { default as JitmSlot } from './components/jitm-slot/index.js';
36
37
  export * from './components/icons/index.js';
37
38
  export { default as ThemeProvider } from './components/theme-provider/index.js';
38
39
  export { default as Text, H2, H3, Title } from './components/text/index.js';
@@ -48,11 +49,9 @@ export { default as ProductPrice } from './components/product-price/index.js';
48
49
  export { default as DotPager } from './components/dot-pager/index.js';
49
50
  export { default as DonutMeter } from './components/donut-meter/index.js';
50
51
  export { default as RecordMeterBar } from './components/record-meter-bar/index.js';
51
- export { default as Notice } from './components/notice/index.js';
52
52
  export { default as Popover } from './components/popover/index.js';
53
53
  export { default as IndeterminateProgressBar } from './components/indeterminate-progress-bar/index.js';
54
54
  export { default as ActionPopover } from './components/action-popover/index.js';
55
- export { default as ZendeskChat } from './components/zendesk-chat/index.js';
56
55
  export { default as ProgressBar } from './components/progress-bar/index.js';
57
56
  export { default as UpsellBanner } from './components/upsell-banner/index.js';
58
57
  export { getUserLocale, cleanLocale } from './lib/locale/index.js';
@@ -105,9 +105,9 @@
105
105
  // We override only the bits that need to differ from those defaults.
106
106
  float: none;
107
107
  text-align: end;
108
- background: var(--wpds-color-background-surface-neutral-strong, #fff);
108
+ background: var(--wpds-color-background-surface-neutral-strong);
109
109
  font-style: italic;
110
- color: var(--wpds-color-foreground-content-neutral-weak, #87a6bc);
110
+ color: var(--wpds-color-foreground-content-neutral-weak);
111
111
  border-bottom: none;
112
112
 
113
113
  @media (max-width: 659px) {
@@ -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 } from 'react';
7
+ import type { JSX, FC } from 'react';
8
8
 
9
9
  export const CopyToClipboard: FC< CopyToClipboardProps > = ( {
10
10
  buttonStyle = 'icon',
@@ -14,7 +14,7 @@ export const CopyToClipboard: FC< CopyToClipboardProps > = ( {
14
14
  } ) => {
15
15
  const [ hasCopied, setHasCopied ] = useState( false );
16
16
 
17
- const copyTimer = useRef< ReturnType< typeof setTimeout > | undefined >();
17
+ const copyTimer = useRef< ReturnType< typeof setTimeout > | undefined >( undefined );
18
18
 
19
19
  const copyRef = useCopyToClipboard( textToCopy, () => {
20
20
  if ( copyTimer.current ) {
@@ -29,7 +29,7 @@
29
29
  display: block;
30
30
  position: absolute;
31
31
  top: 24px;
32
- left: 24px;
32
+ inset-inline-start: 24px;
33
33
  width: 38px;
34
34
  height: 8px;
35
35
  background-image: url('data:image/svg+xml,<svg width="38" height="8" viewBox="0 0 38 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 7C1 7 2.37087 1 6.89831 1C11.4257 1 14.3709 7 18.8983 7C23.4257 7 26.7777 1 31.3051 1C35.912 1 37 7 37 7" stroke="white" stroke-width="1.5" stroke-linejoin="round"/></svg>');
@@ -43,6 +43,9 @@
43
43
  &__icon-container {
44
44
  position: absolute;
45
45
  top: 50%;
46
+ // Kept physical: translate(-50%) shifts by a fixed direction, not a
47
+ // logical one, so left must stay paired with it or the box goes
48
+ // off-center by its own width under RTL without rtlcss.
46
49
  left: 50%;
47
50
  transform: translate(-50%, -50%);
48
51
  width: 80px;
@@ -2,6 +2,7 @@ import { Fragment } from 'react';
2
2
  import parseFilename from './parse-filename.ts';
3
3
  import parsePatch from './parse-patch.ts';
4
4
  import styles from './styles.module.scss';
5
+ import type { JSX } from 'react';
5
6
 
6
7
  const filename = ( {
7
8
  oldFileName,
@@ -0,0 +1,25 @@
1
+ GlobalNotices
2
+ =========
3
+
4
+ > **Deprecated.** Use `SnackbarNotices` from `@wordpress/notices` instead, and
5
+ > dispatch to that store directly with `{ type: 'snackbar' }`. Pages rendered by
6
+ > `@wordpress/boot` already mount a `SnackbarNotices`, so mounting this one too
7
+ > shows every notice twice. Retained as a compatibility shim for downstream
8
+ > consumers and will be removed in the next major version.
9
+
10
+ Renders the snackbar notices held in the `@wordpress/notices` store.
11
+
12
+ ## General Usage:
13
+
14
+ ```js
15
+ import { GlobalNotices } from '@automattic/jetpack-components';
16
+ render() {
17
+ return (
18
+ <GlobalNotices />
19
+ );
20
+ }
21
+ ```
22
+
23
+ ## Props
24
+
25
+ - `maxVisibleNotices`: how many notices to show at once. Defaults to 3.
@@ -9,6 +9,10 @@ export type GlobalNoticesProps = {
9
9
  /**
10
10
  * Renders the global notices.
11
11
  *
12
+ * @deprecated Use `SnackbarNotices` from `@wordpress/notices` instead. Pages
13
+ * rendered by `@wordpress/boot` already mount one, so mounting this as well
14
+ * shows every notice twice.
15
+ *
12
16
  * @param {GlobalNoticesProps} props - Component props.
13
17
  *
14
18
  * @return {import('react').ReactNode} The rendered notices list.
@@ -9,6 +9,9 @@ export type TGlobalNotices = ReturnType< NoticesStore[ 'getActions' ] > &
9
9
  /**
10
10
  * The global notices hook.
11
11
  *
12
+ * @deprecated Dispatch to `@wordpress/notices` directly, passing
13
+ * `{ type: 'snackbar' }` — this hook only defaulted that option.
14
+ *
12
15
  * @return {TGlobalNotices} The global notices selectors and actions.
13
16
  */
14
17
  export function useGlobalNotices(): TGlobalNotices {
@@ -8,6 +8,7 @@ import { __ } from '@wordpress/i18n';
8
8
  import clsx from 'clsx';
9
9
  import { sha256 } from 'js-sha256';
10
10
  import './style.scss';
11
+ import type { JSX } from 'react';
11
12
 
12
13
  /**
13
14
  * Gravatar `defaultImage` styles, mirroring https://docs.gravatar.com/sdk/images/#default-image