@deriv-com/dds-marketing 0.2.0 → 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 (279) hide show
  1. package/.llm/components/accordion.md +8 -0
  2. package/.llm/components/accordionitem.md +8 -0
  3. package/.llm/components/aspectratio.md +8 -0
  4. package/.llm/components/badge.md +8 -0
  5. package/.llm/components/bottomsheet.md +8 -0
  6. package/.llm/components/breadcrumb.md +8 -0
  7. package/.llm/components/breadcrumbitem.md +8 -0
  8. package/.llm/components/button.md +46 -22
  9. package/.llm/components/buttonicon.md +14 -6
  10. package/.llm/components/card.md +15 -1
  11. package/.llm/components/cardbody.md +28 -2
  12. package/.llm/components/cardbutton.md +15 -1
  13. package/.llm/components/checkbox.md +8 -0
  14. package/.llm/components/ctabanner.md +186 -0
  15. package/.llm/components/ctabannerslider.md +204 -0
  16. package/.llm/components/divider.md +8 -0
  17. package/.llm/components/dropdownmenu.md +13 -5
  18. package/.llm/components/faqaccordion.md +217 -0
  19. package/.llm/components/featurebullets.md +254 -0
  20. package/.llm/components/featurecards.md +313 -0
  21. package/.llm/components/featurescroll.md +284 -0
  22. package/.llm/components/featuresplit.md +249 -0
  23. package/.llm/components/filterchip.md +8 -0
  24. package/.llm/components/footer.md +8 -0
  25. package/.llm/components/footergroup.md +8 -0
  26. package/.llm/components/grid.md +8 -0
  27. package/.llm/components/icon.md +8 -0
  28. package/.llm/components/landinghero.md +254 -0
  29. package/.llm/components/link.md +9 -1
  30. package/.llm/components/logocolumns.md +235 -0
  31. package/.llm/components/logorows.md +212 -0
  32. package/.llm/components/menuitem.md +8 -0
  33. package/.llm/components/navbar.md +79 -15
  34. package/.llm/components/navbaritem.md +9 -1
  35. package/.llm/components/navbarmenu.md +8 -0
  36. package/.llm/components/navbarmenugroup.md +8 -0
  37. package/.llm/components/navbarpanel.md +9 -1
  38. package/.llm/components/navbarswitcher.md +9 -1
  39. package/.llm/components/navbarswitcheroption.md +8 -0
  40. package/.llm/components/option.md +8 -0
  41. package/.llm/components/pageheader.md +231 -0
  42. package/.llm/components/pagination.md +26 -89
  43. package/.llm/components/paginationdots.md +215 -0
  44. package/.llm/components/producthero.md +248 -0
  45. package/.llm/components/removablechip.md +8 -0
  46. package/.llm/components/row.md +8 -0
  47. package/.llm/components/scroller.md +8 -0
  48. package/.llm/components/searchinput.md +8 -0
  49. package/.llm/components/segmentedcontrol.md +8 -0
  50. package/.llm/components/segmentedcontrolitem.md +8 -0
  51. package/.llm/components/select.md +8 -0
  52. package/.llm/components/selectchip.md +56 -15
  53. package/.llm/components/spinner.md +8 -0
  54. package/.llm/components/stack.md +8 -0
  55. package/.llm/components/statusdot.md +8 -0
  56. package/.llm/components/subdomainnavbar.md +8 -0
  57. package/.llm/components/tab.md +8 -0
  58. package/.llm/components/table.md +8 -0
  59. package/.llm/components/tablebody.md +8 -0
  60. package/.llm/components/tablecell.md +8 -0
  61. package/.llm/components/tablehead.md +8 -0
  62. package/.llm/components/tableheadercell.md +8 -0
  63. package/.llm/components/tablerow.md +8 -0
  64. package/.llm/components/tablist.md +8 -0
  65. package/.llm/components/testimonialrow.md +235 -0
  66. package/.llm/components/testimonialslider.md +254 -0
  67. package/.llm/components/textarea.md +8 -0
  68. package/.llm/components/textinput.md +8 -0
  69. package/.llm/components/tooltip.md +8 -0
  70. package/.llm/components/typography.md +8 -0
  71. package/.llm/digest/button.md +11 -6
  72. package/.llm/digest/buttonicon.md +2 -2
  73. package/.llm/digest/card.md +1 -0
  74. package/.llm/digest/cardbody.md +3 -1
  75. package/.llm/digest/cardbutton.md +1 -0
  76. package/.llm/digest/ctabanner.md +44 -0
  77. package/.llm/digest/ctabannerslider.md +45 -0
  78. package/.llm/digest/dropdownmenu.md +1 -1
  79. package/.llm/digest/faqaccordion.md +43 -0
  80. package/.llm/digest/featurebullets.md +52 -0
  81. package/.llm/digest/featurecards.md +49 -0
  82. package/.llm/digest/featurescroll.md +52 -0
  83. package/.llm/digest/featuresplit.md +54 -0
  84. package/.llm/digest/landinghero.md +53 -0
  85. package/.llm/digest/logocolumns.md +55 -0
  86. package/.llm/digest/logorows.md +48 -0
  87. package/.llm/digest/navbar.md +18 -3
  88. package/.llm/digest/pageheader.md +49 -0
  89. package/.llm/digest/pagination.md +6 -17
  90. package/.llm/digest/paginationdots.md +43 -0
  91. package/.llm/digest/producthero.md +53 -0
  92. package/.llm/digest/selectchip.md +6 -1
  93. package/.llm/digest/testimonialrow.md +51 -0
  94. package/.llm/digest/testimonialslider.md +51 -0
  95. package/.llm/index.md +24 -3
  96. package/.llm/rules.md +24 -0
  97. package/.llm/tokens.md +56 -12
  98. package/AGENTS.md +28 -3
  99. package/README.md +41 -21
  100. package/dist/components/banner-data.d.ts +38 -0
  101. package/dist/components/banner-data.js +15 -0
  102. package/dist/components/blocks/block-reveal.css +90 -0
  103. package/dist/components/blocks/cta-banner/CtaBanner.css +66 -0
  104. package/dist/components/blocks/cta-banner/CtaBanner.d.ts +22 -0
  105. package/dist/components/blocks/cta-banner/CtaBanner.js +31 -0
  106. package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +29 -0
  107. package/dist/components/blocks/cta-banner/CtaBanner.types.js +13 -0
  108. package/dist/components/blocks/cta-banner/cta-banner-impl.css +29 -0
  109. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +90 -0
  110. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +39 -0
  111. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +60 -0
  112. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +41 -0
  113. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +15 -0
  114. package/dist/components/blocks/cta-banner-slider/cta-banner-slider-impl.css +73 -0
  115. package/dist/components/blocks/faq-accordion/FaqAccordion.css +54 -0
  116. package/dist/components/blocks/faq-accordion/FaqAccordion.d.ts +20 -0
  117. package/dist/components/blocks/faq-accordion/FaqAccordion.js +28 -0
  118. package/dist/components/blocks/faq-accordion/FaqAccordion.types.d.ts +25 -0
  119. package/dist/components/blocks/faq-accordion/FaqAccordion.types.js +10 -0
  120. package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +54 -0
  121. package/dist/components/blocks/feature-bullets/FeatureBullets.css +139 -0
  122. package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +31 -0
  123. package/dist/components/blocks/feature-bullets/FeatureBullets.js +55 -0
  124. package/dist/components/blocks/feature-bullets/FeatureBullets.types.d.ts +39 -0
  125. package/dist/components/blocks/feature-bullets/FeatureBullets.types.js +16 -0
  126. package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +104 -0
  127. package/dist/components/blocks/feature-cards/FeatureCards.css +84 -0
  128. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +24 -0
  129. package/dist/components/blocks/feature-cards/FeatureCards.js +66 -0
  130. package/dist/components/blocks/feature-cards/FeatureCards.types.d.ts +33 -0
  131. package/dist/components/blocks/feature-cards/FeatureCards.types.js +14 -0
  132. package/dist/components/blocks/feature-cards/feature-cards-impl.css +245 -0
  133. package/dist/components/blocks/feature-scroll/FeatureScroll.css +137 -0
  134. package/dist/components/blocks/feature-scroll/FeatureScroll.d.ts +39 -0
  135. package/dist/components/blocks/feature-scroll/FeatureScroll.js +167 -0
  136. package/dist/components/blocks/feature-scroll/FeatureScroll.types.d.ts +40 -0
  137. package/dist/components/blocks/feature-scroll/FeatureScroll.types.js +17 -0
  138. package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +292 -0
  139. package/dist/components/blocks/feature-split/FeatureSplit.css +95 -0
  140. package/dist/components/blocks/feature-split/FeatureSplit.d.ts +30 -0
  141. package/dist/components/blocks/feature-split/FeatureSplit.js +38 -0
  142. package/dist/components/blocks/feature-split/FeatureSplit.types.d.ts +39 -0
  143. package/dist/components/blocks/feature-split/FeatureSplit.types.js +14 -0
  144. package/dist/components/blocks/feature-split/feature-split-impl.css +83 -0
  145. package/dist/components/blocks/landing-hero/LandingHero.css +85 -0
  146. package/dist/components/blocks/landing-hero/LandingHero.d.ts +41 -0
  147. package/dist/components/blocks/landing-hero/LandingHero.js +54 -0
  148. package/dist/components/blocks/landing-hero/LandingHero.types.d.ts +37 -0
  149. package/dist/components/blocks/landing-hero/LandingHero.types.js +15 -0
  150. package/dist/components/blocks/landing-hero/landing-hero-impl.css +355 -0
  151. package/dist/components/blocks/logo-columns/LogoColumns.css +161 -0
  152. package/dist/components/blocks/logo-columns/LogoColumns.d.ts +23 -0
  153. package/dist/components/blocks/logo-columns/LogoColumns.js +46 -0
  154. package/dist/components/blocks/logo-columns/LogoColumns.types.d.ts +46 -0
  155. package/dist/components/blocks/logo-columns/LogoColumns.types.js +18 -0
  156. package/dist/components/blocks/logo-columns/logo-columns-impl.css +188 -0
  157. package/dist/components/blocks/logo-rows/LogoRows.css +122 -0
  158. package/dist/components/blocks/logo-rows/LogoRows.d.ts +20 -0
  159. package/dist/components/blocks/logo-rows/LogoRows.js +43 -0
  160. package/dist/components/blocks/logo-rows/LogoRows.types.d.ts +36 -0
  161. package/dist/components/blocks/logo-rows/LogoRows.types.js +16 -0
  162. package/dist/components/blocks/logo-rows/logo-rows-impl.css +179 -0
  163. package/dist/components/blocks/page-header/PageHeader.css +79 -0
  164. package/dist/components/blocks/page-header/PageHeader.d.ts +34 -0
  165. package/dist/components/blocks/page-header/PageHeader.js +43 -0
  166. package/dist/components/blocks/page-header/PageHeader.types.d.ts +32 -0
  167. package/dist/components/blocks/page-header/PageHeader.types.js +14 -0
  168. package/dist/components/blocks/page-header/page-header-impl.css +176 -0
  169. package/dist/components/blocks/product-hero/ProductHero.css +86 -0
  170. package/dist/components/blocks/product-hero/ProductHero.d.ts +35 -0
  171. package/dist/components/blocks/product-hero/ProductHero.js +48 -0
  172. package/dist/components/blocks/product-hero/ProductHero.types.d.ts +37 -0
  173. package/dist/components/blocks/product-hero/ProductHero.types.js +15 -0
  174. package/dist/components/blocks/product-hero/product-hero-impl.css +354 -0
  175. package/dist/components/blocks/testimonial-row/TestimonialRow.css +245 -0
  176. package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +20 -0
  177. package/dist/components/blocks/testimonial-row/TestimonialRow.js +128 -0
  178. package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +54 -0
  179. package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +26 -0
  180. package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +195 -0
  181. package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +181 -0
  182. package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +22 -0
  183. package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +36 -0
  184. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +49 -0
  185. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +21 -0
  186. package/dist/components/blocks/testimonial-slider/testimonial-slider-impl.css +202 -0
  187. package/dist/components/blocks/use-reveal.d.ts +46 -0
  188. package/dist/components/blocks/use-reveal.js +120 -0
  189. package/dist/components/blocks/use-rotation.d.ts +62 -0
  190. package/dist/components/blocks/use-rotation.js +176 -0
  191. package/dist/components/core/accordion-item/AccordionItem.css +6 -5
  192. package/dist/components/core/bottom-sheet/BottomSheet.css +1 -1
  193. package/dist/components/core/button/Button.css +63 -16
  194. package/dist/components/core/button/Button.d.ts +14 -12
  195. package/dist/components/core/button/Button.js +43 -14
  196. package/dist/components/core/button/Button.types.d.ts +9 -14
  197. package/dist/components/core/button/button-impl.css +25 -0
  198. package/dist/components/core/button-icon/ButtonIcon.css +31 -15
  199. package/dist/components/core/button-icon/ButtonIcon.d.ts +2 -2
  200. package/dist/components/core/button-icon/ButtonIcon.js +2 -2
  201. package/dist/components/core/button-icon/ButtonIcon.types.d.ts +4 -4
  202. package/dist/components/core/card/Card.css +13 -4
  203. package/dist/components/core/card/Card.types.d.ts +2 -1
  204. package/dist/components/core/card-body/CardBody.css +11 -6
  205. package/dist/components/core/card-body/CardBody.d.ts +1 -1
  206. package/dist/components/core/card-body/CardBody.js +2 -2
  207. package/dist/components/core/card-body/CardBody.types.d.ts +4 -1
  208. package/dist/components/core/card-body/CardBody.types.js +1 -0
  209. package/dist/components/core/card-button/CardButton.css +13 -4
  210. package/dist/components/core/card-button/CardButton.types.d.ts +2 -1
  211. package/dist/components/core/checkbox/Checkbox.css +8 -1
  212. package/dist/components/core/dropdown-menu/DropdownMenu.css +13 -12
  213. package/dist/components/core/dropdown-menu/DropdownMenu.d.ts +5 -3
  214. package/dist/components/core/dropdown-menu/DropdownMenu.js +5 -3
  215. package/dist/components/core/filter-chip/FilterChip.css +2 -1
  216. package/dist/components/core/footer/footer-impl.css +1 -1
  217. package/dist/components/core/footer-group/FooterGroup.css +8 -7
  218. package/dist/components/core/icon/Icon.css +1 -1
  219. package/dist/components/core/link/Link.css +1 -1
  220. package/dist/components/core/menu-item/MenuItem.css +1 -1
  221. package/dist/components/core/navbar/Navbar.css +7 -8
  222. package/dist/components/core/navbar/Navbar.d.ts +17 -1
  223. package/dist/components/core/navbar/Navbar.js +139 -12
  224. package/dist/components/core/navbar/Navbar.types.d.ts +23 -4
  225. package/dist/components/core/navbar/Navbar.types.js +1 -1
  226. package/dist/components/core/navbar/navbar-impl.css +10 -3
  227. package/dist/components/core/navbar/navbar-material.css +96 -0
  228. package/dist/components/core/navbar-item/NavbarItem.css +4 -4
  229. package/dist/components/core/navbar-menu/NavbarMenu.css +7 -5
  230. package/dist/components/core/navbar-menu/NavbarMenu.d.ts +1 -0
  231. package/dist/components/core/navbar-menu/NavbarMenu.js +1 -0
  232. package/dist/components/core/navbar-menu/navbar-menu-impl.css +7 -7
  233. package/dist/components/core/navbar-switcher/NavbarSwitcher.css +6 -5
  234. package/dist/components/core/navbar-switcher/NavbarSwitcher.d.ts +1 -0
  235. package/dist/components/core/navbar-switcher/NavbarSwitcher.js +2 -1
  236. package/dist/components/core/option/Option.css +1 -1
  237. package/dist/components/core/pagination/Pagination.css +1 -75
  238. package/dist/components/core/pagination/Pagination.d.ts +4 -4
  239. package/dist/components/core/pagination/Pagination.js +11 -51
  240. package/dist/components/core/pagination/Pagination.types.d.ts +5 -17
  241. package/dist/components/core/pagination/Pagination.types.js +1 -3
  242. package/dist/components/core/pagination-dots/PaginationDots.css +60 -0
  243. package/dist/components/core/pagination-dots/PaginationDots.d.ts +25 -0
  244. package/dist/components/core/pagination-dots/PaginationDots.js +104 -0
  245. package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +38 -0
  246. package/dist/components/core/pagination-dots/PaginationDots.types.js +9 -0
  247. package/dist/components/core/removable-chip/RemovableChip.css +3 -3
  248. package/dist/components/core/search-input/SearchInput.css +1 -1
  249. package/dist/components/core/segmented-control/SegmentedControl.css +1 -1
  250. package/dist/components/core/segmented-control-item/SegmentedControlItem.css +1 -1
  251. package/dist/components/core/select/Select.css +2 -1
  252. package/dist/components/core/select-chip/SelectChip.css +14 -7
  253. package/dist/components/core/select-chip/SelectChip.d.ts +1 -1
  254. package/dist/components/core/select-chip/SelectChip.js +7 -4
  255. package/dist/components/core/select-chip/SelectChip.types.d.ts +7 -1
  256. package/dist/components/core/select-chip/SelectChip.types.js +1 -1
  257. package/dist/components/core/select-chip/select-chip-impl.css +14 -3
  258. package/dist/components/core/status-dot/StatusDot.css +1 -1
  259. package/dist/components/core/subdomain-navbar/SubdomainNavbar.css +3 -3
  260. package/dist/components/core/tab/Tab.css +1 -1
  261. package/dist/components/core/tab-list/TabList.css +1 -1
  262. package/dist/components/core/table/table-impl.css +36 -14
  263. package/dist/components/core/table-header-cell/TableHeaderCell.css +5 -5
  264. package/dist/components/core/text-area/TextArea.css +1 -1
  265. package/dist/components/core/text-input/TextInput.css +1 -1
  266. package/dist/components/core/tooltip/Tooltip.css +1 -1
  267. package/dist/components/core/typography/Typography.css +5 -5
  268. package/dist/components/index.d.ts +35 -0
  269. package/dist/components/index.js +30 -0
  270. package/dist/components/logo-data.d.ts +24 -0
  271. package/dist/components/logo-data.js +11 -0
  272. package/dist/components/scroll-data.d.ts +64 -0
  273. package/dist/components/scroll-data.js +17 -0
  274. package/dist/components/testimonial-row-data.d.ts +51 -0
  275. package/dist/components/testimonial-row-data.js +11 -0
  276. package/dist/components/testimonial-slider-data.d.ts +26 -0
  277. package/dist/components/testimonial-slider-data.js +10 -0
  278. package/dist/marketing.css +385 -14
  279. package/package.json +1 -6
