@deriv-com/dds-marketing 0.1.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.
- package/.llm/components/accordion.md +201 -0
- package/.llm/components/accordionitem.md +244 -0
- package/.llm/components/aspectratio.md +220 -0
- package/.llm/components/badge.md +239 -0
- package/.llm/components/bottomsheet.md +412 -0
- package/.llm/components/breadcrumb.md +205 -0
- package/.llm/components/breadcrumbitem.md +186 -0
- package/.llm/components/button.md +295 -0
- package/.llm/components/buttonicon.md +321 -0
- package/.llm/components/card.md +327 -0
- package/.llm/components/cardbody.md +435 -0
- package/.llm/components/cardbutton.md +273 -0
- package/.llm/components/checkbox.md +293 -0
- package/.llm/components/ctabanner.md +186 -0
- package/.llm/components/ctabannerslider.md +204 -0
- package/.llm/components/divider.md +238 -0
- package/.llm/components/dropdownmenu.md +461 -0
- package/.llm/components/faqaccordion.md +217 -0
- package/.llm/components/featurebullets.md +254 -0
- package/.llm/components/featurecards.md +313 -0
- package/.llm/components/featurescroll.md +284 -0
- package/.llm/components/featuresplit.md +249 -0
- package/.llm/components/filterchip.md +322 -0
- package/.llm/components/footer.md +416 -0
- package/.llm/components/footergroup.md +249 -0
- package/.llm/components/grid.md +667 -0
- package/.llm/components/icon.md +202 -0
- package/.llm/components/landinghero.md +254 -0
- package/.llm/components/link.md +362 -0
- package/.llm/components/logocolumns.md +235 -0
- package/.llm/components/logorows.md +212 -0
- package/.llm/components/menuitem.md +226 -0
- package/.llm/components/navbar.md +522 -0
- package/.llm/components/navbaritem.md +246 -0
- package/.llm/components/navbarmenu.md +327 -0
- package/.llm/components/navbarmenugroup.md +192 -0
- package/.llm/components/navbarpanel.md +263 -0
- package/.llm/components/navbarswitcher.md +351 -0
- package/.llm/components/navbarswitcheroption.md +164 -0
- package/.llm/components/option.md +276 -0
- package/.llm/components/pageheader.md +231 -0
- package/.llm/components/pagination.md +298 -0
- package/.llm/components/paginationdots.md +215 -0
- package/.llm/components/producthero.md +248 -0
- package/.llm/components/removablechip.md +255 -0
- package/.llm/components/row.md +389 -0
- package/.llm/components/scroller.md +478 -0
- package/.llm/components/searchinput.md +287 -0
- package/.llm/components/segmentedcontrol.md +366 -0
- package/.llm/components/segmentedcontrolitem.md +280 -0
- package/.llm/components/select.md +517 -0
- package/.llm/components/selectchip.md +375 -0
- package/.llm/components/spinner.md +230 -0
- package/.llm/components/stack.md +342 -0
- package/.llm/components/statusdot.md +213 -0
- package/.llm/components/subdomainnavbar.md +335 -0
- package/.llm/components/tab.md +299 -0
- package/.llm/components/table.md +668 -0
- package/.llm/components/tablebody.md +266 -0
- package/.llm/components/tablecell.md +256 -0
- package/.llm/components/tablehead.md +256 -0
- package/.llm/components/tableheadercell.md +306 -0
- package/.llm/components/tablerow.md +293 -0
- package/.llm/components/tablist.md +218 -0
- package/.llm/components/testimonialrow.md +235 -0
- package/.llm/components/testimonialslider.md +254 -0
- package/.llm/components/textarea.md +370 -0
- package/.llm/components/textinput.md +475 -0
- package/.llm/components/tooltip.md +275 -0
- package/.llm/components/typography.md +253 -0
- package/.llm/digest/accordion.md +43 -0
- package/.llm/digest/accordionitem.md +47 -0
- package/.llm/digest/aspectratio.md +47 -0
- package/.llm/digest/badge.md +45 -0
- package/.llm/digest/bottomsheet.md +69 -0
- package/.llm/digest/breadcrumb.md +38 -0
- package/.llm/digest/breadcrumbitem.md +43 -0
- package/.llm/digest/button.md +53 -0
- package/.llm/digest/buttonicon.md +61 -0
- package/.llm/digest/card.md +51 -0
- package/.llm/digest/cardbody.md +70 -0
- package/.llm/digest/cardbutton.md +50 -0
- package/.llm/digest/checkbox.md +63 -0
- package/.llm/digest/ctabanner.md +44 -0
- package/.llm/digest/ctabannerslider.md +45 -0
- package/.llm/digest/divider.md +52 -0
- package/.llm/digest/dropdownmenu.md +67 -0
- package/.llm/digest/faqaccordion.md +43 -0
- package/.llm/digest/featurebullets.md +52 -0
- package/.llm/digest/featurecards.md +49 -0
- package/.llm/digest/featurescroll.md +52 -0
- package/.llm/digest/featuresplit.md +54 -0
- package/.llm/digest/filterchip.md +68 -0
- package/.llm/digest/footer.md +57 -0
- package/.llm/digest/footergroup.md +42 -0
- package/.llm/digest/grid.md +62 -0
- package/.llm/digest/icon.md +45 -0
- package/.llm/digest/landinghero.md +53 -0
- package/.llm/digest/link.md +59 -0
- package/.llm/digest/logocolumns.md +55 -0
- package/.llm/digest/logorows.md +48 -0
- package/.llm/digest/menuitem.md +46 -0
- package/.llm/digest/navbar.md +75 -0
- package/.llm/digest/navbaritem.md +54 -0
- package/.llm/digest/navbarmenu.md +54 -0
- package/.llm/digest/navbarmenugroup.md +40 -0
- package/.llm/digest/navbarpanel.md +53 -0
- package/.llm/digest/navbarswitcher.md +51 -0
- package/.llm/digest/navbarswitcheroption.md +37 -0
- package/.llm/digest/option.md +54 -0
- package/.llm/digest/pageheader.md +49 -0
- package/.llm/digest/pagination.md +60 -0
- package/.llm/digest/paginationdots.md +43 -0
- package/.llm/digest/producthero.md +53 -0
- package/.llm/digest/removablechip.md +51 -0
- package/.llm/digest/row.md +60 -0
- package/.llm/digest/scroller.md +59 -0
- package/.llm/digest/searchinput.md +56 -0
- package/.llm/digest/segmentedcontrol.md +59 -0
- package/.llm/digest/segmentedcontrolitem.md +59 -0
- package/.llm/digest/select.md +92 -0
- package/.llm/digest/selectchip.md +73 -0
- package/.llm/digest/spinner.md +52 -0
- package/.llm/digest/stack.md +56 -0
- package/.llm/digest/statusdot.md +46 -0
- package/.llm/digest/subdomainnavbar.md +54 -0
- package/.llm/digest/tab.md +60 -0
- package/.llm/digest/table.md +71 -0
- package/.llm/digest/tablebody.md +49 -0
- package/.llm/digest/tablecell.md +55 -0
- package/.llm/digest/tablehead.md +50 -0
- package/.llm/digest/tableheadercell.md +65 -0
- package/.llm/digest/tablerow.md +56 -0
- package/.llm/digest/tablist.md +46 -0
- package/.llm/digest/testimonialrow.md +51 -0
- package/.llm/digest/testimonialslider.md +51 -0
- package/.llm/digest/textarea.md +78 -0
- package/.llm/digest/textinput.md +95 -0
- package/.llm/digest/tooltip.md +58 -0
- package/.llm/digest/typography.md +59 -0
- package/.llm/index.md +107 -0
- package/.llm/rules.md +146 -0
- package/.llm/tokens.md +385 -0
- package/AGENTS.md +189 -0
- package/LICENSE +21 -0
- package/README.md +78 -10
- package/dist/components/banner-data.d.ts +38 -0
- package/dist/components/banner-data.js +15 -0
- package/dist/components/blocks/block-reveal.css +90 -0
- package/dist/components/blocks/cta-banner/CtaBanner.css +66 -0
- package/dist/components/blocks/cta-banner/CtaBanner.d.ts +22 -0
- package/dist/components/blocks/cta-banner/CtaBanner.js +31 -0
- package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +29 -0
- package/dist/components/blocks/cta-banner/CtaBanner.types.js +13 -0
- package/dist/components/blocks/cta-banner/cta-banner-impl.css +29 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +90 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +39 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +60 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +41 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +15 -0
- package/dist/components/blocks/cta-banner-slider/cta-banner-slider-impl.css +73 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.css +54 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.d.ts +20 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.js +28 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.types.d.ts +25 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.types.js +10 -0
- package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +54 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.css +139 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +31 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.js +55 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.types.d.ts +39 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.types.js +16 -0
- package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +104 -0
- package/dist/components/blocks/feature-cards/FeatureCards.css +84 -0
- package/dist/components/blocks/feature-cards/FeatureCards.d.ts +24 -0
- package/dist/components/blocks/feature-cards/FeatureCards.js +66 -0
- package/dist/components/blocks/feature-cards/FeatureCards.types.d.ts +33 -0
- package/dist/components/blocks/feature-cards/FeatureCards.types.js +14 -0
- package/dist/components/blocks/feature-cards/feature-cards-impl.css +245 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.css +137 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.d.ts +39 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.js +167 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.types.d.ts +40 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.types.js +17 -0
- package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +292 -0
- package/dist/components/blocks/feature-split/FeatureSplit.css +95 -0
- package/dist/components/blocks/feature-split/FeatureSplit.d.ts +30 -0
- package/dist/components/blocks/feature-split/FeatureSplit.js +38 -0
- package/dist/components/blocks/feature-split/FeatureSplit.types.d.ts +39 -0
- package/dist/components/blocks/feature-split/FeatureSplit.types.js +14 -0
- package/dist/components/blocks/feature-split/feature-split-impl.css +83 -0
- package/dist/components/blocks/landing-hero/LandingHero.css +85 -0
- package/dist/components/blocks/landing-hero/LandingHero.d.ts +41 -0
- package/dist/components/blocks/landing-hero/LandingHero.js +54 -0
- package/dist/components/blocks/landing-hero/LandingHero.types.d.ts +37 -0
- package/dist/components/blocks/landing-hero/LandingHero.types.js +15 -0
- package/dist/components/blocks/landing-hero/landing-hero-impl.css +355 -0
- package/dist/components/blocks/logo-columns/LogoColumns.css +161 -0
- package/dist/components/blocks/logo-columns/LogoColumns.d.ts +23 -0
- package/dist/components/blocks/logo-columns/LogoColumns.js +46 -0
- package/dist/components/blocks/logo-columns/LogoColumns.types.d.ts +46 -0
- package/dist/components/blocks/logo-columns/LogoColumns.types.js +18 -0
- package/dist/components/blocks/logo-columns/logo-columns-impl.css +188 -0
- package/dist/components/blocks/logo-rows/LogoRows.css +122 -0
- package/dist/components/blocks/logo-rows/LogoRows.d.ts +20 -0
- package/dist/components/blocks/logo-rows/LogoRows.js +43 -0
- package/dist/components/blocks/logo-rows/LogoRows.types.d.ts +36 -0
- package/dist/components/blocks/logo-rows/LogoRows.types.js +16 -0
- package/dist/components/blocks/logo-rows/logo-rows-impl.css +179 -0
- package/dist/components/blocks/page-header/PageHeader.css +79 -0
- package/dist/components/blocks/page-header/PageHeader.d.ts +34 -0
- package/dist/components/blocks/page-header/PageHeader.js +43 -0
- package/dist/components/blocks/page-header/PageHeader.types.d.ts +32 -0
- package/dist/components/blocks/page-header/PageHeader.types.js +14 -0
- package/dist/components/blocks/page-header/page-header-impl.css +176 -0
- package/dist/components/blocks/product-hero/ProductHero.css +86 -0
- package/dist/components/blocks/product-hero/ProductHero.d.ts +35 -0
- package/dist/components/blocks/product-hero/ProductHero.js +48 -0
- package/dist/components/blocks/product-hero/ProductHero.types.d.ts +37 -0
- package/dist/components/blocks/product-hero/ProductHero.types.js +15 -0
- package/dist/components/blocks/product-hero/product-hero-impl.css +354 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.css +245 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +20 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.js +128 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +54 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +26 -0
- package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +195 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +181 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +22 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +36 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +49 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +21 -0
- package/dist/components/blocks/testimonial-slider/testimonial-slider-impl.css +202 -0
- package/dist/components/blocks/use-reveal.d.ts +46 -0
- package/dist/components/blocks/use-reveal.js +120 -0
- package/dist/components/blocks/use-rotation.d.ts +62 -0
- package/dist/components/blocks/use-rotation.js +176 -0
- package/dist/components/core/accordion/Accordion.css +10 -0
- package/dist/components/core/accordion/Accordion.d.ts +20 -0
- package/dist/components/core/accordion/Accordion.js +69 -0
- package/dist/components/core/accordion/Accordion.types.d.ts +20 -0
- package/dist/components/core/accordion/Accordion.types.js +7 -0
- package/dist/components/core/accordion-item/AccordionItem.css +99 -0
- package/dist/components/core/accordion-item/AccordionItem.d.ts +44 -0
- package/dist/components/core/accordion-item/AccordionItem.js +63 -0
- package/dist/components/core/accordion-item/AccordionItem.types.d.ts +32 -0
- package/dist/components/core/accordion-item/AccordionItem.types.js +13 -0
- package/dist/components/core/accordion-item/accordion-item-impl.css +107 -0
- package/dist/components/core/badge/Badge.css +37 -0
- package/dist/components/core/badge/Badge.d.ts +37 -0
- package/dist/components/core/badge/Badge.js +40 -0
- package/dist/components/core/badge/Badge.types.d.ts +24 -0
- package/dist/components/core/badge/Badge.types.js +8 -0
- package/dist/components/core/badge/badge-impl.css +32 -0
- package/dist/components/core/bottom-sheet/BottomSheet.css +178 -0
- package/dist/components/core/bottom-sheet/BottomSheet.d.ts +22 -0
- package/dist/components/core/bottom-sheet/BottomSheet.js +359 -0
- package/dist/components/core/bottom-sheet/BottomSheet.types.d.ts +67 -0
- package/dist/components/core/bottom-sheet/BottomSheet.types.js +18 -0
- package/dist/components/core/bottom-sheet/bottom-sheet-impl.css +57 -0
- package/dist/components/core/breadcrumb/Breadcrumb.css +19 -0
- package/dist/components/core/breadcrumb/Breadcrumb.d.ts +16 -0
- package/dist/components/core/breadcrumb/Breadcrumb.js +22 -0
- package/dist/components/core/breadcrumb/Breadcrumb.types.d.ts +18 -0
- package/dist/components/core/breadcrumb/Breadcrumb.types.js +9 -0
- package/dist/components/core/breadcrumb-item/BreadcrumbItem.css +34 -0
- package/dist/components/core/breadcrumb-item/BreadcrumbItem.d.ts +12 -0
- package/dist/components/core/breadcrumb-item/BreadcrumbItem.js +15 -0
- package/dist/components/core/breadcrumb-item/BreadcrumbItem.types.d.ts +22 -0
- package/dist/components/core/breadcrumb-item/BreadcrumbItem.types.js +9 -0
- package/dist/components/core/button/Button.css +188 -0
- package/dist/components/core/button/Button.d.ts +27 -0
- package/dist/components/core/button/Button.js +57 -0
- package/dist/components/core/button/Button.types.d.ts +35 -0
- package/dist/components/core/button/Button.types.js +9 -0
- package/dist/components/core/button/button-impl.css +71 -0
- package/dist/components/core/button-icon/ButtonIcon.css +151 -0
- package/dist/components/core/button-icon/ButtonIcon.d.ts +34 -0
- package/dist/components/core/button-icon/ButtonIcon.js +117 -0
- package/dist/components/core/button-icon/ButtonIcon.types.d.ts +41 -0
- package/dist/components/core/button-icon/ButtonIcon.types.js +8 -0
- package/dist/components/core/card/Card.css +74 -0
- package/dist/components/core/card/Card.d.ts +24 -0
- package/dist/components/core/card/Card.js +26 -0
- package/dist/components/core/card/Card.types.d.ts +34 -0
- package/dist/components/core/card/Card.types.js +7 -0
- package/dist/components/core/card-body/CardBody.css +144 -0
- package/dist/components/core/card-body/CardBody.d.ts +62 -0
- package/dist/components/core/card-body/CardBody.js +97 -0
- package/dist/components/core/card-body/CardBody.types.d.ts +64 -0
- package/dist/components/core/card-body/CardBody.types.js +16 -0
- package/dist/components/core/card-body/card-body-impl.css +87 -0
- package/dist/components/core/card-button/CardButton.css +82 -0
- package/dist/components/core/card-button/CardButton.d.ts +27 -0
- package/dist/components/core/card-button/CardButton.js +37 -0
- package/dist/components/core/card-button/CardButton.types.d.ts +25 -0
- package/dist/components/core/card-button/CardButton.types.js +7 -0
- package/dist/components/core/card-button/card-button-impl.css +39 -0
- package/dist/components/core/checkbox/Checkbox.css +156 -0
- package/dist/components/core/checkbox/Checkbox.d.ts +21 -0
- package/dist/components/core/checkbox/Checkbox.js +97 -0
- package/dist/components/core/checkbox/Checkbox.types.d.ts +46 -0
- package/dist/components/core/checkbox/Checkbox.types.js +15 -0
- package/dist/components/core/checkbox/checkbox-impl.css +90 -0
- package/dist/components/core/divider/Divider.css +41 -0
- package/dist/components/core/divider/Divider.d.ts +46 -0
- package/dist/components/core/divider/Divider.js +51 -0
- package/dist/components/core/divider/Divider.types.d.ts +25 -0
- package/dist/components/core/divider/Divider.types.js +9 -0
- package/dist/components/core/dropdown-menu/DropdownMenu.css +147 -0
- package/dist/components/core/dropdown-menu/DropdownMenu.d.ts +24 -0
- package/dist/components/core/dropdown-menu/DropdownMenu.js +311 -0
- package/dist/components/core/dropdown-menu/DropdownMenu.types.d.ts +62 -0
- package/dist/components/core/dropdown-menu/DropdownMenu.types.js +12 -0
- package/dist/components/core/dropdown-menu/dropdown-menu-impl.css +70 -0
- package/dist/components/core/filter-chip/FilterChip.css +150 -0
- package/dist/components/core/filter-chip/FilterChip.d.ts +23 -0
- package/dist/components/core/filter-chip/FilterChip.js +37 -0
- package/dist/components/core/filter-chip/FilterChip.types.d.ts +53 -0
- package/dist/components/core/filter-chip/FilterChip.types.js +12 -0
- package/dist/components/core/footer/Footer.css +104 -0
- package/dist/components/core/footer/Footer.d.ts +43 -0
- package/dist/components/core/footer/Footer.js +53 -0
- package/dist/components/core/footer/Footer.types.d.ts +47 -0
- package/dist/components/core/footer/Footer.types.js +18 -0
- package/dist/components/core/footer/footer-impl.css +218 -0
- package/dist/components/core/footer-group/FooterGroup.css +91 -0
- package/dist/components/core/footer-group/FooterGroup.d.ts +32 -0
- package/dist/components/core/footer-group/FooterGroup.js +64 -0
- package/dist/components/core/footer-group/FooterGroup.types.d.ts +25 -0
- package/dist/components/core/footer-group/FooterGroup.types.js +12 -0
- package/dist/components/core/footer-group/footer-group-impl.css +78 -0
- package/dist/components/core/icon/Icon.css +40 -0
- package/dist/components/core/icon/Icon.d.ts +23 -0
- package/dist/components/core/icon/Icon.js +79 -0
- package/dist/components/core/icon/Icon.types.d.ts +24 -0
- package/dist/components/core/icon/Icon.types.js +7 -0
- package/dist/components/core/link/Link.css +89 -0
- package/dist/components/core/link/Link.d.ts +37 -0
- package/dist/components/core/link/Link.js +141 -0
- package/dist/components/core/link/Link.types.d.ts +41 -0
- package/dist/components/core/link/Link.types.js +9 -0
- package/dist/components/core/link/link-impl.css +168 -0
- package/dist/components/core/menu-item/MenuItem.css +70 -0
- package/dist/components/core/menu-item/MenuItem.d.ts +11 -0
- package/dist/components/core/menu-item/MenuItem.js +26 -0
- package/dist/components/core/menu-item/MenuItem.types.d.ts +28 -0
- package/dist/components/core/menu-item/MenuItem.types.js +9 -0
- package/dist/components/core/navbar/Navbar.css +134 -0
- package/dist/components/core/navbar/Navbar.d.ts +52 -0
- package/dist/components/core/navbar/Navbar.js +498 -0
- package/dist/components/core/navbar/Navbar.types.d.ts +68 -0
- package/dist/components/core/navbar/Navbar.types.js +17 -0
- package/dist/components/core/navbar/navbar-impl.css +597 -0
- package/dist/components/core/navbar/navbar-material.css +96 -0
- package/dist/components/core/navbar-item/NavbarItem.css +75 -0
- package/dist/components/core/navbar-item/NavbarItem.d.ts +22 -0
- package/dist/components/core/navbar-item/NavbarItem.js +40 -0
- package/dist/components/core/navbar-item/NavbarItem.types.d.ts +34 -0
- package/dist/components/core/navbar-item/NavbarItem.types.js +10 -0
- package/dist/components/core/navbar-menu/NavbarMenu.css +130 -0
- package/dist/components/core/navbar-menu/NavbarMenu.d.ts +6 -0
- package/dist/components/core/navbar-menu/NavbarMenu.js +187 -0
- package/dist/components/core/navbar-menu/NavbarMenu.types.d.ts +45 -0
- package/dist/components/core/navbar-menu/NavbarMenu.types.js +12 -0
- package/dist/components/core/navbar-menu/navbar-menu-impl.css +186 -0
- package/dist/components/core/navbar-menu-group/NavbarMenuGroup.css +26 -0
- package/dist/components/core/navbar-menu-group/NavbarMenuGroup.d.ts +17 -0
- package/dist/components/core/navbar-menu-group/NavbarMenuGroup.js +22 -0
- package/dist/components/core/navbar-menu-group/NavbarMenuGroup.types.d.ts +14 -0
- package/dist/components/core/navbar-menu-group/NavbarMenuGroup.types.js +7 -0
- package/dist/components/core/navbar-panel/NavbarPanel.css +87 -0
- package/dist/components/core/navbar-panel/NavbarPanel.d.ts +34 -0
- package/dist/components/core/navbar-panel/NavbarPanel.js +46 -0
- package/dist/components/core/navbar-panel/NavbarPanel.types.d.ts +29 -0
- package/dist/components/core/navbar-panel/NavbarPanel.types.js +12 -0
- package/dist/components/core/navbar-panel/navbar-panel-impl.css +104 -0
- package/dist/components/core/navbar-panel/use-navbar-panel.d.ts +26 -0
- package/dist/components/core/navbar-panel/use-navbar-panel.js +135 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.css +118 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.d.ts +21 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.js +91 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.types.d.ts +46 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.types.js +13 -0
- package/dist/components/core/navbar-switcher/navbar-switcher-impl.css +179 -0
- package/dist/components/core/navbar-switcher-option/NavbarSwitcherOption.css +39 -0
- package/dist/components/core/navbar-switcher-option/NavbarSwitcherOption.d.ts +16 -0
- package/dist/components/core/navbar-switcher-option/NavbarSwitcherOption.js +19 -0
- package/dist/components/core/navbar-switcher-option/NavbarSwitcherOption.types.d.ts +18 -0
- package/dist/components/core/navbar-switcher-option/NavbarSwitcherOption.types.js +7 -0
- package/dist/components/core/option/Option.css +133 -0
- package/dist/components/core/option/Option.d.ts +25 -0
- package/dist/components/core/option/Option.js +31 -0
- package/dist/components/core/option/Option.types.d.ts +44 -0
- package/dist/components/core/option/Option.types.js +14 -0
- package/dist/components/core/pagination/Pagination.css +163 -0
- package/dist/components/core/pagination/Pagination.d.ts +30 -0
- package/dist/components/core/pagination/Pagination.js +366 -0
- package/dist/components/core/pagination/Pagination.types.d.ts +47 -0
- package/dist/components/core/pagination/Pagination.types.js +12 -0
- package/dist/components/core/pagination/pagination-impl.css +17 -0
- package/dist/components/core/pagination-dots/PaginationDots.css +60 -0
- package/dist/components/core/pagination-dots/PaginationDots.d.ts +25 -0
- package/dist/components/core/pagination-dots/PaginationDots.js +104 -0
- package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +38 -0
- package/dist/components/core/pagination-dots/PaginationDots.types.js +9 -0
- package/dist/components/core/removable-chip/RemovableChip.css +125 -0
- package/dist/components/core/removable-chip/RemovableChip.d.ts +25 -0
- package/dist/components/core/removable-chip/RemovableChip.js +36 -0
- package/dist/components/core/removable-chip/RemovableChip.types.d.ts +38 -0
- package/dist/components/core/removable-chip/RemovableChip.types.js +13 -0
- package/dist/components/core/search-input/SearchInput.css +205 -0
- package/dist/components/core/search-input/SearchInput.d.ts +5 -0
- package/dist/components/core/search-input/SearchInput.js +81 -0
- package/dist/components/core/search-input/SearchInput.types.d.ts +43 -0
- package/dist/components/core/search-input/SearchInput.types.js +13 -0
- package/dist/components/core/search-input/search-input-impl.css +20 -0
- package/dist/components/core/segmented-control/SegmentedControl.css +57 -0
- package/dist/components/core/segmented-control/SegmentedControl.d.ts +37 -0
- package/dist/components/core/segmented-control/SegmentedControl.js +269 -0
- package/dist/components/core/segmented-control/SegmentedControl.types.d.ts +38 -0
- package/dist/components/core/segmented-control/SegmentedControl.types.js +8 -0
- package/dist/components/core/segmented-control/segmented-control-impl.css +57 -0
- package/dist/components/core/segmented-control-item/SegmentedControlItem.css +151 -0
- package/dist/components/core/segmented-control-item/SegmentedControlItem.d.ts +50 -0
- package/dist/components/core/segmented-control-item/SegmentedControlItem.js +101 -0
- package/dist/components/core/segmented-control-item/SegmentedControlItem.types.d.ts +37 -0
- package/dist/components/core/segmented-control-item/SegmentedControlItem.types.js +10 -0
- package/dist/components/core/segmented-control-item/segmented-control-item-impl.css +69 -0
- package/dist/components/core/select/Select.css +401 -0
- package/dist/components/core/select/Select.d.ts +29 -0
- package/dist/components/core/select/Select.js +524 -0
- package/dist/components/core/select/Select.types.d.ts +96 -0
- package/dist/components/core/select/Select.types.js +26 -0
- package/dist/components/core/select/select-impl.css +114 -0
- package/dist/components/core/select-chip/SelectChip.css +162 -0
- package/dist/components/core/select-chip/SelectChip.d.ts +20 -0
- package/dist/components/core/select-chip/SelectChip.js +276 -0
- package/dist/components/core/select-chip/SelectChip.types.d.ts +62 -0
- package/dist/components/core/select-chip/SelectChip.types.js +14 -0
- package/dist/components/core/select-chip/select-chip-impl.css +49 -0
- package/dist/components/core/spinner/Spinner.css +76 -0
- package/dist/components/core/spinner/Spinner.d.ts +5 -0
- package/dist/components/core/spinner/Spinner.js +29 -0
- package/dist/components/core/spinner/Spinner.types.d.ts +37 -0
- package/dist/components/core/spinner/Spinner.types.js +10 -0
- package/dist/components/core/spinner/spinner-impl.css +27 -0
- package/dist/components/core/status-dot/StatusDot.css +43 -0
- package/dist/components/core/status-dot/StatusDot.d.ts +35 -0
- package/dist/components/core/status-dot/StatusDot.js +46 -0
- package/dist/components/core/status-dot/StatusDot.types.d.ts +29 -0
- package/dist/components/core/status-dot/StatusDot.types.js +7 -0
- package/dist/components/core/status-dot/status-dot-impl.css +42 -0
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.css +89 -0
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.d.ts +21 -0
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.js +127 -0
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.types.d.ts +43 -0
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.types.js +13 -0
- package/dist/components/core/subdomain-navbar/subdomain-navbar-impl.css +147 -0
- package/dist/components/core/tab/Tab.css +113 -0
- package/dist/components/core/tab/Tab.d.ts +36 -0
- package/dist/components/core/tab/Tab.js +53 -0
- package/dist/components/core/tab/Tab.types.d.ts +42 -0
- package/dist/components/core/tab/Tab.types.js +13 -0
- package/dist/components/core/tab/tab-impl.css +49 -0
- package/dist/components/core/tab-list/TabList.css +40 -0
- package/dist/components/core/tab-list/TabList.d.ts +26 -0
- package/dist/components/core/tab-list/TabList.js +124 -0
- package/dist/components/core/tab-list/TabList.types.d.ts +20 -0
- package/dist/components/core/tab-list/TabList.types.js +7 -0
- package/dist/components/core/tab-list/tab-list-impl.css +56 -0
- package/dist/components/core/table/Table.css +40 -0
- package/dist/components/core/table/Table.d.ts +19 -0
- package/dist/components/core/table/Table.js +55 -0
- package/dist/components/core/table/Table.types.d.ts +59 -0
- package/dist/components/core/table/Table.types.js +9 -0
- package/dist/components/core/table/table-impl.css +97 -0
- package/dist/components/core/table/use-table-sort.d.ts +43 -0
- package/dist/components/core/table/use-table-sort.js +51 -0
- package/dist/components/core/table-body/TableBody.css +6 -0
- package/dist/components/core/table-body/TableBody.d.ts +12 -0
- package/dist/components/core/table-body/TableBody.js +26 -0
- package/dist/components/core/table-body/TableBody.types.d.ts +36 -0
- package/dist/components/core/table-body/TableBody.types.js +7 -0
- package/dist/components/core/table-cell/TableCell.css +76 -0
- package/dist/components/core/table-cell/TableCell.d.ts +21 -0
- package/dist/components/core/table-cell/TableCell.js +31 -0
- package/dist/components/core/table-cell/TableCell.types.d.ts +43 -0
- package/dist/components/core/table-cell/TableCell.types.js +7 -0
- package/dist/components/core/table-head/TableHead.css +6 -0
- package/dist/components/core/table-head/TableHead.d.ts +22 -0
- package/dist/components/core/table-head/TableHead.js +52 -0
- package/dist/components/core/table-head/TableHead.types.d.ts +39 -0
- package/dist/components/core/table-head/TableHead.types.js +7 -0
- package/dist/components/core/table-head/table-head-impl.css +51 -0
- package/dist/components/core/table-header-cell/TableHeaderCell.css +126 -0
- package/dist/components/core/table-header-cell/TableHeaderCell.d.ts +21 -0
- package/dist/components/core/table-header-cell/TableHeaderCell.js +34 -0
- package/dist/components/core/table-header-cell/TableHeaderCell.types.d.ts +56 -0
- package/dist/components/core/table-header-cell/TableHeaderCell.types.js +9 -0
- package/dist/components/core/table-header-cell/table-header-cell-impl.css +74 -0
- package/dist/components/core/table-row/TableRow.css +18 -0
- package/dist/components/core/table-row/TableRow.d.ts +17 -0
- package/dist/components/core/table-row/TableRow.js +32 -0
- package/dist/components/core/table-row/TableRow.types.d.ts +45 -0
- package/dist/components/core/table-row/TableRow.types.js +7 -0
- package/dist/components/core/table-row/table-row-impl.css +31 -0
- package/dist/components/core/text-area/TextArea.css +325 -0
- package/dist/components/core/text-area/TextArea.d.ts +13 -0
- package/dist/components/core/text-area/TextArea.js +133 -0
- package/dist/components/core/text-area/TextArea.types.d.ts +67 -0
- package/dist/components/core/text-area/TextArea.types.js +19 -0
- package/dist/components/core/text-area/text-area-impl.css +36 -0
- package/dist/components/core/text-input/TextInput.css +400 -0
- package/dist/components/core/text-input/TextInput.d.ts +5 -0
- package/dist/components/core/text-input/TextInput.js +130 -0
- package/dist/components/core/text-input/TextInput.types.d.ts +96 -0
- package/dist/components/core/text-input/TextInput.types.js +24 -0
- package/dist/components/core/text-input/text-input-impl.css +36 -0
- package/dist/components/core/tooltip/Tooltip.css +101 -0
- package/dist/components/core/tooltip/Tooltip.d.ts +51 -0
- package/dist/components/core/tooltip/Tooltip.js +87 -0
- package/dist/components/core/tooltip/Tooltip.types.d.ts +38 -0
- package/dist/components/core/tooltip/Tooltip.types.js +9 -0
- package/dist/components/core/tooltip/TooltipAnchor.d.ts +52 -0
- package/dist/components/core/tooltip/TooltipAnchor.js +69 -0
- package/dist/components/core/tooltip/tooltip-anchor.css +145 -0
- package/dist/components/core/tooltip/tooltip-impl.css +54 -0
- package/dist/components/core/tooltip/use-tooltip.d.ts +111 -0
- package/dist/components/core/tooltip/use-tooltip.js +189 -0
- package/dist/components/core/typography/Typography.css +96 -0
- package/dist/components/core/typography/Typography.d.ts +297 -0
- package/dist/components/core/typography/Typography.js +28 -0
- package/dist/components/core/typography/Typography.types.d.ts +40 -0
- package/dist/components/core/typography/Typography.types.js +7 -0
- package/dist/components/css-time.d.ts +20 -0
- package/dist/components/css-time.js +30 -0
- package/dist/components/given.d.ts +7 -0
- package/dist/components/given.js +9 -0
- package/dist/components/hover-open.d.ts +34 -0
- package/dist/components/hover-open.js +90 -0
- package/dist/components/index.d.ts +149 -1
- package/dist/components/index.js +145 -2
- package/dist/components/layout/aspect-ratio/AspectRatio.css +29 -0
- package/dist/components/layout/aspect-ratio/AspectRatio.d.ts +24 -0
- package/dist/components/layout/aspect-ratio/AspectRatio.js +26 -0
- package/dist/components/layout/aspect-ratio/AspectRatio.types.d.ts +22 -0
- package/dist/components/layout/aspect-ratio/AspectRatio.types.js +7 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio-impl.css +44 -0
- package/dist/components/layout/grid/Grid.css +76 -0
- package/dist/components/layout/grid/Grid.d.ts +31 -0
- package/dist/components/layout/grid/Grid.js +33 -0
- package/dist/components/layout/grid/Grid.types.d.ts +48 -0
- package/dist/components/layout/grid/Grid.types.js +7 -0
- package/dist/components/layout/grid/grid-impl.css +62 -0
- package/dist/components/layout/row/Row.css +85 -0
- package/dist/components/layout/row/Row.d.ts +32 -0
- package/dist/components/layout/row/Row.js +34 -0
- package/dist/components/layout/row/Row.types.d.ts +45 -0
- package/dist/components/layout/row/Row.types.js +7 -0
- package/dist/components/layout/scroller/Scroller.css +59 -0
- package/dist/components/layout/scroller/Scroller.d.ts +34 -0
- package/dist/components/layout/scroller/Scroller.js +36 -0
- package/dist/components/layout/scroller/Scroller.types.d.ts +38 -0
- package/dist/components/layout/scroller/Scroller.types.js +7 -0
- package/dist/components/layout/scroller/scroller-impl.css +45 -0
- package/dist/components/layout/stack/Stack.css +73 -0
- package/dist/components/layout/stack/Stack.d.ts +28 -0
- package/dist/components/layout/stack/Stack.js +30 -0
- package/dist/components/layout/stack/Stack.types.d.ts +41 -0
- package/dist/components/layout/stack/Stack.types.js +7 -0
- package/dist/components/logo-data.d.ts +24 -0
- package/dist/components/logo-data.js +11 -0
- package/dist/components/scroll-data.d.ts +64 -0
- package/dist/components/scroll-data.js +17 -0
- package/dist/components/testimonial-row-data.d.ts +51 -0
- package/dist/components/testimonial-row-data.js +11 -0
- package/dist/components/testimonial-slider-data.d.ts +26 -0
- package/dist/components/testimonial-slider-data.js +10 -0
- package/dist/components/visually-hidden.d.ts +22 -0
- package/dist/components/visually-hidden.js +31 -0
- package/dist/fonts/LICENSE-inter.txt +93 -0
- package/dist/fonts/LICENSE-red-hat-mono.txt +93 -0
- package/dist/fonts/fonts.css +111 -0
- package/dist/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/dist/fonts/red-hat-mono-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/red-hat-mono-latin-wght-normal.woff2 +0 -0
- package/dist/marketing.css +581 -162
- package/package.json +13 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import './Select.css';
|
|
2
|
+
import './select-impl.css';
|
|
3
|
+
import { type SelectProps } from './Select.types.js';
|
|
4
|
+
export type { SelectProps };
|
|
5
|
+
/**
|
|
6
|
+
* A control for choosing one answer from a set the reader scans.
|
|
7
|
+
*
|
|
8
|
+
* Built as marketing's TextInput is built — a box that draws the border and the fill, and one
|
|
9
|
+
* element inside it holding the value — because that is what makes a form of mixed controls line
|
|
10
|
+
* up. The difference is the element inside: a text field's is an `<input>`, which can hold
|
|
11
|
+
* nothing, so its ornaments sit beside it and part of its box is not the field. Here it is a
|
|
12
|
+
* `<button>`, which can hold what it needs, so the whole box presses.
|
|
13
|
+
*
|
|
14
|
+
* Announced as a combobox, which is the name for a control that owns a list of answers whether
|
|
15
|
+
* or not anything can be typed into it. That role replaces the button's own activation on the
|
|
16
|
+
* way in, so Enter and Space are written here rather than inherited.
|
|
17
|
+
*
|
|
18
|
+
* Focus never leaves the box. Nothing here takes the caret — not even the search field, which is
|
|
19
|
+
* reachable with a pointer and otherwise fed from the box: every letter typed at the control goes
|
|
20
|
+
* into the query, so a keyboard never needs to be in the field to narrow the list. The rows are
|
|
21
|
+
* not focusable either, and the box says through `aria-activedescendant` which one the arrow keys
|
|
22
|
+
* are resting on; moving focus into the list would cost a tab stop per answer, which on a
|
|
23
|
+
* marketing page is a tab stop between the visitor and the button that submits the form.
|
|
24
|
+
*
|
|
25
|
+
* So Tab is one stop whether the list is open or shut. Open, it shuts the list on the way past
|
|
26
|
+
* rather than walking into the surface — the field and its clear button are furniture, and a
|
|
27
|
+
* reader tabbing out of a control has said they are done with it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function Select(props: SelectProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Children, cloneElement, isValidElement, useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { ChevronDownRegularIcon, CircleCheckSolidIcon, CircleExclamationSolidIcon, CircleInfoRegularIcon, TriangleExclamationSolidIcon, } from '@deriv-com/quill-icons-web';
|
|
5
|
+
import './Select.css';
|
|
6
|
+
import './select-impl.css';
|
|
7
|
+
import { Icon } from '../icon/Icon.js';
|
|
8
|
+
import { SearchInput } from '../search-input/SearchInput.js';
|
|
9
|
+
import { Spinner } from '../spinner/Spinner.js';
|
|
10
|
+
import { TooltipAnchor } from '../tooltip/TooltipAnchor.js';
|
|
11
|
+
import { given } from '../../given.js';
|
|
12
|
+
import { selectDefaults, selectGlyphStep, selectParts } from './Select.types.js';
|
|
13
|
+
/**
|
|
14
|
+
* What each reported status looks like.
|
|
15
|
+
*
|
|
16
|
+
* The same marks TextInput draws, and the same argument: circle for error, triangle for
|
|
17
|
+
* warning, because the two colours are close enough that a red-green deficiency can converge
|
|
18
|
+
* them and the shape is what separates the pair at a glance. A form mixing these two controls
|
|
19
|
+
* cannot have them disagree about which shape means what.
|
|
20
|
+
*
|
|
21
|
+
* An exclamation rather than a cross, which is not interchangeable here: the cross is what
|
|
22
|
+
* SearchInput draws on its clear button, and this control holds a SearchInput whenever it is
|
|
23
|
+
* searchable. The same mark meaning "this failed" underneath and "press to empty" inside is
|
|
24
|
+
* one glyph doing two jobs in one component.
|
|
25
|
+
*/
|
|
26
|
+
const statusGlyph = {
|
|
27
|
+
none: null,
|
|
28
|
+
pending: null,
|
|
29
|
+
error: _jsx(CircleExclamationSolidIcon, {}),
|
|
30
|
+
warning: _jsx(TriangleExclamationSolidIcon, {}),
|
|
31
|
+
success: _jsx(CircleCheckSolidIcon, {}),
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The answers, as a list this control can index.
|
|
35
|
+
*
|
|
36
|
+
* Levelled out of `children` rather than assumed to be an array: a set built from data arrives
|
|
37
|
+
* through a `.map()`, one written by hand may arrive in a fragment, and a set with a condition
|
|
38
|
+
* in it arrives with a `false` among the rows. `Children.toArray` flattens all three and drops
|
|
39
|
+
* the falsy entries, which is the whole reason to go through it rather than reading `children`.
|
|
40
|
+
*/
|
|
41
|
+
function optionsOf(children) {
|
|
42
|
+
return Children.toArray(children).filter((child) => isValidElement(child));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* How long a run of keystrokes stays one search rather than starting the next.
|
|
46
|
+
*
|
|
47
|
+
* Half a second, which is roughly what a native select allows and comfortably longer than the gap
|
|
48
|
+
* between two letters somebody is typing on purpose. Too short and "si" becomes a search for "s"
|
|
49
|
+
* followed by a search for "i", which lands on whatever begins with I; too long and a letter
|
|
50
|
+
* pressed a beat later joins a word the reader had finished with.
|
|
51
|
+
*
|
|
52
|
+
* Compared as timestamps rather than cleared by a timer. There is nothing to cancel on unmount
|
|
53
|
+
* and nothing to get out of step with a re-render — the buffer is only ever read at the moment a
|
|
54
|
+
* key arrives, so the question is how old it is, not whether something has expired.
|
|
55
|
+
*/
|
|
56
|
+
const TYPING_WINDOW = 500;
|
|
57
|
+
/**
|
|
58
|
+
* An option's words, flattened enough to match a letter against.
|
|
59
|
+
*
|
|
60
|
+
* An option is given text, but `allowedChildren` says text and React says ReactNode, so a set
|
|
61
|
+
* built from data can arrive holding numbers or nested fragments. Anything that is not a string
|
|
62
|
+
* or a number contributes nothing rather than throwing: a row nobody can type their way to is a
|
|
63
|
+
* smaller failure than a list that cannot render.
|
|
64
|
+
*/
|
|
65
|
+
function words(node) {
|
|
66
|
+
if (typeof node === 'string')
|
|
67
|
+
return node;
|
|
68
|
+
if (typeof node === 'number')
|
|
69
|
+
return String(node);
|
|
70
|
+
if (Array.isArray(node))
|
|
71
|
+
return node.map(words).join('');
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A control for choosing one answer from a set the reader scans.
|
|
76
|
+
*
|
|
77
|
+
* Built as marketing's TextInput is built — a box that draws the border and the fill, and one
|
|
78
|
+
* element inside it holding the value — because that is what makes a form of mixed controls line
|
|
79
|
+
* up. The difference is the element inside: a text field's is an `<input>`, which can hold
|
|
80
|
+
* nothing, so its ornaments sit beside it and part of its box is not the field. Here it is a
|
|
81
|
+
* `<button>`, which can hold what it needs, so the whole box presses.
|
|
82
|
+
*
|
|
83
|
+
* Announced as a combobox, which is the name for a control that owns a list of answers whether
|
|
84
|
+
* or not anything can be typed into it. That role replaces the button's own activation on the
|
|
85
|
+
* way in, so Enter and Space are written here rather than inherited.
|
|
86
|
+
*
|
|
87
|
+
* Focus never leaves the box. Nothing here takes the caret — not even the search field, which is
|
|
88
|
+
* reachable with a pointer and otherwise fed from the box: every letter typed at the control goes
|
|
89
|
+
* into the query, so a keyboard never needs to be in the field to narrow the list. The rows are
|
|
90
|
+
* not focusable either, and the box says through `aria-activedescendant` which one the arrow keys
|
|
91
|
+
* are resting on; moving focus into the list would cost a tab stop per answer, which on a
|
|
92
|
+
* marketing page is a tab stop between the visitor and the button that submits the form.
|
|
93
|
+
*
|
|
94
|
+
* So Tab is one stop whether the list is open or shut. Open, it shuts the list on the way past
|
|
95
|
+
* rather than walking into the surface — the field and its clear button are furniture, and a
|
|
96
|
+
* reader tabbing out of a control has said they are done with it.
|
|
97
|
+
*/
|
|
98
|
+
export function Select(props) {
|
|
99
|
+
const { size = selectDefaults.size, status = selectDefaults.status, fixed = selectDefaults.fixed, label, labelTooltip, placeholder, helperText, value, defaultValue, onChange, open, defaultOpen, onOpenChange, searchable = selectDefaults.searchable, emptyLabel, required = selectDefaults.required, requiredMark = selectDefaults.requiredMark, optional = selectDefaults.optional, statusMessage, children, id, ...rest } = props;
|
|
100
|
+
const fallbackId = useId();
|
|
101
|
+
const rootId = id ?? fallbackId;
|
|
102
|
+
const labelId = `${rootId}-label`;
|
|
103
|
+
const listId = `${rootId}-list`;
|
|
104
|
+
const rowId = (index) => `${rootId}-option-${index}`;
|
|
105
|
+
const box = useRef(null);
|
|
106
|
+
const trigger = useRef(null);
|
|
107
|
+
/**
|
|
108
|
+
* The answer, and whether the list is showing — each held here only while nobody else holds it.
|
|
109
|
+
*
|
|
110
|
+
* `in` rather than a check against undefined: a page clearing its own answer passes an empty
|
|
111
|
+
* string and a page that never held one passes nothing. Reading those as the same thing makes a
|
|
112
|
+
* controlled control uncontrollable the moment it is emptied.
|
|
113
|
+
*/
|
|
114
|
+
const owned = 'value' in props;
|
|
115
|
+
const [held, setHeld] = useState(defaultValue ?? '');
|
|
116
|
+
const chosen = owned ? (value ?? '') : held;
|
|
117
|
+
const driven = 'open' in props;
|
|
118
|
+
const [showing, setShowing] = useState(defaultOpen ?? false);
|
|
119
|
+
/**
|
|
120
|
+
* And never open while the control is unavailable.
|
|
121
|
+
*
|
|
122
|
+
* A browser refuses a press and a keystroke on a disabled button, so no handler here needs to
|
|
123
|
+
* check — but `defaultOpen` reaches past all of them: `<Select disabled defaultOpen>` would
|
|
124
|
+
* render a list hanging off a control nobody can use, from props alone and without an event.
|
|
125
|
+
* Read off `rest` rather than destructured, because the attribute still has to reach the button.
|
|
126
|
+
*/
|
|
127
|
+
const unavailable = rest.disabled === true;
|
|
128
|
+
const expanded = !unavailable && !fixed && (driven ? (open ?? false) : showing);
|
|
129
|
+
const options = optionsOf(children);
|
|
130
|
+
/**
|
|
131
|
+
* What has been typed at the search field, which is not an answer and never becomes one.
|
|
132
|
+
*
|
|
133
|
+
* Held here alone. A query is not a value the page has any use for — it is how the visitor got
|
|
134
|
+
* to the row they took — so there is no controlled twin of it and nothing to report. Emptied
|
|
135
|
+
* when the list shuts, for the same reason: it was a way through the answers, and the answers
|
|
136
|
+
* have gone.
|
|
137
|
+
*/
|
|
138
|
+
const [query, setQuery] = useState('');
|
|
139
|
+
/**
|
|
140
|
+
* The answers a query leaves, or all of them.
|
|
141
|
+
*
|
|
142
|
+
* `includes` rather than the `startsWith` type-ahead uses, and the two are different jobs: a
|
|
143
|
+
* letter pressed at the control is a jump to a row, so it matches from the beginning, where a
|
|
144
|
+
* query typed into a field is a filter, and a visitor typing "land" means to see Ireland as
|
|
145
|
+
* well as Iceland.
|
|
146
|
+
*
|
|
147
|
+
* Everything below indexes into this rather than into the whole set — the rows the arrow keys
|
|
148
|
+
* walk, the row Enter takes, the ids the list carries. An index into a set that is not the one
|
|
149
|
+
* on screen takes the answer next to the one the visitor was looking at.
|
|
150
|
+
*/
|
|
151
|
+
const narrowed = (text) => {
|
|
152
|
+
const needle = text.trim().toLowerCase();
|
|
153
|
+
if (needle === '')
|
|
154
|
+
return options;
|
|
155
|
+
return options.filter((option) => words(option.props.children).toLowerCase().includes(needle));
|
|
156
|
+
};
|
|
157
|
+
const visible = searchable ? narrowed(query) : options;
|
|
158
|
+
const indexOf = (v) => visible.findIndex((option) => option.props.value === v);
|
|
159
|
+
/**
|
|
160
|
+
* Where the arrow keys are resting, which is not what has been chosen.
|
|
161
|
+
*
|
|
162
|
+
* Two facts, and the visitor needs both: what they answered, and what they are about to answer.
|
|
163
|
+
* Arrowing past the chosen row does not unchoose it, and opening puts the keys on the answer
|
|
164
|
+
* rather than at the top — somebody who has to find their own answer again before changing it
|
|
165
|
+
* has been made to do the work twice.
|
|
166
|
+
*
|
|
167
|
+
* An index rather than a value, so a set that repeats a value cannot light two rows at once.
|
|
168
|
+
*/
|
|
169
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
170
|
+
/**
|
|
171
|
+
* What has been typed at the list, and when.
|
|
172
|
+
*
|
|
173
|
+
* A ref rather than state: nothing renders from it, and a keystroke has to read what the last
|
|
174
|
+
* one left within the same event rather than after a re-render. Held together in one object so
|
|
175
|
+
* the letters and their age cannot get out of step.
|
|
176
|
+
*/
|
|
177
|
+
const typed = useRef({ text: '', at: 0 });
|
|
178
|
+
const setOpen = (next) => {
|
|
179
|
+
// Forgotten on the way out, so a list reopened later does not show a marker for a position
|
|
180
|
+
// the visitor is no longer in, nor continue a search they finished.
|
|
181
|
+
if (!next) {
|
|
182
|
+
setActiveIndex(-1);
|
|
183
|
+
typed.current = { text: '', at: 0 };
|
|
184
|
+
// The query goes with it. A list reopened on the last visitor's search would be a list
|
|
185
|
+
// missing most of its answers for a reason nothing on screen explains.
|
|
186
|
+
setQuery('');
|
|
187
|
+
}
|
|
188
|
+
if (!driven)
|
|
189
|
+
setShowing(next);
|
|
190
|
+
onOpenChange?.(next);
|
|
191
|
+
};
|
|
192
|
+
const openAt = (index) => {
|
|
193
|
+
setActiveIndex(index);
|
|
194
|
+
setOpen(true);
|
|
195
|
+
};
|
|
196
|
+
const take = (index) => {
|
|
197
|
+
const option = visible[index];
|
|
198
|
+
if (option === undefined || option.props.disabled === true)
|
|
199
|
+
return;
|
|
200
|
+
const next = option.props.value ?? '';
|
|
201
|
+
if (!owned)
|
|
202
|
+
setHeld(next);
|
|
203
|
+
onChange?.(next);
|
|
204
|
+
// And the focus with it. From the box this is where it already was; from the field, or from
|
|
205
|
+
// a press on a row while the caret was in the field, the popup is about to be unmounted with
|
|
206
|
+
// the focus inside it — which drops the reader on <body>, at the top of the page, from a
|
|
207
|
+
// control halfway down it. Escape and Tab both already did this; taking an answer, which is
|
|
208
|
+
// the ordinary way out, did not.
|
|
209
|
+
shutAndReturn();
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* The next row the keys can rest on, stepping over the ones that cannot be taken.
|
|
213
|
+
*
|
|
214
|
+
* Stepped over rather than stopped at. A row the keys land on and refuse is a dead end the
|
|
215
|
+
* visitor has to press through, and pressing down twice to move once reads as a control that
|
|
216
|
+
* is broken rather than one that is being careful. The rows stay in the list and stay
|
|
217
|
+
* readable — a set that hid what it will not accept would leave somebody hunting for
|
|
218
|
+
* something that was there last week.
|
|
219
|
+
*/
|
|
220
|
+
const step = (from, by) => {
|
|
221
|
+
for (let i = 1; i <= visible.length; i += 1) {
|
|
222
|
+
const at = (from + by * i + visible.length * i) % visible.length;
|
|
223
|
+
if (visible[at]?.props.disabled !== true)
|
|
224
|
+
return at;
|
|
225
|
+
}
|
|
226
|
+
// Nothing can be rested on — an empty narrowed set, or every answer refused. -1 is the state
|
|
227
|
+
// a list nobody has moved in is already in, so no row is marked and Enter has nothing to
|
|
228
|
+
// take. Returning `from` instead handed `lastOpen` the length of the list, and
|
|
229
|
+
// `aria-activedescendant` then named a row id that was never rendered: a screen reader is
|
|
230
|
+
// told where it is and finds nothing there, which is worse than not being told.
|
|
231
|
+
return -1;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* The first answer whose words begin with what has been typed, searched circularly.
|
|
235
|
+
*
|
|
236
|
+
* From `after` rather than from the top, which is what makes a letter pressed twice walk
|
|
237
|
+
* through the answers beginning with it instead of returning to the first one each time — the
|
|
238
|
+
* behaviour a native select has, and the reason type-ahead is usable on a list of countries
|
|
239
|
+
* at all.
|
|
240
|
+
*
|
|
241
|
+
* Refusals are skipped, for the reason the arrow keys skip them: landing on an answer that
|
|
242
|
+
* cannot be taken reads as the control being broken rather than as it being careful.
|
|
243
|
+
*/
|
|
244
|
+
const matching = (run, after) => {
|
|
245
|
+
for (let i = 1; i <= visible.length; i += 1) {
|
|
246
|
+
const at = (after + i + visible.length * i) % visible.length;
|
|
247
|
+
const option = visible[at];
|
|
248
|
+
if (option?.props.disabled === true)
|
|
249
|
+
continue;
|
|
250
|
+
if (words(option?.props.children).trim().toLowerCase().startsWith(run))
|
|
251
|
+
return at;
|
|
252
|
+
}
|
|
253
|
+
return -1;
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* A query, and the row the keys should be resting on once it lands.
|
|
257
|
+
*
|
|
258
|
+
* The ring moves to the first answer the new query leaves rather than staying where it was.
|
|
259
|
+
* An index means nothing across two different sets — row three of "land" is not row three of
|
|
260
|
+
* "l" — and a ring left behind pointed at whatever had taken that position, which is the answer
|
|
261
|
+
* Enter would then have given.
|
|
262
|
+
*
|
|
263
|
+
* Nothing matching leaves it at -1, which is the same state as a list nobody has moved in: no
|
|
264
|
+
* row is marked, and Enter has nothing to take.
|
|
265
|
+
*/
|
|
266
|
+
const narrow = (next) => {
|
|
267
|
+
setQuery(next);
|
|
268
|
+
setActiveIndex(narrowed(next).findIndex((option) => option.props.disabled !== true));
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* A letter, and what it means given what came just before it.
|
|
272
|
+
*
|
|
273
|
+
* One character repeated is a request for the next answer beginning with it, which is why the
|
|
274
|
+
* buffer collapses to a single letter in that case rather than searching for "ss". Anything
|
|
275
|
+
* else refines the run: typing "s" then "i" searches for "si" from where "s" landed, so the
|
|
276
|
+
* match narrows rather than jumping to whatever begins with I.
|
|
277
|
+
*/
|
|
278
|
+
const typeAhead = (key) => {
|
|
279
|
+
/**
|
|
280
|
+
* Where there is a field, the letter is the start of a query rather than a jump to a row.
|
|
281
|
+
*
|
|
282
|
+
* Kept rather than dropped: somebody who presses S at a shut control has begun typing, and a
|
|
283
|
+
* control that opens a field and swallows the first letter is one they have to notice and
|
|
284
|
+
* type twice.
|
|
285
|
+
*
|
|
286
|
+
* Appended once the list is open, and only replacing the query on the press that opened it.
|
|
287
|
+
*/
|
|
288
|
+
if (searchable) {
|
|
289
|
+
if (expanded)
|
|
290
|
+
narrow(query + key);
|
|
291
|
+
else {
|
|
292
|
+
setOpen(true);
|
|
293
|
+
narrow(key);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const now = Date.now();
|
|
298
|
+
const run = now - typed.current.at > TYPING_WINDOW ? key : typed.current.text + key;
|
|
299
|
+
typed.current = { text: run, at: now };
|
|
300
|
+
const repeated = run.length > 1 && [...run].every((each) => each === run[0]);
|
|
301
|
+
const sought = (repeated ? run[0] : run).toLowerCase();
|
|
302
|
+
// A repeated letter walks on from where it is; a growing run re-reads from one before, so the
|
|
303
|
+
// answer it just landed on is still the first candidate. Floored at -1, or a run typed at a
|
|
304
|
+
// list nothing is resting on yet would start from -2 and skip the first answer — which is
|
|
305
|
+
// every list opened by `defaultOpen`.
|
|
306
|
+
const found = matching(sought, repeated ? activeIndex : Math.max(activeIndex - 1, -1));
|
|
307
|
+
if (found === -1)
|
|
308
|
+
return;
|
|
309
|
+
if (!expanded)
|
|
310
|
+
openAt(found);
|
|
311
|
+
else
|
|
312
|
+
setActiveIndex(found);
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Back to the box, which is where the visitor was before the field took the caret.
|
|
316
|
+
*
|
|
317
|
+
* Focus cannot be left on an element that is about to be removed — the popup goes when the
|
|
318
|
+
* list shuts, and focus with it lands on the body, which is a reader dropped at the top of the
|
|
319
|
+
* page from a control halfway down it.
|
|
320
|
+
*/
|
|
321
|
+
const shutAndReturn = () => {
|
|
322
|
+
setOpen(false);
|
|
323
|
+
trigger.current?.focus();
|
|
324
|
+
};
|
|
325
|
+
const firstOpen = () => step(-1, 1);
|
|
326
|
+
const lastOpen = () => step(visible.length, -1);
|
|
327
|
+
const onTheAnswer = () => (chosen === '' ? firstOpen() : indexOf(chosen));
|
|
328
|
+
/**
|
|
329
|
+
* A key that is a letter somebody typed, rather than a command.
|
|
330
|
+
*
|
|
331
|
+
* Space is excluded, and it has to be: here it does what Enter does, so a space that started a
|
|
332
|
+
* search would be a space that stopped taking the answer. Which costs nothing worth having —
|
|
333
|
+
* type-ahead is for the first few letters of a name, and nobody reaches a space before they
|
|
334
|
+
* have found what they were after.
|
|
335
|
+
*
|
|
336
|
+
* A modifier means the keystroke belongs to the browser or the operating system.
|
|
337
|
+
*/
|
|
338
|
+
const printable = (event) => event.key.length === 1 && event.key !== ' ' && !event.ctrlKey && !event.metaKey;
|
|
339
|
+
const onKeyDown = (event) => {
|
|
340
|
+
const key = event.key;
|
|
341
|
+
if (!expanded) {
|
|
342
|
+
// All four open it, which is the whole of what a shut combobox does. Enter and Space are
|
|
343
|
+
// here rather than inherited, the role having replaced the button's own activation.
|
|
344
|
+
if (key === 'Enter' || key === ' ' || key === 'ArrowDown') {
|
|
345
|
+
event.preventDefault();
|
|
346
|
+
openAt(onTheAnswer());
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (key === 'ArrowUp') {
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
openAt(chosen === '' ? lastOpen() : indexOf(chosen));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (printable(event)) {
|
|
355
|
+
// Typing at a shut control opens it rather than answering it. A native select changes the
|
|
356
|
+
// value outright, which is how brushing a key while tabbing past one silently replaces an
|
|
357
|
+
// answer somebody already gave — the list opening on the match says the same thing and
|
|
358
|
+
// asks before committing to it.
|
|
359
|
+
event.preventDefault();
|
|
360
|
+
typeAhead(key);
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (key === 'ArrowDown') {
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
setActiveIndex(step(activeIndex, 1));
|
|
367
|
+
}
|
|
368
|
+
else if (key === 'ArrowUp') {
|
|
369
|
+
event.preventDefault();
|
|
370
|
+
setActiveIndex(step(activeIndex, -1));
|
|
371
|
+
}
|
|
372
|
+
else if (key === 'Home') {
|
|
373
|
+
event.preventDefault();
|
|
374
|
+
setActiveIndex(firstOpen());
|
|
375
|
+
}
|
|
376
|
+
else if (key === 'End') {
|
|
377
|
+
event.preventDefault();
|
|
378
|
+
setActiveIndex(lastOpen());
|
|
379
|
+
}
|
|
380
|
+
else if (key === 'Enter' || key === ' ') {
|
|
381
|
+
event.preventDefault();
|
|
382
|
+
take(activeIndex);
|
|
383
|
+
}
|
|
384
|
+
else if (key === 'Escape') {
|
|
385
|
+
// The answer is left alone. Escape is a visitor saying they did not mean to open the list,
|
|
386
|
+
// not that they have changed their mind about what they already answered.
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
setOpen(false);
|
|
389
|
+
}
|
|
390
|
+
else if (key === 'Tab') {
|
|
391
|
+
// Not prevented, so the press carries on to the next field — but the list goes first.
|
|
392
|
+
// Without this, Tab from an open searchable box walks into the popup and stops on the
|
|
393
|
+
// search field and its clear button, with the list still open behind it. The rows are not
|
|
394
|
+
// tab stops by design; neither should the furniture above them be.
|
|
395
|
+
setOpen(false);
|
|
396
|
+
}
|
|
397
|
+
else if (key === 'Backspace' && searchable) {
|
|
398
|
+
// The counterpart to the letters above. Somebody typing at the box can see the query in the
|
|
399
|
+
// field and cannot reach it with the caret, so without this the only way back from a typo
|
|
400
|
+
// is the mouse — and a control that has to be corrected with the mouse is one the keyboard
|
|
401
|
+
// cannot finish.
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
narrow(query.slice(0, -1));
|
|
404
|
+
}
|
|
405
|
+
else if (printable(event)) {
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
typeAhead(key);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* The same keys, read from the field instead of the box.
|
|
412
|
+
*
|
|
413
|
+
* Not the box's handler with a branch in it, because three of its keys mean something else in
|
|
414
|
+
* a field: Space types a space, and Home and End move the caret to the ends of the query —
|
|
415
|
+
* taking those would make the field a worse field than the one it replaced. What is left is
|
|
416
|
+
* the movement and the two ways out.
|
|
417
|
+
*/
|
|
418
|
+
const onSearchKeyDown = (event) => {
|
|
419
|
+
const key = event.key;
|
|
420
|
+
if (key === 'ArrowDown') {
|
|
421
|
+
event.preventDefault();
|
|
422
|
+
setActiveIndex(step(activeIndex, 1));
|
|
423
|
+
}
|
|
424
|
+
else if (key === 'ArrowUp') {
|
|
425
|
+
event.preventDefault();
|
|
426
|
+
setActiveIndex(step(activeIndex, -1));
|
|
427
|
+
}
|
|
428
|
+
else if (key === 'Enter') {
|
|
429
|
+
event.preventDefault();
|
|
430
|
+
take(activeIndex);
|
|
431
|
+
}
|
|
432
|
+
else if (key === 'Escape') {
|
|
433
|
+
// The answer is left alone, as it is from the box.
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
shutAndReturn();
|
|
436
|
+
}
|
|
437
|
+
else if (key === 'Tab') {
|
|
438
|
+
// Not prevented. The list shuts and the box takes the focus back, and the press the visitor
|
|
439
|
+
// made carries on from there — so Tab out of an open control lands on the next field, which
|
|
440
|
+
// is where it would have landed had the control been shut.
|
|
441
|
+
shutAndReturn();
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* Shutting on a press elsewhere, which is the only way out that is not a keystroke.
|
|
446
|
+
*
|
|
447
|
+
* Bound while open and unbound the moment it shuts, rather than left listening: one document
|
|
448
|
+
* handler per Select on a form of six is six of them running on every click anywhere on the
|
|
449
|
+
* page. `pointerdown` rather than `click`, so the list is gone before whatever was underneath
|
|
450
|
+
* it has moved.
|
|
451
|
+
*/
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
if (!expanded)
|
|
454
|
+
return;
|
|
455
|
+
const onPointerDown = (event) => {
|
|
456
|
+
if (box.current?.contains(event.target) === true)
|
|
457
|
+
return;
|
|
458
|
+
setOpen(false);
|
|
459
|
+
};
|
|
460
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
461
|
+
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
462
|
+
});
|
|
463
|
+
const checking = status === 'pending';
|
|
464
|
+
const reporting = status !== 'none';
|
|
465
|
+
const showsMessage = reporting && given(statusMessage);
|
|
466
|
+
const showsHelper = given(helperText) && !reporting;
|
|
467
|
+
const messageId = showsMessage ? `${rootId}-status` : showsHelper ? `${rootId}-helper` : undefined;
|
|
468
|
+
// Read off the whole set rather than the narrowed one: the box shows the answer that was
|
|
469
|
+
// given, and a query that happens not to match it does not unanswer the control.
|
|
470
|
+
const taken = chosen === '' ? undefined : options.find((option) => option.props.value === chosen);
|
|
471
|
+
return (_jsxs("div", { className: selectParts.root,
|
|
472
|
+
/* The consumer's own, when they gave one, and a generated one when they did not. It was
|
|
473
|
+
being read for the child ids below and then applied to nothing, so `<Select id="country">`
|
|
474
|
+
put "country" on no element at all and any anchor or `getElementById` naming it found
|
|
475
|
+
nothing. */
|
|
476
|
+
id: rootId, "data-size": size, "data-status": status, "data-fixed": String(fixed), children: [_jsxs("div", { className: selectParts['label-row'], children: [_jsxs("span", { className: selectParts.label, id: labelId, children: [label, required ? (_jsx("span", { className: selectParts['required-mark'], "aria-hidden": "true", children: requiredMark === 'asterisk' ? '*' : 'required' })) : null, optional ? _jsx("span", { className: selectParts['optional-mark'], children: "optional" }) : null] }), given(labelTooltip) ? (_jsx(TooltipAnchor, { content: labelTooltip, placement: "top", children: _jsx("button", { type: "button", className: selectParts['label-tooltip'], "aria-label": `About ${label}`, children: _jsx(Icon, { size: selectGlyphStep[size], children: _jsx(CircleInfoRegularIcon, {}) }) }) })) : null] }), _jsxs("div", { className: selectParts.control, ref: box, children: [_jsxs("button", { ref: trigger, type: "button", className: selectParts.field, role: "combobox", "aria-labelledby": labelId, "aria-controls": listId, "aria-expanded": expanded, "aria-haspopup": "listbox", "aria-activedescendant": expanded && activeIndex !== -1 ? rowId(activeIndex) : undefined, "aria-invalid": status === 'error' || undefined, "aria-required": required || undefined, "aria-busy": checking || undefined, "aria-disabled": fixed || undefined, "aria-describedby": messageId, onClick: fixed ? undefined : () => (expanded ? setOpen(false) : openAt(onTheAnswer())), onKeyDown: fixed ? undefined : onKeyDown, ...rest, children: [_jsxs("span", { className: selectParts.answer, children: [taken?.props.leadingIcon, _jsx("span", { className: selectParts.value, "data-placeholder": taken === undefined || undefined, children: taken?.props.children ?? placeholder })] }), _jsx("span", { className: selectParts['trailing-icon'], "aria-hidden": "true", children: _jsx(Icon, { size: selectGlyphStep[size], children: _jsx(ChevronDownRegularIcon, { style: expanded ? { rotate: '180deg' } : undefined }) }) })] }), expanded ? (_jsxs("div", { className: selectParts.popup, children: [searchable ? (
|
|
477
|
+
/* The field the answers are narrowed with, above them rather than in the box — a
|
|
478
|
+
<ul> may hold options and little else, so this could not live in the list, and the
|
|
479
|
+
box below is a button, which nothing can be typed into. */
|
|
480
|
+
_jsx("div", { className: selectParts.search, children: _jsx(SearchInput
|
|
481
|
+
/* The smallest step at every size of control, where a row takes the control's
|
|
482
|
+
own. The difference is what each one is: a row is an answer and has to read at
|
|
483
|
+
the size the form is set in, and this is the way to an answer — so a
|
|
484
|
+
touch-first Select spends its room on the answers rather than on the field
|
|
485
|
+
that finds them. */
|
|
486
|
+
, {
|
|
487
|
+
/* The smallest step at every size of control, where a row takes the control's
|
|
488
|
+
own. The difference is what each one is: a row is an answer and has to read at
|
|
489
|
+
the size the form is set in, and this is the way to an answer — so a
|
|
490
|
+
touch-first Select spends its room on the answers rather than on the field
|
|
491
|
+
that finds them. */
|
|
492
|
+
size: "sm",
|
|
493
|
+
/* Named after the control it narrows, the way the label's own explanation is. A
|
|
494
|
+
second field inside a control, announced as nothing, is a box a screen reader
|
|
495
|
+
arrives at without being told what it is for. Clipped rather than drawn, which
|
|
496
|
+
is SearchInput's own default — the placeholder below carries the words for an
|
|
497
|
+
eye. */
|
|
498
|
+
label: `Search ${label}`, placeholder: `Search ${label}`, clearLabel: "Clear search", value: query, onChange: narrow, "aria-controls": listId, "aria-activedescendant": activeIndex === -1 ? undefined : rowId(activeIndex), onKeyDown: onSearchKeyDown }) })) : null, _jsx("ul", { className: selectParts.listbox, id: listId, role: "listbox", "aria-labelledby": labelId, children: visible.map((option, index) => cloneElement(option, {
|
|
499
|
+
key: option.props.value ?? index,
|
|
500
|
+
id: rowId(index),
|
|
501
|
+
/* Marketing's Option is drawn at two steps where this control has three, so a
|
|
502
|
+
large Select holds rows at the step below it. Which is the honest mapping
|
|
503
|
+
rather than a gap: `lg` is a touch-first box, and its rows are already the
|
|
504
|
+
largest an Option is cut for. */
|
|
505
|
+
size: size === 'sm' ? 'sm' : 'md',
|
|
506
|
+
selected: option.props.value === chosen && chosen !== '',
|
|
507
|
+
active: index === activeIndex,
|
|
508
|
+
// The row reports the press and the control decides what it means, which is why
|
|
509
|
+
// a row that cannot be taken still gets a handler: `take` refuses it in one
|
|
510
|
+
// place rather than every caller remembering to.
|
|
511
|
+
onClick: () => take(index),
|
|
512
|
+
// The pointer moves the ring, so there is one marker rather than two competing.
|
|
513
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
514
|
+
// A press on a row must not take focus off the box. Without this the button
|
|
515
|
+
// blurs, `aria-activedescendant` stops meaning anything, and the handler above
|
|
516
|
+
// reads the press as having landed outside.
|
|
517
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
518
|
+
})) }), searchable ? (
|
|
519
|
+
/* Here whenever the list can be narrowed, and shown only once it has been narrowed
|
|
520
|
+
to nothing. A list that empties reads as a control that broke; a sentence in its
|
|
521
|
+
place reads as an answer, and the visitor needs to know which before they know
|
|
522
|
+
whether to keep typing. */
|
|
523
|
+
_jsx("span", { className: selectParts.empty, "data-shown": visible.length === 0 || undefined, children: emptyLabel })) : null] })) : null] }), showsHelper ? (_jsx("span", { className: selectParts.helper, id: messageId, children: helperText })) : null, showsMessage ? (_jsxs("span", { className: selectParts.message, id: messageId, children: [_jsx("span", { className: selectParts['message-icon'], "aria-hidden": "true", children: checking ? (_jsx(Spinner, { size: "sm", variant: "inherit" })) : (_jsx(Icon, { size: "xs", children: statusGlyph[status] })) }), statusMessage] })) : null] }));
|
|
524
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface SelectOwnProps {
|
|
3
|
+
/**
|
|
4
|
+
* `sm` — A filter row above an index, or a narrowing that sits in a bar rather than in a form. Desktop only: below what a finger reliably hits, which is why a filter row that has to work on a phone takes md
|
|
5
|
+
* `md` — The default, and what a marketing form wants. It matches TextInput at the same step, so a contact form's country field and its email field are the same height
|
|
6
|
+
* `lg` — A hero, a conversion band, or a form that is the whole point of the page. The step that pairs with a large Button, so the field and the thing that submits it agree
|
|
7
|
+
*/
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
/** The answer is settled and still worth reading — a region a page was entered through, a plan a link arrived with. The box keeps its ink and its border and stops opening, so a reader can see what was answered without being invited to change it.
|
|
10
|
+
|
|
11
|
+
Named for what it is rather than borrowed from the family. A text field says this with the platform's `readonly`, which the browser owns and enforces; there is no such attribute for a button, so this is the component's own. */
|
|
12
|
+
fixed?: boolean;
|
|
13
|
+
/** Always — a control with no label is unusable by a screen reader, and on a marketing page it is also the only thing telling a visitor what is being asked before they commit to opening it. A noun phrase naming the answer, in sentence case: "Country", not "Please choose your country" */
|
|
14
|
+
label: string;
|
|
15
|
+
/** A short explanation the label has no room for — why the answer is asked for, what it changes further down the form. Revealed on hover and on focus, so it costs no height */
|
|
16
|
+
labelTooltip?: string;
|
|
17
|
+
/** Always. What the box says with nothing chosen — an instruction rather than an example, there being nothing to spell: "Select a country". Required where a text field's is optional, and the difference is what an empty one looks like: an empty text field looks empty, while an empty box with a chevron looks like it might already hold something */
|
|
18
|
+
placeholder: string;
|
|
19
|
+
/** What the visitor needs to know before they choose — what the answer affects, or where to look if none of the options seem right. On a marketing form this is often the line that decides whether the field gets answered at all: "This only changes which prices we show you" removes a reason to abandon the page */
|
|
20
|
+
helperText?: string;
|
|
21
|
+
/** The chosen answer, when the consumer owns it — the controlled form. The value and not the words, so a page holds "sg" while the box shows "Singapore" and the words can be reworded or translated without changing what any form submits. Which matters more here than in an app: these pages are translated, and a value that travelled as its label would change meaning with the locale */
|
|
22
|
+
value?: string;
|
|
23
|
+
/** The answer the control starts on — a region guessed from where the visitor arrived, or a sort an index defaults to. Leave it out and the placeholder shows, which is what a set with no safe default wants */
|
|
24
|
+
defaultValue?: string;
|
|
25
|
+
/** Always, and it is required for that reason. It hands back the value of the answer taken, each time one is taken — the value rather than an event, there being no keystroke here to read one off. It is also the only way the answer reaches the page: this draws a button rather than a <select>, so there is no form control for a browser to submit on its own */
|
|
26
|
+
onChange: (value: string) => void;
|
|
27
|
+
/** Whether the list is showing, when the consumer owns that too. Left out, the control opens and shuts itself, which is what almost every page wants */
|
|
28
|
+
open?: boolean;
|
|
29
|
+
/** Open on first render. For a documentation page or a specimen that has to show the list without anybody pressing anything — not for a page a visitor lands on, where a list that opens by itself covers whatever is under it */
|
|
30
|
+
defaultOpen?: boolean;
|
|
31
|
+
/** Told when the list opens and shuts, for a page that has to react — closing something else, or holding a scroll position */
|
|
32
|
+
onOpenChange?: (open: boolean) => void;
|
|
33
|
+
/**
|
|
34
|
+
* `text` — Short forms, public ones, and anywhere the label row has room. The safer default and the right one for nearly every marketing form: it needs nothing looked up and still means something read out of context
|
|
35
|
+
* `asterisk` — Long or dense forms where the word repeats down the page and one legend can say it once instead. Only where that legend is visible from the fields it governs
|
|
36
|
+
*/
|
|
37
|
+
requiredMark?: 'text' | 'asterisk';
|
|
38
|
+
/** Allowed: Option */
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
export type SelectProps = SelectOwnProps & Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'size' | 'status' | 'fixed' | 'label' | 'labelTooltip' | 'placeholder' | 'helperText' | 'value' | 'defaultValue' | 'onChange' | 'open' | 'defaultOpen' | 'onOpenChange' | 'searchable' | 'emptyLabel' | 'required' | 'requiredMark' | 'optional' | 'statusMessage'> & ({
|
|
42
|
+
required?: boolean;
|
|
43
|
+
optional?: false;
|
|
44
|
+
} | {
|
|
45
|
+
required?: false;
|
|
46
|
+
optional?: boolean;
|
|
47
|
+
}) & ({
|
|
48
|
+
status?: 'none' | 'pending' | 'error' | 'warning' | 'success';
|
|
49
|
+
statusMessage: string;
|
|
50
|
+
} | {
|
|
51
|
+
status?: Exclude<'none' | 'pending' | 'error' | 'warning' | 'success', 'error' | 'warning' | 'pending'>;
|
|
52
|
+
statusMessage?: string;
|
|
53
|
+
}) & ({
|
|
54
|
+
searchable?: boolean;
|
|
55
|
+
emptyLabel: string;
|
|
56
|
+
} | {
|
|
57
|
+
searchable?: false;
|
|
58
|
+
emptyLabel?: string;
|
|
59
|
+
});
|
|
60
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
61
|
+
export declare const selectParts: {
|
|
62
|
+
readonly root: "dds-select";
|
|
63
|
+
readonly 'label-row': "dds-select__label-row";
|
|
64
|
+
readonly label: "dds-select__label";
|
|
65
|
+
readonly 'label-tooltip': "dds-select__label-tooltip";
|
|
66
|
+
readonly 'required-mark': "dds-select__required-mark";
|
|
67
|
+
readonly 'optional-mark': "dds-select__optional-mark";
|
|
68
|
+
readonly control: "dds-select__control";
|
|
69
|
+
readonly field: "dds-select__field";
|
|
70
|
+
readonly answer: "dds-select__answer";
|
|
71
|
+
readonly value: "dds-select__value";
|
|
72
|
+
readonly 'trailing-icon': "dds-select__trailing-icon";
|
|
73
|
+
readonly popup: "dds-select__popup";
|
|
74
|
+
readonly search: "dds-select__search";
|
|
75
|
+
readonly listbox: "dds-select__listbox";
|
|
76
|
+
readonly empty: "dds-select__empty";
|
|
77
|
+
readonly helper: "dds-select__helper";
|
|
78
|
+
readonly message: "dds-select__message";
|
|
79
|
+
readonly 'message-icon': "dds-select__message-icon";
|
|
80
|
+
};
|
|
81
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
82
|
+
export declare const selectDefaults: {
|
|
83
|
+
readonly size: "md";
|
|
84
|
+
readonly status: "none";
|
|
85
|
+
readonly fixed: false;
|
|
86
|
+
readonly searchable: false;
|
|
87
|
+
readonly required: false;
|
|
88
|
+
readonly requiredMark: "text";
|
|
89
|
+
readonly optional: false;
|
|
90
|
+
};
|
|
91
|
+
/** The step this component draws a glyph slot at. Generated — do not retype. */
|
|
92
|
+
export declare const selectGlyphStep: {
|
|
93
|
+
readonly sm: "xs";
|
|
94
|
+
readonly md: "sm";
|
|
95
|
+
readonly lg: "sm";
|
|
96
|
+
};
|