@wordpress/ui 0.11.0 → 0.12.1-next.v.202604201441.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 (207) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +4 -4
  3. package/build/alert-dialog/popup.cjs +4 -4
  4. package/build/alert-dialog/popup.cjs.map +2 -2
  5. package/build/collapsible-card/header.cjs +10 -0
  6. package/build/collapsible-card/header.cjs.map +3 -3
  7. package/build/dialog/context.cjs +21 -9
  8. package/build/dialog/context.cjs.map +2 -2
  9. package/build/dialog/footer.cjs +4 -4
  10. package/build/dialog/footer.cjs.map +2 -2
  11. package/build/dialog/header.cjs +4 -4
  12. package/build/dialog/header.cjs.map +2 -2
  13. package/build/dialog/popup.cjs +4 -4
  14. package/build/dialog/popup.cjs.map +2 -2
  15. package/build/dialog/title.cjs +9 -6
  16. package/build/dialog/title.cjs.map +2 -2
  17. package/build/form/primitives/select/item.cjs +3 -3
  18. package/build/form/primitives/select/item.cjs.map +2 -2
  19. package/build/form/primitives/select/popup.cjs +3 -3
  20. package/build/form/primitives/select/popup.cjs.map +2 -2
  21. package/build/link/link.cjs +8 -18
  22. package/build/link/link.cjs.map +2 -2
  23. package/build/link/types.cjs.map +1 -1
  24. package/build/notice/action-button.cjs +3 -3
  25. package/build/notice/action-button.cjs.map +2 -2
  26. package/build/notice/action-link.cjs +8 -7
  27. package/build/notice/action-link.cjs.map +2 -2
  28. package/build/notice/actions.cjs +3 -3
  29. package/build/notice/actions.cjs.map +2 -2
  30. package/build/notice/close-icon.cjs +3 -3
  31. package/build/notice/close-icon.cjs.map +2 -2
  32. package/build/notice/description.cjs +3 -3
  33. package/build/notice/description.cjs.map +2 -2
  34. package/build/notice/root.cjs +3 -3
  35. package/build/notice/root.cjs.map +2 -2
  36. package/build/notice/title.cjs +3 -3
  37. package/build/notice/title.cjs.map +2 -2
  38. package/build/popover/arrow.cjs +4 -4
  39. package/build/popover/arrow.cjs.map +2 -2
  40. package/build/popover/context.cjs +21 -9
  41. package/build/popover/context.cjs.map +2 -2
  42. package/build/popover/description.cjs +4 -4
  43. package/build/popover/description.cjs.map +2 -2
  44. package/build/popover/popup.cjs +8 -5
  45. package/build/popover/popup.cjs.map +2 -2
  46. package/build/popover/title.cjs +5 -2
  47. package/build/popover/title.cjs.map +2 -2
  48. package/build/tabs/context.cjs +9 -22
  49. package/build/tabs/context.cjs.map +2 -2
  50. package/build/tabs/list.cjs +4 -4
  51. package/build/tabs/list.cjs.map +2 -2
  52. package/build/tabs/panel.cjs +19 -6
  53. package/build/tabs/panel.cjs.map +3 -3
  54. package/build/tabs/tab.cjs +4 -4
  55. package/build/tabs/tab.cjs.map +2 -2
  56. package/build/tooltip/popup.cjs +4 -4
  57. package/build/tooltip/popup.cjs.map +2 -2
  58. package/build/utils/use-schedule-validation.cjs +59 -0
  59. package/build/utils/use-schedule-validation.cjs.map +7 -0
  60. package/build-module/alert-dialog/popup.mjs +4 -4
  61. package/build-module/alert-dialog/popup.mjs.map +2 -2
  62. package/build-module/collapsible-card/header.mjs +10 -0
  63. package/build-module/collapsible-card/header.mjs.map +3 -3
  64. package/build-module/dialog/context.mjs +21 -9
  65. package/build-module/dialog/context.mjs.map +2 -2
  66. package/build-module/dialog/footer.mjs +4 -4
  67. package/build-module/dialog/footer.mjs.map +2 -2
  68. package/build-module/dialog/header.mjs +4 -4
  69. package/build-module/dialog/header.mjs.map +2 -2
  70. package/build-module/dialog/popup.mjs +4 -4
  71. package/build-module/dialog/popup.mjs.map +2 -2
  72. package/build-module/dialog/title.mjs +10 -7
  73. package/build-module/dialog/title.mjs.map +2 -2
  74. package/build-module/form/primitives/select/item.mjs +3 -3
  75. package/build-module/form/primitives/select/item.mjs.map +2 -2
  76. package/build-module/form/primitives/select/popup.mjs +3 -3
  77. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  78. package/build-module/link/link.mjs +8 -18
  79. package/build-module/link/link.mjs.map +2 -2
  80. package/build-module/notice/action-button.mjs +3 -3
  81. package/build-module/notice/action-button.mjs.map +2 -2
  82. package/build-module/notice/action-link.mjs +8 -7
  83. package/build-module/notice/action-link.mjs.map +2 -2
  84. package/build-module/notice/actions.mjs +3 -3
  85. package/build-module/notice/actions.mjs.map +2 -2
  86. package/build-module/notice/close-icon.mjs +3 -3
  87. package/build-module/notice/close-icon.mjs.map +2 -2
  88. package/build-module/notice/description.mjs +3 -3
  89. package/build-module/notice/description.mjs.map +2 -2
  90. package/build-module/notice/root.mjs +3 -3
  91. package/build-module/notice/root.mjs.map +2 -2
  92. package/build-module/notice/title.mjs +3 -3
  93. package/build-module/notice/title.mjs.map +2 -2
  94. package/build-module/popover/arrow.mjs +4 -4
  95. package/build-module/popover/arrow.mjs.map +2 -2
  96. package/build-module/popover/context.mjs +21 -9
  97. package/build-module/popover/context.mjs.map +2 -2
  98. package/build-module/popover/description.mjs +4 -4
  99. package/build-module/popover/description.mjs.map +2 -2
  100. package/build-module/popover/popup.mjs +8 -5
  101. package/build-module/popover/popup.mjs.map +2 -2
  102. package/build-module/popover/title.mjs +6 -3
  103. package/build-module/popover/title.mjs.map +2 -2
  104. package/build-module/tabs/context.mjs +11 -24
  105. package/build-module/tabs/context.mjs.map +2 -2
  106. package/build-module/tabs/list.mjs +4 -4
  107. package/build-module/tabs/list.mjs.map +2 -2
  108. package/build-module/tabs/panel.mjs +19 -6
  109. package/build-module/tabs/panel.mjs.map +3 -3
  110. package/build-module/tabs/tab.mjs +4 -4
  111. package/build-module/tabs/tab.mjs.map +2 -2
  112. package/build-module/tooltip/popup.mjs +4 -4
  113. package/build-module/tooltip/popup.mjs.map +2 -2
  114. package/build-module/utils/use-schedule-validation.mjs +34 -0
  115. package/build-module/utils/use-schedule-validation.mjs.map +7 -0
  116. package/build-types/alert-dialog/stories/index.story.d.ts +1 -1
  117. package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -1
  118. package/build-types/badge/stories/index.story.d.ts.map +1 -1
  119. package/build-types/collapsible-card/header.d.ts.map +1 -1
  120. package/build-types/dialog/context.d.ts +1 -1
  121. package/build-types/dialog/context.d.ts.map +1 -1
  122. package/build-types/dialog/title.d.ts.map +1 -1
  123. package/build-types/empty-state/stories/index.story.d.ts +1 -1
  124. package/build-types/empty-state/stories/index.story.d.ts.map +1 -1
  125. package/build-types/form/input-control/stories/index.story.d.ts +1 -1
  126. package/build-types/form/input-control/stories/index.story.d.ts.map +1 -1
  127. package/build-types/form/primitives/field/stories/index.story.d.ts +1 -1
  128. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  129. package/build-types/form/primitives/fieldset/stories/index.story.d.ts +1 -1
  130. package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
  131. package/build-types/form/primitives/input/stories/index.story.d.ts +1 -1
  132. package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
  133. package/build-types/form/primitives/input-layout/stories/index.story.d.ts +1 -1
  134. package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
  135. package/build-types/form/primitives/select/stories/index.story.d.ts +1 -1
  136. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  137. package/build-types/link/link.d.ts.map +1 -1
  138. package/build-types/link/types.d.ts +1 -2
  139. package/build-types/link/types.d.ts.map +1 -1
  140. package/build-types/notice/action-link.d.ts.map +1 -1
  141. package/build-types/popover/context.d.ts +1 -1
  142. package/build-types/popover/context.d.ts.map +1 -1
  143. package/build-types/popover/popup.d.ts.map +1 -1
  144. package/build-types/popover/stories/index.story.d.ts +1 -1
  145. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  146. package/build-types/popover/title.d.ts.map +1 -1
  147. package/build-types/stack/stories/index.story.d.ts.map +1 -1
  148. package/build-types/tabs/context.d.ts.map +1 -1
  149. package/build-types/tabs/panel.d.ts.map +1 -1
  150. package/build-types/tabs/stories/index.story.d.ts +1 -1
  151. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  152. package/build-types/text/stories/index.story.d.ts.map +1 -1
  153. package/build-types/tooltip/stories/index.story.d.ts +1 -1
  154. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  155. package/build-types/tooltip/stories/usage-guidelines.story.d.ts.map +1 -1
  156. package/build-types/utils/use-schedule-validation.d.ts +13 -0
  157. package/build-types/utils/use-schedule-validation.d.ts.map +1 -0
  158. package/package.json +11 -11
  159. package/src/alert-dialog/stories/index.story.tsx +2 -2
  160. package/src/badge/stories/choosing-intent.story.tsx +1 -1
  161. package/src/badge/stories/index.story.tsx +1 -0
  162. package/src/collapsible-card/header.tsx +2 -0
  163. package/src/dialog/context.tsx +28 -15
  164. package/src/dialog/style.module.css +12 -0
  165. package/src/dialog/test/index.test.tsx +222 -142
  166. package/src/dialog/title.tsx +6 -4
  167. package/src/empty-state/stories/index.story.tsx +2 -1
  168. package/src/form/input-control/stories/index.story.tsx +4 -1
  169. package/src/form/primitives/field/stories/index.story.tsx +1 -1
  170. package/src/form/primitives/fieldset/stories/index.story.tsx +1 -1
  171. package/src/form/primitives/input/stories/index.story.tsx +2 -1
  172. package/src/form/primitives/input-layout/stories/index.story.tsx +2 -1
  173. package/src/form/primitives/select/stories/index.story.tsx +1 -1
  174. package/src/link/link.tsx +12 -26
  175. package/src/link/style.module.css +4 -16
  176. package/src/link/test/index.test.tsx +31 -27
  177. package/src/link/types.ts +1 -2
  178. package/src/notice/action-link.tsx +7 -4
  179. package/src/notice/style.module.css +5 -5
  180. package/src/popover/context.tsx +28 -12
  181. package/src/popover/popup.tsx +4 -1
  182. package/src/popover/stories/index.story.tsx +2 -1
  183. package/src/popover/style.module.css +23 -1
  184. package/src/popover/test/index.test.tsx +146 -70
  185. package/src/popover/title.tsx +6 -3
  186. package/src/stack/stories/index.story.tsx +1 -0
  187. package/src/tabs/context.tsx +14 -34
  188. package/src/tabs/panel.tsx +7 -2
  189. package/src/tabs/stories/index.story.tsx +2 -1
  190. package/src/tabs/style.module.css +0 -17
  191. package/src/tabs/test/index.test.tsx +7 -3
  192. package/src/text/stories/index.story.tsx +1 -0
  193. package/src/tooltip/stories/index.story.tsx +2 -1
  194. package/src/tooltip/stories/usage-guidelines.story.tsx +5 -1
  195. package/src/tooltip/style.module.css +12 -0
  196. package/src/utils/css/item-popup.module.css +12 -0
  197. package/src/utils/use-schedule-validation.ts +45 -0
  198. package/build/types/css-modules.d.cjs +0 -2
  199. package/build/types/css-modules.d.cjs.map +0 -7
  200. package/build/types/react.d.cjs +0 -5
  201. package/build/types/react.d.cjs.map +0 -7
  202. package/build-module/types/css-modules.d.mjs +0 -1
  203. package/build-module/types/css-modules.d.mjs.map +0 -7
  204. package/build-module/types/react.d.mjs +0 -3
  205. package/build-module/types/react.d.mjs.map +0 -7
  206. package/src/types/css-modules.d.ts +0 -4
  207. package/src/types/react.d.ts +0 -7