@@ -0,0 +1,120 @@
1
+ 'use client';
2
+ import { useLayoutEffect, useRef, useState } from 'react';
3
+ /**
4
+ * Whether a block has arrived on screen yet, so its stylesheet can bring it in.
5
+ *
6
+ * Every trigger CSS alone can offer is invisible somewhere, and both were tried here first. A
7
+ * timed animation fires when the element is created, so a block below the fold has finished
8
+ * arriving before a reader scrolls to it. A scroll-driven one — `animation-timeline: view()` —
9
+ * fires as the element enters the viewport, so a block that is *already* in view at the first
10
+ * frame is past the range before anything is painted, and an opening band never moves at all.
11
+ *
12
+ * What is wanted is "when it comes into view, counting already being there as coming into view",
13
+ * and that is the one thing a view timeline cannot say. An observer can, which is the whole reason
14
+ * this file exists.
15
+ *
16
+ * It answers `true` before it knows anything, and that is deliberate. A block hidden until
17
+ * JavaScript says otherwise is content behind a script — a reader with none, or one whose bundle
18
+ * failed, gets a blank band. So the resting answer is visible, and hiding is something this only
19
+ * does once it is running and has somewhere to put the block back.
20
+ *
21
+ * `useLayoutEffect` so the hiding lands before the browser paints. In an effect it would run after
22
+ * the first frame, and every block would show itself, hide, and come back — a flash on every
23
+ * section of the page.
24
+ *
25
+ * It fires once. A section that re-arrived every time a reader scrolled back up would be a page
26
+ * that will not settle, and the observer is disconnected rather than left listening.
27
+ */
28
+ export function useReveal() {
29
+ const held = useRef(null);
30
+ const [revealed, setRevealed] = useState(true);
31
+ useLayoutEffect(() => {
32
+ const element = held.current;
33
+ if (element === null)
34
+ return;
35
+ // A reader who asked for less movement is not hidden and then shown; they are simply shown.
36
+ // The stylesheet drops the transition as well, so this is the half that matters: without it
37
+ // the block would still be hidden for a frame before appearing, which is the flicker that
38
+ // preference exists to remove.
39
+ //
40
+ // Asked defensively, like the observer below. A renderer with no `matchMedia` cannot be
41
+ // asked what the reader prefers, and the honest answer to a question that cannot be asked
42
+ // is the one that hides nothing.
43
+ const asked = typeof window.matchMedia === 'function';
44
+ if (asked && window.matchMedia('(prefers-reduced-motion: reduce)').matches)
45
+ return;
46
+ // Anything the browser cannot observe arrives already arrived, which is the same answer this
47
+ // hook starts on: seen rather than waiting.
48
+ if (typeof IntersectionObserver === 'undefined')
49
+ return;
50
+ /**
51
+ * A box that does not exist cannot come into view, so it is never hidden.
52
+ *
53
+ * `display: contents` is the case, and it cost a section its words: FeatureScroll's text column
54
+ * gives up its box on a phone so the picture can sit between the title and the line under it,
55
+ * an observer watching it was never going to report anything, and the copy stayed waiting for
56
+ * an arrival that could not happen. Permanently invisible content is the worst failure this
57
+ * hook has available to it, so it checks.
58
+ *
59
+ * A caller that wants motion for such a group watches something inside it that does have a box
60
+ * and marks the group — the ref and the attribute need not sit on the same element. This is the
61
+ * floor under that, not a substitute for it.
62
+ */
63
+ if (element.getClientRects().length === 0)
64
+ return;
65
+ setRevealed(false);
66
+ /**
67
+ * The arrival is put off to the next frame, and that one frame is the whole of it.
68
+ *
69
+ * A transition needs a painted starting state. For a block already on screen the observer's
70
+ * first callback is delivered in the rendering steps of the frame the block was laid out in —
71
+ * so flipping there applies the landed state before the waiting one has ever been painted, and
72
+ * the browser has nothing to move between. It goes straight to the end, which looks exactly
73
+ * like no animation at all.
74
+ *
75
+ * That was the bug, and it hid itself well: a block below the fold worked, because the
76
+ * observer does not fire until a reader scrolls and by then the waiting state has been on
77
+ * screen for many frames. Everything a reader sees first — the heading, the first block, any
78
+ * band shown on its own — did not.
79
+ */
80
+ let frame = 0;
81
+ const watching = new IntersectionObserver((entries) => {
82
+ if (!entries.some((entry) => entry.isIntersecting))
83
+ return;
84
+ watching.disconnect();
85
+ frame = window.requestAnimationFrame(() => {
86
+ frame = 0;
87
+ setRevealed(true);
88
+ });
89
+ },
90
+ // A tenth of the block, so a band a screen tall is not held back until a reader has scrolled
91
+ // most of it into view, and a short one is not brought in by a sliver at the edge.
92
+ { threshold: 0.1 });
93
+ watching.observe(element);
94
+ return () => {
95
+ watching.disconnect();
96
+ if (frame !== 0)
97
+ window.cancelAnimationFrame(frame);
98
+ };
99
+ }, []);
100
+ return [
101
+ (element) => {
102
+ held.current = element;
103
+ },
104
+ revealed,
105
+ ];
106
+ }
107
+ /**
108
+ * One part's place in the order its block arrives in.
109
+ *
110
+ * Spread onto each part that moves: `<p {...arriving(1)} …>`. Two spellings of one number, because
111
+ * CSS needs both and can do neither job with the other's — an attribute is what a selector can
112
+ * match, and a custom property is what `calc()` can multiply into a delay.
113
+ *
114
+ * The number is the reading order rather than the DOM order, and they are not always the same: a
115
+ * hero's picture sits before its words in the markup, because a band paints its ground before it
116
+ * sets type on it, and a reader still meets the claim first.
117
+ */
118
+ export function arriving(at) {
119
+ return { 'data-reveal-at': at, style: { '--dds-reveal-at': at } };
120
+ }
@@ -0,0 +1,62 @@
1
+ import { type FocusEvent, type MouseEvent, type PointerEvent } from 'react';
2
+ /**
3
+ * One block rotating between a few things in place — which one is in view, and every way a reader
4
+ * moves it, holds it or stops it.
5
+ *
6
+ * Shared because two blocks rotate, CtaBannerSlider and TestimonialSlider, and a carousel is the
7
+ * kind of thing that drifts: one stops on a press and the other does not, one swallows the click a swipe
8
+ * ends in and the other follows the link. The rules are stated once here and each block spreads
9
+ * what this hands back onto its own parts.
10
+ *
11
+ * The clock is not here. PaginationDots holds the timer and the reduced-motion stillness, and this
12
+ * tells it when to wait — so there is one clock per block, and it is the one a reader can see.
13
+ *
14
+ * What it hands back:
15
+ * - `root`, the ref for the block's own element, which the swipe threshold and the reading
16
+ * direction are read off;
17
+ * - `region`, spread on that element: the carousel's role description and the hold and stop
18
+ * handlers;
19
+ * - `track`, spread on the element holding the items: the swipe and what is announced;
20
+ * - `item(at)`, spread on each item: its group role, its "2 / 3" name and its inert state;
21
+ * - `dots`, spread on PaginationDots alongside the block's own label.
22
+ *
23
+ * Fewer than two items is a set with nothing to move to, drawn still rather than broken: every
24
+ * attribute that says "carousel" is left off and the dots are the block's to omit.
25
+ */
26
+ export declare function useRotation(count: number, autoplay: boolean): {
27
+ current: number;
28
+ rotating: boolean;
29
+ root: import("react").RefObject<HTMLElement | null>;
30
+ region: {
31
+ 'aria-roledescription': string | undefined;
32
+ onPointerEnter: (event: PointerEvent<HTMLElement>) => void;
33
+ onPointerLeave: (event: PointerEvent<HTMLElement>) => void;
34
+ onFocus: () => void;
35
+ onPointerDownCapture: (event: PointerEvent<HTMLElement>) => void;
36
+ onBlur: (event: FocusEvent<HTMLElement>) => void;
37
+ };
38
+ track: {
39
+ 'aria-live': "polite" | "off";
40
+ onPointerDown: (event: PointerEvent<HTMLElement>) => void;
41
+ onPointerUp: (event: PointerEvent<HTMLElement>) => void;
42
+ onPointerCancel: () => void;
43
+ onPointerLeave: () => void;
44
+ onClickCapture: (event: MouseEvent<HTMLElement>) => void;
45
+ };
46
+ /** Whether an item is out of view: inert, hidden, and waiting rather than arrived. */
47
+ away: (at: number) => boolean;
48
+ item: (at: number) => {
49
+ role: string | undefined;
50
+ 'aria-roledescription': string | undefined;
51
+ 'aria-label': string | undefined;
52
+ 'aria-hidden': true | undefined;
53
+ inert: true | undefined;
54
+ };
55
+ dots: {
56
+ page: number;
57
+ totalPages: number;
58
+ onPageChange: (page: number) => void;
59
+ autoplay: boolean;
60
+ paused: boolean;
61
+ };
62
+ };
@@ -0,0 +1,176 @@
1
+ 'use client';
2
+ import { useRef, useState } from 'react';
3
+ /**
4
+ * How soon after a swipe a click still belongs to it. The browser sends the click a swipe ends
5
+ * with straight after the release; anything later is a press of its own.
6
+ */
7
+ const SWIPE_CLICK_MS = 300;
8
+ /** A token read off the block's own element, so it is the value the page actually resolved. */
9
+ function tokenOn(element, name) {
10
+ return element === null ? '' : getComputedStyle(element).getPropertyValue(name);
11
+ }
12
+ /**
13
+ * One block rotating between a few things in place — which one is in view, and every way a reader
14
+ * moves it, holds it or stops it.
15
+ *
16
+ * Shared because two blocks rotate, CtaBannerSlider and TestimonialSlider, and a carousel is the
17
+ * kind of thing that drifts: one stops on a press and the other does not, one swallows the click a swipe
18
+ * ends in and the other follows the link. The rules are stated once here and each block spreads
19
+ * what this hands back onto its own parts.
20
+ *
21
+ * The clock is not here. PaginationDots holds the timer and the reduced-motion stillness, and this
22
+ * tells it when to wait — so there is one clock per block, and it is the one a reader can see.
23
+ *
24
+ * What it hands back:
25
+ * - `root`, the ref for the block's own element, which the swipe threshold and the reading
26
+ * direction are read off;
27
+ * - `region`, spread on that element: the carousel's role description and the hold and stop
28
+ * handlers;
29
+ * - `track`, spread on the element holding the items: the swipe and what is announced;
30
+ * - `item(at)`, spread on each item: its group role, its "2 / 3" name and its inert state;
31
+ * - `dots`, spread on PaginationDots alongside the block's own label.
32
+ *
33
+ * Fewer than two items is a set with nothing to move to, drawn still rather than broken: every
34
+ * attribute that says "carousel" is left off and the dots are the block's to omit.
35
+ */
36
+ export function useRotation(count, autoplay) {
37
+ const rotating = count > 1;
38
+ const root = useRef(null);
39
+ const [index, setIndex] = useState(0);
40
+ /*
41
+ A pointer over the block or focus inside it, which holds the rotation only for as long as it
42
+ lasts. The rotation itself is PaginationDots' — the timer and the reduced-motion stillness — so
43
+ there is one clock, and this only tells it to wait while the reader is on the item.
44
+ */
45
+ const [held, setHeld] = useState(false);
46
+ /*
47
+ Stopped for good once the reader presses anything in the block — an item, its action, a dot —
48
+ or swipes it. The WAI carousel pattern's rule: a reader who has touched the carousel is
49
+ reading it, and moving it on under them takes that back. It is also the way this offers a
50
+ stop without a control for one. A hold lets go; this does not.
51
+ */
52
+ const [stopped, setStopped] = useState(false);
53
+ // A count that shrinks under the index would leave the track pointing past its end.
54
+ const current = count === 0 ? 0 : index % count;
55
+ const go = (to) => setIndex(((to % count) + count) % count);
56
+ // A move the reader made — a swipe — moves the set and stops it.
57
+ const step = (to) => {
58
+ setStopped(true);
59
+ go(to);
60
+ };
61
+ /*
62
+ A swipe is a pointer that travelled. How far counts as travelling is the smallest target the
63
+ package names, size.target.min: a move shorter than something a reader could aim at is a
64
+ press that wobbled, not a gesture.
65
+ */
66
+ const from = useRef(null);
67
+ /*
68
+ When a gesture last travelled far enough to be a swipe, spent on the click the browser sends
69
+ after it. Without it a drag that starts and ends on an item's call to action both moves the set
70
+ and follows the link.
71
+
72
+ A time rather than a flag, because the click does not always come: a touch that travelled
73
+ usually ends with no click at all. A flag left standing then swallowed the next click that
74
+ arrived without a press on the track — Enter on the call to action, or a screen reader's
75
+ activation — and the link went nowhere. Only a click that follows the swipe closely is its own.
76
+ */
77
+ const swipedAt = useRef(null);
78
+ const onPointerDown = (event) => {
79
+ // The primary pointer and, for a mouse, the primary button: a right-button drag or a second
80
+ // finger is not a swipe.
81
+ if (!event.isPrimary || (event.pointerType === 'mouse' && event.button !== 0))
82
+ return;
83
+ from.current = event.clientX;
84
+ swipedAt.current = null;
85
+ };
86
+ // The browser took the gesture — a vertical scroll, a system swipe — or the pointer left the
87
+ // track before it was let go, so there is no swipe to read. Left standing, the start of an
88
+ // abandoned drag would be measured against the next release on the track.
89
+ const onPointerCancel = () => {
90
+ from.current = null;
91
+ };
92
+ const onPointerUp = (event) => {
93
+ const start = from.current;
94
+ from.current = null;
95
+ if (start === null || !rotating || !event.isPrimary)
96
+ return;
97
+ const threshold = Number.parseFloat(tokenOn(root.current, '--size-target-min'));
98
+ const travel = event.clientX - start;
99
+ if (!Number.isFinite(threshold) || Math.abs(travel) < threshold)
100
+ return;
101
+ // Leftward travel brings the next item in from the end the reader is reading towards; in a
102
+ // right-to-left page that end is the left one, so the direction follows the page.
103
+ const rtl = root.current !== null && getComputedStyle(root.current).direction === 'rtl';
104
+ swipedAt.current = event.timeStamp;
105
+ step(current + ((travel < 0) !== rtl ? 1 : -1));
106
+ };
107
+ const onClickCapture = (event) => {
108
+ const at = swipedAt.current;
109
+ swipedAt.current = null;
110
+ if (at === null || event.timeStamp - at > SWIPE_CLICK_MS)
111
+ return;
112
+ event.preventDefault();
113
+ event.stopPropagation();
114
+ };
115
+ // Held while focus is anywhere inside, released only when it leaves the block altogether.
116
+ const onBlur = (event) => {
117
+ if (!event.currentTarget.contains(event.relatedTarget))
118
+ setHeld(false);
119
+ };
120
+ return {
121
+ current,
122
+ rotating,
123
+ root,
124
+ region: {
125
+ 'aria-roledescription': rotating ? 'carousel' : undefined,
126
+ onPointerEnter: (event) => {
127
+ if (event.pointerType === 'mouse')
128
+ setHeld(true);
129
+ },
130
+ onPointerLeave: (event) => {
131
+ if (event.pointerType === 'mouse')
132
+ setHeld(false);
133
+ },
134
+ onFocus: () => setHeld(true),
135
+ // Any primary press in the block stops the rotation; the press itself still does what it does.
136
+ onPointerDownCapture: (event) => {
137
+ if (event.isPrimary && (event.pointerType !== 'mouse' || event.button === 0))
138
+ setStopped(true);
139
+ },
140
+ onBlur,
141
+ },
142
+ track: {
143
+ /*
144
+ An item that changes on its own is not announced — a reader would be talked over every few
145
+ seconds. One the reader moved to is, because they asked for it: whenever autoplay is off,
146
+ and whenever it is held — which it always is while focus is on a dot, the only way a
147
+ keyboard or screen reader user moves it.
148
+ */
149
+ 'aria-live': (rotating && (!autoplay || held || stopped) ? 'polite' : 'off'),
150
+ onPointerDown,
151
+ onPointerUp,
152
+ onPointerCancel,
153
+ onPointerLeave: onPointerCancel,
154
+ onClickCapture,
155
+ },
156
+ /** Whether an item is out of view: inert, hidden, and waiting rather than arrived. */
157
+ away: (at) => rotating && at !== current,
158
+ item: (at) => {
159
+ const away = rotating && at !== current;
160
+ return {
161
+ role: rotating ? 'group' : undefined,
162
+ 'aria-roledescription': rotating ? 'slide' : undefined,
163
+ 'aria-label': rotating ? `${at + 1} / ${count}` : undefined,
164
+ 'aria-hidden': away || undefined,
165
+ inert: away || undefined,
166
+ };
167
+ },
168
+ dots: {
169
+ page: current + 1,
170
+ totalPages: count,
171
+ onPageChange: (page) => go(page - 1),
172
+ autoplay,
173
+ paused: held || stopped,
174
+ },
175
+ };
176
+ }
@@ -44,6 +44,11 @@
44
44
  line-height: var(--font-label-md-line-height);
