@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
@@ -0,0 +1,184 @@
1
+ import { LitElement, html, nothing } from "lit";
2
+ // Installs the design tokens into document scope; see src/tokens/tokens.js
3
+ import "../../tokens/tokens.js";
4
+
5
+ /**
6
+ * The tab strip on its own — the row of buttons, the selected underline, and
7
+ * the WAI-ARIA tabs keyboard pattern. It owns no panel and no layout; the
8
+ * section that uses it decides what a panel is and how the page around it is
9
+ * arranged.
10
+ *
11
+ * ## Light DOM, on purpose
12
+ *
13
+ * `aria-controls` and `aria-labelledby` are IDREFs, and an IDREF does not
14
+ * resolve across a shadow boundary. A tab inside this element's shadow root
15
+ * therefore could not point at a panel living in the consumer's shadow root.
16
+ * Rendering into light DOM keeps the buttons in the same node tree as the
17
+ * panel, so the wiring is real rather than decorative.
18
+ *
19
+ * The consequence is that `static styles` would never apply. Import
20
+ * `tabListCSS` from ./tab-list.css.js and add it to the consuming component's
21
+ * own `static styles` instead.
22
+ *
23
+ * ## Two appearances, one control
24
+ *
25
+ * `appearance="switcher"` draws the Content Switcher: a pill track with the
26
+ * selected option on a white pill. It is a skin rather than its own element
27
+ * because everything below it -- role="tablist", role="tab", aria-selected,
28
+ * the roving tabindex and the Arrow/Home/End pattern with automatic
29
+ * activation -- is identical, and the light-DOM decision above is the whole
30
+ * reason this element exists. A separate <bd-content-switcher> would have had
31
+ * to re-solve the IDREF problem and keep a second copy of the keyboard
32
+ * pattern in step with this one.
33
+ *
34
+ * Storybook lists it as Components/Content Switcher so it is findable by the
35
+ * name it was designed under.
36
+ */
37
+ export class BdTabList extends LitElement {
38
+ static properties = {
39
+ tabs : { type: Array },
40
+ selected : { type: Number, reflect: true },
41
+ label : { type: String },
42
+ panelId : { type: String, attribute: "panel-id" },
43
+ idPrefix : { type: String, attribute: "id-prefix" },
44
+ /**
45
+ * The skin. Omitted gives the underlined strip this element has always
46
+ * drawn; "switcher" gives the pill-track content switcher. It is an
47
+ * appearance and not a second element on purpose -- see the note above
48
+ * the class for why.
49
+ */
50
+ appearance: { type: String, reflect: true }
51
+ };
52
+
53
+ // See the note above: light DOM is what makes the ARIA wiring work.
54
+ createRenderRoot() {
55
+ return this;
56
+ }
57
+
58
+ constructor() {
59
+ super();
60
+ this.tabs = [];
61
+ this.selected = 0;
62
+ this.label = "";
63
+ this.panelId = "";
64
+ this.idPrefix = "bd-tab";
65
+ }
66
+
67
+ tabId(index) {
68
+ return `${this.idPrefix}-${index}`;
69
+ }
70
+
71
+ _select(index, { focus = false } = {}) {
72
+ if (index === this.selected) return;
73
+ this.selected = index;
74
+ this.dispatchEvent(new CustomEvent("bd-tab-select", {
75
+ detail : { index },
76
+ bubbles : true,
77
+ composed: true
78
+ }));
79
+ if (focus) this.updateComplete.then(() => this._focusSelected());
80
+ }
81
+
82
+ /**
83
+ * The switcher's equal-width rule, which CSS alone cannot express.
84
+ *
85
+ * The design asks for every option to be as wide as the LONGEST label, so a
86
+ * translation cannot make one option wider than another. That needs three
87
+ * things at once -- the track hugs its content, the columns are equal, and
88
+ * the equal width is the widest option's -- and no grid declaration gives
89
+ * all three. Measured on the Unequal labels story: `1fr` produced 127.21 and
90
+ * 141.79 and wrapped both labels, and `minmax(max-content, 1fr)` produced
91
+ * 78.35 and 248.77. Both are correct CSS; an `fr` track needs definite free
92
+ * space to equalise, and a shrink-to-fit container has none -- and once the
93
+ * width IS definite, `fr` splits it evenly rather than matching the widest.
94
+ *
95
+ * So the widest option is measured and published as a custom property the
96
+ * grid reads. The property is cleared first: without that, a re-render after
97
+ * the labels get SHORTER would measure the widths this rule had already
98
+ * equalised and the track could never shrink again.
99
+ */
100
+ _sizeSwitcherOptions() {
101
+ if (this.appearance !== "switcher") return;
102
+ const track = this.querySelector(".bd-tabs-container");
103
+ const options = [...this.querySelectorAll(".bd-tab-button")];
104
+ if (!track || !options.length) return;
105
+
106
+ track.style.removeProperty("--bd-switcher-option-width");
107
+ const widest = Math.max(...options.map(o => o.getBoundingClientRect().width));
108
+ if (widest > 0) track.style.setProperty("--bd-switcher-option-width", `${widest}px`);
109
+ }
110
+
111
+ updated(changed) {
112
+ super.updated(changed);
113
+ this._sizeSwitcherOptions();
114
+ }
115
+
116
+ firstUpdated() {
117
+ /* The measurement is only as good as the font that was loaded when it ran.
118
+ IBM Plex arrives after first paint, and a fallback face has different
119
+ advance widths -- so a switcher measured on the fallback keeps a width
120
+ that no longer matches its own text. One re-measure once the faces are
121
+ in is enough; fonts.ready settles once per document. */
122
+ document.fonts?.ready.then(() => this._sizeSwitcherOptions());
123
+ }
124
+
125
+ _focusSelected() {
126
+ this.querySelector(`#${CSS.escape(this.tabId(this.selected))}`)?.focus();
127
+ }
128
+
129
+ /**
130
+ * Arrow keys move and activate in one step (automatic activation), which is
131
+ * the WAI-ARIA recommendation when revealing a panel is cheap — it is here,
132
+ * the panels are already in the DOM.
133
+ */
134
+ _onKeydown(event) {
135
+ const last = this.tabs.length - 1;
136
+ if (last < 0) return;
137
+
138
+ let next;
139
+ switch (event.key) {
140
+ case "ArrowRight": next = this.selected === last ? 0 : this.selected + 1; break;
141
+ case "ArrowLeft": next = this.selected === 0 ? last : this.selected - 1; break;
142
+ case "Home": next = 0; break;
143
+ case "End": next = last; break;
144
+ default: return;
145
+ }
146
+
147
+ event.preventDefault();
148
+ this._select(next, { focus: true });
149
+ }
150
+
151
+ render() {
152
+ return html`
153
+ <div
154
+ class="bd-tabs-container ${this.appearance === "switcher" ? "bd-tabs-container--switcher" : ""}"
155
+ role="tablist"
156
+ aria-label=${this.label || nothing}
157
+ @keydown=${this._onKeydown}
158
+ >
159
+ ${this.tabs.map((tab, index) => {
160
+ const isSelected = this.selected === index;
161
+ return html`
162
+ <button
163
+ type="button"
164
+ role="tab"
165
+ id=${this.tabId(index)}
166
+ aria-selected=${isSelected ? "true" : "false"}
167
+ aria-controls=${this.panelId || nothing}
168
+ tabindex=${isSelected ? "0" : "-1"}
169
+ class="bd-tab-button ${isSelected ? "bd-selected" : ""}"
170
+ @click=${() => this._select(index)}
171
+ >
172
+ ${tab.icon
173
+ ? html`<img src=${tab.icon} alt=${tab.iconAlt || ""} class="bd-tab-icon" />`
174
+ : ""}
175
+ <span class="bd-tab-label">${tab.content ?? tab.label}</span>
176
+ </button>
177
+ `;
178
+ })}
179
+ </div>
180
+ `;
181
+ }
182
+ }
183
+
184
+ customElements.define("bd-tab-list", BdTabList);
@@ -0,0 +1,221 @@
1
+ import { html } from "lit";
2
+ import "../paragraph/paragraph.js";
3
+ import "./tab-list.js";
4
+ import { tabListCSS } from "./tab-list.css.js";
5
+
6
+ /**
7
+ * The stories render <bd-tab-list> at document scope, where a component's
8
+ * `static styles` would not reach it anyway — see the note in tab-list.js on
9
+ * why this element is light DOM. Inside a real component you add `tabListCSS`
10
+ * to that component's own `static styles` instead of doing this.
11
+ */
12
+ const withStyles = template => html`
13
+ <style>${tabListCSS.cssText}</style>
14
+ <div style="font-family: var(--typography-fontFamily-sans);">${template}</div>
15
+ `;
16
+
17
+ export default {
18
+ title : "Components/Tab List",
19
+ tags : ["autodocs"],
20
+ parameters: {
21
+ docs: {
22
+ description: {
23
+ component: `
24
+
25
+ ### This atom does not style itself
26
+
27
+ \`bd-tab-list\` renders into **light DOM**, so its stylesheet is not adopted the
28
+ way every other atom's is. Importing the element gets you the markup and the
29
+ behaviour and nothing else — a consumer has to put \`tabListCSS\` into its own
30
+ \`static styles\` ahead of any override:
31
+
32
+ \`\`\`js
33
+ import { tabListCSS } from '@repobit/dex-system-design/tab-list-css';
34
+
35
+ static styles = [tabListCSS, myComponentCSS];
36
+ \`\`\`
37
+
38
+ That is deliberate — a tab strip has to sit inside its host's own layout and
39
+ inherit its type — but it is the only atom that works this way, so it is worth
40
+ saying plainly rather than leaving to be discovered.
41
+ **BdTabList** is the tab strip on its own: the row of buttons, the selected
42
+ underline, and the WAI-ARIA tabs keyboard pattern. It owns no panel and no
43
+ surrounding layout — the section using it decides what a panel is.
44
+
45
+ It was extracted from \`<bd-tabs>\`, \`<bd-tabs-component>\` and
46
+ \`<bd-tabs-component-family>\`, which each hand-rolled the same strip. Those three
47
+ now compose this one.
48
+
49
+ ### Usage
50
+ \`\`\`js
51
+ import "@repobit/dex-system-design/tab-list";
52
+ import { tabListCSS } from "@repobit/dex-system-design/tab-list-css";
53
+
54
+ static styles = [tabListCSS, myComponentCSS];
55
+ \`\`\`
56
+
57
+ \`\`\`js
58
+ html\`
59
+ <bd-tab-list
60
+ .tabs=\${[{ label: "Windows" }, { label: "macOS" }]}
61
+ .selected=\${this.selectedTab}
62
+ id-prefix="tab-btn"
63
+ panel-id="tab-panel"
64
+ @bd-tab-select=\${e => this.selectTab(e.detail.index)}
65
+ ></bd-tab-list>
66
+
67
+ <div id="tab-panel" role="tabpanel" aria-labelledby="tab-btn-\${this.selectedTab}">
68
+ …
69
+ </div>
70
+ \`
71
+ \`\`\`
72
+
73
+ ### Props
74
+ | Prop | Values | Default |
75
+ |---|---|---|
76
+ | \`tabs\` | array of \`{ label, content?, icon?, iconAlt? }\` | \`[]\` |
77
+ | \`selected\` | index of the active tab | \`0\` |
78
+ | \`label\` | \`aria-label\` for the tablist | \`''\` |
79
+ | \`panel-id\` | id of the panel every tab controls | \`''\` |
80
+ | \`id-prefix\` | prefix for generated tab ids | \`'bd-tab'\` |
81
+
82
+ \`content\` takes a Lit template and wins over \`label\` when both are set — it
83
+ exists for strips whose label is richer than a string.
84
+
85
+ ### Events
86
+ \`bd-tab-select\` — fires on selection with \`detail: { index }\`. It does not fire
87
+ when the already-selected tab is clicked.
88
+
89
+ ### Light DOM
90
+ \`aria-controls\` and \`aria-labelledby\` are IDREFs, and an IDREF does not resolve
91
+ across a shadow boundary. If the buttons lived in this element's shadow root
92
+ they could not point at a panel in the consuming component's shadow root, so
93
+ this element renders into light DOM and the styles ship as an importable
94
+ \`tabListCSS\` sheet.
95
+
96
+ ### Accessibility
97
+ \`role="tablist"\` with \`role="tab"\` buttons, roving \`tabindex\` so the strip is a
98
+ single tab stop, and arrow keys / Home / End moving between tabs with automatic
99
+ activation. Every tab points at the panel via \`aria-controls\`; the panel points
100
+ back via \`aria-labelledby\`.
101
+
102
+ ### Theming
103
+ Component-local custom properties, all optional:
104
+ \`--bd-tab-color\`, \`--bd-tab-color-selected\`, \`--bd-tab-border-color\`,
105
+ \`--bd-tab-underline-color\`, \`--bd-tab-underline-height\`,
106
+ \`--bd-tab-underline-duration\`, \`--bd-tab-padding\`, \`--bd-tab-padding-mobile\`,
107
+ \`--bd-tab-gap\`, \`--bd-tab-font-size\`, \`--bd-tab-font-weight\`.
108
+ `
109
+ }
110
+ }
111
+ }
112
+ };
113
+
114
+ const PLATFORMS = [
115
+ { label: "Windows" },
116
+ { label: "macOS" },
117
+ { label: "iOS" },
118
+ { label: "Android" }
119
+ ];
120
+
121
+ /** A strip wired to a panel, which is how it is meant to be used. */
122
+ export const Default = {
123
+ name : "Default",
124
+ render: () => withStyles(html`
125
+ <bd-tab-list
126
+ .tabs=${PLATFORMS}
127
+ .selected=${0}
128
+ label="Platform"
129
+ id-prefix="demo-tab"
130
+ panel-id="demo-panel"
131
+ @bd-tab-select=${e => {
132
+ const panel = document.getElementById("demo-panel");
133
+ panel.textContent = `${PLATFORMS[e.detail.index].label} panel`;
134
+ panel.setAttribute("aria-labelledby", `demo-tab-${e.detail.index}`);
135
+ }}
136
+ ></bd-tab-list>
137
+ <div
138
+ id="demo-panel"
139
+ role="tabpanel"
140
+ aria-labelledby="demo-tab-0"
141
+ style="padding: var(--spacing-24) 0;"
142
+ >Windows panel</div>
143
+ `)
144
+ };
145
+
146
+ /** Selection can start anywhere; the strip does not assume the first tab. */
147
+ export const PreSelected = {
148
+ name : "Pre-selected tab",
149
+ render: () => withStyles(html`
150
+ <bd-tab-list .tabs=${PLATFORMS} .selected=${2} id-prefix="pre-tab"></bd-tab-list>
151
+ `)
152
+ };
153
+
154
+ /** An optional icon per tab. Sizing stays with the consuming section. */
155
+ export const WithIcons = {
156
+ name : "With icons",
157
+ render: () => withStyles(html`
158
+ <style>.bd-tab-icon { width: 28px; height: 28px; object-fit: contain; }</style>
159
+ <bd-tab-list
160
+ id-prefix="icon-tab"
161
+ .tabs=${[
162
+ { label: "Admin", icon: "/assets/pic1.png", iconAlt: "" },
163
+ { label: "Adult", icon: "/assets/pic2.png", iconAlt: "" },
164
+ { label: "Teen", icon: "/assets/pic3.png", iconAlt: "" }
165
+ ]}
166
+ ></bd-tab-list>
167
+ `)
168
+ };
169
+
170
+ /** `content` replaces `label` when the strip needs richer markup. */
171
+ export const RichLabels = {
172
+ name : "Rich labels (content)",
173
+ render: () => withStyles(html`
174
+ <bd-tab-list
175
+ id-prefix="rich-tab"
176
+ .tabs=${PLATFORMS.map(p => ({
177
+ content: html`<bd-p variant="small"><strong>${p.label}</strong></bd-p>`
178
+ }))}
179
+ ></bd-tab-list>
180
+ `)
181
+ };
182
+
183
+ /** Every knob moved off its default at once, to show what is themeable. */
184
+ export const Themed = {
185
+ name : "Themed via custom properties",
186
+ render: () => withStyles(html`
187
+ <div style="
188
+ --bd-tab-color: var(--color-neutral-700);
189
+ --bd-tab-color-selected: var(--color-green-700);
190
+ --bd-tab-underline-color: var(--color-green-700);
191
+ --bd-tab-underline-height: var(--border-width-4);
192
+ --bd-tab-padding: var(--spacing-12) var(--spacing-32);
193
+ --bd-tab-border-color: var(--color-neutral-50);
194
+ ">
195
+ <bd-tab-list .tabs=${PLATFORMS} .selected=${1} id-prefix="themed-tab"></bd-tab-list>
196
+ </div>
197
+ `)
198
+ };
199
+
200
+ /**
201
+ * Tab, then arrow keys. The strip is one tab stop and arrows move within it —
202
+ * the behaviour none of the three sections had before the extraction.
203
+ */
204
+ export const KeyboardNavigation = {
205
+ name : "Keyboard navigation",
206
+ render: () => withStyles(html`
207
+ <p style="font-family: var(--typography-fontFamily-sans);">
208
+ Focus the strip with <kbd>Tab</kbd>, then use <kbd>←</kbd> <kbd>→</kbd>
209
+ <kbd>Home</kbd> <kbd>End</kbd>.
210
+ </p>
211
+ <bd-tab-list .tabs=${PLATFORMS} label="Platform" id-prefix="kbd-tab"></bd-tab-list>
212
+ `)
213
+ };
214
+
215
+ /** A single tab still renders a valid tablist. */
216
+ export const SingleTab = {
217
+ name : "Single tab (edge case)",
218
+ render: () => withStyles(html`
219
+ <bd-tab-list .tabs=${[{ label: "Only one" }]} id-prefix="single-tab"></bd-tab-list>
220
+ `)
221
+ };
@@ -2,7 +2,6 @@ import { css } from "lit";
2
2
 
