@wordpress/ui 0.8.0 → 0.9.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 (357) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +106 -0
  3. package/build/badge/badge.cjs +3 -3
  4. package/build/badge/badge.cjs.map +2 -2
  5. package/build/button/button.cjs +7 -7
  6. package/build/button/button.cjs.map +2 -2
  7. package/build/card/content.cjs +54 -0
  8. package/build/card/content.cjs.map +7 -0
  9. package/build/card/full-bleed.cjs +57 -0
  10. package/build/card/full-bleed.cjs.map +7 -0
  11. package/build/card/header.cjs +54 -0
  12. package/build/card/header.cjs.map +7 -0
  13. package/build/card/index.cjs +43 -0
  14. package/build/card/index.cjs.map +7 -0
  15. package/build/card/root.cjs +73 -0
  16. package/build/card/root.cjs.map +7 -0
  17. package/build/card/title.cjs +55 -0
  18. package/build/card/title.cjs.map +7 -0
  19. package/build/card/types.cjs +19 -0
  20. package/build/card/types.cjs.map +7 -0
  21. package/build/collapsible/index.cjs +37 -0
  22. package/build/collapsible/index.cjs.map +7 -0
  23. package/build/collapsible/panel.cjs +38 -0
  24. package/build/collapsible/panel.cjs.map +7 -0
  25. package/build/collapsible/root.cjs +38 -0
  26. package/build/collapsible/root.cjs.map +7 -0
  27. package/build/collapsible/trigger.cjs +38 -0
  28. package/build/collapsible/trigger.cjs.map +7 -0
  29. package/build/collapsible/types.cjs +19 -0
  30. package/build/collapsible/types.cjs.map +7 -0
  31. package/build/collapsible-card/content.cjs +77 -0
  32. package/build/collapsible-card/content.cjs.map +7 -0
  33. package/build/collapsible-card/header.cjs +102 -0
  34. package/build/collapsible-card/header.cjs.map +7 -0
  35. package/build/collapsible-card/index.cjs +37 -0
  36. package/build/collapsible-card/index.cjs.map +7 -0
  37. package/build/collapsible-card/root.cjs +56 -0
  38. package/build/collapsible-card/root.cjs.map +7 -0
  39. package/build/collapsible-card/types.cjs +19 -0
  40. package/build/collapsible-card/types.cjs.map +7 -0
  41. package/build/dialog/footer.cjs +3 -3
  42. package/build/dialog/footer.cjs.map +2 -2
  43. package/build/dialog/header.cjs +3 -3
  44. package/build/dialog/header.cjs.map +2 -2
  45. package/build/dialog/popup.cjs +3 -3
  46. package/build/dialog/popup.cjs.map +2 -2
  47. package/build/dialog/title.cjs +3 -3
  48. package/build/dialog/title.cjs.map +2 -2
  49. package/build/dialog/types.cjs.map +1 -1
  50. package/build/form/primitives/field/label.cjs +6 -1
  51. package/build/form/primitives/field/label.cjs.map +2 -2
  52. package/build/form/primitives/field/types.cjs.map +1 -1
  53. package/build/form/primitives/fieldset/legend.cjs +5 -1
  54. package/build/form/primitives/fieldset/legend.cjs.map +2 -2
  55. package/build/form/primitives/fieldset/types.cjs.map +1 -1
  56. package/build/form/primitives/input/input.cjs +4 -4
  57. package/build/form/primitives/input/input.cjs.map +2 -2
  58. package/build/form/primitives/input-layout/slot.cjs +3 -2
  59. package/build/form/primitives/input-layout/slot.cjs.map +2 -2
  60. package/build/form/primitives/select/item.cjs +3 -3
  61. package/build/form/primitives/select/item.cjs.map +2 -2
  62. package/build/form/primitives/select/popup.cjs +3 -3
  63. package/build/form/primitives/select/popup.cjs.map +2 -2
  64. package/build/form/primitives/select/trigger.cjs +7 -7
  65. package/build/form/primitives/select/trigger.cjs.map +2 -2
  66. package/build/index.cjs +13 -0
  67. package/build/index.cjs.map +2 -2
  68. package/build/link/index.cjs +31 -0
  69. package/build/link/index.cjs.map +7 -0
  70. package/build/link/link.cjs +125 -0
  71. package/build/link/link.cjs.map +7 -0
  72. package/build/link/types.cjs +19 -0
  73. package/build/link/types.cjs.map +7 -0
  74. package/build/notice/action-button.cjs +3 -3
  75. package/build/notice/action-button.cjs.map +2 -2
  76. package/build/notice/action-link.cjs +17 -18
  77. package/build/notice/action-link.cjs.map +2 -2
  78. package/build/notice/actions.cjs +3 -3
  79. package/build/notice/actions.cjs.map +2 -2
  80. package/build/notice/close-icon.cjs +3 -3
  81. package/build/notice/close-icon.cjs.map +2 -2
  82. package/build/notice/description.cjs +26 -15
  83. package/build/notice/description.cjs.map +3 -3
  84. package/build/notice/root.cjs +3 -3
  85. package/build/notice/root.cjs.map +2 -2
  86. package/build/notice/title.cjs +26 -12
  87. package/build/notice/title.cjs.map +3 -3
  88. package/build/notice/types.cjs.map +1 -1
  89. package/build/tabs/list.cjs +3 -3
  90. package/build/tabs/list.cjs.map +2 -2
  91. package/build/tabs/panel.cjs +3 -3
  92. package/build/tabs/panel.cjs.map +2 -2
  93. package/build/tabs/tab.cjs +3 -3
  94. package/build/tabs/tab.cjs.map +2 -2
  95. package/build/text/index.cjs +31 -0
  96. package/build/text/index.cjs.map +7 -0
  97. package/build/text/text.cjs +65 -0
  98. package/build/text/text.cjs.map +7 -0
  99. package/build/text/types.cjs +19 -0
  100. package/build/text/types.cjs.map +7 -0
  101. package/build/tooltip/popup.cjs +1 -1
  102. package/build/tooltip/popup.cjs.map +1 -1
  103. package/build/visually-hidden/visually-hidden.cjs +3 -3
  104. package/build/visually-hidden/visually-hidden.cjs.map +2 -2
  105. package/build-module/badge/badge.mjs +3 -3
  106. package/build-module/badge/badge.mjs.map +2 -2
  107. package/build-module/button/button.mjs +7 -7
  108. package/build-module/button/button.mjs.map +2 -2
  109. package/build-module/card/content.mjs +29 -0
  110. package/build-module/card/content.mjs.map +7 -0
  111. package/build-module/card/full-bleed.mjs +32 -0
  112. package/build-module/card/full-bleed.mjs.map +7 -0
  113. package/build-module/card/header.mjs +29 -0
  114. package/build-module/card/header.mjs.map +7 -0
  115. package/build-module/card/index.mjs +14 -0
  116. package/build-module/card/index.mjs.map +7 -0
  117. package/build-module/card/root.mjs +38 -0
  118. package/build-module/card/root.mjs.map +7 -0
  119. package/build-module/card/title.mjs +30 -0
  120. package/build-module/card/title.mjs.map +7 -0
  121. package/build-module/card/types.mjs +1 -0
  122. package/build-module/card/types.mjs.map +7 -0
  123. package/build-module/collapsible/index.mjs +10 -0
  124. package/build-module/collapsible/index.mjs.map +7 -0
  125. package/build-module/collapsible/panel.mjs +13 -0
  126. package/build-module/collapsible/panel.mjs.map +7 -0
  127. package/build-module/collapsible/root.mjs +13 -0
  128. package/build-module/collapsible/root.mjs.map +7 -0
  129. package/build-module/collapsible/trigger.mjs +13 -0
  130. package/build-module/collapsible/trigger.mjs.map +7 -0
  131. package/build-module/collapsible/types.mjs +1 -0
  132. package/build-module/collapsible/types.mjs.map +7 -0
  133. package/build-module/collapsible-card/content.mjs +42 -0
  134. package/build-module/collapsible-card/content.mjs.map +7 -0
  135. package/build-module/collapsible-card/header.mjs +67 -0
  136. package/build-module/collapsible-card/header.mjs.map +7 -0
  137. package/build-module/collapsible-card/index.mjs +10 -0
  138. package/build-module/collapsible-card/index.mjs.map +7 -0
  139. package/build-module/collapsible-card/root.mjs +21 -0
  140. package/build-module/collapsible-card/root.mjs.map +7 -0
  141. package/build-module/collapsible-card/types.mjs +1 -0
  142. package/build-module/collapsible-card/types.mjs.map +7 -0
  143. package/build-module/dialog/footer.mjs +3 -3
  144. package/build-module/dialog/footer.mjs.map +2 -2
  145. package/build-module/dialog/header.mjs +3 -3
  146. package/build-module/dialog/header.mjs.map +2 -2
  147. package/build-module/dialog/popup.mjs +3 -3
  148. package/build-module/dialog/popup.mjs.map +2 -2
  149. package/build-module/dialog/title.mjs +3 -3
  150. package/build-module/dialog/title.mjs.map +2 -2
  151. package/build-module/form/primitives/field/label.mjs +6 -1
  152. package/build-module/form/primitives/field/label.mjs.map +2 -2
  153. package/build-module/form/primitives/fieldset/legend.mjs +5 -1
  154. package/build-module/form/primitives/fieldset/legend.mjs.map +2 -2
  155. package/build-module/form/primitives/input/input.mjs +4 -4
  156. package/build-module/form/primitives/input/input.mjs.map +2 -2
  157. package/build-module/form/primitives/input-layout/slot.mjs +3 -2
  158. package/build-module/form/primitives/input-layout/slot.mjs.map +2 -2
  159. package/build-module/form/primitives/select/item.mjs +3 -3
  160. package/build-module/form/primitives/select/item.mjs.map +2 -2
  161. package/build-module/form/primitives/select/popup.mjs +3 -3
  162. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  163. package/build-module/form/primitives/select/trigger.mjs +7 -7
  164. package/build-module/form/primitives/select/trigger.mjs.map +2 -2
  165. package/build-module/index.mjs +8 -0
  166. package/build-module/index.mjs.map +2 -2
  167. package/build-module/link/index.mjs +6 -0
  168. package/build-module/link/index.mjs.map +7 -0
  169. package/build-module/link/link.mjs +90 -0
  170. package/build-module/link/link.mjs.map +7 -0
  171. package/build-module/link/types.mjs +1 -0
  172. package/build-module/link/types.mjs.map +7 -0
  173. package/build-module/notice/action-button.mjs +3 -3
  174. package/build-module/notice/action-button.mjs.map +2 -2
  175. package/build-module/notice/action-link.mjs +17 -18
  176. package/build-module/notice/action-link.mjs.map +2 -2
  177. package/build-module/notice/actions.mjs +3 -3
  178. package/build-module/notice/actions.mjs.map +2 -2
  179. package/build-module/notice/close-icon.mjs +3 -3
  180. package/build-module/notice/close-icon.mjs.map +2 -2
  181. package/build-module/notice/description.mjs +16 -15
  182. package/build-module/notice/description.mjs.map +2 -2
  183. package/build-module/notice/root.mjs +3 -3
  184. package/build-module/notice/root.mjs.map +2 -2
  185. package/build-module/notice/title.mjs +16 -12
  186. package/build-module/notice/title.mjs.map +2 -2
  187. package/build-module/tabs/list.mjs +3 -3
  188. package/build-module/tabs/list.mjs.map +2 -2
  189. package/build-module/tabs/panel.mjs +3 -3
  190. package/build-module/tabs/panel.mjs.map +2 -2
  191. package/build-module/tabs/tab.mjs +3 -3
  192. package/build-module/tabs/tab.mjs.map +2 -2
  193. package/build-module/text/index.mjs +6 -0
  194. package/build-module/text/index.mjs.map +7 -0
  195. package/build-module/text/text.mjs +30 -0
  196. package/build-module/text/text.mjs.map +7 -0
  197. package/build-module/text/types.mjs +1 -0
  198. package/build-module/text/types.mjs.map +7 -0
  199. package/build-module/tooltip/popup.mjs +1 -1
  200. package/build-module/tooltip/popup.mjs.map +1 -1
  201. package/build-module/visually-hidden/visually-hidden.mjs +3 -3
  202. package/build-module/visually-hidden/visually-hidden.mjs.map +2 -2
  203. package/build-types/card/content.d.ts +6 -0
  204. package/build-types/card/content.d.ts.map +1 -0
  205. package/build-types/card/full-bleed.d.ts +9 -0
  206. package/build-types/card/full-bleed.d.ts.map +1 -0
  207. package/build-types/card/header.d.ts +7 -0
  208. package/build-types/card/header.d.ts.map +1 -0
  209. package/build-types/card/index.d.ts +7 -0
  210. package/build-types/card/index.d.ts.map +1 -0
  211. package/build-types/card/root.d.ts +23 -0
  212. package/build-types/card/root.d.ts.map +1 -0
  213. package/build-types/card/stories/index.story.d.ts +22 -0
  214. package/build-types/card/stories/index.story.d.ts.map +1 -0
  215. package/build-types/card/test/index.test.d.ts +2 -0
  216. package/build-types/card/test/index.test.d.ts.map +1 -0
  217. package/build-types/card/title.d.ts +7 -0
  218. package/build-types/card/title.d.ts.map +1 -0
  219. package/build-types/card/types.d.ts +34 -0
  220. package/build-types/card/types.d.ts.map +1 -0
  221. package/build-types/collapsible/index.d.ts +5 -0
  222. package/build-types/collapsible/index.d.ts.map +1 -0
  223. package/build-types/collapsible/panel.d.ts +16 -0
  224. package/build-types/collapsible/panel.d.ts.map +1 -0
  225. package/build-types/collapsible/root.d.ts +15 -0
  226. package/build-types/collapsible/root.d.ts.map +1 -0
  227. package/build-types/collapsible/stories/index.story.d.ts +18 -0
  228. package/build-types/collapsible/stories/index.story.d.ts.map +1 -0
  229. package/build-types/collapsible/test/index.test.d.ts +2 -0
  230. package/build-types/collapsible/test/index.test.d.ts.map +1 -0
  231. package/build-types/collapsible/trigger.d.ts +15 -0
  232. package/build-types/collapsible/trigger.d.ts.map +1 -0
  233. package/build-types/collapsible/types.d.ts +22 -0
  234. package/build-types/collapsible/types.d.ts.map +1 -0
  235. package/build-types/collapsible-card/content.d.ts +7 -0
  236. package/build-types/collapsible-card/content.d.ts.map +1 -0
  237. package/build-types/collapsible-card/header.d.ts +12 -0
  238. package/build-types/collapsible-card/header.d.ts.map +1 -0
  239. package/build-types/collapsible-card/index.d.ts +5 -0
  240. package/build-types/collapsible-card/index.d.ts.map +1 -0
  241. package/build-types/collapsible-card/root.d.ts +24 -0
  242. package/build-types/collapsible-card/root.d.ts.map +1 -0
  243. package/build-types/collapsible-card/stories/index.story.d.ts +28 -0
  244. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -0
  245. package/build-types/collapsible-card/test/index.test.d.ts +2 -0
  246. package/build-types/collapsible-card/test/index.test.d.ts.map +1 -0
  247. package/build-types/collapsible-card/types.d.ts +52 -0
  248. package/build-types/collapsible-card/types.d.ts.map +1 -0
  249. package/build-types/dialog/types.d.ts +3 -3
  250. package/build-types/form/primitives/field/label.d.ts +1 -0
  251. package/build-types/form/primitives/field/label.d.ts.map +1 -1
  252. package/build-types/form/primitives/field/stories/index.story.d.ts +5 -0
  253. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  254. package/build-types/form/primitives/field/types.d.ts +7 -0
  255. package/build-types/form/primitives/field/types.d.ts.map +1 -1
  256. package/build-types/form/primitives/fieldset/legend.d.ts +1 -0
  257. package/build-types/form/primitives/fieldset/legend.d.ts.map +1 -1
  258. package/build-types/form/primitives/fieldset/stories/index.story.d.ts +5 -0
  259. package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
  260. package/build-types/form/primitives/fieldset/types.d.ts +7 -0
  261. package/build-types/form/primitives/fieldset/types.d.ts.map +1 -1
  262. package/build-types/form/primitives/input-layout/slot.d.ts.map +1 -1
  263. package/build-types/index.d.ts +5 -0
  264. package/build-types/index.d.ts.map +1 -1
  265. package/build-types/link/index.d.ts +2 -0
  266. package/build-types/link/index.d.ts.map +1 -0
  267. package/build-types/link/link.d.ts +7 -0
  268. package/build-types/link/link.d.ts.map +1 -0
  269. package/build-types/link/stories/index.story.d.ts +18 -0
  270. package/build-types/link/stories/index.story.d.ts.map +1 -0
  271. package/build-types/link/test/index.test.d.ts +2 -0
  272. package/build-types/link/test/index.test.d.ts.map +1 -0
  273. package/build-types/link/types.d.ts +33 -0
  274. package/build-types/link/types.d.ts.map +1 -0
  275. package/build-types/notice/action-link.d.ts +0 -2
  276. package/build-types/notice/action-link.d.ts.map +1 -1
  277. package/build-types/notice/description.d.ts +1 -1
  278. package/build-types/notice/description.d.ts.map +1 -1
  279. package/build-types/notice/title.d.ts.map +1 -1
  280. package/build-types/notice/types.d.ts +3 -2
  281. package/build-types/notice/types.d.ts.map +1 -1
  282. package/build-types/text/index.d.ts +2 -0
  283. package/build-types/text/index.d.ts.map +1 -0
  284. package/build-types/text/stories/index.story.d.ts +9 -0
  285. package/build-types/text/stories/index.story.d.ts.map +1 -0
  286. package/build-types/text/test/index.test.d.ts +2 -0
  287. package/build-types/text/test/index.test.d.ts.map +1 -0
  288. package/build-types/text/text.d.ts +7 -0
  289. package/build-types/text/text.d.ts.map +1 -0
  290. package/build-types/text/types.d.ts +15 -0
  291. package/build-types/text/types.d.ts.map +1 -0
  292. package/package.json +14 -14
  293. package/src/badge/style.module.css +5 -2
  294. package/src/button/style.module.css +2 -0
  295. package/src/card/content.tsx +20 -0
  296. package/src/card/full-bleed.tsx +26 -0
  297. package/src/card/header.tsx +21 -0
  298. package/src/card/index.ts +7 -0
  299. package/src/card/root.tsx +42 -0
  300. package/src/card/stories/index.story.tsx +128 -0
  301. package/src/card/style.module.css +48 -0
  302. package/src/card/test/index.test.tsx +96 -0
  303. package/src/card/title.tsx +22 -0
  304. package/src/card/types.ts +38 -0
  305. package/src/collapsible/index.ts +5 -0
  306. package/src/collapsible/panel.tsx +16 -0
  307. package/src/collapsible/root.tsx +15 -0
  308. package/src/collapsible/stories/index.story.tsx +108 -0
  309. package/src/collapsible/test/index.test.tsx +228 -0
  310. package/src/collapsible/trigger.tsx +15 -0
  311. package/src/collapsible/types.ts +24 -0
  312. package/src/collapsible-card/content.tsx +33 -0
  313. package/src/collapsible-card/header.tsx +53 -0
  314. package/src/collapsible-card/index.ts +5 -0
  315. package/src/collapsible-card/root.tsx +37 -0
  316. package/src/collapsible-card/stories/index.story.tsx +207 -0
  317. package/src/collapsible-card/style.module.css +78 -0
  318. package/src/collapsible-card/test/index.test.tsx +181 -0
  319. package/src/collapsible-card/types.ts +54 -0
  320. package/src/dialog/style.module.css +5 -5
  321. package/src/dialog/types.ts +3 -3
  322. package/src/form/primitives/field/label.tsx +9 -1
  323. package/src/form/primitives/field/stories/index.story.tsx +17 -0
  324. package/src/form/primitives/field/test/index.test.tsx +13 -0
  325. package/src/form/primitives/field/types.ts +7 -0
  326. package/src/form/primitives/fieldset/legend.tsx +8 -1
  327. package/src/form/primitives/fieldset/stories/index.story.tsx +20 -0
  328. package/src/form/primitives/fieldset/test/index.test.tsx +14 -0
  329. package/src/form/primitives/fieldset/types.ts +7 -0
  330. package/src/form/primitives/input-layout/slot.tsx +6 -2
  331. package/src/index.ts +5 -0
  332. package/src/link/index.ts +1 -0
  333. package/src/link/link.tsx +73 -0
  334. package/src/link/stories/index.story.tsx +92 -0
  335. package/src/link/style.module.css +68 -0
  336. package/src/link/test/index.test.tsx +93 -0
  337. package/src/link/types.ts +36 -0
  338. package/src/notice/action-link.tsx +12 -18
  339. package/src/notice/description.tsx +12 -14
  340. package/src/notice/style.module.css +9 -22
  341. package/src/notice/test/index.test.tsx +2 -2
  342. package/src/notice/title.tsx +11 -10
  343. package/src/notice/types.ts +3 -2
  344. package/src/tabs/style.module.css +1 -1
  345. package/src/text/index.ts +1 -0
  346. package/src/text/stories/index.story.tsx +68 -0
  347. package/src/text/style.module.css +67 -0
  348. package/src/text/test/index.test.tsx +46 -0
  349. package/src/text/text.tsx +25 -0
  350. package/src/text/types.ts +25 -0
  351. package/src/tooltip/popup.tsx +1 -1
  352. package/src/utils/css/focus.module.css +4 -2
  353. package/src/utils/css/item-popup.module.css +1 -0
  354. package/src/utils/css/select-trigger.module.css +1 -0
  355. package/src/visually-hidden/style.module.css +1 -0
  356. package/AGENTS.md +0 -9
  357. package/CLAUDE.md +0 -1