45
45
  }
46
46
 
47
+ :where([data-dds-context="marketing"]) .dds-accordionitem .dds-accordionitem__marker {
48
+ transition-property: rotate;
49
+ color: var(--color-fg-muted);
50
+ }
51
+
47
52
  :where([data-dds-context="marketing"]) .dds-accordionitem .dds-accordionitem__panel {
48
53
  overflow: hidden;
49
54
  }
@@ -68,10 +73,6 @@
68
73
  transition-timing-function: var(--motion-easing-enter);
69
74
  }
70
75
 
71
- :where([data-dds-context="marketing"]) .dds-accordionitem .dds-accordionitem__marker {
72
- color: var(--color-fg-muted);
73
- }
74
-
75
76
  :where([data-dds-context="marketing"]) .dds-accordionitem .dds-accordionitem__trigger:hover:where(:not(:disabled)) {
76
77
  color: var(--color-fg-muted);
77
78
  }
@@ -93,6 +94,6 @@
93
94
 
94
95
  @media (prefers-reduced-motion: reduce) {
95
96
  :where([data-dds-context="marketing"]) .dds-accordionitem,
96
- :where([data-dds-context="marketing"]) .dds-accordionitem * { transition: none; }
97
+ :where([data-dds-context="marketing"]) .dds-accordionitem * { transition: none !important; }
97
98
  }
