@wordpress/ui 0.13.1-next.v.202605131006.0 → 0.14.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 (282) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +34 -0
  3. package/README.md +15 -0
  4. package/build/alert-dialog/portal.cjs.map +2 -2
  5. package/build/alert-dialog/types.cjs.map +1 -1
  6. package/build/button/button.cjs +1 -1
  7. package/build/button/button.cjs.map +2 -2
  8. package/build/card/content.cjs +1 -1
  9. package/build/card/content.cjs.map +2 -2
  10. package/build/card/full-bleed.cjs +1 -1
  11. package/build/card/full-bleed.cjs.map +2 -2
  12. package/build/card/header.cjs +1 -1
  13. package/build/card/header.cjs.map +2 -2
  14. package/build/card/root.cjs +1 -1
  15. package/build/card/root.cjs.map +2 -2
  16. package/build/collapsible-card/header.cjs.map +2 -2
  17. package/build/dialog/portal.cjs.map +2 -2
  18. package/build/dialog/types.cjs.map +1 -1
  19. package/build/drawer/portal.cjs.map +2 -2
  20. package/build/drawer/types.cjs.map +1 -1
  21. package/build/form/primitives/autocomplete/clear.cjs +4 -1
  22. package/build/form/primitives/autocomplete/clear.cjs.map +2 -2
  23. package/build/form/primitives/autocomplete/empty.cjs +1 -1
  24. package/build/form/primitives/autocomplete/empty.cjs.map +2 -2
  25. package/build/form/primitives/autocomplete/index.cjs +4 -1
  26. package/build/form/primitives/autocomplete/index.cjs.map +2 -2
  27. package/build/form/primitives/autocomplete/item.cjs +1 -1
  28. package/build/form/primitives/autocomplete/item.cjs.map +2 -2
  29. package/build/form/primitives/autocomplete/list-body.cjs +1 -1
  30. package/build/form/primitives/autocomplete/list-body.cjs.map +2 -2
  31. package/build/form/primitives/autocomplete/list.cjs +1 -1
  32. package/build/form/primitives/autocomplete/list.cjs.map +2 -2
  33. package/build/form/primitives/autocomplete/popup.cjs +14 -31
  34. package/build/form/primitives/autocomplete/popup.cjs.map +3 -3
  35. package/build/form/primitives/autocomplete/portal.cjs +10 -2
  36. package/build/form/primitives/autocomplete/portal.cjs.map +2 -2
  37. package/build/form/primitives/autocomplete/positioner.cjs +158 -0
  38. package/build/form/primitives/autocomplete/positioner.cjs.map +7 -0
  39. package/build/form/primitives/autocomplete/types.cjs.map +1 -1
  40. package/build/form/primitives/constants.cjs.map +2 -2
  41. package/build/form/primitives/select/index.cjs +4 -1
  42. package/build/form/primitives/select/index.cjs.map +2 -2
  43. package/build/form/primitives/select/item.cjs +1 -1
  44. package/build/form/primitives/select/item.cjs.map +2 -2
  45. package/build/form/primitives/select/popup.cjs +18 -36
  46. package/build/form/primitives/select/popup.cjs.map +3 -3
  47. package/build/form/primitives/select/portal.cjs +11 -5
  48. package/build/form/primitives/select/portal.cjs.map +2 -2
  49. package/build/form/primitives/select/positioner.cjs +159 -0
  50. package/build/form/primitives/select/positioner.cjs.map +7 -0
  51. package/build/form/primitives/select/types.cjs.map +1 -1
  52. package/build/icon-button/icon-button.cjs +1 -1
  53. package/build/icon-button/icon-button.cjs.map +2 -2
  54. package/build/index.cjs +7 -1
  55. package/build/index.cjs.map +2 -2
  56. package/build/popover/index.cjs +3 -0
  57. package/build/popover/index.cjs.map +2 -2
  58. package/build/popover/popup.cjs +23 -51
  59. package/build/popover/popup.cjs.map +3 -3
  60. package/build/popover/portal.cjs.map +2 -2
  61. package/build/popover/positioner.cjs +168 -0
  62. package/build/popover/positioner.cjs.map +7 -0
  63. package/build/popover/root.cjs.map +2 -2
  64. package/build/popover/types.cjs.map +1 -1
  65. package/build/tooltip/portal.cjs +10 -2
  66. package/build/tooltip/portal.cjs.map +2 -2
  67. package/build/tooltip/positioner.cjs.map +2 -2
  68. package/build/tooltip/types.cjs.map +1 -1
  69. package/build/utils/create-overlay-title-validation.cjs.map +2 -2
  70. package/build/utils/render-slot-with-children.cjs +4 -1
  71. package/build/utils/render-slot-with-children.cjs.map +2 -2
  72. package/build/utils/use-enable-wp-compat-overlay-slot.cjs +39 -0
  73. package/build/utils/use-enable-wp-compat-overlay-slot.cjs.map +7 -0
  74. package/build/utils/wp-compat-overlay-slot.cjs +177 -0
  75. package/build/utils/wp-compat-overlay-slot.cjs.map +7 -0
  76. package/build-module/alert-dialog/portal.mjs.map +2 -2
  77. package/build-module/button/button.mjs +1 -1
  78. package/build-module/button/button.mjs.map +2 -2
  79. package/build-module/card/content.mjs +1 -1
  80. package/build-module/card/content.mjs.map +2 -2
  81. package/build-module/card/full-bleed.mjs +1 -1
  82. package/build-module/card/full-bleed.mjs.map +2 -2
  83. package/build-module/card/header.mjs +1 -1
  84. package/build-module/card/header.mjs.map +2 -2
  85. package/build-module/card/root.mjs +1 -1
  86. package/build-module/card/root.mjs.map +2 -2
  87. package/build-module/collapsible-card/header.mjs.map +2 -2
  88. package/build-module/dialog/portal.mjs.map +2 -2
  89. package/build-module/drawer/portal.mjs.map +2 -2
  90. package/build-module/form/primitives/autocomplete/clear.mjs +4 -1
  91. package/build-module/form/primitives/autocomplete/clear.mjs.map +2 -2
  92. package/build-module/form/primitives/autocomplete/empty.mjs +1 -1
  93. package/build-module/form/primitives/autocomplete/empty.mjs.map +2 -2
  94. package/build-module/form/primitives/autocomplete/index.mjs +3 -1
  95. package/build-module/form/primitives/autocomplete/index.mjs.map +2 -2
  96. package/build-module/form/primitives/autocomplete/item.mjs +1 -1
  97. package/build-module/form/primitives/autocomplete/item.mjs.map +2 -2
  98. package/build-module/form/primitives/autocomplete/list-body.mjs +1 -1
  99. package/build-module/form/primitives/autocomplete/list-body.mjs.map +2 -2
  100. package/build-module/form/primitives/autocomplete/list.mjs +1 -1
  101. package/build-module/form/primitives/autocomplete/list.mjs.map +2 -2
  102. package/build-module/form/primitives/autocomplete/popup.mjs +14 -31
  103. package/build-module/form/primitives/autocomplete/popup.mjs.map +3 -3
  104. package/build-module/form/primitives/autocomplete/portal.mjs +10 -2
  105. package/build-module/form/primitives/autocomplete/portal.mjs.map +2 -2
  106. package/build-module/form/primitives/autocomplete/positioner.mjs +123 -0
  107. package/build-module/form/primitives/autocomplete/positioner.mjs.map +7 -0
  108. package/build-module/form/primitives/constants.mjs.map +2 -2
  109. package/build-module/form/primitives/select/index.mjs +3 -1
  110. package/build-module/form/primitives/select/index.mjs.map +2 -2
  111. package/build-module/form/primitives/select/item.mjs +1 -1
  112. package/build-module/form/primitives/select/item.mjs.map +2 -2
  113. package/build-module/form/primitives/select/popup.mjs +18 -36
  114. package/build-module/form/primitives/select/popup.mjs.map +3 -3
  115. package/build-module/form/primitives/select/portal.mjs +11 -5
  116. package/build-module/form/primitives/select/portal.mjs.map +2 -2
  117. package/build-module/form/primitives/select/positioner.mjs +124 -0
  118. package/build-module/form/primitives/select/positioner.mjs.map +7 -0
  119. package/build-module/icon-button/icon-button.mjs +1 -1
  120. package/build-module/icon-button/icon-button.mjs.map +2 -2
  121. package/build-module/index.mjs +5 -1
  122. package/build-module/index.mjs.map +2 -2
  123. package/build-module/popover/index.mjs +2 -0
  124. package/build-module/popover/index.mjs.map +2 -2
  125. package/build-module/popover/popup.mjs +23 -51
  126. package/build-module/popover/popup.mjs.map +3 -3
  127. package/build-module/popover/portal.mjs.map +2 -2
  128. package/build-module/popover/positioner.mjs +133 -0
  129. package/build-module/popover/positioner.mjs.map +7 -0
  130. package/build-module/popover/root.mjs.map +2 -2
  131. package/build-module/tooltip/portal.mjs +10 -2
  132. package/build-module/tooltip/portal.mjs.map +2 -2
  133. package/build-module/tooltip/positioner.mjs.map +2 -2
  134. package/build-module/utils/create-overlay-title-validation.mjs.map +2 -2
  135. package/build-module/utils/render-slot-with-children.mjs +4 -1
  136. package/build-module/utils/render-slot-with-children.mjs.map +2 -2
  137. package/build-module/utils/use-enable-wp-compat-overlay-slot.mjs +14 -0
  138. package/build-module/utils/use-enable-wp-compat-overlay-slot.mjs.map +7 -0
  139. package/build-module/utils/wp-compat-overlay-slot.mjs +148 -0
  140. package/build-module/utils/wp-compat-overlay-slot.mjs.map +7 -0
  141. package/build-types/alert-dialog/portal.d.ts +8 -5
  142. package/build-types/alert-dialog/portal.d.ts.map +1 -1
  143. package/build-types/alert-dialog/types.d.ts +2 -2
  144. package/build-types/alert-dialog/types.d.ts.map +1 -1
  145. package/build-types/badge/stories/e2e/index.story.d.ts +7 -0
  146. package/build-types/badge/stories/e2e/index.story.d.ts.map +1 -0
  147. package/build-types/button/stories/e2e/index.story.d.ts +8 -0
  148. package/build-types/button/stories/e2e/index.story.d.ts.map +1 -0
  149. package/build-types/card/full-bleed.d.ts +18 -0
  150. package/build-types/card/full-bleed.d.ts.map +1 -1
  151. package/build-types/card/stories/index.story.d.ts +23 -0
  152. package/build-types/card/stories/index.story.d.ts.map +1 -1
  153. package/build-types/collapsible-card/header.d.ts +2 -0
  154. package/build-types/collapsible-card/header.d.ts.map +1 -1
  155. package/build-types/collapsible-card/stories/index.story.d.ts +14 -0
  156. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -1
  157. package/build-types/dialog/portal.d.ts +8 -6
  158. package/build-types/dialog/portal.d.ts.map +1 -1
  159. package/build-types/dialog/types.d.ts +2 -2
  160. package/build-types/dialog/types.d.ts.map +1 -1
  161. package/build-types/drawer/portal.d.ts +8 -6
  162. package/build-types/drawer/portal.d.ts.map +1 -1
  163. package/build-types/drawer/types.d.ts +2 -2
  164. package/build-types/drawer/types.d.ts.map +1 -1
  165. package/build-types/form/primitives/autocomplete/clear.d.ts.map +1 -1
  166. package/build-types/form/primitives/autocomplete/index.d.ts +2 -1
  167. package/build-types/form/primitives/autocomplete/index.d.ts.map +1 -1
  168. package/build-types/form/primitives/autocomplete/popup.d.ts +1 -0
  169. package/build-types/form/primitives/autocomplete/popup.d.ts.map +1 -1
  170. package/build-types/form/primitives/autocomplete/portal.d.ts +9 -4
  171. package/build-types/form/primitives/autocomplete/portal.d.ts.map +1 -1
  172. package/build-types/form/primitives/autocomplete/positioner.d.ts +12 -0
  173. package/build-types/form/primitives/autocomplete/positioner.d.ts.map +1 -0
  174. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts.map +1 -1
  175. package/build-types/form/primitives/autocomplete/types.d.ts +11 -9
  176. package/build-types/form/primitives/autocomplete/types.d.ts.map +1 -1
  177. package/build-types/form/primitives/constants.d.ts +9 -4
  178. package/build-types/form/primitives/constants.d.ts.map +1 -1
  179. package/build-types/form/primitives/select/index.d.ts +2 -1
  180. package/build-types/form/primitives/select/index.d.ts.map +1 -1
  181. package/build-types/form/primitives/select/popup.d.ts +1 -0
  182. package/build-types/form/primitives/select/popup.d.ts.map +1 -1
  183. package/build-types/form/primitives/select/portal.d.ts +9 -4
  184. package/build-types/form/primitives/select/portal.d.ts.map +1 -1
  185. package/build-types/form/primitives/select/positioner.d.ts +12 -0
  186. package/build-types/form/primitives/select/positioner.d.ts.map +1 -0
  187. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  188. package/build-types/form/primitives/select/types.d.ts +11 -2
  189. package/build-types/form/primitives/select/types.d.ts.map +1 -1
  190. package/build-types/icon/stories/index.story.d.ts.map +1 -1
  191. package/build-types/index.d.ts +2 -0
  192. package/build-types/index.d.ts.map +1 -1
  193. package/build-types/popover/index.d.ts +2 -1
  194. package/build-types/popover/index.d.ts.map +1 -1
  195. package/build-types/popover/popup.d.ts +2 -1
  196. package/build-types/popover/popup.d.ts.map +1 -1
  197. package/build-types/popover/portal.d.ts +8 -5
  198. package/build-types/popover/portal.d.ts.map +1 -1
  199. package/build-types/popover/positioner.d.ts +12 -0
  200. package/build-types/popover/positioner.d.ts.map +1 -0
  201. package/build-types/popover/root.d.ts +5 -2
  202. package/build-types/popover/root.d.ts.map +1 -1
  203. package/build-types/popover/stories/index.story.d.ts +10 -21
  204. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  205. package/build-types/popover/types.d.ts +12 -3
  206. package/build-types/popover/types.d.ts.map +1 -1
  207. package/build-types/tooltip/portal.d.ts +9 -4
  208. package/build-types/tooltip/portal.d.ts.map +1 -1
  209. package/build-types/tooltip/positioner.d.ts +8 -5
  210. package/build-types/tooltip/positioner.d.ts.map +1 -1
  211. package/build-types/tooltip/types.d.ts +3 -3
  212. package/build-types/tooltip/types.d.ts.map +1 -1
  213. package/build-types/utils/render-slot-with-children.d.ts.map +1 -1
  214. package/build-types/utils/test/use-enable-wp-compat-overlay-slot.test.d.ts +2 -0
  215. package/build-types/utils/test/use-enable-wp-compat-overlay-slot.test.d.ts.map +1 -0
  216. package/build-types/utils/test/wp-compat-overlay-slot.test.d.ts +2 -0
  217. package/build-types/utils/test/wp-compat-overlay-slot.test.d.ts.map +1 -0
  218. package/build-types/utils/use-deprioritized-initial-focus.d.ts +1 -1
  219. package/build-types/utils/use-enable-wp-compat-overlay-slot.d.ts +17 -0
  220. package/build-types/utils/use-enable-wp-compat-overlay-slot.d.ts.map +1 -0
  221. package/build-types/utils/wp-compat-overlay-slot.d.ts +30 -0
  222. package/build-types/utils/wp-compat-overlay-slot.d.ts.map +1 -0
  223. package/package.json +14 -14
  224. package/src/alert-dialog/portal.tsx +1 -4
  225. package/src/alert-dialog/types.ts +2 -4
  226. package/src/badge/stories/e2e/index.story.tsx +20 -0
  227. package/src/button/stories/e2e/index.story.tsx +130 -0
  228. package/src/button/stories/index.story.tsx +1 -1
  229. package/src/button/style.module.css +17 -12
  230. package/src/card/full-bleed.tsx +18 -0
  231. package/src/card/stories/index.story.tsx +115 -1
  232. package/src/card/style.module.css +16 -0
  233. package/src/card/test/index.test.tsx +18 -1
  234. package/src/collapsible-card/header.tsx +2 -0
  235. package/src/collapsible-card/stories/index.story.tsx +66 -0
  236. package/src/dialog/portal.tsx +1 -5
  237. package/src/dialog/types.ts +2 -2
  238. package/src/drawer/portal.tsx +1 -5
  239. package/src/drawer/types.ts +2 -2
  240. package/src/form/primitives/autocomplete/clear.tsx +10 -4
  241. package/src/form/primitives/autocomplete/index.ts +2 -1
  242. package/src/form/primitives/autocomplete/popup.tsx +17 -21
  243. package/src/form/primitives/autocomplete/portal.tsx +11 -5
  244. package/src/form/primitives/autocomplete/positioner.tsx +29 -0
  245. package/src/form/primitives/autocomplete/stories/index.story.tsx +1 -0
  246. package/src/form/primitives/autocomplete/test/index.test.tsx +219 -0
  247. package/src/form/primitives/autocomplete/types.ts +15 -15
  248. package/src/form/primitives/constants.ts +7 -4
  249. package/src/form/primitives/select/index.ts +2 -1
  250. package/src/form/primitives/select/popup.tsx +30 -34
  251. package/src/form/primitives/select/portal.tsx +15 -8
  252. package/src/form/primitives/select/positioner.tsx +33 -0
  253. package/src/form/primitives/select/stories/index.story.tsx +1 -0
  254. package/src/form/primitives/select/test/index.test.tsx +134 -0
  255. package/src/form/primitives/select/types.ts +12 -2
  256. package/src/form/select-control/test/index.test.tsx +64 -0
  257. package/src/icon/stories/index.story.tsx +3 -2
  258. package/src/icon-button/icon-button.tsx +1 -1
  259. package/src/icon-button/test/index.test.tsx +10 -10
  260. package/src/index.ts +2 -0
  261. package/src/popover/index.ts +12 -1
  262. package/src/popover/popup.tsx +28 -45
  263. package/src/popover/portal.tsx +1 -4
  264. package/src/popover/positioner.tsx +42 -0
  265. package/src/popover/root.tsx +5 -2
  266. package/src/popover/stories/index.story.tsx +85 -138
  267. package/src/popover/test/index.test.tsx +36 -1
  268. package/src/popover/types.ts +13 -15
  269. package/src/tabs/stories/index.story.tsx +2 -2
  270. package/src/tooltip/portal.tsx +11 -5
  271. package/src/tooltip/positioner.tsx +1 -4
  272. package/src/tooltip/style.module.css +1 -1
  273. package/src/tooltip/test/index.test.tsx +110 -0
  274. package/src/tooltip/types.ts +3 -5
  275. package/src/utils/create-overlay-title-validation.tsx +1 -1
  276. package/src/utils/css/item-popup.module.css +7 -4
  277. package/src/utils/css/wp-compat-overlay-slot.module.css +35 -0
  278. package/src/utils/render-slot-with-children.ts +4 -1
  279. package/src/utils/test/use-enable-wp-compat-overlay-slot.test.tsx +74 -0
  280. package/src/utils/test/wp-compat-overlay-slot.test.ts +300 -0
  281. package/src/utils/use-enable-wp-compat-overlay-slot.ts +32 -0
  282. package/src/utils/wp-compat-overlay-slot.ts +129 -0