3
3
  export default css`
4
4
  :host {
5
- --bd-accesibility-focus: var(--color-blue-500);
6
5
  font-size: 100%;
7
6
  display: block;
8
7
  font-family: var(--typography-fontFamily-sans);
@@ -17,8 +16,6 @@ export default css`
17
16
  display: flex;
18
17
  flex-direction: column;
19
18
  align-items: center;
20
- max-width: 1290px;
21
- margin-inline: auto;
22
19
  box-sizing: border-box;
23
20
  }
24
21
 
@@ -38,13 +35,8 @@ export default css`
38
35
  margin-bottom: var(--spacing-32);
39
36
  }
40
37
 
41
- /* Tab buttons row */
42
- .bd-tabs-container {
43
- display: inline-flex;
44
- justify-content: center;
45
- flex-wrap: wrap;
46
- border-bottom: 1px solid var(--color-neutral-100);
47
- }
38
+ /* The tab strip itself lives in Components/Tab List (tabListCSS, included in
39
+ static styles above). Only this section's own layout stays here. */
48
40
 
49
41
  /* Panel — full width, fara padding inline */
50
42
  .bd-panel-wrapper {
@@ -52,118 +44,17 @@ export default css`
52
44
  box-sizing: border-box;
53
45
  }
54
46
 