98
99
 
@@ -173,6 +173,6 @@
173
173
 
174
174
  @media (prefers-reduced-motion: reduce) {
175
175
  :where([data-dds-context="marketing"]) .dds-bottomsheet,
176
- :where([data-dds-context="marketing"]) .dds-bottomsheet * { transition: none; }
176
+ :where([data-dds-context="marketing"]) .dds-bottomsheet * { transition: none !important; }
177
177
  }
178
178
 
@@ -10,8 +10,10 @@
10
10
  cursor: pointer;
11
11
  display: inline-flex;
12
12
  flex-direction: row;
13
+ font-family: inherit;
13
14
  justify-content: center;
14
15
  outline-style: solid;
16
+ padding-block: 0;
15
17
  position: relative;
16
18
  text-decoration-line: none;
17
19
  white-space: nowrap;
@@ -31,11 +33,6 @@
31
33
  flex: none;
32
34
  }
33
35
 
34
- :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="inherit"] {
35
- border-color: currentcolor;
36
- color: inherit;
37
- }
38
-
39
36
  :where([data-dds-context="marketing"]) .dds-button:focus-visible {
40
37
  outline-color: var(--color-border-focus);
41
38
  outline-width: var(--border-width-focus);
@@ -102,26 +99,21 @@
102
99
 
103
100
  :where([data-dds-context="marketing"]) .dds-button[data-variant="primary"] {
104
101
  background-color: var(--color-bg-brand);
105
- color: var(--color-fg-on-solid);
102
+ color: var(--color-fg-on-brand);
106
103
  }
107
104
 
108
105
  :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"] {
109
- border-color: var(--color-border-strong);
110
106
  color: var(--color-fg-default);
111
107
  }