@@ -7,6 +7,24 @@ import type { FullBleedProps } from './types';
7
7
  * A container that breaks out of the card's padding to span edge-to-edge.
8
8
  * Useful for full-width images, dividers, or embedded content.
9
9
  *
10
+ * Additional edge-bumping behavior based on placement:
11
+ *
12
+ * - As the **first child** of `Card.Header`, it extends flush to the card's
13
+ * top edge — ideal for hero images.
14
+ * - As the **only child** of `Card.Content`, it extends flush to the card's
15
+ * top edge when `Content` is the first card section, and to the bottom edge
16
+ * when it is the last.
17
+ *
18
+ * Inter-sibling spacing inside `Card.Header` / `Card.Content` is consumer-
19
+ * managed. To add space between a hero `FullBleed` and the following
20
+ * siblings, compose the parent with `Stack` via the `render` prop:
21
+ * `<Card.Header render={ <Stack direction="column" gap="lg" /> }>`. This
22
+ * keeps `FullBleed` a direct child of `Card.Header` so the edge-bump still
23
+ * fires, while `Stack` provides the gap.
24
+ *
25
+ * Inside `CollapsibleCard`, place full-bleed media in `CollapsibleCard.Content`
26
+ * (not the header). The trigger/panel gap is preserved by design.
27
+ *
10
28
  * Must be used as a direct child of `Card.Content` or `Card.Header`.