55
- .bd-tab-button {
56
- display: inline-flex;
57
- align-items: center;
58
- justify-content: center;
59
- background: none;
60
- border: none;
61
- padding: var(--spacing-10) var(--spacing-16);
62
- cursor: pointer;
63
- color: var(--color-blue-500);
64
- position: relative;
65
- font-family: var(--typography-fontFamily-sans);
66
- font-size: var(--typography-body-large-fontSize);
67
- font-weight: var(--typography-fontWeight-semibold);
68
- transition: color var(--transition-duration-fast) var(--transition-easing-smooth);
69
- }
70
-
71
- .bd-tab-button::after {
72
- content: "";
73
- height: 3px;
74
- background: var(--color-blue-500);
75
- position: absolute;
76
- left: 0;
77
- bottom: 0;
78
- width: 0;
79
- transition: width var(--transition-duration-slow) var(--transition-easing-smooth);
80
- }
81
-
82
- .bd-tab-button.bd-selected::after {
83
- width: 100%;
84
- }
85
-
86
- .bd-tab-button.bd-selected {
87
- color: var(--color-neutral-900);
88
- font-weight: var(--typography-fontWeight-semibold);
89
- }
90
-
91
- .bd-tab-button:focus-visible {
92
- outline: 2px solid var(--bd-accesibility-focus);
93
- outline-offset: 2px;
94
- border-radius: var(--radius-sm);
95
- }
96
-
97
- .bd-tab-label {
98
- pointer-events: none;
99
- }
100
-
101
- /* Columns grid */
102
- .bd-columns-container {
103
- display: grid;
104
- gap: var(--spacing-32);
105
- width: 100%;
106
- justify-content: center;
107
- }
108
-
109
- /* Coloana individuala */
110
- .bd-col {
111
- display: flex;
112
- flex-direction: column;
113
- width: 300px;
114
- }
115
-
116
- /* Icon wrapper */
117
- .col-icon-wrapper {
118
- display: flex;
119
- align-items: flex-start;
120
- margin-bottom: var(--spacing-8);
121
- overflow: hidden;
122
- }
123
-
124
- /* Title wrapper */
125
- .col-title-wrapper {
126
- display: flex;
127
- align-items: flex-start;
128
- margin-bottom: var(--spacing-8);
129
- overflow: hidden;
130
- }
131
-
132
- .col-title {
133
- font-family: var(--typography-fontFamily-sans);
134
- font-size: var(--typography-fontSize-2xl);
135
- font-weight: var(--typography-fontWeight-bold);
136
- color: var(--color-neutral-900);
137
- line-height: var(--typography-lineHeight-normal);
138
- }
139
-
140
- .col-description {
141
- margin-bottom: var(--spacing-16);
142
- font-family: var(--typography-fontFamily-sans);
143
- font-size: var(--typography-fontSize-md);
144
- color: var(--color-neutral-700);
145
- line-height: var(--typography-lineHeight-normal);
146
- overflow: hidden;
147
- }
47
+ /* No column rules. A tab panel's columns are bd-features' -- every panel in
48
+ every story wraps its content in <bd-features>, which renders .bd-col,
49
+ .col-icon-wrapper, .col-title-wrapper, .col-title and .col-description
50
+ into its own shadow root and styles them from features.css.js. A copy of
51
+ six of those selectors stood here and could not reach across that
52
+ boundary: measured 0 matches for every one of them across 9 bd-tabs shadow
53
+ roots in 604 built stories.
148
54
 
149
- /* Responsive */
150
- @media (max-width: 992px) {
151
- .bd-col {
152
- width: 260px;
153
- }
154
- }
155
-
156
- @media (max-width: 600px) {
157
- .bd-col {
158
- width: 100%;
159
- }
160
-
161
- .bd-tabs-container {
162
- gap: var(--spacing-4);
163
- }
164
-
165
- .bd-tab-button {
166
- padding: var(--spacing-8) var(--spacing-12);
167
- }
168
- }
55
+ Two recorded findings went with them, and neither was a question about
56
+ tabs. The 24px/1.5 column title in scripts/typography-pairings.json was
57
+ this file's .col-title, so the pairing it recorded rendered nowhere; and
58
+ the two responsive widths were this file's entries in the lg and 600
59
+ column groups, where bd-features carries the live ones. */
169
60
  `;
@@ -1,11 +1,15 @@
1
1
  import { LitElement, html } from "lit";
2
+ // Installs the design tokens into document scope; see src/tokens/tokens.js
3
+ import "../../tokens/tokens.js";
2
4
  import { keyed } from "lit/directives/keyed.js";
3
- import { tokens } from "../../tokens/tokens.js";
4
5
  import "../features/features.js";
5
6
  import "../heading/heading.js";
6
7
  import "../highlight/highlight.js";
7
8
  import "../paragraph/paragraph.js";
9
+ import "../tab-list/tab-list.js";
10
+ import { tabListCSS } from "../tab-list/tab-list.css.js";
8
11
  import tabsCSS from "./tabs.css.js";
12
+ import { ensembleCSS } from "../shared/ensemble.css.js";
9
13
 
10
14
  class BdTabPanel extends LitElement {
11
15
  static styles = [];
@@ -25,7 +29,7 @@ class BdTabs extends LitElement {
25
29
  _panelMinHeight: { type: Number, state: true },
26
30
  bgBlue : { type: Boolean, reflect: true, attribute: "bg-blue" } };
27
31
 
28
- static styles = [tokens, tabsCSS];
32
+ static styles = [ensembleCSS, tabListCSS, tabsCSS];
29
33
 
30
34
  constructor() {
31
35
  super();
@@ -78,33 +82,26 @@ class BdTabs extends LitElement {
78
82
  const activePanel = panels[this.selectedTab];
79
83
 
80
84
  return html`