112
108
 
113
- :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"]:hover {
109
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"]:hover:where(:not(:disabled)) {
114
110
  background-image: linear-gradient(var(--color-bg-hover-inverse), var(--color-bg-hover-inverse));
115
111
  }
116
112
 
117
- :where([data-dds-context="marketing"]) .dds-button[data-variant="primary"]:hover {
113
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="primary"]:hover:where(:not(:disabled)) {
118
114
  background-color: var(--color-bg-brand-hover);
119
115
  }
120
116
 
121
- :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"]:hover {
122
- background-color: var(--color-bg-hover);
123
- }
124
-
125
117
  :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"]:active {
126
118
  background-image: linear-gradient(var(--color-bg-active-inverse), var(--color-bg-active-inverse));
127
119
  }
@@ -130,12 +122,67 @@
130
122
  background-color: var(--color-bg-brand-active);
131
123
  }
132
124
 
133
- :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"]:active {
134
- background-color: var(--color-bg-active);
125
+ :where([data-dds-context="marketing"]) .dds-button[data-tone="brand"]:focus-visible {
126
+ outline-color: var(--color-border-focus-on-brand-strong);
127
+ }
128
+
129
+ :where([data-dds-context="marketing"]) .dds-button[data-tone="inverse"]:focus-visible {
130
+ outline-color: var(--color-border-focus-on-brand-strong);
131
+ }
132
+
133
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"][data-tone="brand"] {
134
+ background-color: var(--color-bg-strong-on-brand);
135
+ color: var(--color-fg-on-strong-on-brand);
136
+ }
137
+
138
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="brand"] {
139
+ background-color: var(--color-bg-subtle-on-brand-strong);
140
+ color: var(--color-fg-on-brand-strong);
141
+ }
142
+
143
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="default"] {
144
+ background-color: var(--color-bg-subtle);
145
+ }
146
+
147
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="inverse"] {
148
+ background-color: var(--color-bg-subtle-on-brand-strong);
149
+ color: var(--color-fg-on-brand-strong);
150
+ }
151
+
152
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"][data-tone="brand"]:hover:where(:not(:disabled)) {
153
+ background-image: linear-gradient(var(--color-bg-hover-on-brand-strong), var(--color-bg-hover-on-brand-strong));
154
+ }
155
+
156
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="brand"]:hover:where(:not(:disabled)) {
157
+ background-image: linear-gradient(var(--color-bg-hover-on-brand-strong), var(--color-bg-hover-on-brand-strong));
158
+ }
159
+
160
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="default"]:hover:where(:not(:disabled)) {
161
+ background-image: linear-gradient(var(--color-bg-hover), var(--color-bg-hover));
162
+ }
163
+
164
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="inverse"]:hover:where(:not(:disabled)) {
165
+ background-image: linear-gradient(var(--color-bg-hover-on-brand-strong), var(--color-bg-hover-on-brand-strong));
166
+ }
167
+
168
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="inverse"][data-tone="brand"]:active {
169
+ background-image: linear-gradient(var(--color-bg-active-on-brand-strong), var(--color-bg-active-on-brand-strong));
170
+ }
171
+
172
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="brand"]:active {
173
+ background-image: linear-gradient(var(--color-bg-active-on-brand-strong), var(--color-bg-active-on-brand-strong));
174
+ }
175
+
176
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="default"]:active {
177
+ background-image: linear-gradient(var(--color-bg-active), var(--color-bg-active));
178
+ }
179
+
180
+ :where([data-dds-context="marketing"]) .dds-button[data-variant="secondary"][data-tone="inverse"]:active {
181
+ background-image: linear-gradient(var(--color-bg-active-on-brand-strong), var(--color-bg-active-on-brand-strong));
135
182
  }