11
29
  */
12
30
  export const FullBleed = forwardRef< HTMLDivElement, FullBleedProps >(
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Stack } from '@wordpress/ui';
2
3
  import * as Card from '../index';
3
4
 
4
5
  /**
@@ -71,6 +72,56 @@ export const Default: Story = {
71
72
  },
72
73
  };
73
74
 
75
+ /**
76
+ * `Card.FullBleed` as the sole child of `Card.Content` spans edge-to-edge
77
+ * with no padding around it.
78
+ */
79
+ export const FullBleedCoverOnly: Story = {
80
+ args: {
81
+ children: (
82
+ <Card.Content>
83
+ <Card.FullBleed>
84
+ <div
85
+ style={ {
86
+ height: 180,
87
+ background:
88
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
89
+ } }
90
+ />
91
+ </Card.FullBleed>
92
+ </Card.Content>
93
+ ),
94
+ },
95
+ };
96
+
97
+ /**
98
+ * When `Card.FullBleed` is the sole child of `Card.Content` and a
99
+ * `Card.Header` sits above it, the image bumps against the card's side and
100
+ * bottom edges while the header retains its normal padding.
101
+ */
102
+ export const FullBleedCoverWithHeader: Story = {
103
+ args: {
104
+ children: (
105
+ <>
106
+ <Card.Header>
107
+ <Card.Title>Card title</Card.Title>
108
+ </Card.Header>
109
+ <Card.Content>
110
+ <Card.FullBleed>
111
+ <div
112
+ style={ {
113
+ height: 180,
114
+ background:
115
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
116
+ } }
117
+ />
118
+ </Card.FullBleed>
119
+ </Card.Content>
120
+ </>
121
+ ),
122
+ },
123
+ };
124
+
74
125
  /**
75
126
  * `Card.FullBleed` breaks out of the card's padding to span
76
127
  * edge-to-edge. Useful for images, dividers, or embedded content.
@@ -82,7 +133,7 @@ export const WithFullBleed: Story = {
82
133
  <Card.Header>
83
134
  <Card.Title>Featured image</Card.Title>
84
135
  </Card.Header>
85
- <Card.Content>
136
+ <Card.Content render={ <Stack direction="column" gap="lg" /> }>
86
137
  <Card.FullBleed>
87
138
  <div
88
139
  style={ {
@@ -112,6 +163,69 @@ export const HeaderOnly: Story = {
112
163
  },
113
164
  };
114
165
 
166
+ /**
167
+ * When `Card.FullBleed` is the **first child** of `Card.Header`, it extends
168
+ * flush to the card's top and side edges — ideal for hero images. Content
169
+ * that follows inside the header is padded normally.
170
+ */
171
+ export const FullBleedHeroWithTitle: Story = {
172
+ args: {
173
+ children: (
174
+ <>
175
+ <Card.Header render={ <Stack direction="column" gap="lg" /> }>
176
+ <Card.FullBleed>
177
+ <div
178
+ style={ {
179
+ height: 180,
180
+ background:
181
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
182
+ } }
183
+ />
184
+ </Card.FullBleed>
185
+ <Card.Title>Hero image card</Card.Title>
186
+ </Card.Header>
187
+ <Card.Content>
188
+ <Text>
189
+ The image above bleeds to the card&apos;s top and side
190
+ edges.
191
+ </Text>
192
+ </Card.Content>
193
+ </>
194
+ ),
195
+ },
196
+ };
197
+
198
+ /**
199
+ * When `Card.FullBleed` is the **only child** of `Card.Header`, it fills the
200
+ * header entirely — top and sides flush to the card edges, no extra padding
201
+ * below.
202
+ */
203
+ export const FullBleedHeroOnly: Story = {
204
+ args: {
205
+ children: (
206
+ <>
207
+ <Card.Header>
208
+ <Card.FullBleed>
209
+ <div
210
+ style={ {
211
+ height: 180,
212
+ background:
213
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
214
+ } }
215
+ />
216
+ </Card.FullBleed>
217
+ </Card.Header>
218
+ <Card.Content>
219
+ <Text>
220
+ The image above bleeds to the card&apos;s top and side
221
+ edges.
222
+ </Text>
223
+ </Card.Content>
224
+ </>
225
+ ),
226
+ },
227
+ };
228
+
115
229
  /**
116
230
  * Use the `render` prop to change the underlying HTML elements for
117
231
  * better semantics. Here, `Card.Root` renders as a `<section>` and
@@ -38,4 +38,20 @@
38
38
  margin-inline: calc(-1 * var(--wp-ui-card-padding));
39
39
  width: calc(100% + 2 * var(--wp-ui-card-padding));
40
40
  }
41
+
42
+ /*
43
+ * When FullBleed sits at the start of the first *root* card section, extend
44
+ * it flush to the card's top edge. Must use `.root >` so `Card.Content`
45
+ * nested inside `CollapsibleCard`'s panel is not treated as `:first-child`
46
+ * (which would pull FullBleed up into the header gap).
47
+ */
48
+ .root > :is(.header, .content):first-child > .fullbleed:first-child {
49
+ margin-block-start: calc(-1 * var(--wp-ui-card-padding));
50
+ }
51
+
52
+ /* When FullBleed sits at the end of the last card section, extend it flush to the card's bottom edge. */
53
+ :is(.header, .content):last-child > .fullbleed:last-child {
54
+ margin-block-end: calc(-1 * var(--wp-ui-card-padding));
55
+ }
56
+
41
57
  }