@@ -0,0 +1,181 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { userEvent } from '@testing-library/user-event';
3
+ import { createRef } from '@wordpress/element';
4
+ import * as Card from '../../card';
5
+ import * as CollapsibleCard from '../index';
6
+
7
+ describe( 'CollapsibleCard', () => {
8
+ describe( 'basic behaviour', () => {
9
+ it( 'forwards ref', () => {
10
+ const rootRef = createRef< HTMLDivElement >();
11
+ const headerRef = createRef< HTMLDivElement >();
12
+ const contentRef = createRef< HTMLDivElement >();
13
+
14
+ render(
15
+ <CollapsibleCard.Root ref={ rootRef } defaultOpen>
16
+ <CollapsibleCard.Header ref={ headerRef }>
17
+ <Card.Title>Title</Card.Title>
18
+ </CollapsibleCard.Header>
19
+ <CollapsibleCard.Content ref={ contentRef }>
20
+ <p>Content</p>
21
+ </CollapsibleCard.Content>
22
+ </CollapsibleCard.Root>
23
+ );
24
+
25
+ expect( rootRef.current ).toBeInstanceOf( HTMLDivElement );
26
+ expect( headerRef.current ).toBeInstanceOf( HTMLDivElement );
27
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
28
+ } );
29
+
30
+ it( 'renders header content', () => {
31
+ render(
32
+ <CollapsibleCard.Root>
33
+ <CollapsibleCard.Header>
34
+ <Card.Title>Card heading</Card.Title>
35
+ </CollapsibleCard.Header>
36
+ </CollapsibleCard.Root>
37
+ );
38
+
39
+ expect( screen.getByText( 'Card heading' ) ).toBeVisible();
40
+ } );
41
+ } );
42
+
43
+ describe( 'collapsing', () => {
44
+ it( 'hides content when collapsed (default)', () => {
45
+ render(
46
+ <CollapsibleCard.Root>
47
+ <CollapsibleCard.Header>
48
+ <Card.Title>Title</Card.Title>
49
+ </CollapsibleCard.Header>
50
+ <CollapsibleCard.Content>
51
+ <p>Hidden content</p>
52
+ </CollapsibleCard.Content>
53
+ </CollapsibleCard.Root>
54
+ );
55
+
56
+ expect( screen.getByText( 'Hidden content' ) ).not.toBeVisible();
57
+ } );
58
+
59
+ it( 'shows content when defaultOpen is true', () => {
60
+ render(
61
+ <CollapsibleCard.Root defaultOpen>
62
+ <CollapsibleCard.Header>
63
+ <Card.Title>Title</Card.Title>
64
+ </CollapsibleCard.Header>
65
+ <CollapsibleCard.Content>
66
+ <p>Visible content</p>
67
+ </CollapsibleCard.Content>
68
+ </CollapsibleCard.Root>
69
+ );
70
+
71
+ expect( screen.getByText( 'Visible content' ) ).toBeVisible();
72
+ } );
73
+
74
+ it( 'toggles content on trigger click', async () => {
75
+ const user = userEvent.setup();
76
+
77
+ render(
78
+ <CollapsibleCard.Root>
79
+ <CollapsibleCard.Header>
80
+ <Card.Title>Title</Card.Title>
81
+ </CollapsibleCard.Header>
82
+ <CollapsibleCard.Content>
83
+ <p>Toggle content</p>
84
+ </CollapsibleCard.Content>
85
+ </CollapsibleCard.Root>
86
+ );
87
+
88
+ expect( screen.getByText( 'Toggle content' ) ).not.toBeVisible();
89
+
90
+ await user.click(
91
+ screen.getByRole( 'button', {
92
+ name: 'Title',
93
+ expanded: false,
94
+ } )
95
+ );
96
+
97
+ expect( screen.getByText( 'Toggle content' ) ).toBeVisible();
98
+
99
+ await user.click(
100
+ screen.getByRole( 'button', {
101
+ name: 'Title',
102
+ expanded: true,
103
+ } )
104
+ );
105
+
106
+ expect( screen.getByText( 'Toggle content' ) ).not.toBeVisible();
107
+ } );
108
+
109
+ it( 'calls onOpenChange when toggled', async () => {
110
+ const onOpenChange = jest.fn();
111
+ const user = userEvent.setup();
112
+
113
+ render(
114
+ <CollapsibleCard.Root onOpenChange={ onOpenChange }>
115
+ <CollapsibleCard.Header>
116
+ <Card.Title>Title</Card.Title>
117
+ </CollapsibleCard.Header>
118
+ <CollapsibleCard.Content>
119
+ <p>Content</p>
120
+ </CollapsibleCard.Content>
121
+ </CollapsibleCard.Root>
122
+ );
123
+
124
+ await user.click(
125
+ screen.getByRole( 'button', {
126
+ name: 'Title',
127
+ expanded: false,
128
+ } )
129
+ );
130
+
131
+ expect( onOpenChange.mock.calls[ 0 ][ 0 ] ).toBe( true );
132
+ } );
133
+ } );
134
+
135
+ describe( 'disabled', () => {
136
+ it( 'does not toggle when disabled', async () => {
137
+ const user = userEvent.setup();
138
+
139
+ render(
140
+ <CollapsibleCard.Root defaultOpen disabled>
141
+ <CollapsibleCard.Header>
142
+ <Card.Title>Title</Card.Title>
143
+ </CollapsibleCard.Header>
144
+ <CollapsibleCard.Content>
145
+ <p>Should stay visible</p>
146
+ </CollapsibleCard.Content>
147
+ </CollapsibleCard.Root>
148
+ );
149
+
150
+ expect( screen.getByText( 'Should stay visible' ) ).toBeVisible();
151
+
152
+ await user.click(
153
+ screen.getByRole( 'button', {
154
+ name: 'Title',
155
+ expanded: true,
156
+ } )
157
+ );
158
+
159
+ expect( screen.getByText( 'Should stay visible' ) ).toBeVisible();
160
+ } );
161
+ } );
162
+
163
+ describe( 'trigger', () => {
164
+ it( 'renders the header as a toggle button', () => {
165
+ render(
166
+ <CollapsibleCard.Root>
167
+ <CollapsibleCard.Header>
168
+ <Card.Title>Title</Card.Title>
169
+ </CollapsibleCard.Header>
170
+ </CollapsibleCard.Root>
171
+ );
172
+
173
+ expect(
174
+ screen.getByRole( 'button', {
175
+ name: 'Title',
176
+ expanded: false,
177
+ } )
178
+ ).toBeVisible();
179
+ } );
180
+ } );
181
+ } );
@@ -0,0 +1,54 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PanelProps } from '../collapsible/types';
3
+ import type { ComponentProps } from '../utils/types';
4
+
5
+ export interface RootProps extends ComponentProps< 'div' > {
6
+ /**
7
+ * The content to be rendered inside the collapsible card.
8
+ * Should include `CollapsibleCard.Header` and `CollapsibleCard.Content`.
9
+ */
10
+ children?: ReactNode;
11
+ /**
12
+ * Whether the collapsible panel is currently open (controlled).
13
+ *
14
+ * To render an uncontrolled collapsible card, use `defaultOpen` instead.
15
+ */
16
+ open?: boolean;
17
+ /**
18
+ * Whether the collapsible panel is initially open (uncontrolled).
19
+ * @default false
20
+ */
21
+ defaultOpen?: boolean;
22
+ /**
23
+ * Event handler called when the panel is opened or closed.
24
+ */
25
+ onOpenChange?: ( open: boolean ) => void;
26
+ /**
27
+ * Whether the component should ignore user interaction.
28
+ * @default false
29
+ */
30
+ disabled?: boolean;
31
+ }
32
+
33
+ export interface HeaderProps extends ComponentProps< 'div' > {
34
+ /**
35
+ * The content to be rendered inside the header.
36
+ */
37
+ children?: ReactNode;
38
+ }
39
+
40
+ export interface ContentProps extends ComponentProps< 'div' > {
41
+ /**
42
+ * The content to be rendered inside the collapsible content area.
43
+ */
44
+ children?: ReactNode;
45
+ /**
46
+ * Allows the browser’s built-in page search to find and expand the panel contents.
47
+ *
48
+ * Overrides the `keepMounted` prop and uses `hidden="until-found"`
49
+ * to hide the element without removing it from the DOM.
50
+ *
51
+ * @default true
52
+ */
53
+ hiddenUntilFound?: PanelProps[ 'hiddenUntilFound' ];
54
+ }
@@ -29,7 +29,7 @@
29
29
  z-index: var(--wp-ui-dialog-z-index, initial);