136
183
 
137
184
  @media (prefers-reduced-motion: reduce) {
138
185
  :where([data-dds-context="marketing"]) .dds-button,
139
- :where([data-dds-context="marketing"]) .dds-button * { transition: none; }
186
+ :where([data-dds-context="marketing"]) .dds-button * { transition: none !important; }
140
187
  }
141
188
 
@@ -8,18 +8,20 @@ export type { ButtonProps };
8
8
  * generated from the same anatomy, and a class retyped slightly differently
9
9
  * renders a part bare with its rule sitting unused.
10
10
  *
11
- * An anchor, not a button, and that is the whole shape of this component. It
12
- * navigates a visitor to the next step of a funnel, so the element has to be the
13
- * one the browser already knows how to navigate with: cmd-click and middle-click
14
- * open a new tab, the status bar shows the destination on hover, and assistive
15
- * tech announces a link rather than a control that fires something in place.
16
- * `href` is required in the generated type for the same reason — an anchor
17
- * without one is not focusable and is announced as plain text, so the control
18
- * simply disappears for anyone not using a mouse.
11
+ * Which element it renders is decided by `href` and nothing else, the shape CardButton
12
+ * and ButtonIcon already take. A CTA that goes to a page and one that sends a newsletter
13
+ * form are the same press with different destinations, and the element is what tells a
14
+ * browser which: an `<a>` gives cmd-click, a destination in the status bar and a copyable
15
+ * link, and a `<button>` answers Space and does what the page says.
19
16
  *