81
- <div class="bd-tabs-component">
85
+ <div class="bd-ensemble bd-tabs-component">
82
86
 
83
87
  ${this.title
84
88
  ? html`<bd-highlight highlight-text="${this.highlightText}">${this.title}</bd-highlight>`
85
89
  : ""}
86
90
 
87
91
  ${this.subtitle
88
- ? html`<bd-p kind="large" class="bd-tabs-subtitle">${this.subtitle}</bd-p>`
92
+ ? html`<bd-p variant="large" class="bd-tabs-subtitle">${this.subtitle}</bd-p>`
89
93
  : ""}
90
94
 
91
- <div class="bd-tabs-container" role="tablist">
92
- ${panels.map((panel, index) => html`
93
- <button
94
- role="tab"
95
- aria-selected="${this.selectedTab === index ? "true" : "false"}"
96
- aria-controls="tab-panel-${index}"
97
- id="tab-btn-${index}"
98
- class="bd-tab-button ${this.selectedTab === index ? "bd-selected" : ""}"
99
- @click=${() => this.selectTab(index)}
100
- >
101
- <span class="bd-tab-label">${panel.getAttribute("title")}</span>
102
- </button>
103
- `)}
104
- </div>
95
+ <bd-tab-list
96
+ .tabs=${panels.map(panel => ({ label: panel.getAttribute("title") }))}
97
+ .selected=${this.selectedTab}
98
+ id-prefix="tab-btn"
99
+ panel-id="tab-panel"
100
+ @bd-tab-select=${e => this.selectTab(e.detail.index)}
101
+ ></bd-tab-list>
105
102
 
106
103
  <div
107
- id="tab-panel-${this.selectedTab}"
104
+ id="tab-panel"
108
105
  role="tabpanel"
109
106
  aria-labelledby="tab-btn-${this.selectedTab}"
110
107
  class="bd-panel-wrapper"