30
30
  transform: translate(-50%, -50%);
31
31
  box-sizing: border-box;
32
- min-width: 320px;
32
+ min-width: var(--wpds-dimension-surface-width-sm);
33
33
  width: calc(100vw - 2 * var(--wpds-dimension-padding-2xl));
34
34
  max-height: calc(100vh - 2 * var(--wpds-dimension-padding-2xl));
35
35
  padding: var(--wpds-dimension-padding-2xl);
@@ -61,19 +61,19 @@
61
61
  }
62
62
 
63
63
  @media (min-width: 480px) {
64
- min-width: 384px;
64
+ min-width: var(--wpds-dimension-surface-width-md);
65
65
  }
66
66
 
67
67
  &.is-small {
68
- max-width: 384px;
68
+ max-width: var(--wpds-dimension-surface-width-md);
69
69
  }
70
70
 
71
71
  &.is-medium {
72
- max-width: 512px;
72
+ max-width: var(--wpds-dimension-surface-width-lg);
73
73
  }
74
74
 
75
75
  &.is-large {
76
- max-width: 840px;
76
+ max-width: var(--wpds-dimension-surface-width-2xl);
77
77
  }
78
78
 
79
79
  &.is-stretch {
@@ -32,9 +32,9 @@ export interface PopupProps extends ComponentProps< 'div' > {
32
32
  * Renders the dialog at a preset width (excluding additional padding from
33
33
  * the viewport edges).
34
34
  *
35
- * - `'small'` — max-width of 384px.
36
- * - `'medium'` — max-width of 512px.
37
- * - `'large'` — max-width of 840px.
35
+ * - `'small'` — narrow max-width.
36
+ * - `'medium'` — moderate max-width.
37
+ * - `'large'` — wide max-width.
38
38
  * - `'stretch'` — no max-width, stretches to fill available space.
39
39
  * - `'full'` — stretches to fill available width and height.
40
40
  *
@@ -2,10 +2,14 @@ import clsx from 'clsx';
2
2
  import { Field as _Field } from '@base-ui/react/field';
3
3
  import { forwardRef } from '@wordpress/element';
4
4
  import fieldStyles from '../../../utils/css/field.module.css';
5
+ import { VisuallyHidden } from '../../../visually-hidden';
5
6
  import type { FieldLabelProps } from './types';
6
7
 
7
8
  export const Label = forwardRef< HTMLLabelElement, FieldLabelProps >(
8
- function Label( { className, variant, ...restProps }, ref ) {
9
+ function Label(
10
+ { className, hideFromVision, variant, ...restProps },
11
+ ref
12
+ ) {
9
13
  return (
10
14
  <_Field.Label
11
15
  ref={ ref }
@@ -14,6 +18,10 @@ export const Label = forwardRef< HTMLLabelElement, FieldLabelProps >(
14
18
  variant && fieldStyles[ `is-${ variant }` ],
15
19
  className
16
20
  ) }
21
+ { ...( hideFromVision && {
22
+ render: <VisuallyHidden />,
23
+ nativeLabel: false,
24
+ } ) }
17
25
  { ...restProps }
18
26
  />
19
27
  );
@@ -100,6 +100,23 @@ export const UsingAriaLabelledby: StoryObj< typeof Field.Root > = {
100
100
  },
101
101
  };
102
102
 
103
+ /**
104
+ * When `hideFromVision` is set on `Field.Label`, the label is visually
105
+ * hidden but remains accessible to screen readers.
106
+ */
107
+ export const HiddenLabel: StoryObj< typeof Field.Root > = {
108
+ args: {
109
+ children: (
110
+ <>
111
+ <Field.Label hideFromVision>Label</Field.Label>
112
+ <Field.Control
113
+ render={ <input type="text" placeholder="Placeholder" /> }
114
+ />
115
+ </>
116
+ ),
117
+ },
118
+ };
119
+
103
120
  /**
104
121
  * To add rich content (such as links) to the description, use `Field.Details`.
105
122
  *
@@ -34,6 +34,19 @@ describe( 'Field', () => {
34
34
  expect( detailsRef.current ).toBeInstanceOf( HTMLDivElement );
35
35
  } );
36
36
 
37
+ it( 'keeps the accessible name when the label is visually hidden', () => {
38
+ render(
39
+ <Field.Root>
40
+ <Field.Label hideFromVision>Field Label</Field.Label>
41
+ <Field.Control render={ <input /> } />
42
+ </Field.Root>
43
+ );
44
+
45
+ expect(
46
+ screen.getByRole( 'textbox', { name: 'Field Label' } )
47
+ ).toBeVisible();
48
+ } );
49
+
37
50
  it( 'renders details with a semantically associated description for the control', () => {
38
51
  render(
39
52
  <Field.Root>
@@ -33,6 +33,13 @@ export type FieldLabelProps = ComponentProps< typeof Field.Label > & {
33
33
  * elements such as links or buttons.
34
34
  */
35
35
  children?: Field.Label.Props[ 'children' ];
36
+ /**
37
+ * Whether to visually hide the label while keeping it accessible
38
+ * to screen readers.
39
+ *
40
+ * @default false
41
+ */
42
+ hideFromVision?: boolean;
36
43
  /**
37
44
  * The visual variant of the label.
38
45
  *
@@ -2,14 +2,21 @@ import clsx from 'clsx';
2
2
  import { Fieldset as _Fieldset } from '@base-ui/react/fieldset';
3
3
  import { forwardRef } from '@wordpress/element';
4
4
  import fieldStyles from '../../../utils/css/field.module.css';
5
+ import { VisuallyHidden } from '../../../visually-hidden';
5
6
  import type { FieldsetLegendProps } from './types';
6
7
 
7
8
  export const FieldsetLegend = forwardRef< HTMLDivElement, FieldsetLegendProps >(
8
- function FieldsetLegend( { className, ...restProps }, ref ) {
9
+ function FieldsetLegend(
10
+ { className, hideFromVision, ...restProps },
11
+ ref
12
+ ) {
9
13
  return (
10
14
  <_Fieldset.Legend
11
15
  ref={ ref }
12
16
  className={ clsx( fieldStyles.label, className ) }
17
+ { ...( hideFromVision && {
18
+ render: <VisuallyHidden />,
19
+ } ) }
13
20
  { ...restProps }
14
21
  />
15
22
  );
@@ -33,6 +33,26 @@ export const Default: Story = {
33
33
  },
34
34
  };
35
35
 
36
+ /**
37
+ * When `hideFromVision` is set on `Fieldset.Legend`, the legend is visually
38
+ * hidden but remains accessible to screen readers.
39
+ */
40
+ export const HiddenLegend: Story = {
41
+ args: {
42
+ children: (
43
+ <>
44
+ <Fieldset.Legend hideFromVision>Legend</Fieldset.Legend>
45
+ { [ 'Apples', 'Bananas' ].map( ( fruit ) => (
46
+ // eslint-disable-next-line jsx-a11y/label-has-associated-control
47
+ <label key={ fruit }>
48
+ <input type="checkbox" /> { fruit }
49
+ </label>
50
+ ) ) }
51
+ </>
52
+ ),
53
+ },
54
+ };
55
+
36
56
  /**
37
57
  * To add rich content (such as links) to the description, use `Fieldset.Details`.
38
58
  *
@@ -29,6 +29,20 @@ describe( 'Fieldset', () => {
29
29
  expect( detailsRef.current ).toBeInstanceOf( HTMLDivElement );
30
30
  } );
31
31
 
32
+ it( 'keeps the accessible name when the legend is visually hidden', () => {
33
+ render(
34
+ <Fieldset.Root>
35
+ <Fieldset.Legend hideFromVision>
36
+ Choose your options
37
+ </Fieldset.Legend>
38
+ </Fieldset.Root>
39
+ );
40
+
41
+ expect(
42
+ screen.getByRole( 'group', { name: 'Choose your options' } )
43
+ ).toBeVisible();
44
+ } );
45
+
32
46
  it( 'accessibly associates description when Fieldset.Description is present', () => {
33
47
  render(
34
48
  <Fieldset.Root>
@@ -7,6 +7,13 @@ export type FieldsetRootProps = ComponentProps< typeof _Fieldset.Root > & {
7
7
 
8
8
  export type FieldsetLegendProps = ComponentProps< typeof _Fieldset.Legend > & {
9
9
  children?: React.ReactNode;
10
+ /**
11
+ * Whether to visually hide the legend while keeping it accessible
12
+ * to screen readers.
13
+ *
14
+ * @default false
15
+ */
16
+ hideFromVision?: boolean;
10
17
  };
11
18
 
12
19
  export type FieldsetDescriptionProps = ComponentProps< 'p' > & {
@@ -9,13 +9,17 @@ import type { InputLayoutSlotProps } from './types';
9
9
  export const InputLayoutSlot = forwardRef<
10
10
  HTMLDivElement,
11
11
  InputLayoutSlotProps
12
- >( function InputLayoutSlot( { padding = 'default', ...restProps }, ref ) {
12
+ >( function InputLayoutSlot(
13
+ { padding = 'default', className, ...restProps },
14
+ ref
15
+ ) {
13
16
  return (
14
17
  <div
15
18
  ref={ ref }
16
19
  className={ clsx(
17
20
  styles[ 'input-layout-slot' ],
18
- styles[ `is-padding-${ padding }` ]
21
+ styles[ `is-padding-${ padding }` ],
22
+ className
19
23
  ) }
20
24
  { ...restProps }
21
25
  />
package/src/index.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  export * from './badge';
2
2
  export * from './button';
3
+ export * as Card from './card';
4
+ export * as Collapsible from './collapsible';
5
+ export * as CollapsibleCard from './collapsible-card';
3
6
  export * as Dialog from './dialog';
4
7
  export * from './form/primitives';
5
8
  export * from './icon';
6
9
  export * from './icon-button';
10
+ export * from './link';
7
11
  export * as Notice from './notice';
8
12
  export * from './stack';
9
13
  export * as Tabs from './tabs';
14
+ export * from './text';
10
15
  export * as Tooltip from './tooltip';
11
16
  export * from './visually-hidden';
@@ -0,0 +1 @@
1
+ export { Link } from './link';
@@ -0,0 +1,73 @@
1
+ import { useRender, mergeProps } from '@base-ui/react';
2
+ import clsx from 'clsx';
3
+ import { forwardRef } from '@wordpress/element';
4
+ import { __ } from '@wordpress/i18n';
5
+ import { type LinkProps } from './types';
6
+ import resetStyles from '../utils/css/resets.module.css';
7
+ import focusStyles from '../utils/css/focus.module.css';
8
+ import styles from './style.module.css';
9
+
10
+ /**
11
+ * A styled anchor element with support for semantic color tones and an
12
+ * unstyled escape hatch.
13
+ */
14
+ export const Link = forwardRef< HTMLAnchorElement, LinkProps >( function Link(
15
+ {
16
+ children,
17
+ variant = 'default',
18
+ tone = 'brand',
19
+ openInNewTab = false,
20
+ render,
21
+ className,
22
+ onClick,
23
+ ...props
24
+ },
25
+ ref
26
+ ) {
27
+ const isInternalAnchor = !! props.href?.startsWith( '#' );
28
+
29
+ const handleClick = ( event: React.MouseEvent< HTMLAnchorElement > ) => {
30
+ if ( openInNewTab && isInternalAnchor ) {
31
+ event.preventDefault();
32
+ }
33
+ onClick?.( event );
34
+ };
35
+
36
+ const element = useRender( {
37
+ render,
38
+ defaultTagName: 'a',
39
+ ref,
40
+ props: mergeProps< 'a' >( props, {
41
+ className: clsx(
42
+ resetStyles[ 'box-sizing' ],
43
+ focusStyles[ 'outset-ring--focus' ],
44
+ variant !== 'unstyled' && styles.link,
45
+ variant !== 'unstyled' && styles[ `is-${ tone }` ],
46
+ variant === 'unstyled' && styles[ 'is-unstyled' ],
47
+ openInNewTab && styles[ 'has-link-icon' ],
48
+ className
49
+ ),
50
+ onClick: handleClick,
51
+ target: openInNewTab ? '_blank' : undefined,
52
+ children: openInNewTab ? (
53
+ <>
54
+ <span className={ styles[ 'link-contents' ] }>
55
+ { children }
56
+ </span>
57
+ <span
58
+ className={ styles[ 'link-icon' ] }
59
+ role="img"
60
+ aria-label={
61
+ /* translators: accessibility text appended to link text */
62
+ __( '(opens in a new tab)' )
63
+ }
64
+ />
65
+ </>
66
+ ) : (
67
+ children
68
+ ),
69
+ } ),
70
+ } );
71
+
72
+ return element;
73
+ } );
@@ -0,0 +1,92 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Link } from '../index';
3
+ import { Stack } from '../../stack';
4
+ import { Text } from '../../text';
5
+
6
+ const meta: Meta< typeof Link > = {
7
+ title: 'Design System/Components/Link',
8
+ component: Link,
9
+ };
10
+ export default meta;
11
+
12
+ type Story = StoryObj< typeof Link >;
13
+
14
+ export const Default: Story = {
15
+ args: {
16
+ children: 'Learn more',
17
+ href: '#',
18
+ },
19
+ };
20
+
21
+ /**
22
+ * Note: `tone` has no effect on `unstyled` variant
23
+ */
24
+ export const AllTonesAndVariants: Story = {
25
+ ...Default,
26
+ argTypes: {
27
+ tone: {
28
+ control: false,
29
+ },
30
+ variant: {
31
+ control: false,
32
+ },
33
+ },
34
+ render: ( args ) => (
35
+ <Stack direction="column" gap="lg">
36
+ { ( [ 'brand', 'neutral' ] as const ).map( ( tone ) =>
37
+ ( [ 'default', 'unstyled' ] as const ).map( ( variant ) => (
38
+ <Stack
39
+ direction="column"
40
+ gap="xs"
41
+ key={ `${ tone }-${ variant }` }
42
+ >
43
+ <Text variant="heading-sm">
44
+ { tone } tone, { variant } variant
45
+ </Text>
46
+ <Link { ...args } tone={ tone } variant={ variant } />
47
+ </Stack>
48
+ ) )
49
+ ) }
50
+ </Stack>
51
+ ),
52
+ };
53
+
54
+ export const Inline: Story = {
55
+ ...Default,
56
+ args: {
57
+ ...Default.args,
58
+ children: 'inline link',
59
+ },
60
+ render: ( args ) => (
61
+ <Text variant="body-md" render={ <p /> }>
62
+ This is a paragraph with an <Link { ...args } /> that inherits its
63
+ typography from the parent Text component.
64
+ </Text>
65
+ ),
66
+ };
67
+
68
+ /**
69
+ * When composing `Text` and `Link` via the `render` prop, the order matters:
70
+ * - `<Text render={ <Link /> } />` renders an `<a>` element (Link's default tag wins).
71
+ * - `<Link render={ <Text /> } />` renders a `<span>` element (Text's default tag wins).
72
+ */
73
+ export const Standalone: Story = {
74
+ args: {
75
+ href: '#',
76
+ },
77
+ argTypes: {
78
+ children: {
79
+ control: false,
80
+ },
81
+ },
82
+ render: ( args ) => (
83
+ <Stack direction="column" gap="md">
84
+ <Text variant="body-md" render={ <Link { ...args } /> }>
85
+ A standalone link with body-md typography
86
+ </Text>
87
+ <Text variant="body-sm" render={ <Link { ...args } /> }>
88
+ A standalone link with body-sm typography
89
+ </Text>
90
+ </Stack>
91
+ ),
92
+ };