20
- * There is no `loading` and no `disabled`, unlike product's Button. Neither has
21
- * an honest anchor form: the browser owns the pending state and shows it in the
22
- * chrome, and `aria-disabled` on a link still follows the link when pressed, so
23
- * a disabled prop here would style a control that keeps working.
17
+ * It is typed against `AnchorHTMLAttributes`, the branch a marketing CTA takes in the
18
+ * ordinary case, and that is what puts `target` and `rel` in reach. ButtonIcon records the
19
+ * same trade: the generator takes one interface, so the `<button>`'s own `form`, `name` and
20
+ * `value` are what it gives up, and `type` survives as a string so a submit still compiles.
21
+ *
22
+ * There is no `loading` and no `disabled`, unlike product's Button. Neither has an honest
23
+ * anchor form: the browser owns a link's pending state and shows it in the chrome, and
24
+ * `aria-disabled` on a link still follows the link when pressed. A prop that meant one
25
+ * thing on one branch and nothing on the other would be worse than its absence.
24
26
  */
25
27
  export declare function Button({ variant, size, tone, href, showIcon, children, ...rest }: ButtonProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowRightRegularIcon } from '@deriv-com/quill-icons-web';
3
3
  import './Button.css';
4
4
  import './button-impl.css';
@@ -9,20 +9,49 @@ import { buttonDefaults, buttonParts } from './Button.types.js';
9
9
  * generated from the same anatomy, and a class retyped slightly differently