@@ -49,7 +49,7 @@ describe( 'Card', () => {
49
49
  } );
50
50
 
51
51
  describe( 'fullbleed', () => {
52
- it( 'renders children', () => {
52
+ it( 'renders children inside Content', () => {
53
53
  render(
54
54
  <Card.Root>
55
55
  <Card.Content>
@@ -65,6 +65,23 @@ describe( 'Card', () => {
65
65
 
66
66
  expect( screen.getByRole( 'img', { name: 'test' } ) ).toBeVisible();
67
67
  } );
68
+
69
+ it( 'renders children inside Header', () => {
70
+ render(
71
+ <Card.Root>
72
+ <Card.Header>
73
+ <Card.FullBleed>
74
+ <img
75
+ src="https://example.com/hero.jpg"
76
+ alt="hero"
77
+ />
78
+ </Card.FullBleed>
79
+ </Card.Header>
80
+ </Card.Root>
81
+ );
82
+
83
+ expect( screen.getByRole( 'img', { name: 'hero' } ) ).toBeVisible();
84
+ } );
68
85
  } );
69
86
 
70
87
  describe( 'render prop', () => {
@@ -25,6 +25,8 @@ import type { HeaderProps } from './types';
25
25
  * Avoid placing interactive elements (buttons, links, inputs) inside the
26
26
  * header, since the entire area is clickable and their events will bubble
27
27
  * to trigger the collapse toggle.
28
+ *
29
+ * Place full-bleed media in `CollapsibleCard.Content`, not the header.
28
30
  */
29
31
  export const Header = forwardRef< HTMLDivElement, HeaderProps >(
30
32
  function CollapsibleCardHeader(
@@ -33,6 +33,7 @@ const meta: Meta< typeof CollapsibleCard.Root > = {
33
33
  'CollapsibleCard.Header': CollapsibleCard.Header,
34
34
  'CollapsibleCard.HeaderDescription': CollapsibleCard.HeaderDescription,
35
35
  'CollapsibleCard.Content': CollapsibleCard.Content,
36
+ 'Card.FullBleed': Card.FullBleed,
36
37
  },
37
38
  parameters: {
38
39
  componentStatus: {
@@ -314,3 +315,68 @@ export const ComparedToCard: Story = {
314
315
  </div>
315
316
  ),
316
317
  };
318
+
319
+ /**
320
+ * When `Card.FullBleed` is the sole child of `CollapsibleCard.Content` and a
321
+ * header sits above it, the media bumps against the card&apos;s side and
322
+ * bottom edges while the header retains its normal padding. (Unlike a plain
323
+ * `Card`, a header is always required here for the collapse trigger — see
324
+ * `Card` stories for a body-only `FullBleedCoverOnly` example.)
325
+ */
326
+ export const FullBleedCoverWithHeader: Story = {
327
+ argTypes: { open: { control: false } },
328
+ args: {
329
+ defaultOpen: true,
330
+ children: (
331
+ <>
332
+ <CollapsibleCard.Header>
333
+ <Card.Title>Card title</Card.Title>
334
+ </CollapsibleCard.Header>
335
+ <CollapsibleCard.Content>
336
+ <Card.FullBleed>
337
+ <div
338
+ style={ {
339
+ height: 180,
340
+ background:
341
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
342
+ } }
343
+ />
344
+ </Card.FullBleed>
345
+ </CollapsibleCard.Content>
346
+ </>
347
+ ),
348
+ },
349
+ };
350
+
351
+ /**
352
+ * `Card.FullBleed` breaks out of the content padding to span edge-to-edge.
353
+ * Useful for images, dividers, or embedded content inside the collapsible
354
+ * region.
355
+ */
356
+ export const WithFullBleed: Story = {
357
+ argTypes: { open: { control: false } },
358
+ args: {
359
+ defaultOpen: true,
360
+ children: (
361
+ <>
362
+ <CollapsibleCard.Header>
363
+ <Card.Title>Featured image</Card.Title>
364
+ </CollapsibleCard.Header>
365
+ <CollapsibleCard.Content
366
+ render={ <Stack direction="column" gap="lg" /> }
367
+ >
368
+ <Card.FullBleed>
369
+ <div
370
+ style={ {
371
+ height: 160,
372
+ background:
373
+ 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
374
+ } }
375
+ />
376
+ </Card.FullBleed>
377
+ <Text>Content below the full-bleed area.</Text>
378
+ </CollapsibleCard.Content>
379
+ </>
380
+ ),
381
+ },
382
+ };
@@ -3,11 +3,7 @@ import { forwardRef } from '@wordpress/element';
3
3
  import type { PortalProps } from './types';
4
4
 
5
5
  /**
6
- * Root element that portals `Dialog` overlay content (`Backdrop`, inner
7
- * `Popup`, etc.) outside the DOM hierarchy. Pass to `Dialog.Popup`'s
8
- * `portal` prop to customize `container`, `className`, `style`, and other
9
- * Base UI portal options. When `portal` is omitted, `Dialog.Popup` uses this
10
- * component with default props.
6
+ * Used to apply custom portal behavior to `Dialog`'s overlay content.
11
7
  */
12
8
  const Portal = forwardRef< HTMLDivElement, PortalProps >(
13
9
  function DialogPortal( props, ref ) {
@@ -1,11 +1,11 @@
1
1
  import type { Dialog as _Dialog } from '@base-ui/react/dialog';
2
- import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';
2
+ import type { ReactElement, ReactNode } from 'react';
3
3
 
4
4
  import type { Button } from '../button';
5
5
  import type { IconButton } from '../icon-button';
6
6
  import type { ComponentProps } from '../utils/types';
7
7
 
8
- export type PortalProps = ComponentPropsWithoutRef< typeof _Dialog.Portal >;
8
+ export type PortalProps = ComponentProps< typeof _Dialog.Portal >;
9
9
 
10
10
  export interface RootProps
11
11
  extends Pick<
@@ -3,11 +3,7 @@ import { forwardRef } from '@wordpress/element';
3
3
  import type { PortalProps } from './types';
4
4
 
5
5
  /**
6
- * Root element that portals `Drawer` overlay content (`Backdrop`, `Viewport`
7
- * with the inner `Popup`, etc.) outside the DOM hierarchy. Pass to
8
- * `Drawer.Popup`'s `portal` prop to customize `container`, `className`,
9
- * `style`, and other Base UI portal options. When `portal` is omitted,
10
- * `Drawer.Popup` uses this component with default props.
6
+ * Used to apply custom portal behavior to `Drawer`'s overlay content.
11
7
  */
12
8
  const Portal = forwardRef< HTMLDivElement, PortalProps >(
13
9
  function DrawerPortal( props, ref ) {
@@ -1,10 +1,10 @@
1
1
  import type { Drawer as _Drawer } from '@base-ui/react/drawer';
2
- import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';
2
+ import type { ReactElement, ReactNode } from 'react';
3
3
  import type { Button } from '../button';
4
4
  import type { IconButton } from '../icon-button';
5
5
  import type { ComponentProps } from '../utils/types';
6
6
 
7
- export type PortalProps = ComponentPropsWithoutRef< typeof _Drawer.Portal >;
7
+ export type PortalProps = ComponentProps< typeof _Drawer.Portal >;
8
8
 
9
9
  export interface RootProps
10
10
  extends Pick<
@@ -5,12 +5,18 @@ import { __ } from '@wordpress/i18n';
5
5
  import { IconButton } from '../../../icon-button';
6
6
  import type { AutocompleteClearProps } from './types';
7
7
 
8
- const DEFAULT_RENDER = ( {
9
- 'aria-label': ariaLabel = __( 'Clear' ),
10
- ...props
11
- }: AutocompleteClearProps ) => (
8
+ const DEFAULT_RENDER = (
9
+ {
10
+ 'aria-label': ariaLabel = __( 'Clear' ),
11
+ ...props
12
+ }: AutocompleteClearProps,
13
+ { disabled }: _Autocomplete.Clear.State
14
+ ) => (
12
15
  <IconButton
13
16
  icon={ closeSmall }
17
+ focusableWhenDisabled={ false }
18
+ disabled={ disabled }
19
+ aria-hidden={ disabled || undefined }
14
20
  size="small"
15
21
  variant="minimal"
16
22
  tone="neutral"
@@ -6,7 +6,8 @@ export { Input } from './input';
6
6
  export { Item } from './item';
7
7
  export { List } from './list';
8
8
  export { ListBody } from './list-body';
9
- export { Portal } from './portal';
10
9
  export { Popup } from './popup';
10
+ export { Portal } from './portal';
11
+ export { Positioner } from './positioner';
11
12
  export { Root } from './root';
12
13
  export { Value } from './value';
@@ -8,35 +8,31 @@ import {
8
8
  import { unlock } from '../../../lock-unlock';
9
9
  import { renderSlotWithChildren } from '../../../utils/render-slot-with-children';
10
10
  import itemPopupStyles from '../../../utils/css/item-popup.module.css';
11
- import resetStyles from '../../../utils/css/resets.module.css';
12
- import styles from './style.module.css';
13
11
  import { Portal } from './portal';
12
+ import { Positioner } from './positioner';
14
13
  import type { AutocompletePopupProps } from './types';
15
- import { ITEM_POPUP_POSITIONER_PROPS } from '../constants';
16
14
 
17
15
  const ThemeProvider: typeof ThemeProviderType =
18
16
  unlock( themePrivateApis ).ThemeProvider;
19
17
 
20
18
  export const Popup = forwardRef< HTMLDivElement, AutocompletePopupProps >(
21
- function Popup( { className, portal, ...restProps }, ref ) {
22
- const portalChildren = (
23
- <_Autocomplete.Positioner
24
- { ...ITEM_POPUP_POSITIONER_PROPS }
25
- className={ clsx(
26
- resetStyles[ 'box-sizing' ],
27
- styles.positioner
28
- ) }
29
- >
30
- <ThemeProvider>
31
- <_Autocomplete.Popup
32
- ref={ ref }
33
- className={ clsx( itemPopupStyles.popup, className ) }
34
- { ...restProps }
35
- />
36
- </ThemeProvider>
37
- </_Autocomplete.Positioner>
19
+ function Popup( { className, portal, positioner, ...restProps }, ref ) {
20
+ const popupContent = (
21
+ <ThemeProvider>
22
+ <_Autocomplete.Popup
23
+ ref={ ref }
24
+ className={ clsx( itemPopupStyles.popup, className ) }
25
+ { ...restProps }
26
+ />
27
+ </ThemeProvider>
38
28
  );
39
29
 
40
- return renderSlotWithChildren( portal, <Portal />, portalChildren );
30
+ const positionedPopup = renderSlotWithChildren(
31
+ positioner,
32
+ <Positioner />,
33
+ popupContent
34
+ );
35
+
36
+ return renderSlotWithChildren( portal, <Portal />, positionedPopup );
41
37
  }
42
38
  );
@@ -1,15 +1,21 @@
1
1
  import { Autocomplete as _Autocomplete } from '@base-ui/react/autocomplete';
2
2
  import { forwardRef } from '@wordpress/element';
3
3
  import type { PortalProps } from './types';
4
+ import { getWpCompatOverlaySlot } from '../../../utils/wp-compat-overlay-slot';
4
5
 
5
6
  /**
6
- * Root element that portals `Autocomplete` popup content. Pass to
7
- * `Autocomplete.Popup`'s `portal` prop. When `portal` is omitted,
8
- * `Autocomplete.Popup` uses this component with default props.
7
+ * Used to apply custom portal behavior to `Autocomplete`'s popup content.
8
+ * `container` defaults to the `@wordpress/ui` compat overlay slot.
9
9
  */
10
10
  const Portal = forwardRef< HTMLDivElement, PortalProps >(
11
- function AutocompletePortal( props, ref ) {
12
- return <_Autocomplete.Portal ref={ ref } { ...props } />;
11
+ function AutocompletePortal( { container, ...restProps }, ref ) {
12
+ return (
13
+ <_Autocomplete.Portal
14
+ container={ container ?? getWpCompatOverlaySlot() }
15
+ { ...restProps }
16
+ ref={ ref }
17
+ />
18
+ );
13
19
  }
14
20
  );
15
21
 
@@ -0,0 +1,29 @@
1
+ import clsx from 'clsx';
2
+ import { Autocomplete as _Autocomplete } from '@base-ui/react/autocomplete';
3
+ import { forwardRef } from '@wordpress/element';
4
+ import type { PositionerProps } from './types';
5
+ import resetStyles from '../../../utils/css/resets.module.css';
6
+ import styles from './style.module.css';
7
+ import { ITEM_POPUP_POSITIONER_PROPS } from '../constants';
8
+
9
+ /**
10
+ * Used to apply custom positioning to `Autocomplete`'s popup content.
11
+ */
12
+ const Positioner = forwardRef< HTMLDivElement, PositionerProps >(
13
+ function AutocompletePositioner( { className, ...props }, ref ) {
14
+ return (
15
+ <_Autocomplete.Positioner
16
+ { ...ITEM_POPUP_POSITIONER_PROPS }
17
+ { ...props }
18
+ ref={ ref }
19
+ className={ clsx(
20
+ resetStyles[ 'box-sizing' ],
21
+ styles.positioner,
22
+ className
23
+ ) }
24
+ />
25
+ );
26
+ }
27
+ );
28
+
29
+ export { Positioner };
@@ -13,6 +13,7 @@ const meta: Meta< typeof Autocomplete.Root > = {
13
13
  component: Autocomplete.Root,
14
14
  subcomponents: {
15
15
  'Autocomplete.Portal': Autocomplete.Portal,
16
+ 'Autocomplete.Positioner': Autocomplete.Positioner,
16
17
  'Autocomplete.Popup': Autocomplete.Popup,
17
18
  'Autocomplete.Input': Autocomplete.Input,
18
19
  'Autocomplete.InputGroup': Autocomplete.InputGroup,