@repobit/dex-system-design 0.25.12 → 1.0.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 +204 -0
  2. package/README.md +131 -8
  3. package/package.json +96 -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,86 @@
1
+ import { html } from 'lit';
2
+ import './switch.js';
3
+
4
+ export default {
5
+ title : 'Atoms/Switch',
6
+ tags : ['autodocs'],
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: `
11
+ **BdSwitch** turns something on or off, taking effect immediately. If the
12
+ change only applies once a form is submitted, use \`<bd-checkbox>\` instead —
13
+ that difference is the whole reason both exist.
14
+
15
+ ### Usage
16
+ \`\`\`html
17
+ <bd-switch label="Autopilot"></bd-switch>
18
+ <bd-switch label="Scam alerts" checked></bd-switch>
19
+ <bd-switch label="Unavailable" disabled></bd-switch>
20
+
21
+ <bd-switch @bd-change=\${e => console.log(e.detail)}>Autopilot</bd-switch>
22
+ \`\`\`
23
+
24
+ ### Props
25
+ | Prop | Values | Default |
26
+ |---|---|---|
27
+ | \`checked\` | boolean, on or off | \`false\` |
28
+ | \`disabled\` | boolean | \`false\` |
29
+ | \`label\` | label text | \`''\` |
30
+ | \`name\` | carried for your own use | \`''\` |
31
+
32
+ Content can also come from the default slot, which renders after \`label\` — so
33
+ set one or the other, not both.
34
+
35
+ ### Events
36
+ \`bd-change\` — fires on toggle with \`detail\` set to the new boolean.
37
+
38
+ ### Accessibility
39
+ It is a real \`<input type="checkbox">\` with \`role="switch"\`, inside a
40
+ \`<label>\`, so it is keyboard-operable and announced as a switch with an on/off
41
+ state rather than checked/unchecked.
42
+
43
+ ### Not form-associated
44
+ The input lives in this element's shadow root, and a native input inside a
45
+ shadow root does not participate in an outer \`<form>\`. **\`name\` is carried but
46
+ nothing is submitted** — read the state from \`bd-change\` or the \`checked\`
47
+ property.
48
+ `
49
+ }
50
+ }
51
+ },
52
+ argTypes: {
53
+ checked: {
54
+ control : 'boolean',
55
+ description: 'On or off. Takes effect immediately, which is what separates a switch from a checkbox.',
56
+ table : { type: { summary: 'boolean' }, defaultValue: { summary: "false" }, category: 'State' }
57
+ },
58
+ disabled: {
59
+ control : 'boolean',
60
+ description: 'Blocks interaction.',
61
+ table : { type: { summary: 'boolean' }, defaultValue: { summary: "false" }, category: 'State' }
62
+ },
63
+ label: {
64
+ control : 'text',
65
+ description: 'Label text, rendered before any slotted content.',
66
+ table : { type: { summary: 'string' }, defaultValue: { summary: "" }, category: 'Content' }
67
+ },
68
+ name: {
69
+ control : 'text',
70
+ description: 'Carried for your own use. NOT submitted with a form — see the notes.',
71
+ table : { type: { summary: 'string' }, defaultValue: { summary: "" }, category: 'Data' }
72
+ }
73
+ }
74
+ };
75
+
76
+ export const Default = () => html`
77
+ <bd-switch label="Automatic updates"></bd-switch>
78
+ `;
79
+
80
+ export const Checked = () => html`
81
+ <bd-switch checked label="Real-time protection"></bd-switch>
82
+ `;
83
+
84
+ export const Disabled = () => html`
85
+ <bd-switch disabled label="Unavailable on this plan"></bd-switch>
86
+ `;
@@ -0,0 +1,189 @@
1
+ import { html } from "lit";
2
+ import "./tab-list.js";
3
+ import { contentSwitcherCSS, tabListCSS } from "./tab-list.css.js";
4
+
5
+ /**
6
+ * The Content Switcher is `bd-tab-list` wearing `appearance="switcher"`, so it
7
+ * renders into light DOM like the strip does and needs both sheets in scope —
8
+ * see the note in tab-list.js. Inside a real component you add them to that
9
+ * component's own `static styles` instead of doing this.
10
+ */
11
+ const withStyles = template => html`
12
+ <style>${tabListCSS.cssText}${contentSwitcherCSS.cssText}</style>
13
+ <div style="font-family: var(--typography-fontFamily-sans);">${template}</div>
14
+ `;
15
+
16
+ export default {
17
+ title : "Components/Content Switcher",
18
+ tags : ["autodocs"],
19
+ parameters: {
20
+ docs: {
21
+ description: {
22
+ component: `
23
+ **The Content Switcher** is a pill track of mutually exclusive options with the
24
+ selected one on a white pill. Designed in Figma as *Content switcher*
25
+ (node \`311:931\`).
26
+
27
+ ### It is an appearance, not its own element
28
+
29
+ It is \`<bd-tab-list appearance="switcher">\`. Everything underneath —
30
+ \`role="tablist"\`, \`role="tab"\`, \`aria-selected\`, the roving \`tabindex\` and the
31
+ Arrow/Home/End pattern with automatic activation — is the same control the
32
+ underlined strip uses, so a second element would have meant a second copy of
33
+ that keyboard pattern to keep in step. CLAUDE.md's own rule: *when two
34
+ components implement the same visual role, prefer extracting the role over
35
+ spelling it twice.*
36
+
37
+ ### Both options are as wide as the longer label
38
+
39
+ This is the part the design is built around: a translation cannot make one
40
+ option wider than the other, because every option is as wide as the **longest**
41
+ label.
42
+
43
+ **CSS alone cannot express that**, and the two obvious declarations were both
44
+ tried and measured on the *Unequal labels* story below:
45
+
46
+ | | result |
47
+ |---|---|
48
+ | \`grid-auto-columns: 1fr\` | 127.21px and 141.79px — unequal, and both narrower than their content, so the labels wrapped to four lines |
49
+ | \`grid-auto-columns: minmax(max-content, 1fr)\` | 78.35px and 248.77px — no wrapping, still unequal |
50
+
51
+ An \`fr\` track needs definite free space to equalise and a shrink-to-fit
52
+ container has none; once the width *is* definite, \`fr\` splits it evenly rather
53
+ than matching the widest. Hug, equal, and equal-to-the-widest cannot all come
54
+ from one grid declaration.
55
+
56
+ So the width is measured. \`_sizeSwitcherOptions\` in tab-list.js reads the
57
+ widest option after each render and publishes it as
58
+ \`--bd-switcher-option-width\`, which \`grid-auto-columns\` consumes; it clears
59
+ the property before measuring, so the track can shrink again when labels get
60
+ shorter, and it re-measures once \`document.fonts.ready\` settles, because a
61
+ width measured on a fallback face does not match IBM Plex.
62
+
63
+ Figma cannot express it either: in the file one option hugs and the other is
64
+ fixed at the same width, which is equal at this text and stops being equal as
65
+ soon as a label changes length. The code carries the rule; the file carries a
66
+ snapshot of it.
67
+
68
+ ### Usage
69
+
70
+ \`\`\`js
71
+ import "@repobit/dex-system-design/tab-list";
72
+ import { tabListCSS, contentSwitcherCSS } from "@repobit/dex-system-design/tab-list-css";
73
+
74
+ static styles = [tabListCSS, contentSwitcherCSS, myComponentCSS];
75
+ \`\`\`
76
+
77
+ \`\`\`js
78
+ html\`
79
+ <bd-tab-list
80
+ appearance="switcher"
81
+ label="Plan type"
82
+ .tabs=\${[{ label: "Individual" }, { label: "Family" }]}
83
+ .selected=\${0}
84
+ @bd-tab-select=\${e => (this.plan = e.detail.index)}
85
+ ></bd-tab-list>
86
+ \`
87
+ \`\`\`
88
+
89
+ ### Accessibility
90
+
91
+ \`label\` becomes the \`aria-label\` on the \`tablist\` and is not optional in
92
+ practice — without it a screen reader announces a tab list with no name. The
93
+ selected option is the only one in the tab order; arrow keys move between them
94
+ and select in one step.
95
+
96
+ The unselected label is white on \`--color-action-primary\`, which measures
97
+ **4.53:1** — it passes the 4.5:1 minimum for 16px/500 text with very little to
98
+ spare. CLAUDE.md names blue-500 as the step with no headroom, so if this track
99
+ ever needs to sit on a tint, re-measure before assuming it still passes.
100
+ `
101
+ }
102
+ }
103
+ }
104
+ };
105
+
106
+ const switcher = (tabs, selected = 0, label = "Plan type") => withStyles(html`
107
+ <bd-tab-list
108
+ appearance="switcher"
109
+ label=${label}
110
+ .tabs=${tabs}
111
+ .selected=${selected}
112
+ ></bd-tab-list>
113
+ `);
114
+
115
+ export const Default = {
116
+ name : "Default",
117
+ render : () => switcher([{ label: "Individual" }, { label: "Family" }], 0),
118
+ parameters: {
119
+ docs: { description: { story: "The design as drawn: the first option selected." } }
120
+ }
121
+ };
122
+
123
+ export const SecondSelected = {
124
+ name : "Second selected",
125
+ render : () => switcher([{ label: "Individual" }, { label: "Family" }], 1),
126
+ parameters: {
127
+ docs: {
128
+ description: {
129
+ story: "The other variant in the Figma set. Only the pill and the ink move — "
130
+ + "both labels stay at the action-label weight, so nothing reflows on switch."
131
+ }
132
+ }
133
+ }
134
+ };
135
+
136
+ export const UnequalLabels = {
137
+ name : "Unequal labels",
138
+ render : () => switcher([{ label: "One" }, { label: "A considerably longer label" }], 0),
139
+ parameters: {
140
+ docs: {
141
+ description: {
142
+ story: "The equal-width rule under load. \"One\" is far shorter than its "
143
+ + "sibling and still occupies exactly the same width, which is what makes "
144
+ + "the control safe to translate. Measure both options in this story rather "
145
+ + "than the one above — in Default the two labels are close enough that a "
146
+ + "plain flex row would look almost the same."
147
+ }
148
+ }
149
+ }
150
+ };
151
+
152
+ export const ThreeOptions = {
153
+ name : "Three options",
154
+ render : () => switcher([{ label: "Monthly" }, { label: "Yearly" }, { label: "Lifetime" }], 1),
155
+ parameters: {
156
+ docs: {
157
+ description: {
158
+ story: "Nothing in the control assumes two. The Figma component is drawn with "
159
+ + "two because that is what pricing needs; the grid adds a column per option "
160
+ + "and they all stay equal."
161
+ }
162
+ }
163
+ }
164
+ };
165
+
166
+ export const LongestLabelDrivesBoth = {
167
+ name : "In a narrow container",
168
+ render: () => withStyles(html`
169
+ <div style="width: 320px; outline: 1px dashed var(--color-border-subtle); padding: var(--spacing-16);">
170
+ <bd-tab-list
171
+ appearance="switcher"
172
+ label="Plan type"
173
+ .tabs=${[{ label: "Individual" }, { label: "Family" }]}
174
+ .selected=${0}
175
+ ></bd-tab-list>
176
+ </div>
177
+ `),
178
+ parameters: {
179
+ docs: {
180
+ description: {
181
+ story: "The track hugs its content rather than filling its parent, so it does "
182
+ + "not stretch in a wide column. At 320px both options plus the track "
183
+ + "padding still fit — measured, the pair comes to 256px — which is why the "
184
+ + "switcher holds its 12/24 option padding below `sm` instead of taking the "
185
+ + "strip's narrower step."
186
+ }
187
+ }
188
+ }
189
+ };
@@ -0,0 +1,250 @@
1
+ import { css, unsafeCSS } from "lit";
2
+ /* unsafeCSS is how a Lit css`` template accepts a computed string. The
3
+ value is not arbitrary: below() resolves a name from the closed
4
+ breakpoint set and throws on anything else, so the only strings that
5
+ reach here are the ones the scale produced. */
6
+ import { below } from "../shared/token-scales.js";
7
+
8
+ /**
9
+ * Base styling for <bd-tab-list>.
10
+ *
11
+ * The element renders into light DOM (see tab-list.js), so this sheet is not
12
+ * applied automatically — a consumer adds it to its own `static styles` ahead
13
+ * of its overrides:
14
+ *
15
+ * static styles = [tabListCSS, myComponentCSS];
16
+ *
17
+ * Everything that varies between the sections that use a tab strip is a custom
18
+ * property, so an override is a token change rather than a re-declaration of
19
+ * the whole button.
20
+ */
21
+ export const tabListCSS = css`
22
+ /* The element is a pass-through wrapper: the strip itself stays the flex/grid
23
+ item it was before the extraction, so surrounding layout is unaffected. */
24
+ bd-tab-list {
25
+ display: contents;
26
+ }
27
+
28
+ .bd-tabs-container {
29
+ display: inline-flex;
30
+ justify-content: center;
31
+ flex-wrap: wrap;
32
+ border-bottom: var(--border-width-1) solid var(--bd-tab-border-color, var(--color-border-container));
33
+ }
34
+
35
+ .bd-tab-button {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ gap: var(--bd-tab-gap, var(--spacing-8));
40
+ background: none;
41
+ border: none;
42
+ padding: var(--bd-tab-padding, var(--spacing-10) var(--spacing-16));
43
+ cursor: pointer;
44
+ position: relative;
45
+ color: var(--bd-tab-color, var(--color-text-brand));
46
+ font-family: var(--typography-fontFamily-sans);
47
+ /* body-regular, the same role bd-anchor-nav's and bd-page-switcher's items
48
+ resolve through.
49
+
50
+ This was body-large at semibold -- 18px/600 -- with no leading declared
51
+ at all, on the argument that a tab's type is a control's rather than a
52
+ text role's. The wayfinding family review settled it the other way: all
53
+ three of these components are labels with an action attached, they mark
54
+ the current one the same way, and there was no reason for one of them to
55
+ be a size and a weight apart. At 16/1.5 the tab box is 10 + 24 + 10, so
56
+ all three now measure 44px.
57
+
58
+ The leading is declared either way, and that part was a defect on its
59
+ own. An 18px label sat on IBM Plex Sans's own metric while the nav bars
60
+ beside it sat on a declared 24px -- and a leading nobody declares is the
61
+ blind spot neither gate can see: lint:tokens needs a DECLARED value to
62
+ report, and verify:typography only fails on multi-line text, which a
63
+ single-line tab label never is. */
64
+ font-size: var(--bd-tab-font-size, var(--typography-body-regular-fontSize));
65
+ line-height: var(--bd-tab-line-height, var(--typography-body-regular-lineHeight));
66
+ font-weight: var(--bd-tab-font-weight, var(--typography-body-regular-fontWeight));
67
+ transition: color var(--transition-duration-fast) var(--transition-easing-smooth);
68
+ }
69
+
70
+ .bd-tab-button::after {
71
+ content: "";
72
+ height: var(--bd-tab-underline-height, var(--indicator-underline-height));
73
+ background: var(--bd-tab-underline-color, var(--indicator-color));
74
+ position: absolute;
75
+ left: 0;
76
+ bottom: 0;
77
+ width: 0;
78
+ transition: width var(--bd-tab-underline-duration, var(--indicator-underline-duration))
79
+ var(--indicator-underline-easing);
80
+ }
81
+
82
+ /* The transition two rules up had nothing to animate on pointer: this sheet
83
+ declared a colour transition and then no :hover rule anywhere, so a tab
84
+ was the one control in the package that gave no feedback under the
85
+ cursor.
86
+
87
+ action/primary-active is the step this system already uses for an
88
+ interactive element under the pointer -- bd-link's :hover and the anchor
89
+ nav's engaged dropdown option both resolve to it -- and it is a step
90
+ darker than the resting --color-text-brand, so the change is visible
91
+ without becoming the selected ink. Excluding .bd-selected keeps the
92
+ selected tab's text/primary from moving under the cursor. */
93
+ .bd-tab-button:hover:not(.bd-selected) {
94
+ color: var(--color-action-primary-active);
95
+ }
96
+
97
+ .bd-tab-button.bd-selected::after {
98
+ width: 100%;
99
+ }
100
+
101
+ .bd-tab-button.bd-selected {
102
+ color: var(--bd-tab-color-selected, var(--color-text-primary));
103
+ /* no role: the weight that marks the current item -- a state or modifier
104
+ weight, not a text-content type. See button.css.js's .button--bold for
105
+ the argument, and bd-link's .bd-link--active, which is the same 700 on
106
+ the same job in the other two components of this family. The tab used to
107
+ carry semibold at rest AND selected, so its weight said nothing about
108
+ which item you were on -- colour and the indicator carried it alone. */
109
+ font-weight: var(--bd-tab-font-weight-selected, var(--typography-fontWeight-bold));
110
+ }
111
+
112
+ .bd-tab-button:focus-visible {
113
+ /* the one focus ring — see Foundations/Focus */
114
+ outline: none;
115
+ box-shadow: var(--focus-outline-primary);
116
+ border-radius: var(--focus-outline-radius);
117
+ }
118
+
119
+ /* Icon sizing is deliberately not defaulted here: the two sections that use
120
+ icons size them differently and neither value has a dimension token, so
121
+ each keeps its own rule rather than this atom inventing a size. */
122
+
123
+ .bd-tab-label {
124
+ pointer-events: none;
125
+ }
126
+
127
+ @media (prefers-reduced-motion: reduce) {
128
+ .bd-tab-button,
129
+ .bd-tab-button::after {
130
+ transition: none;
131
+ }
132
+ }
133
+
134
+ /* Narrower than sm. A media condition cannot read a custom property, so
135
+ below() interpolates the value; 600px was on no scale at all, and the
136
+ 576px that replaced it still matched at exactly 576px, where sm begins. */
137
+ @media ${unsafeCSS(below('sm'))} {
138
+ .bd-tabs-container {
139
+ gap: var(--spacing-4);
140
+ }
141
+
142
+ .bd-tab-button {
143
+ padding: var(--bd-tab-padding-mobile, var(--spacing-8) var(--spacing-12));
144
+ }
145
+ }
146
+ `;
147
+
148
+
149
+ /* ---- appearance="switcher": the Content Switcher --------------------------
150
+ Figma: Content switcher, node 311:931. Every value below is the one the
151
+ component carries there, and the geometry reconciles: label 24 tall + 12/24
152
+ padding = a 48px option, + 4px track padding top and bottom = 56, which is
153
+ the frame height. "Individual" at 16/500 measures 75, + 24 + 24 = the 123px
154
+ option, and 4 + 123 + 4 + 123 + 4 = the 258px track.
155
+
156
+ THE EQUAL-WIDTH RULE IS THE POINT, and CSS expresses it where Figma cannot.
157
+ The design asks for both options to be as wide as the LONGER label so a
158
+ translation cannot make one tab bigger than the other. In the file one
159
+ option HUGs and the other is FIXED at the same width, which is equal at this
160
+ text and stops being equal the moment a label changes length. An auto-sized
161
+ inline grid whose columns are 1fr sizes every column to the widest column's
162
+ max-content, so the track still hugs its content AND the two options stay
163
+ identical -- which is the stated intent rather than a snapshot of it.
164
+
165
+ The selected option is marked by the white pill alone. The base sheet moves
166
+ the selected tab to bold, and that is right for an underlined strip where
167
+ weight and the indicator carry the state together; here the fill carries it,
168
+ so both weights stay at the action-label step and the label does not reflow
169
+ when you switch. That is also why every override here goes through the
170
+ --bd-tab-* custom properties the base sheet exposes rather than
171
+ re-declaring the button. */
172
+ export const contentSwitcherCSS = css`
173
+ .bd-tabs-container--switcher {
174
+ display: inline-grid;
175
+ grid-auto-flow: column;
176
+ /* minmax(max-content, 1fr), NOT 1fr. Measured on the Unequal labels story:
177
+ a bare 1fr gave 127.21px and 141.79px -- two unequal columns, both
178
+ narrower than their content, so the labels wrapped to four lines. An fr
179
+ track under a shrink-to-fit container does not equalise to the widest
180
+ sibling, which is the assumption that was wrong.
181
+ .
182
+ minmax(max-content, 1fr) was the next attempt and gave 78.35 and 248.77
183
+ -- each column at its own max-content, still unequal. Once the width IS
184
+ definite an fr track splits it evenly instead, which is a different
185
+ wrong answer. There is no grid declaration that gives all three of hug,
186
+ equal, and equal-to-the-widest.
187
+ .
188
+ So the width arrives measured: _sizeSwitcherOptions in tab-list.js
189
+ publishes the widest option's width and this reads it. max-content is
190
+ the fallback for the frame before the first measurement, which is also
191
+ what keeps the geometry right if the property is ever absent -- and it
192
+ is a component-local property, the one place CLAUDE.md allows a
193
+ fallback. */
194
+ grid-auto-columns: var(--bd-switcher-option-width, max-content);
195
+ gap: var(--spacing-4);
196
+ padding: var(--spacing-4);
197
+ border-bottom: none;
198
+ border-radius: var(--radius-full);
199
+ background: var(--color-action-primary);
200
+ }
201
+
202
+ .bd-tabs-container--switcher .bd-tab-button {
203
+ /* the action-label step, which is what a button's label carries */
204
+ --bd-tab-font-weight: var(--typography-control-label-large-fontWeight);
205
+ --bd-tab-font-weight-selected: var(--typography-control-label-large-fontWeight);
206
+ --bd-tab-color: var(--color-text-inverse);
207
+ --bd-tab-color-selected: var(--color-text-primary);
208
+ --bd-tab-padding: var(--spacing-12) var(--spacing-24);
209
+ /* held at the same padding below sm: the base sheet drops to 8/12 there,
210
+ which would take the pill off the 48px option the design specifies, and
211
+ two 123px options plus the track padding still fit a 375px viewport. */
212
+ --bd-tab-padding-mobile: var(--spacing-12) var(--spacing-24);
213
+ border-radius: var(--radius-full);
214
+ /* An option is a control label, so it is one line by definition. The base
215
+ sheet lets a tab wrap, which is right for a strip that reflows; here a
216
+ wrap would break the pill geometry the design specifies. */
217
+ white-space: nowrap;
218
+ transition: color var(--transition-duration-fast) var(--transition-easing-smooth),
219
+ background-color var(--transition-duration-fast) var(--transition-easing-smooth);
220
+ }
221
+
222
+ .bd-tabs-container--switcher .bd-tab-button.bd-selected {
223
+ background: var(--color-surface-default);
224
+ }
225
+
226
+ /* The underline belongs to the other appearance. Left as display:none rather
227
+ than content:none so the base rule's width transition has nothing to
228
+ animate either. */
229
+ .bd-tabs-container--switcher .bd-tab-button::after {
230
+ display: none;
231
+ }
232
+
233
+ /* The base hover goes to action/primary-active, a darker blue, which on this
234
+ blue track would be close to invisible and is the wrong direction anyway.
235
+ The unselected option is already white, so the pointer affordance is the
236
+ pill appearing under it -- white at 12%, which reads on blue-500 without
237
+ competing with the selected option's solid white. */
238
+ .bd-tabs-container--switcher .bd-tab-button:hover:not(.bd-selected) {
239
+ color: var(--color-text-inverse);
240
+ background: var(--color-border-container-inverse);
241
+ }
242
+
243
+ @media (prefers-reduced-motion: reduce) {
244
+ .bd-tabs-container--switcher .bd-tab-button {
245
+ transition: none;
246
+ }
247
+ }
248
+ `;
249
+
250
+ export default tabListCSS;