@repobit/dex-system-design 0.25.11 → 0.26.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 (336) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +131 -8
  3. package/package.json +95 -13
  4. package/schemas/component-manifest.json +3294 -405
  5. package/schemas/manifest-omissions.json +173 -0
  6. package/src/blog/src/breakpoints.js +6 -6
  7. package/src/blog/src/content/blog-atoms-showcase.js +1 -1
  8. package/src/blog/src/content/blog-page-layout.js +2 -2
  9. package/src/blog/src/lib/article-toc.js +7 -7
  10. package/src/blog/src/shell/blog-shell.js +1 -1
  11. package/src/blog/src/tokens/blog.js +2 -2
  12. package/src/blog/src/tokens/core.js +34 -35
  13. package/src/blog/src/tokens/legacy.js +0 -9
  14. package/src/blog/tokens/blog.js +2 -2
  15. package/src/blog/tokens/core.js +34 -35
  16. package/src/blog/tokens/legacy.js +0 -9
  17. package/src/blog/tokens.css +36 -46
  18. package/src/components/accordion/accordion-bg.css.js +83 -59
  19. package/src/components/accordion/accordion-bg.js +29 -10
  20. package/src/components/accordion/accordion-bg.stories.js +67 -67
  21. package/src/components/accordion/accordion-keyboard.js +80 -0
  22. package/src/components/accordion/accordion.css.js +142 -80
  23. package/src/components/accordion/accordion.js +37 -10
  24. package/src/components/accordion/accordion.stories.js +7 -7
  25. package/src/components/agent-endpoints/agent-endpoints.css.js +36 -39
  26. package/src/components/agent-endpoints/agent-endpoints.js +30 -20
  27. package/src/components/agent-endpoints/agent-endpoints.stories.js +1 -1
  28. package/src/components/ai-guardian-demo.stories.js +91 -91
  29. package/src/components/alert/alert.css.js +198 -0
  30. package/src/components/alert/alert.js +103 -0
  31. package/src/components/alert/alert.stories.js +206 -0
  32. package/src/components/anchor/anchor-nav.css.js +213 -101
  33. package/src/components/anchor/anchor-nav.js +26 -40
  34. package/src/components/anchor/anchor.stories.js +22 -12
  35. package/src/components/annotation/annotation.css.js +58 -0
  36. package/src/components/annotation/annotation.js +67 -0
  37. package/src/components/annotation/annotation.stories.js +63 -0
  38. package/src/components/architecture.stories.js +285 -0
  39. package/src/components/atoms.stories.js +342 -0
  40. package/src/components/avatar/avatar.css.js +67 -0
  41. package/src/components/avatar/avatar.js +42 -0
  42. package/src/components/avatar/avatar.stories.js +95 -0
  43. package/src/components/awards/awards-flex.css.js +209 -23
  44. package/src/components/awards/awards-flex.js +66 -7
  45. package/src/components/awards/awards-flex.stories.js +1 -1
  46. package/src/components/awards/{awards-light.stories.js → awards-summary.stories.js} +54 -64
  47. package/src/components/awards/awards.css.js +116 -58
  48. package/src/components/awards/awards.js +102 -28
  49. package/src/components/awards/awards.stories.js +1 -1
  50. package/src/components/back/back.css.js +51 -22
  51. package/src/components/back/back.js +28 -7
  52. package/src/components/back/back.stories.js +9 -3
  53. package/src/components/badge/badge.css.js +158 -46
  54. package/src/components/badge/badge.js +62 -74
  55. package/src/components/badge/badge.stories.js +176 -248
  56. package/src/components/bento-grid/bento-grid.css.js +21 -16
  57. package/src/components/bento-grid/bento-grid.js +16 -4
  58. package/src/components/bento-grid/bento-grid.stories.js +19 -18
  59. package/src/components/bento-grid/bento-item.css.js +52 -28
  60. package/src/components/bento-grid/bento-item.js +2 -2
  61. package/src/components/blockquote/blockquote.css.js +47 -0
  62. package/src/components/blockquote/blockquote.js +29 -0
  63. package/src/components/blockquote/blockquote.stories.js +62 -0
  64. package/src/components/breadcrumb/breadcrumb.css.js +92 -22
  65. package/src/components/breadcrumb/breadcrumb.js +56 -11
  66. package/src/components/button/button.css.js +531 -161
  67. package/src/components/button/button.js +206 -214
  68. package/src/components/button/button.stories.js +322 -174
  69. package/src/components/caption/caption.css.js +21 -0
  70. package/src/components/caption/caption.js +31 -0
  71. package/src/components/caption/caption.stories.js +62 -0
  72. package/src/components/card-features/card-features.css.js +14 -22
  73. package/src/components/card-features/card-features.js +68 -9
  74. package/src/components/card-features/card-features.stories.js +11 -11
  75. package/src/components/cards/card.css.js +60 -81
  76. package/src/components/cards/card.js +56 -25
  77. package/src/components/cards/card.stories.js +39 -39
  78. package/src/components/carousel/carousel.css.js +93 -99
  79. package/src/components/carousel/carousel.js +32 -4
  80. package/src/components/checkbox/checkbox.css.js +111 -93
  81. package/src/components/checkbox/checkbox.js +64 -90
  82. package/src/components/checkbox/checkbox.stories.js +96 -29
  83. package/src/components/code/code.css.js +70 -0
  84. package/src/components/code/code.js +40 -0
  85. package/src/components/code/code.stories.js +73 -0
  86. package/src/components/compare/compare.css.js +179 -43
  87. package/src/components/compare/compare.js +52 -25
  88. package/src/components/compare/compare.stories.js +61 -4
  89. package/src/components/comparison-panel/comparison-panel.css.js +38 -51
  90. package/src/components/comparison-panel/comparison-panel.js +5 -4
  91. package/src/components/comparison-panel/comparison-panel.stories.js +1 -1
  92. package/src/components/compatibility-table/compatibility-table.css.js +180 -80
  93. package/src/components/compatibility-table/compatibility-table.js +37 -9
  94. package/src/components/control-alignment.stories.js +393 -0
  95. package/src/components/cta-banner/cta-banner.css.js +41 -13
  96. package/src/components/cta-banner/cta-banner.js +6 -5
  97. package/src/components/cta-banner/cta-banner.stories.js +43 -98
  98. package/src/components/display/display.css.js +25 -1
  99. package/src/components/display/display.js +46 -13
  100. package/src/components/display/display.stories.js +270 -40
  101. package/src/components/divider/divider-horizontal.js +21 -5
  102. package/src/components/divider/divider-vertical.js +15 -7
  103. package/src/components/divider/divider.css.js +2 -2
  104. package/src/components/divider/divider.stories.js +38 -34
  105. package/src/components/dropdown/dropdown.css.js +93 -126
  106. package/src/components/dropdown/dropdown.js +114 -34
  107. package/src/components/dropdown/dropdown.stories.js +56 -24
  108. package/src/components/features/features.css.js +24 -11
  109. package/src/components/features/features.js +7 -3
  110. package/src/components/features/features.stories.js +37 -37
  111. package/src/components/features-tab/features-tab.css.js +19 -8
  112. package/src/components/features-tab/features-tab.js +6 -5
  113. package/src/components/features-tab/features-tab.stories.js +23 -23
  114. package/src/components/fieldset/fieldset.css.js +30 -0
  115. package/src/components/fieldset/fieldset.js +28 -0
  116. package/src/components/fieldset/fieldset.stories.js +84 -0
  117. package/src/components/footer/footer-links-group.css.js +10 -14
  118. package/src/components/footer/footer-links-group.js +4 -3
  119. package/src/components/footer/footer-lp.css.js +59 -30
  120. package/src/components/footer/footer-lp.js +17 -12
  121. package/src/components/footer/footer-lp.stories.js +63 -75
  122. package/src/components/footer/footer-nav-menu.css.js +26 -4
  123. package/src/components/footer/footer-nav-menu.js +3 -2
  124. package/src/components/footer/footer.css.js +146 -264
  125. package/src/components/footer/footer.js +176 -106
  126. package/src/components/footer/footer.stories.js +9 -9
  127. package/src/components/footer/localeMap.js +1 -1
  128. package/src/components/grid/grid.css.js +1 -1
  129. package/src/components/grid/grid.js +49 -7
  130. package/src/components/grid/grid.stories.js +111 -0
  131. package/src/components/header/header-code.css.js +61 -51
  132. package/src/components/header/header-code.js +28 -14
  133. package/src/components/header/header-code.stories.js +5 -5
  134. package/src/components/header/header.css.js +655 -176
  135. package/src/components/header/header.js +269 -36
  136. package/src/components/header/header.stories.js +841 -18
  137. package/src/components/heading/heading.css.js +14 -17
  138. package/src/components/heading/heading.js +48 -33
  139. package/src/components/heading/heading.stories.js +27 -25
  140. package/src/components/help-text/help-text.css.js +21 -0
  141. package/src/components/help-text/help-text.js +25 -0
  142. package/src/components/help-text/help-text.stories.js +66 -0
  143. package/src/components/highlight/highlight.css.js +86 -64
  144. package/src/components/highlight/highlight.js +18 -27
  145. package/src/components/highlight/highlight.stories.js +14 -3
  146. package/src/components/icons/analysis-icon.js +6 -16
  147. package/src/components/icons/arrow-down-icon.js +6 -19
  148. package/src/components/icons/arrow-icon.js +44 -0
  149. package/src/components/icons/arrow-right-icon.js +30 -23
  150. package/src/components/icons/arrow-up-icon.js +19 -18
  151. package/src/components/icons/bd-ai-icon.js +1 -1
  152. package/src/components/icons/brand-marks.stories.js +279 -0
  153. package/src/components/icons/brand-signature-icon.js +42 -0
  154. package/src/components/icons/check-icon.js +27 -0
  155. package/src/components/icons/circle-info-icon.js +33 -0
  156. package/src/components/icons/close-icon.js +6 -18
  157. package/src/components/icons/family-icon.js +8 -20
  158. package/src/components/icons/globe-icon.js +6 -18
  159. package/src/components/icons/guide-icon.js +6 -18
  160. package/src/components/icons/icon-element.js +99 -0
  161. package/src/components/icons/icons.js +36 -0
  162. package/src/components/icons/icons.stories.js +184 -52
  163. package/src/components/icons/individual-icon.js +6 -18
  164. package/src/components/icons/laurel-icon.js +9 -19
  165. package/src/components/icons/machine-learning-icon.js +6 -17
  166. package/src/components/icons/marks.js +41 -0
  167. package/src/components/icons/minus-icon.js +7 -21
  168. package/src/components/icons/missing-icon.js +52 -0
  169. package/src/components/icons/plus-icon.js +7 -21
  170. package/src/components/icons/quote-icon.js +19 -19
  171. package/src/components/image/image.css.js +72 -25
  172. package/src/components/image/image.js +11 -6
  173. package/src/components/image/image.stories.js +2 -2
  174. package/src/components/input/custom-form.stories.js +214 -62
  175. package/src/components/input/input-clipboard.css.js +51 -144
  176. package/src/components/input/input-clipboard.js +31 -15
  177. package/src/components/input/input.css.js +8 -146
  178. package/src/components/input/input.js +93 -38
  179. package/src/components/install-options/install-options.css.js +84 -77
  180. package/src/components/install-options/install-options.js +33 -30
  181. package/src/components/install-options/install-options.stories.js +3 -3
  182. package/src/components/label/label.css.js +35 -0
  183. package/src/components/label/label.js +38 -0
  184. package/src/components/label/label.stories.js +82 -0
  185. package/src/components/light-carousel/light-carousel.css.js +111 -63
  186. package/src/components/light-carousel/light-carousel.js +85 -11
  187. package/src/components/light-carousel/light-carousel.stories.js +126 -82
  188. package/src/components/link/link.css.js +150 -99
  189. package/src/components/link/link.js +86 -11
  190. package/src/components/link/link.stories.js +124 -30
  191. package/src/components/list/list-item/list-item.css.js +74 -22
  192. package/src/components/list/list-item/list-item.js +32 -10
  193. package/src/components/list/list-item/list-item.stories.js +23 -23
  194. package/src/components/list/list.css.js +8 -2
  195. package/src/components/list/list.js +3 -2
  196. package/src/components/list/list.stories.js +222 -0
  197. package/src/components/modal/modal.css.js +114 -24
  198. package/src/components/modal/modal.js +83 -7
  199. package/src/components/modal/modal.stories.js +9 -9
  200. package/src/components/overview/overview.css.js +19 -14
  201. package/src/components/overview/overview.js +6 -5
  202. package/src/components/overview/overview.stories.js +10 -10
  203. package/src/components/page-switcher/page-switcher.css.js +97 -40
  204. package/src/components/page-switcher/page-switcher.js +28 -5
  205. package/src/components/page-switcher/page-switcher.stories.js +95 -0
  206. package/src/components/paragraph/paragraph.css.js +15 -10
  207. package/src/components/paragraph/paragraph.js +41 -44
  208. package/src/components/paragraph/paragraph.stories.js +56 -21
  209. package/src/components/picture/picture.css.js +103 -44
  210. package/src/components/picture/picture.js +100 -30
  211. package/src/components/picture/picture.stories.js +1 -1
  212. package/src/components/pricing-cards/new-pricing-card.js +3 -3
  213. package/src/components/pricing-cards/new-pricing.css.js +24 -15
  214. package/src/components/pricing-cards/pricing-card-actions.css.js +1 -1
  215. package/src/components/pricing-cards/pricing-card-actions.js +3 -4
  216. package/src/components/pricing-cards/pricing-card-container.css.js +3 -3
  217. package/src/components/pricing-cards/pricing-card-container.js +3 -2
  218. package/src/components/pricing-cards/pricing-card-header.css.js +10 -8
  219. package/src/components/pricing-cards/pricing-card-header.js +6 -7
  220. package/src/components/pricing-cards/pricing-card-pricing.css.js +16 -9
  221. package/src/components/pricing-cards/pricing-card-pricing.js +8 -6
  222. package/src/components/pricing-cards/pricing-card-show-more.css.js +11 -3
  223. package/src/components/pricing-cards/pricing-card-show-more.js +8 -3
  224. package/src/components/pricing-cards/pricing-card.css.js +244 -190
  225. package/src/components/pricing-cards/pricing-card.js +54 -70
  226. package/src/components/pricing-cards/pricing-feature-item.css.js +25 -8
  227. package/src/components/pricing-cards/pricing-feature-item.js +8 -3
  228. package/src/components/process-steps/process-steps.css.js +26 -14
  229. package/src/components/process-steps/process-steps.js +23 -6
  230. package/src/components/process-steps/process-steps.stories.js +1 -1
  231. package/src/components/progress/progress.css.js +80 -0
  232. package/src/components/progress/progress.js +42 -0
  233. package/src/components/progress/progress.stories.js +79 -0
  234. package/src/components/quick-answer/quick-answer.css.js +75 -13
  235. package/src/components/quick-answer/quick-answer.js +8 -3
  236. package/src/components/radio/radio.css.js +92 -128
  237. package/src/components/radio/radio.js +106 -127
  238. package/src/components/radio/radio.stories.js +54 -19
  239. package/src/components/review/review-grid.css.js +5 -6
  240. package/src/components/review/review-grid.js +14 -11
  241. package/src/components/review/review.css.js +11 -6
  242. package/src/components/review/review.js +3 -8
  243. package/src/components/shared/ensemble.css.js +93 -0
  244. package/src/components/shared/form-field.css.js +206 -0
  245. package/src/components/shared/prop-vocabulary.js +48 -0
  246. package/src/components/shared/token-scales.js +306 -0
  247. package/src/components/skeleton/skeleton.css.js +34 -0
  248. package/src/components/skeleton/skeleton.js +32 -0
  249. package/src/components/skeleton/skeleton.stories.js +70 -0
  250. package/src/components/spinner/spinner.css.js +62 -0
  251. package/src/components/spinner/spinner.js +38 -0
  252. package/src/components/spinner/spinner.stories.js +92 -0
  253. package/src/components/spotlight-section/spotlight-section.css.js +14 -13
  254. package/src/components/spotlight-section/spotlight-section.js +42 -39
  255. package/src/components/switch/switch.css.js +103 -0
  256. package/src/components/switch/switch.js +44 -0
  257. package/src/components/switch/switch.stories.js +86 -0
  258. package/src/components/tab-list/content-switcher.stories.js +189 -0
  259. package/src/components/tab-list/tab-list.css.js +250 -0
  260. package/src/components/tab-list/tab-list.js +184 -0
  261. package/src/components/tab-list/tab-list.stories.js +221 -0
  262. package/src/components/tabs/tabs.css.js +14 -123
  263. package/src/components/tabs/tabs.js +16 -19
  264. package/src/components/tabs/tabs.stories.js +95 -95
  265. package/src/components/tag/tag.css.js +54 -0
  266. package/src/components/tag/tag.js +32 -0
  267. package/src/components/tag/tag.stories.js +126 -0
  268. package/src/components/terminal/terminal-message.css.js +164 -60
  269. package/src/components/terminal/terminal-message.js +5 -5
  270. package/src/components/terminal/terminal-message.stories.js +70 -1
  271. package/src/components/termsOfUse/terms.css.js +25 -20
  272. package/src/components/termsOfUse/terms.js +13 -9
  273. package/src/components/termsOfUse/terms.stories.js +1 -1
  274. package/src/components/textarea/textarea.css.js +51 -0
  275. package/src/components/textarea/textarea.js +136 -0
  276. package/src/components/textarea/textarea.stories.js +191 -0
  277. package/src/components/tooltip/tooltip.css.js +75 -0
  278. package/src/components/tooltip/tooltip.js +156 -0
  279. package/src/components/tooltip/tooltip.stories.js +161 -0
  280. package/src/components/trust-panel/trust-panel.css.js +85 -33
  281. package/src/components/trust-panel/trust-panel.js +18 -10
  282. package/src/components/trust-panel/trust-panel.stories.js +1 -1
  283. package/src/components/ultimate-security-demo.stories.js +431 -0
  284. package/src/components/video/video.css.js +17 -0
  285. package/src/components/video/video.js +40 -0
  286. package/src/components/video/video.stories.js +106 -0
  287. package/src/components/video-player/video-player.css.js +67 -11
  288. package/src/components/video-player/video-player.js +35 -4
  289. package/src/components/video-player/video-player.stories.js +1 -1
  290. package/src/components/visual-alignment.stories.js +411 -0
  291. package/src/components/visually-hidden/visually-hidden.css.js +24 -0
  292. package/src/components/visually-hidden/visually-hidden.js +16 -0
  293. package/src/components/visually-hidden/visually-hidden.stories.js +56 -0
  294. package/src/stories/Button.stories.js +17 -17
  295. package/src/stories/Header.stories.js +10 -10
  296. package/src/stories/Page.js +1 -1
  297. package/src/stories/Page.stories.js +6 -6
  298. package/src/tokens/borders.stories.js +339 -0
  299. package/src/tokens/color-responsibilities.stories.js +227 -0
  300. package/src/tokens/dark-derivation.js +309 -0
  301. package/src/tokens/dark-findings.js +197 -0
  302. package/src/tokens/dark-mode.stories.js +207 -0
  303. package/src/tokens/dimension.stories.js +251 -0
  304. package/src/tokens/elevation.stories.js +143 -0
  305. package/src/tokens/focus.stories.js +276 -0
  306. package/src/tokens/fonts.css +20 -20
  307. package/src/tokens/fonts.stories.js +354 -374
  308. package/src/tokens/layout.css +25 -11
  309. package/src/tokens/motion.stories.js +231 -0
  310. package/src/tokens/shape.stories.js +272 -0
  311. package/src/tokens/spacing-rhythm.stories.js +359 -0
  312. package/src/tokens/spacing.stories.js +208 -703
  313. package/src/tokens/story-chrome.js +503 -0
  314. package/src/tokens/surfaces.stories.js +224 -0
  315. package/src/tokens/text-on-surface.stories.js +164 -0
  316. package/src/tokens/token-map.js +220 -0
  317. package/src/tokens/token-map.stories.js +257 -0
  318. package/src/tokens/token-purposes.js +266 -0
  319. package/src/tokens/tokens-grid.stories.js +388 -900
  320. package/src/tokens/tokens.css +1633 -1045
  321. package/src/tokens/tokens.js +1130 -492
  322. package/src/tokens/tokens.stories.js +137 -47
  323. package/src/tokens/type-ceiling-words.js +299 -0
  324. package/src/tokens/typography.stories.js +1003 -965
  325. package/src/tokens/vertical-rhythm.stories.js +224 -0
  326. package/src/components/awards/awards-icon.js +0 -56
  327. package/src/components/awards/awards-light.js +0 -240
  328. package/src/components/banner/banner.js +0 -60
  329. package/src/components/banner/banner.stories.js +0 -51
  330. package/src/components/button/icons.js +0 -24
  331. package/src/components/input/index.js +0 -0
  332. package/src/components/light-carousel/light-carousel-simple.css.js +0 -183
  333. package/src/components/light-carousel/light-carousel-simple.js +0 -74
  334. package/src/tokens/colors.js +0 -121
  335. package/src/tokens/typography.css.js +0 -207
  336. package/src/tokens/typography.js +0 -0