10
10
  * renders a part bare with its rule sitting unused.
11
11
  *
12
- * An anchor, not a button, and that is the whole shape of this component. It
13
- * navigates a visitor to the next step of a funnel, so the element has to be the
14
- * one the browser already knows how to navigate with: cmd-click and middle-click
15
- * open a new tab, the status bar shows the destination on hover, and assistive
16
- * tech announces a link rather than a control that fires something in place.
17
- * `href` is required in the generated type for the same reason — an anchor
18
- * without one is not focusable and is announced as plain text, so the control
19
- * simply disappears for anyone not using a mouse.
12
+ * Which element it renders is decided by `href` and nothing else, the shape CardButton
13
+ * and ButtonIcon already take. A CTA that goes to a page and one that sends a newsletter
14
+ * form are the same press with different destinations, and the element is what tells a
15
+ * browser which: an `<a>` gives cmd-click, a destination in the status bar and a copyable
16
+ * link, and a `<button>` answers Space and does what the page says.
20
17
  *
21
- * There is no `loading` and no `disabled`, unlike product's Button. Neither has
22
- * an honest anchor form: the browser owns the pending state and shows it in the
23
- * chrome, and `aria-disabled` on a link still follows the link when pressed, so
24
- * a disabled prop here would style a control that keeps working.
18
+ * It is typed against `AnchorHTMLAttributes`, the branch a marketing CTA takes in the
19
+ * ordinary case, and that is what puts `target` and `rel` in reach. ButtonIcon records the
20
+ * same trade: the generator takes one interface, so the `<button>`'s own `form`, `name` and
21
+ * `value` are what it gives up, and `type` survives as a string so a submit still compiles.
22
+ *
23
+ * There is no `loading` and no `disabled`, unlike product's Button. Neither has an honest
24
+ * anchor form: the browser owns a link's pending state and shows it in the chrome, and
25
+ * `aria-disabled` on a link still follows the link when pressed. A prop that meant one
26
+ * thing on one branch and nothing on the other would be worse than its absence.
25
27
  */
26
28
  export function Button({ variant = buttonDefaults.variant, size = buttonDefaults.size, tone = buttonDefaults.tone, href, showIcon = buttonDefaults.showIcon, children, ...rest }) {
27
- return (_jsxs("a", { className: buttonParts.root, href: href, "data-variant": variant, "data-tone": tone, "data-size": size, ...rest, children: [_jsx("span", { className: buttonParts.label, children: children }), showIcon ? (_jsx(ArrowRightRegularIcon, { className: buttonParts.icon, "aria-hidden": "true" })) : null] }));
29
+ /*
30
+ What both branches carry. Spread after `rest` so none of it can be written over from
31
+ the call site: the class is how the generated stylesheet finds the root.
32
+ */
33
+ const shared = {
34
+ className: buttonParts.root,
35
+ 'data-variant': variant,
36
+ 'data-tone': tone,
37
+ 'data-size': size,
38
+ };
39
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: buttonParts.label, children: children }), showIcon ? (_jsx(ArrowRightRegularIcon, { className: buttonParts.icon, "aria-hidden": "true" })) : null] }));
40
+ if (href !== undefined) {
41
+ // No cast: `rest` is already the anchor's own attribute set, so `target` and `rel`
42
+ // arrive typed and land on the element that understands them.
43
+ return (_jsx("a", { ...rest, ...shared, href: href, children: content }));
44
+ }
45
+ return (
46
+ /*
47
+ A button, not a submit. The element's own default is `submit`, so a CTA dropped into
48
+ a newsletter form beside its real submit would send it. Above the spread rather than
49
+ below it, so a consumer who does want this to submit the form still wins — ButtonIcon
50
+ takes the same order for the same reason.
51
+
52
+ The cast is on this branch because the props are the anchor's: an `<a>`'s `type` is
53
+ the destination's MIME type and so is a plain string, which a `<button>`'s own `type`
54
+ will not accept. Runtime is unaffected.
55
+ */
56
+ _jsx("button", { type: "button", ...rest, ...shared, children: content }));
28
57
  }