@@ -1,6 +1,6 @@
1
1
  import { Dialog as _Dialog } from '@base-ui/react/dialog';
2
2
  import { useMergeRefs } from '@wordpress/compose';
3
- import { forwardRef, useLayoutEffect, useRef } from '@wordpress/element';
3
+ import { forwardRef, useEffect, useRef } from '@wordpress/element';
4
4
  import { Text } from '../text';
5
5
  import { useDialogValidationContext } from './context';
6
6
  import styles from './style.module.css';
@@ -30,9 +30,11 @@ const Title = forwardRef< HTMLHeadingElement, TitleProps >(
30
30
  const internalRef = useRef< HTMLHeadingElement >( null );
31
31
  const mergedRef = useMergeRefs( [ internalRef, forwardedRef ] );
32
32
 
33
- // Register this title with the parent Popup for validation (dev only)
34
- useLayoutEffect( () => {
35
- validationContext?.registerTitle( internalRef.current );
33
+ useEffect( () => {
34
+ if ( validationContext ) {
35
+ return validationContext.registerTitle( internalRef.current );
36
+ }
37
+ return undefined;
36
38
  }, [ validationContext ] );
37
39
 
38
40
  return (
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { search } from '@wordpress/icons';
3
- import { Button, EmptyState } from '../..';
3
+ import { Button } from '../../button';
4
+ import * as EmptyState from '../';
4
5
 
5
6
  const meta: Meta< typeof EmptyState.Root > = {
6
7
  title: 'Design System/Components/EmptyState',
@@ -1,7 +1,10 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { useState } from '@wordpress/element';
3
3
  import { plus, reset, seen, unseen } from '@wordpress/icons';
4
- import { IconButton, InputControl, InputLayout, Stack } from '../../..';
4
+ import { InputControl } from '../';
5
+ import { IconButton } from '../../../icon-button';
6
+ import { InputLayout } from '../../primitives/input-layout';
7
+ import { Stack } from '../../../stack';
5
8
  import {
6
9
  WithPrefix,
7
10
  WithSuffixControl,
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { useId } from '@wordpress/element';
3
- import { Field } from '../../../..';
3
+ import * as Field from '../';
4
4
  import { DETAILS_EXAMPLE } from '../../../stories/shared';
5
5
 
6
6
  const meta: Meta< typeof Field.Root > = {
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Fieldset } from '../../../..';
2
+ import * as Fieldset from '../';
3
3
  import { DETAILS_EXAMPLE } from '../../../stories/shared';
4
4
 
5
5
  const meta: Meta< typeof Fieldset.Root > = {
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Input, InputLayout } from '../../../..';
2
+ import { Input } from '../';
3
+ import { InputLayout } from '../../input-layout';
3
4
  import { WithSuffixControl } from '../../input-layout/stories/index.story';
4
5
 
5
6
  const meta: Meta< typeof Input > = {
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { copy } from '@wordpress/icons';
3
- import { IconButton, InputLayout } from '../../../..';
3
+ import { InputLayout } from '../';
4
+ import { IconButton } from '../../../../icon-button';
4
5
 
5
6
  const meta: Meta< typeof InputLayout > = {
6
7
  title: 'Design System/Components/Form/Primitives/InputLayout',
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Select } from '../../../..';
2
+ import * as Select from '../';
3
3
 
4
4
  const meta: Meta< typeof Select.Root > = {
5
5
  title: 'Design System/Components/Form/Primitives/Select',
package/src/link/link.tsx CHANGED
@@ -20,20 +20,10 @@ export const Link = forwardRef< HTMLAnchorElement, LinkProps >( function Link(
20
20
  openInNewTab = false,
21
21
  render,
22
22
  className,
23
- onClick,
24
23
  ...props
25
24
  },
26
25
  ref
27
26
  ) {
28
- const isInternalAnchor = !! props.href?.startsWith( '#' );
29
-
30
- const handleClick = ( event: React.MouseEvent< HTMLAnchorElement > ) => {
31
- if ( openInNewTab && isInternalAnchor ) {
32
- event.preventDefault();
33
- }
34
- onClick?.( event );
35
- };
36
-
37
27
  const element = useRender( {
38
28
  render,
39
29
  defaultTagName: 'a',
@@ -46,27 +36,23 @@ export const Link = forwardRef< HTMLAnchorElement, LinkProps >( function Link(
46
36
  variant !== 'unstyled' && styles.link,
47
37
  variant !== 'unstyled' && styles[ `is-${ tone }` ],
48
38
  variant === 'unstyled' && styles[ 'is-unstyled' ],
49
- openInNewTab && styles[ 'has-link-icon' ],
50
39
  className
51
40
  ),
52
- onClick: handleClick,
53
41
  target: openInNewTab ? '_blank' : undefined,
54
- children: openInNewTab ? (
42
+ children: (
55
43
  <>
56
- <span className={ styles[ 'link-contents' ] }>
57
- { children }
58
- </span>
59
- <span
60
- className={ styles[ 'link-icon' ] }
61
- role="img"
62
- aria-label={
63
- /* translators: accessibility text appended to link text */
64
- __( '(opens in a new tab)' )
65
- }
66
- />
44
+ { children }
45
+ { openInNewTab && (
46
+ <span
47
+ className={ styles[ 'link-icon' ] }
48
+ role="img"
49
+ aria-label={
50
+ /* translators: accessibility text appended to link text */
51
+ __( '(opens in a new tab)' )
52
+ }
53
+ />
54
+ ) }
67
55
  </>
68
- ) : (
69
- children
70
56
  ),
71
57
  } ),
72
58
  } );
@@ -46,26 +46,14 @@
46
46
  }
47
47
 
48
48
  /* Link icon pattern — arrow rendered via ::after pseudo-element to avoid
49
- * Twemoji replacement and text-selection issues. Root <a> gets
50
- * text-decoration:none; the link-contents span re-applies it.
51
- * text-underline-offset is inherited and propagates from .link
52
- * automatically. text-decoration-thickness and text-decoration-color
53
- * are NOT inherited, so they are explicitly redeclared on
54
- * .link-contents (the latter via `inherit` to carry the neutral
55
- * tone's custom color token through). */
56
- .has-link-icon {
57
- text-decoration: none;
58
- }
59
-
60
- .link-contents {
61
- text-decoration: underline;
62
- text-decoration-color: inherit;
63
- text-decoration-thickness: 0.5px;
64
- }
49
+ * Twemoji replacement and text-selection issues. The icon is an
50
+ * inline-block so the link underline stays scoped to the text. */
65
51
 
66
52
  .link-icon {
53
+ display: inline-block;
67
54
  margin-inline-start: var(--wpds-dimension-padding-xs);
68
55
  font-weight: var(--wpds-typography-font-weight-regular);
56
+ text-decoration: none;
69
57
  }
70
58
 
71
59
  .link-icon::after {
@@ -16,6 +16,24 @@ describe( 'Link', () => {
16
16
  expect( ref.current ).toBeInstanceOf( HTMLAnchorElement );
17
17
  } );
18
18
 
19
+ it( 'calls onClick when clicked (often used for analytics tracking)', async () => {
20
+ const user = userEvent.setup();
21
+ const onClick = jest.fn(
22
+ ( event: React.MouseEvent< HTMLAnchorElement > ) =>
23
+ event.preventDefault()
24
+ );
25
+
26
+ render(
27
+ <Link href="/page" onClick={ onClick }>
28
+ Go to page
29
+ </Link>
30
+ );
31
+
32
+ await user.click( screen.getByRole( 'link', { name: 'Go to page' } ) );
33
+
34
+ expect( onClick ).toHaveBeenCalledTimes( 1 );
35
+ } );
36
+
19
37
  describe( 'openInNewTab', () => {
20
38
  it( 'sets target="_blank" when true', () => {
21
39
  render(
@@ -47,47 +65,33 @@ describe( 'Link', () => {
47
65
 
48
66
  expect(
49
67
  screen.getByLabelText( '(opens in a new tab)' )
50
- ).toBeInTheDocument();
68
+ ).toBeVisible();
51
69
  } );
52
70
 
53
- it( 'prevents default for internal anchor links', async () => {
54
- const user = userEvent.setup();
55
- const onClick = jest.fn();
56
-
71
+ it( 'keeps the link text on the anchor element', () => {
57
72
  render(
58
- <Link href="#section" openInNewTab onClick={ onClick }>
59
- Jump
73
+ <Link href="https://example.com" openInNewTab>
74
+ External
60
75
  </Link>
61
76
  );
62
77
 
63
- await user.click( screen.getByRole( 'link' ) );
64
-
65
- expect( onClick ).toHaveBeenCalledTimes( 1 );
66
- expect( onClick.mock.calls[ 0 ][ 0 ].defaultPrevented ).toBe(
67
- true
78
+ expect( screen.getByText( 'External' ) ).toBe(
79
+ screen.getByRole( 'link' )
68
80
  );
69
81
  } );
70
82
 
71
- it( 'does not prevent default for external links', async () => {
72
- const user = userEvent.setup();
73
- const onClick = jest.fn();
74
-
83
+ it( 'includes the new tab notice in the link name', () => {
75
84
  render(
76
- <Link
77
- href="https://example.com"
78
- openInNewTab
79
- onClick={ onClick }
80
- >
85
+ <Link href="https://example.com" openInNewTab>
81
86
  External
82
87
  </Link>
83
88
  );
84
89
 
85
- await user.click( screen.getByRole( 'link' ) );
86
-
87
- expect( onClick ).toHaveBeenCalledTimes( 1 );
88
- expect( onClick.mock.calls[ 0 ][ 0 ].defaultPrevented ).toBe(
89
- false
90
- );
90
+ expect(
91
+ screen.getByRole( 'link', {
92
+ name: 'External (opens in a new tab)',
93
+ } )
94
+ ).toBeVisible();
91
95
  } );
92
96
  } );
93
97
  } );
package/src/link/types.ts CHANGED
@@ -22,8 +22,7 @@ export interface LinkProps extends Omit< ComponentProps< 'a' >, 'target' > {
22
22
 
23
23
  /**
24
24
  * Whether to open the link in a new browser tab.
25
- * When true, sets `target="_blank"`, appends a visual arrow indicator,
26
- * and prevents navigation for internal anchors (`#`-prefixed hrefs).
25
+ * When true, sets `target="_blank"` and appends a visual arrow indicator.
27
26
  *
28
27
  * @default false
29
28
  */
@@ -1,6 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import { forwardRef } from '@wordpress/element';
3
3
  import { Link } from '../link';
4
+ import { Text } from '../text';
4
5
  import type { ActionLinkProps } from './types';
5
6
  import styles from './style.module.css';
6
7
 
@@ -8,14 +9,16 @@ import styles from './style.module.css';
8
9
  * An action link for use within Notice.Actions.
9
10
  */
10
11
  export const ActionLink = forwardRef< HTMLAnchorElement, ActionLinkProps >(
11
- function NoticeActionLink( { className, ...props }, ref ) {
12
+ function NoticeActionLink( { className, render, ...props }, ref ) {
12
13
  return (
13
- <Link
14
+ <Text
14
15
  ref={ ref }
15
16
  className={ clsx( styles[ 'action-link' ], className ) }
16
17
  { ...props }
17
- tone="neutral"
18
- variant="default"
18
+ variant="body-md"
19
+ render={
20
+ <Link tone="neutral" variant="default" render={ render } />
21
+ }
19
22
  />
20
23
  );
21
24
  }
@@ -64,11 +64,6 @@
64
64
 
65
65
  .action-link {
66
66
  flex-shrink: 0;
67
- font-family: var(--wpds-typography-font-family-body);
68
- font-size: var(--wpds-typography-font-size-md);
69
- font-weight: var(--wpds-typography-font-weight-regular);
70
- line-height: var(--wpds-typography-line-height-sm);
71
- margin-block: auto;
72
67
 
73
68
  /* Add more horizontal space when following another action link/button */
74
69
  &:not(:first-child) {
@@ -125,6 +120,11 @@
125
120
  }
126
121
 
127
122
  @layer wp-ui-compositions {
123
+ /* Override `Text` margin */
124
+ .action-link {
125
+ margin-block: auto;
126
+ }
127
+
128
128
  /* Add partial transparency to CloseIcon and outline/minimal ActionButton
129
129
  * for a better look over tinted backgrounds */
130
130
  .close-icon,
@@ -6,6 +6,7 @@ import {
6
6
  useMemo,
7
7
  useRef,
8
8
  } from '@wordpress/element';
9
+ import { useScheduleValidation } from '../utils/use-schedule-validation';
9
10
 
10
11
  /**
11
12
  * Whether validation is enabled. This is a build-time constant that allows
@@ -14,7 +15,7 @@ import {
14
15
  const VALIDATION_ENABLED = process.env.NODE_ENV !== 'production';
15
16
 
16
17
  type PopoverValidationContextType = {
17
- registerTitle: ( element: HTMLElement | null ) => void;
18
+ registerTitle: ( element: HTMLElement | null ) => () => void;
18
19
  };
19
20
 
20
21
  const PopoverValidationContext = VALIDATION_ENABLED
@@ -47,16 +48,7 @@ function PopoverValidationProviderDev( {
47
48
  } ) {
48
49
  const titleElementRef = useRef< HTMLElement | null >( null );
49
50
 
50
- const registerTitle = useCallback( ( element: HTMLElement | null ) => {
51
- titleElementRef.current = element;
52
- }, [] );
53
-
54
- const contextValue = useMemo(
55
- () => ( { registerTitle } ),
56
- [ registerTitle ]
57
- );
58
-
59
- useEffect( () => {
51
+ const scheduleValidation = useScheduleValidation( () => {
60
52
  const titleElement = titleElementRef.current;
61
53
 
62
54
  if ( ! titleElement ) {
@@ -74,7 +66,31 @@ function PopoverValidationProviderDev( {
74
66
  'Provide meaningful text content for the popover title.'
75
67
  );
76
68
  }
77
- }, [] );
69
+ } );
70
+
71
+ const registerTitle = useCallback(
72
+ ( element: HTMLElement | null ) => {
73
+ titleElementRef.current = element;
74
+ scheduleValidation();
75
+
76
+ return () => {
77
+ titleElementRef.current = null;
78
+ scheduleValidation();
79
+ };
80
+ },
81
+ [ scheduleValidation ]
82
+ );
83
+
84
+ // Schedule an initial validation on mount to catch missing titles
85
+ // (when no Title component is rendered, registerTitle is never called).
86
+ useEffect( () => {
87
+ scheduleValidation();
88
+ }, [ scheduleValidation ] );
89
+
90
+ const contextValue = useMemo(
91
+ () => ( { registerTitle } ),
92
+ [ registerTitle ]
93
+ );
78
94
 
79
95
  return (
80
96
  <PopoverValidationContext.Provider value={ contextValue }>
@@ -84,7 +84,10 @@ const Popup = forwardRef< HTMLDivElement, PopupProps >( function PopoverPopup(
84
84
  ref={ mergedPopupRef }
85
85
  initialFocus={ resolvedInitialFocus }
86
86
  finalFocus={ finalFocus }
87
- className={ clsx( variant !== 'unstyled' && styles.popup ) }
87
+ className={ clsx(
88
+ styles.popup,
89
+ variant !== 'unstyled' && styles.default
90
+ ) }
88
91
  { ...props }
89
92
  >
90
93
  <PopoverValidationProvider>
@@ -2,7 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { useId, useRef, useState } from '@wordpress/element';
3
3
  import { SlotFillProvider, Slot } from '@wordpress/components';
4
4
  import { close, info } from '@wordpress/icons';
5
- import { Popover, VisuallyHidden } from '../..';
5
+ import * as Popover from '../';
6
+ import { VisuallyHidden } from '../../visually-hidden';
6
7
  import { Icon } from '../../icon';
7
8
  import { IconButton } from '../../icon-button';
8
9
  import { GenericIframe, useMeasure } from './utils';
@@ -1,11 +1,33 @@
1
1
  @layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
2
2
 
3
+ /*
4
+ * Temporary workaround for a Base UI tabbability regression with
5
+ * checkVisibility() and display: contents.
6
+ * See: https://github.com/mui/base-ui/issues/4622
7
+ *
8
+ * This must stay outside the CSS layers to override ThemeProvider's
9
+ * unlayered display: contents.
10
+ */
11
+ [data-wpds-theme-provider-id]:has(> .popup) {
12
+ display: block;
13
+ }
14
+
3
15
  @layer wp-ui-components {
4
16
  .positioner {
5
17
  z-index: var(--wp-ui-popover-z-index, initial);
6
18
  }
7
19
 
8
- .popup {
20
+ /*
21
+ * Structural marker; no visual styles. Always applied to the popup so
22
+ * the ThemeProvider display workaround above matches regardless of
23
+ * `variant`. Visuals live in `.default`.
24
+ */
25
+ /* stylelint-disable-next-line block-no-empty -- structural marker for the
26
+ * ThemeProvider display workaround above; kept class-based (not a data
27
+ * attribute) so it is defined alongside the other component classes. */
28
+ .popup {}
29
+
30
+ .default {
9
31
  background-color: var(--wpds-color-bg-surface-neutral-strong);
10
32
  padding: var(--wpds-dimension-padding-lg);
11
33
  border-radius: var(--wpds-border-radius-md);