@@ -1,8 +1,24 @@
1
1
  import { LitElement, html } from "lit";
2
- import { tokens } from "../../tokens/tokens.js";
2
+ // Installs the design tokens into document scope; see src/tokens/tokens.js
3
+ import "../../tokens/tokens.js";
3
4
  import radioCSS from "./radio.css.js";
4
5
 
6
+ const NEXT_KEYS = ["ArrowDown", "ArrowRight"];
7
+ const PREV_KEYS = ["ArrowUp", "ArrowLeft"];
8
+
9
+ /**
10
+ * A single radio. Selecting one clears the others sharing its `name`, and the
11
+ * arrow keys move between them.
12
+ *
13
+ * The group behaviour is worked out by finding same-name siblings in the same
14
+ * root, because there is no group element yet. That covers exclusivity, arrow
15
+ * navigation and the roving tabindex, but it cannot supply role="radiogroup"
16
+ * or a group label — those need an owning element, so a screen reader still
17
+ * gets no "1 of 4" context. A bd-radio-group is the missing piece.
18
+ */
5
19
  class BdRadio extends LitElement {
20
+ static styles = [radioCSS];
21
+
6
22
  static properties = {
7
23
  name : { type: String },
8
24
  value : { type: String },
@@ -11,105 +27,109 @@ class BdRadio extends LitElement {
11
27
  label : { type: String }
12
28
  };
13
29
 
14
- static styles = [tokens, radioCSS];
15
-
16
30
  constructor() {
17
31
  super();
18
32
  this.name = "";
19
33
  this.value = "";
34
+ // checked was left undefined, which rendered aria-checked="" — not a valid
35
+ // value, so an unselected radio announced no state at all
36
+ this.checked = false;
20
37
  this.disabled = false;
21
38
  this.label = "";
22
- this._hadKeyboardFocus = false;
23
-
24
- this._handleDocumentKeydown = (e) => {
25
- if (e.key === "Tab") {
26
- this._hadKeyboardFocus = true;
27
- }
28
- };
29
-
30
- this._handleDocumentMousedown = () => {
31
- setTimeout(() => {
32
- this._hadKeyboardFocus = false;
33
- }, 0);
34
- };
35
39
  }
36
40
 
37
- connectedCallback() {
38
- super.connectedCallback();
39
- document.addEventListener("keydown", this._handleDocumentKeydown);
40
- document.addEventListener("mousedown", this._handleDocumentMousedown);
41
+ /** Same-name radios in this root, in document order. */
42
+ get _group() {
43
+ if (!this.name) return [this];
44
+ const root = this.getRootNode();
45
+ const scope = root instanceof Document || root instanceof ShadowRoot ? root : document;
46
+ return [...scope.querySelectorAll(`bd-radio[name="${this.name}"]`)];
41
47
  }
42
48
 
43
- disconnectedCallback() {
44
- super.disconnectedCallback();
45
- document.removeEventListener("keydown", this._handleDocumentKeydown);
46
- document.removeEventListener("mousedown", this._handleDocumentMousedown);
49
+ /**
50
+ * One tab stop per group, as the radiogroup pattern requires: the selected
51
+ * radio, or the first enabled one when nothing is selected yet.
52
+ */
53
+ get _tabbable() {
54
+ if (this.disabled) return false;
55
+ const group = this._group;
56
+ if (group.some(r => r.checked)) return this.checked;
57
+ return group.find(r => !r.disabled) === this;
47
58
  }
48
59
 
49
- handleKeydown(e) {
50
- if (this.disabled) return;
51
- if (e.key === " " || e.key === "Enter") {
52
- e.preventDefault();
53
- this.handleChange();
54
- }
55
- }
60
+ _select() {
61
+ if (this.disabled || this.checked) return;
56
62
 
57
- handleFocus(e) {
58
- if (this._hadKeyboardFocus) {
59
- e.target.classList.add("focus");
63
+ // the defining behaviour of a radio, and the part that was missing: the
64
+ // rest of the group has to let go
65
+ for (const radio of this._group) {
66
+ if (radio !== this && radio.checked) radio.checked = false;
60
67
  }
61
- }
62
- handleBlur(e) {
63
- e.target.classList.remove("focus");
64
- }
65
- handleChange() {
66
- if (this.disabled) {
67
- console.warn("Blocked because disabled = true");
68
- return;
69
- }
70
-
71
68
  this.checked = true;
72
69
 
73
- this.updateComplete.then(() => {
74
- const outer = this.renderRoot.querySelector(".bd-outer");
75
- if (outer) {
76
- outer.focus();
77
- }
78
- });
79
-
80
70
  this.dispatchEvent(
81
71
  new CustomEvent("change", {
82
- detail : { value: this.value },
72
+ detail : { name: this.name, value: this.value },
83
73
  bubbles : true,
84
74
  composed: true
85
75
  })
86
76
  );
87
77
  }
88
78
 
79
+ /** Move to the next or previous enabled radio, select it, and focus it. */
80
+ _move(step) {
81
+ const enabled = this._group.filter(r => !r.disabled);
82
+ if (enabled.length < 2) return;
83
+ const from = enabled.indexOf(this);
84
+ const next = enabled[(from + step + enabled.length) % enabled.length];
85
+ next._select();
86
+ next.updateComplete.then(() => {
87
+ next.shadowRoot.querySelector(".bd-radio")?.focus();
88
+ });
89
+ }
90
+
91
+ _handleKeydown(e) {
92
+ if (this.disabled) return;
93
+
94
+ if (NEXT_KEYS.includes(e.key)) {
95
+ e.preventDefault();
96
+ this._move(1);
97
+ } else if (PREV_KEYS.includes(e.key)) {
98
+ e.preventDefault();
99
+ this._move(-1);
100
+ } else if (e.key === " ") {
101
+ e.preventDefault();
102
+ this._select();
103
+ }
104
+ }
105
+
89
106
  render() {
90
107
  return html`
91
- <label class="bd-radio ${this.disabled ? "disabled" : ""}">
92
- <span
93
- class="bd-outer ${this.checked && !this.disabled ? "checked" : ""}"
94
- role="radio"
95
- aria-checked="${this.checked}"
96
- aria-disabled="${this.disabled}"
97
- tabindex="${this.disabled ? -1 : 0}"
98
- @click=${this.handleChange}
99
- @keydown=${this.handleKeydown}
100
- @focus=${this.handleFocus}
101
- @blur=${this.handleBlur}
102
- >
103
- <span class="bd-inner ${this.checked ? "visible" : ""}"></span>
104
- </span>
105
- <span class="radio-label">${this.label}</span>
106
- </label>
108
+ <div
109
+ class="bd-radio"
110
+ role="radio"
111
+ aria-checked="${this.checked ? "true" : "false"}"
112
+ aria-disabled="${this.disabled ? "true" : "false"}"
113
+ tabindex="${this._tabbable ? 0 : -1}"
114
+ @click=${this._select}
115
+ @keydown=${this._handleKeydown}
116
+ >
117
+ <span class="box" part="box"><span class="dot"></span></span>
118
+ <span class="label" part="label"><slot>${this.label}</slot></span>
119
+ </div>
107
120
  `;
108
121
  }
109
122
  }
110
123
 
124
+ /**
125
+ * Toggles independently despite looking like a radio, which is why it declares
126
+ * role="checkbox". It duplicates bd-checkbox with a circular skin, and a round
127
+ * control that does not behave exclusively misleads people — it should be
128
+ * retired in favour of bd-checkbox. Kept working here because it is a
129
+ * published element with stories against it.
130
+ */
111
131
  class BdToggleRadio extends LitElement {
112
- static styles = [tokens, radioCSS];
132
+ static styles = [radioCSS];
113
133
 
114
134
  static properties = {
115
135
  name : { type: String },
@@ -123,90 +143,49 @@ class BdToggleRadio extends LitElement {
123
143
  super();
124
144
  this.name = "";
125
145
  this.value = "";
146
+ this.checked = false;
126
147
  this.disabled = false;
127
148
  this.label = "";
128
- this._hadKeyboardFocus = false;
129
-
130
- this._handleDocumentKeydown = (e) => {
131
- if (e.key === "Tab") {
132
- this._hadKeyboardFocus = true;
133
- }
134
- };
135
-
136
- this._handleDocumentMousedown = () => {
137
- setTimeout(() => {
138
- this._hadKeyboardFocus = false;
139
- }, 0);
140
- };
141
- }
142
-
143
- connectedCallback() {
144
- super.connectedCallback();
145
- document.addEventListener("keydown", this._handleDocumentKeydown);
146
- document.addEventListener("mousedown", this._handleDocumentMousedown);
147
149
  }
148
150
 
149
- disconnectedCallback() {
150
- super.disconnectedCallback();
151
- document.removeEventListener("keydown", this._handleDocumentKeydown);
152
- document.removeEventListener("mousedown", this._handleDocumentMousedown);
153
- }
154
-
155
- handleClick() {
151
+ _toggle() {
156
152
  if (this.disabled) return;
157
153
  this.checked = !this.checked;
158
154
 
159
155
  this.dispatchEvent(
160
156
  new CustomEvent("change", {
161
- detail : { value: this.checked ? this.value : null },
157
+ detail : { checked: this.checked, value: this.checked ? this.value : null },
162
158
  bubbles : true,
163
159
  composed: true
164
160
  })
165
161
  );
166
162
  }
167
163
 
168
- handleFocus(e) {
169
- if (this._hadKeyboardFocus) {
170
- e.target.classList.add("focus");
171
- }
172
- }
173
-
174
- handleBlur(e) {
175
- e.target.classList.remove("focus");
176
- }
177
-
178
- handleKeydown(e) {
164
+ _handleKeydown(e) {
179
165
  if (this.disabled) return;
180
166
  if (e.key === " " || e.key === "Enter") {
181
167
  e.preventDefault();
182
- this.handleClick();
168
+ this._toggle();
183
169
  }
184
170
  }
185
171
 
186
-
187
172
  render() {
188
173
  return html`
189
- <label class="bd-radio ${this.disabled ? "disabled" : ""}">
190
- <span
191
- class="bd-outer ${this.checked && !this.disabled ? "checked" : ""}"
192
- role="checkbox"
193
- aria-checked="${this.checked}"
194
- aria-disabled="${this.disabled}"
195
- tabindex="${this.disabled ? -1 : 0}"
196
- @click=${this.handleClick}
197
- @keydown=${this.handleKeydown}
198
- @focus=${this.handleFocus}
199
- @blur=${this.handleBlur}
200
- >
201
- <span class="bd-inner ${this.checked ? "visible" : ""}"></span>
202
- </span>
203
- <span class="radio-label">${this.label}</span>
204
- </label>
174
+ <div
175
+ class="bd-radio"
176
+ role="checkbox"
177
+ aria-checked="${this.checked ? "true" : "false"}"
178
+ aria-disabled="${this.disabled ? "true" : "false"}"
179
+ tabindex="${this.disabled ? -1 : 0}"
180
+ @click=${this._toggle}
181
+ @keydown=${this._handleKeydown}
182
+ >
183
+ <span class="box" part="box"><span class="dot"></span></span>
184
+ <span class="label" part="label"><slot>${this.label}</slot></span>
185
+ </div>
205
186
  `;
206
187
  }
207
188
  }
208
- BdRadio.styles = [tokens, radioCSS];
209
- BdToggleRadio.styles = [tokens, radioCSS];
210
189
 
211
190
  customElements.define("bd-toggle-radio", BdToggleRadio);
212
191
  customElements.define("bd-radio", BdRadio);
@@ -2,39 +2,74 @@ import { html } from 'lit';
2
2
  import './radio.js';
3
3
 
4
4
  export default {
5
- title : 'Components/Radio',
5
+ title : 'Atoms/Radio',
6
6
  tags : ['autodocs'],
7
7
  parameters: {
8
8
  docs: {
9
9
  description: {
10
10
  component: `
11
- Two radio components are available:
12
-
13
- - \`<bd-radio>\` — a standard radio button (once selected, stays selected; dispatches value on change)
14
- - \`<bd-toggle-radio>\` — a toggleable radio that can be unchecked again (behaves like a checkbox with radio styling)
11
+ - \`<bd-radio>\` — one choice from a group. Selecting one clears the others
12
+ that share its \`name\`.
13
+ - \`<bd-toggle-radio>\` — **deprecated.** It is circular like a radio but
14
+ declares \`role="checkbox"\` and toggles independently, so it looks exclusive
15
+ and is not. Use \`<bd-checkbox>\` instead.
15
16
 
16
17
  ### Usage
17
18
  \`\`\`html
18
- <bd-radio name="plan" value="basic" label="Basic Plan"></bd-radio>
19
- <bd-radio name="plan" value="premium" label="Premium Plan" checked></bd-radio>
20
- <bd-toggle-radio name="addon" value="vpn" label="Add VPN"></bd-toggle-radio>
19
+ <bd-radio name="plan" value="basic" label="Basic"></bd-radio>
20
+ <bd-radio name="plan" value="premium" label="Premium" checked></bd-radio>
21
+
22
+ <!-- the label is slotted, so it can carry markup -->
23
+ <bd-radio name="plan" value="custom">See the <a href="/plans">plans</a></bd-radio>
21
24
  \`\`\`
22
25
 
23
- ### Key Differences
24
- | Feature | bd-radio | bd-toggle-radio |
25
- |---|---|---|
26
- | Can be unchecked | No | Yes |
27
- | role | \`radio\` | \`checkbox\` |
28
- | change detail | \`{ value }\` always | \`{ value }\` or \`null\` when unchecked |
26
+ ### Group behaviour
27
+ Radios find their group by \`name\` among their siblings in the same root, so no
28
+ wrapper element is needed for the behaviour below.
29
+
30
+ | | |
31
+ |---|---|
32
+ | Exclusivity | Selecting one clears every other radio with the same \`name\`. |
33
+ | Arrow keys | Up and Left move to the previous option, Down and Right to the next, wrapping at both ends and skipping disabled ones. Moving selects, as it does natively. |
34
+ | Tab | **One stop for the whole group**, on the selected radio, or the first enabled one when nothing is selected. |
35
+ | Space | Selects the focused radio. Re-selecting the current one does nothing. |
36
+
37
+ ### Still missing: role="radiogroup"
38
+ A screen reader gets each radio with its own state but **no group context** — no
39
+ "1 of 4", and no group label. That needs an owning element, so a
40
+ \`bd-radio-group\` is the missing piece. The behaviour above works without it;
41
+ the announcement does not.
42
+
43
+ ### Not form-associated
44
+ \`name\` groups the radios and is carried on the \`change\` event, but this
45
+ element does **not** participate in form submission — a \`<form>\` containing it
46
+ yields no \`FormData\` entry, because there is no native input behind it.
29
47
 
30
48
  ### Accessibility
31
- - Both use \`role\`, \`aria-checked\`, \`aria-disabled\`
32
- - Keyboard: Space and Enter toggle the state
33
- - Focus ring appears only on keyboard Tab navigation
34
- - \`tabindex="-1"\` when disabled
49
+ - The **whole row is the control**, so clicking the label selects, and the
50
+ target clears the 24px WCAG 2.5.8 asks for — a 20px circle cannot on its own.
51
+ - \`role="radio"\` with \`aria-checked\` reporting \`true\` or \`false\`, plus
52
+ \`aria-disabled\`.
53
+ - The focus ring appears on keyboard navigation only, via \`:focus-visible\`, and
54
+ is inset evenly on all four sides.
55
+
56
+ ### Appearance
57
+ Unchecked is a \`--color-blue-500\` ring on white, going \`--color-blue-600\` on
58
+ hover; checked is a \`--color-blue-500\` fill with a white centre, which reads as
59
+ a thick blue ring. The fill-and-white-mark language matches \`bd-checkbox\`.
60
+
61
+ Two deviations from the design, both deliberate:
62
+
63
+ - \`ds-radio\` specifies a neutral ring when unchecked while \`ds-checkbox\`
64
+ specifies a brand one, for the same conceptual state. The two frames disagree
65
+ and one system should not, so both controls use blue here.
66
+ - The design drops the white centre when a checked radio is disabled, leaving a
67
+ plain grey disc. It is kept, so a disabled selection still reads as selected,
68
+ and to stay consistent with \`bd-checkbox\`.
35
69
 
36
70
  ### Events
37
- - \`change\` — CustomEvent with \`detail: { value }\`
71
+ - \`change\` — \`detail: { name, value }\` on \`bd-radio\`;
72
+ \`detail: { checked, value }\` on \`bd-toggle-radio\`.
38
73
  `
39
74
  }
40
75
  }
@@ -1,19 +1,17 @@
1
- import { css } from "lit";
1
+ import { css, unsafeCSS } from "lit";
2
+ import { below } from "../shared/token-scales.js";
2
3
 
3
4
  export default css`
4
5
  :host {
5
6
  display: block;
6
7
  width: 100%;
7
8
  box-sizing: border-box;
8
- max-width: var(--layout-ensemble-max-width, 1290px);
9
- margin: 0 auto;
10
- padding-inline: var(--layout-ensemble-inline-padding, var(--spacing-24));
11
9
  }
12
10
 
13
11
  .bd-reviewgrid-title {
14
12
  text-align: center;
15
13
  margin: 0 0 var(--spacing-32);
16
- color: var(--color-neutral-1000);
14
+ color: var(--color-text-primary);
17
15
  }
18
16
 
19
17
  .bd-reviewgrid-grid {
@@ -32,7 +30,8 @@ export default css`
32
30
  min-width: 0;
33
31
  }
34
32
 
35
- @media (max-width: 899px) {
33
+ /* below lg, as install-options: three cards stack across 900-991 too. */
34
+ @media ${unsafeCSS(below('lg'))} {
36
35
  .bd-reviewgrid-grid {
37
36
  grid-template-columns: 1fr;
38
37
  }
@@ -2,6 +2,7 @@ import { LitElement, html, nothing } from "lit";
2
2
  import { tokens } from "../../tokens/tokens.js";
3
3
  import "../heading/heading.js";
4
4
  import reviewGridCSS from "./review-grid.css.js";
5
+ import { ensembleCSS } from "../shared/ensemble.css.js";
5
6
  import "./review.js";
6
7
 
7
8
 
@@ -11,7 +12,7 @@ class BdReviewGrid extends LitElement {
11
12
  _columns: { state: true }
12
13
  };
13
14
 
14
- static styles = [tokens, reviewGridCSS];
15
+ static styles = [tokens, ensembleCSS, reviewGridCSS];
15
16
 
16
17
  constructor() {
17
18
  super();
@@ -32,16 +33,18 @@ class BdReviewGrid extends LitElement {
32
33
  const singleCard = this._columns === 1;
33
34
 
34
35
  return html`
35
- ${this.title
36
- ? html`<bd-h as="h2" class="bd-reviewgrid-title"><slot name="title">${this.title}</slot></bd-h>`
37
- : nothing}
38
-
39
- <div
40
- class="bd-reviewgrid-grid ${singleCard ? "bd-reviewgrid-grid--single" : ""}"
41
- style="--bd-review-columns: ${this._columns};"
42
- role="list"
43
- >
44
- <slot @slotchange=${this._onSlotChange}></slot>
36
+ <div class="bd-ensemble">
37
+ ${this.title
38
+ ? html`<bd-h as="h2" class="bd-reviewgrid-title"><slot name="title">${this.title}</slot></bd-h>`
39
+ : nothing}
40
+
41
+ <div
42
+ class="bd-reviewgrid-grid ${singleCard ? "bd-reviewgrid-grid--single" : ""}"
43
+ style="--bd-review-columns: ${this._columns};"
44
+ role="list"
45
+ >
46
+ <slot @slotchange=${this._onSlotChange}></slot>
47
+ </div>
45
48
  </div>
46
49
  `;
47
50
  }
@@ -9,7 +9,7 @@ export default css`
9
9
  }
10
10
 
11
11
  .bd-review-card {
12
- background: var(--color-blue-50);
12
+ background: var(--status-brand-surface);
13
13
  border-radius: var(--radius-3xl);
14
14
  padding: var(--spacing-28);
15
15
  box-sizing: border-box;
@@ -19,16 +19,17 @@ export default css`
19
19
  }
20
20
 
21
21
  :host([variant="neutral"]) .bd-review-card {
22
- background: var(--color-neutral-25, #fafafa);
22
+ background: var(--color-surface-container);
23
23
  }
24
24
 
25
25
  .bd-review-mark {
26
26
  display: block;
27
+ color: var(--color-text-brand);
27
28
  margin-bottom: var(--spacing-8);
28
29
  }
29
30
 
30
31
  .bd-review-quote {
31
- --bd-paragraph-color: var(--color-neutral-900);
32
+ --bd-paragraph-color: var(--color-text-primary);
32
33
  margin-bottom: var(--spacing-16);
33
34
  flex-grow: 1;
34
35
  }
@@ -39,12 +40,16 @@ export default css`
39
40
  }
40
41
 
41
42
  .bd-review-source {
42
- --bd-paragraph-color: var(--color-neutral-900);
43
+ --bd-paragraph-color: var(--color-text-primary);
43
44
  margin: 0;
44
45
  }
45
46
 
46
47
  .bd-review-date {
47
- color: var(--color-neutral-600);
48
- font-size: var(--typography-fontSize-sm);
48
+ color: var(--color-text-muted);
49
+ font-family: var(--typography-body-small-fontFamily);
50
+ font-size: var(--typography-body-small-fontSize);
51
+ font-weight: var(--typography-body-small-fontWeight);
52
+ line-height: var(--typography-body-small-lineHeight);
53
+ letter-spacing: var(--typography-body-small-letterSpacing);
49
54
  }
50
55
  `;
@@ -35,19 +35,14 @@ class BdReview extends LitElement {
35
35
  render() {
36
36
  return html`
37
37
  <div class="bd-review-card">
38
- <bd-quote-icon
39
- class="bd-review-mark"
40
- width="34"
41
- height="35"
42
- color="var(--color-blue-500)"
43
- ></bd-quote-icon>
38
+ <bd-quote-icon class="bd-review-mark" size="xl"></bd-quote-icon>
44
39
 
45
- <bd-p kind="regular" class="bd-review-quote">
40
+ <bd-p variant="regular" class="bd-review-quote">
46
41
  <slot name="quote"></slot>
47
42
  </bd-p>
48
43
 
49
44
  <div class="bd-review-meta">
50
- <bd-p kind="small" font-weight="bold" class="bd-review-source"><slot name="source"></slot></bd-p>
45
+ <bd-p variant="small" font-weight="bold" class="bd-review-source"><slot name="source"></slot></bd-p>
51
46
  <span class="bd-review-date"><slot name="date"></slot></span>
52
47
  </div>
53
48
  </div>
@@ -0,0 +1,93 @@
1
+ import { css, unsafeCSS } from "lit";
2
+ import { above } from "./token-scales.js";
3
+
4
+ /**
5
+ * The page column: the one wrapper every page-level section composes, so that
6
+ * a page assembled from independent modules has its content on one pair of
7
+ * edges without any module knowing about the others.
8
+ *
9
+ * It exists because there was no such thing. Foundations/Grid System documents
10
+ * a container -- "each container caps content at its breakpoint's max width and
11
+ * applies the matching horizontal padding" -- and nothing in the package
12
+ * implemented it: bd-grid lays out columns, layout.css's .bd-container is a
13
+ * light-DOM class no shadow root can reach. So fourteen modules each wrote a
14
+ * wrapper of their own, and a composed page measured ten different left edges
15
+ * (16, 86, 88, 99, 113, 137, 240, 307, 310, 409px at 1440). Seven of them had
16
+ * copied 1290px from one another since 2026-04-30 with no reason recorded
17
+ * anywhere; two carried 1400; the hero used a 240px gutter that made it the one
18
+ * section whose width depended on the viewport.
19
+ *
20
+ * This is that container, implemented once, on the scale the docs promised:
21
+ * --grid-container-max-* with --grid-padding-* per breakpoint. A module
22
+ * imports it first and puts the class on the element that holds its content:
23
+ *
24
+ * static styles = [ensembleCSS, ownCSS];
25
+ * <div class="bd-ensemble bd-thing-wrapper">...</div>
26
+ *
27
+ * A band that must run full-bleed keeps its background on :host and puts the
28
+ * column inside. A heading or prose block that is deliberately narrower than
29
+ * the page keeps its own measure; that is a measure, not a column.
30
+ *
31
+ * Sections nest -- bd-features inside bd-tabs, an accordion inside a feature
32
+ * column, bd-features-tab inside a tab panel -- and a column inside a column
33
+ * must not pad or cap again. So the width and gutter are read through two
34
+ * component-local properties with the grid step as fallback, and the column
35
+ * neutralises both on its children. Custom properties inherit through slots
36
+ * and into shadow trees, so a nested column finds `none` and `0` waiting for it
37
+ * and collapses to its parent's edges. No module has to know whether it is the
38
+ * outer one. The neutralisers go on the children rather than the column
39
+ * itself, because a custom property set on an element is what that element's
40
+ * own declarations read.
41
+ *
42
+ * check:ensemble holds this: a unit the registry marks `column: true` must
43
+ * compose this file and write the class, and no section-layer stylesheet may
44
+ * spell a column of its own again.
45
+ */
46
+ export const ensembleCSS = css`
47
+ .bd-ensemble {
48
+ box-sizing: border-box;
49
+ width: 100%;
50
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-xs));
51
+ margin-inline: auto;
52
+ padding-inline: var(--bd-ensemble-inline-padding, var(--grid-padding-xs));
53
+ }
54
+
55
+ /* a column inside this column is not a column */
56
+ .bd-ensemble > * {
57
+ --bd-ensemble-max-width: none;
58
+ --bd-ensemble-inline-padding: 0;
59
+ }
60
+
61
+ @media ${unsafeCSS(above('sm'))} {
62
+ .bd-ensemble {
63
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-sm));
64
+ padding-inline: var(--bd-ensemble-inline-padding, var(--grid-padding-sm));
65
+ }
66
+ }
67
+
68
+ @media ${unsafeCSS(above('md'))} {
69
+ .bd-ensemble {
70
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-md));
71
+ padding-inline: var(--bd-ensemble-inline-padding, var(--grid-padding-md));
72
+ }
73
+ }
74
+
75
+ @media ${unsafeCSS(above('lg'))} {
76
+ .bd-ensemble {
77
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-lg));
78
+ padding-inline: var(--bd-ensemble-inline-padding, var(--grid-padding-lg));
79
+ }
80
+ }
81
+
82
+ @media ${unsafeCSS(above('xl'))} {
83
+ .bd-ensemble {
84
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-xl));
85
+ }
86
+ }
87
+
88
+ @media ${unsafeCSS(above('xxl'))} {
89
+ .bd-ensemble {
90
+ max-width: var(--bd-ensemble-max-width, var(--grid-container-max-xxl));
91
+ }
92
+ }
93
+ `;