@deriv-com/dds-marketing 1.0.2 → 1.1.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/awards.md +209 -0
- package/.llm/components/background.md +89 -40
- package/.llm/components/bottomsheet.md +5 -0
- package/.llm/components/comparisoncards.md +257 -0
- package/.llm/components/comparisontable.md +231 -0
- package/.llm/components/ctabanner.md +9 -12
- package/.llm/components/ctabannerslider.md +15 -2
- package/.llm/components/datatable.md +273 -0
- package/.llm/components/downloadbanner.md +283 -0
- package/.llm/components/featurecards.md +41 -3
- package/.llm/components/featurecardscarousel.md +311 -0
- package/.llm/components/featuresplit.md +5 -0
- package/.llm/components/featurestack.md +227 -0
- package/.llm/components/illustrationcard.md +2 -2
- package/.llm/components/illustrationscene.md +6 -10
- package/.llm/components/imagebanner.md +278 -0
- package/.llm/components/navbar.md +1 -1
- package/.llm/components/navbaritem.md +4 -0
- package/.llm/components/pageheader.md +4 -3
- package/.llm/components/paginationdots.md +6 -1
- package/.llm/components/producthero.md +35 -11
- package/.llm/components/resourcebanner.md +247 -0
- package/.llm/components/scenescroll.md +222 -0
- package/.llm/components/select.md +17 -0
- package/.llm/components/stats.md +219 -0
- package/.llm/components/steps.md +286 -0
- package/.llm/components/stickycards.md +251 -0
- package/.llm/components/tableheadercell.md +19 -0
- package/.llm/components/teamgrid.md +243 -0
- package/.llm/components/testimonialrow.md +5 -0
- package/.llm/components/testimonialslider.md +14 -1
- package/.llm/components/textarea.md +13 -0
- package/.llm/components/textinput.md +13 -0
- package/.llm/components/textscroll.md +187 -0
- package/.llm/components/tiers.md +227 -0
- package/.llm/digest/awards.md +45 -0
- package/.llm/digest/background.md +11 -10
- package/.llm/digest/bottomsheet.md +2 -0
- package/.llm/digest/comparisoncards.md +51 -0
- package/.llm/digest/comparisontable.md +53 -0
- package/.llm/digest/ctabanner.md +4 -4
- package/.llm/digest/ctabannerslider.md +4 -0
- package/.llm/digest/datatable.md +65 -0
- package/.llm/digest/downloadbanner.md +65 -0
- package/.llm/digest/featurecards.md +1 -1
- package/.llm/digest/featurecardscarousel.md +54 -0
- package/.llm/digest/featuresplit.md +1 -0
- package/.llm/digest/featurestack.md +48 -0
- package/.llm/digest/illustrationscene.md +2 -3
- package/.llm/digest/imagebanner.md +64 -0
- package/.llm/digest/navbaritem.md +1 -0
- package/.llm/digest/pageheader.md +2 -1
- package/.llm/digest/paginationdots.md +3 -1
- package/.llm/digest/producthero.md +4 -2
- package/.llm/digest/resourcebanner.md +53 -0
- package/.llm/digest/scenescroll.md +49 -0
- package/.llm/digest/select.md +5 -0
- package/.llm/digest/stats.md +46 -0
- package/.llm/digest/steps.md +55 -0
- package/.llm/digest/stickycards.md +52 -0
- package/.llm/digest/tableheadercell.md +4 -0
- package/.llm/digest/teamgrid.md +53 -0
- package/.llm/digest/testimonialrow.md +2 -0
- package/.llm/digest/testimonialslider.md +4 -0
- package/.llm/digest/textarea.md +4 -0
- package/.llm/digest/textinput.md +4 -0
- package/.llm/digest/textscroll.md +43 -0
- package/.llm/digest/tiers.md +50 -0
- package/.llm/index.md +19 -3
- package/.llm/tokens.md +26 -1
- package/AGENTS.md +17 -1
- package/dist/components/awards-data.d.ts +24 -0
- package/dist/components/awards-data.js +11 -0
- package/dist/components/block-image-data.d.ts +24 -0
- package/dist/components/block-image-data.js +9 -0
- package/dist/components/blocks/awards/Awards.css +134 -0
- package/dist/components/blocks/awards/Awards.d.ts +24 -0
- package/dist/components/blocks/awards/Awards.js +114 -0
- package/dist/components/blocks/awards/Awards.types.d.ts +31 -0
- package/dist/components/blocks/awards/Awards.types.js +16 -0
- package/dist/components/blocks/awards/awards-impl.css +222 -0
- package/dist/components/blocks/cards-of.d.ts +16 -0
- package/dist/components/blocks/cards-of.js +23 -0
- package/dist/components/blocks/comparison-cards/ComparisonCards.css +195 -0
- package/dist/components/blocks/comparison-cards/ComparisonCards.d.ts +24 -0
- package/dist/components/blocks/comparison-cards/ComparisonCards.js +75 -0
- package/dist/components/blocks/comparison-cards/ComparisonCards.types.d.ts +49 -0
- package/dist/components/blocks/comparison-cards/ComparisonCards.types.js +26 -0
- package/dist/components/blocks/comparison-cards/comparison-cards-impl.css +262 -0
- package/dist/components/blocks/comparison-table/ComparisonTable.css +111 -0
- package/dist/components/blocks/comparison-table/ComparisonTable.d.ts +25 -0
- package/dist/components/blocks/comparison-table/ComparisonTable.js +100 -0
- package/dist/components/blocks/comparison-table/ComparisonTable.types.d.ts +36 -0
- package/dist/components/blocks/comparison-table/ComparisonTable.types.js +15 -0
- package/dist/components/blocks/comparison-table/comparison-table-impl.css +143 -0
- package/dist/components/blocks/cta-banner/CtaBanner.css +1 -0
- package/dist/components/blocks/cta-banner/CtaBanner.js +2 -2
- package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +4 -3
- package/dist/components/blocks/cta-banner/CtaBanner.types.js +1 -1
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +1 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +1 -1
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +6 -2
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +8 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +1 -1
- package/dist/components/blocks/data-table/DataTable.css +170 -0
- package/dist/components/blocks/data-table/DataTable.d.ts +29 -0
- package/dist/components/blocks/data-table/DataTable.js +84 -0
- package/dist/components/blocks/data-table/DataTable.types.d.ts +69 -0
- package/dist/components/blocks/data-table/DataTable.types.js +19 -0
- package/dist/components/blocks/data-table/data-table-impl.css +155 -0
- package/dist/components/blocks/download-banner/DownloadBanner.css +228 -0
- package/dist/components/blocks/download-banner/DownloadBanner.d.ts +28 -0
- package/dist/components/blocks/download-banner/DownloadBanner.js +75 -0
- package/dist/components/blocks/download-banner/DownloadBanner.types.d.ts +56 -0
- package/dist/components/blocks/download-banner/DownloadBanner.types.js +23 -0
- package/dist/components/blocks/download-banner/download-banner-impl.css +216 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.css +1 -0
- package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +3 -7
- package/dist/components/blocks/feature-bullets/FeatureBullets.css +1 -0
- package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +14 -1
- package/dist/components/blocks/feature-cards/FeatureCards.css +1 -0
- package/dist/components/blocks/feature-cards/FeatureCards.d.ts +2 -1
- package/dist/components/blocks/feature-cards/FeatureCards.js +4 -21
- package/dist/components/blocks/feature-cards/feature-cards-impl.css +10 -9
- package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.css +115 -0
- package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.d.ts +30 -0
- package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.js +160 -0
- package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.d.ts +52 -0
- package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.js +17 -0
- package/dist/components/blocks/feature-cards-carousel/feature-cards-carousel-impl.css +207 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.css +1 -0
- package/dist/components/blocks/feature-split/FeatureSplit.css +1 -0
- package/dist/components/blocks/feature-split/feature-split-impl.css +14 -1
- package/dist/components/blocks/feature-stack/FeatureStack.css +86 -0
- package/dist/components/blocks/feature-stack/FeatureStack.d.ts +19 -0
- package/dist/components/blocks/feature-stack/FeatureStack.js +27 -0
- package/dist/components/blocks/feature-stack/FeatureStack.types.d.ts +34 -0
- package/dist/components/blocks/feature-stack/FeatureStack.types.js +14 -0
- package/dist/components/blocks/feature-stack/feature-stack-impl.css +87 -0
- package/dist/components/blocks/image-banner/ImageBanner.css +133 -0
- package/dist/components/blocks/image-banner/ImageBanner.d.ts +32 -0
- package/dist/components/blocks/image-banner/ImageBanner.js +41 -0
- package/dist/components/blocks/image-banner/ImageBanner.types.d.ts +54 -0
- package/dist/components/blocks/image-banner/ImageBanner.types.js +14 -0
- package/dist/components/blocks/image-banner/image-banner-impl.css +281 -0
- package/dist/components/blocks/landing-hero/LandingHero.css +1 -0
- package/dist/components/blocks/logo-columns/LogoColumns.css +1 -0
- package/dist/components/blocks/logo-rows/LogoRows.css +1 -0
- package/dist/components/blocks/page-header/PageHeader.css +1 -0
- package/dist/components/blocks/page-header/PageHeader.js +2 -2
- package/dist/components/blocks/page-header/PageHeader.types.d.ts +4 -2
- package/dist/components/blocks/page-header/PageHeader.types.js +1 -1
- package/dist/components/blocks/page-header/page-header-impl.css +5 -3
- package/dist/components/blocks/product-hero/ProductHero.css +8 -3
- package/dist/components/blocks/product-hero/ProductHero.d.ts +1 -1
- package/dist/components/blocks/product-hero/ProductHero.js +5 -2
- package/dist/components/blocks/product-hero/ProductHero.types.d.ts +5 -1
- package/dist/components/blocks/product-hero/ProductHero.types.js +1 -0
- package/dist/components/blocks/product-hero/product-hero-impl.css +43 -12
- package/dist/components/blocks/resource-banner/ResourceBanner.css +106 -0
- package/dist/components/blocks/resource-banner/ResourceBanner.d.ts +32 -0
- package/dist/components/blocks/resource-banner/ResourceBanner.js +51 -0
- package/dist/components/blocks/resource-banner/ResourceBanner.types.d.ts +44 -0
- package/dist/components/blocks/resource-banner/ResourceBanner.types.js +14 -0
- package/dist/components/blocks/resource-banner/resource-banner-impl.css +69 -0
- package/dist/components/blocks/scene-scroll/SceneScroll.css +93 -0
- package/dist/components/blocks/scene-scroll/SceneScroll.d.ts +36 -0
- package/dist/components/blocks/scene-scroll/SceneScroll.js +54 -0
- package/dist/components/blocks/scene-scroll/SceneScroll.types.d.ts +32 -0
- package/dist/components/blocks/scene-scroll/SceneScroll.types.js +15 -0
- package/dist/components/blocks/scene-scroll/scene-scroll-impl.css +275 -0
- package/dist/components/blocks/stats/Stats.css +139 -0
- package/dist/components/blocks/stats/Stats.d.ts +24 -0
- package/dist/components/blocks/stats/Stats.js +120 -0
- package/dist/components/blocks/stats/Stats.types.d.ts +36 -0
- package/dist/components/blocks/stats/Stats.types.js +19 -0
- package/dist/components/blocks/stats/stats-count.d.ts +34 -0
- package/dist/components/blocks/stats/stats-count.js +61 -0
- package/dist/components/blocks/stats/stats-impl.css +284 -0
- package/dist/components/blocks/stats/use-wheel.d.ts +37 -0
- package/dist/components/blocks/stats/use-wheel.js +126 -0
- package/dist/components/blocks/steps/Steps.css +202 -0
- package/dist/components/blocks/steps/Steps.d.ts +32 -0
- package/dist/components/blocks/steps/Steps.js +65 -0
- package/dist/components/blocks/steps/Steps.types.d.ts +48 -0
- package/dist/components/blocks/steps/Steps.types.js +21 -0
- package/dist/components/blocks/steps/steps-impl.css +216 -0
- package/dist/components/blocks/steps/use-turn.d.ts +33 -0
- package/dist/components/blocks/steps/use-turn.js +75 -0
- package/dist/components/blocks/sticky-cards/StickyCards.css +154 -0
- package/dist/components/blocks/sticky-cards/StickyCards.d.ts +35 -0
- package/dist/components/blocks/sticky-cards/StickyCards.js +153 -0
- package/dist/components/blocks/sticky-cards/StickyCards.types.d.ts +43 -0
- package/dist/components/blocks/sticky-cards/StickyCards.types.js +18 -0
- package/dist/components/blocks/sticky-cards/sticky-cards-impl.css +393 -0
- package/dist/components/blocks/team-grid/TeamGrid.css +159 -0
- package/dist/components/blocks/team-grid/TeamGrid.d.ts +28 -0
- package/dist/components/blocks/team-grid/TeamGrid.js +74 -0
- package/dist/components/blocks/team-grid/TeamGrid.types.d.ts +51 -0
- package/dist/components/blocks/team-grid/TeamGrid.types.js +20 -0
- package/dist/components/blocks/team-grid/team-grid-impl.css +129 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.css +1 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +1 -1
- package/dist/components/blocks/testimonial-row/TestimonialRow.js +4 -3
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +3 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +1 -1
- package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +8 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +1 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +1 -1
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +2 -2
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +8 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +1 -1
- package/dist/components/blocks/text-scroll/TextScroll.css +53 -0
- package/dist/components/blocks/text-scroll/TextScroll.d.ts +5 -0
- package/dist/components/blocks/text-scroll/TextScroll.js +71 -0
- package/dist/components/blocks/text-scroll/TextScroll.types.d.ts +22 -0
- package/dist/components/blocks/text-scroll/TextScroll.types.js +10 -0
- package/dist/components/blocks/text-scroll/text-scroll-impl.css +115 -0
- package/dist/components/blocks/tiers/Tiers.css +233 -0
- package/dist/components/blocks/tiers/Tiers.d.ts +32 -0
- package/dist/components/blocks/tiers/Tiers.js +68 -0
- package/dist/components/blocks/tiers/Tiers.types.d.ts +45 -0
- package/dist/components/blocks/tiers/Tiers.types.js +25 -0
- package/dist/components/blocks/tiers/tiers-impl.css +185 -0
- package/dist/components/blocks/use-rotation.d.ts +12 -2
- package/dist/components/blocks/use-rotation.js +6 -28
- package/dist/components/comparison-cards-data.d.ts +59 -0
- package/dist/components/comparison-cards-data.js +12 -0
- package/dist/components/comparison-table-data.d.ts +39 -0
- package/dist/components/comparison-table-data.js +11 -0
- package/dist/components/core/accordion-item/AccordionItem.css +1 -5
- package/dist/components/core/background/Background.css +23 -0
- package/dist/components/core/background/Background.d.ts +10 -4
- package/dist/components/core/background/Background.js +16 -7
- package/dist/components/core/background/Background.types.d.ts +11 -8
- package/dist/components/core/background/Background.types.js +3 -1
- package/dist/components/core/background/background-impl.css +334 -1
- package/dist/components/core/background/halftone.d.ts +11 -0
- package/dist/components/core/background/halftone.js +43 -3
- package/dist/components/core/background/patterns.d.ts +66 -0
- package/dist/components/core/background/patterns.js +244 -0
- package/dist/components/core/background/shader.d.ts +44 -0
- package/dist/components/core/background/shader.js +307 -0
- package/dist/components/core/background/use-shader.d.ts +11 -0
- package/dist/components/core/background/use-shader.js +25 -0
- package/dist/components/core/background/use-spotlight.d.ts +2 -1
- package/dist/components/core/background/use-spotlight.js +9 -1
- package/dist/components/core/bottom-sheet/BottomSheet.js +2 -2
- package/dist/components/core/bottom-sheet/BottomSheet.types.d.ts +3 -1
- package/dist/components/core/breadcrumb/Breadcrumb.d.ts +1 -0
- package/dist/components/core/breadcrumb/Breadcrumb.js +1 -0
- package/dist/components/core/breadcrumb/breadcrumb-impl.css +11 -0
- package/dist/components/core/button/button-impl.css +14 -1
- package/dist/components/core/illustration-card/IllustrationCard.js +1 -1
- package/dist/components/core/illustration-card/illustration-card-impl.css +20 -0
- package/dist/components/core/illustration-scene/IllustrationScene.css +4 -7
- package/dist/components/core/illustration-scene/IllustrationScene.types.d.ts +2 -3
- package/dist/components/core/illustration-scene/illustration-scene-impl.css +7 -1
- package/dist/components/core/link/Link.js +7 -3
- package/dist/components/core/link/link-impl.css +29 -2
- package/dist/components/core/navbar/Navbar.css +2 -2
- package/dist/components/core/navbar/Navbar.types.d.ts +1 -1
- package/dist/components/core/navbar-item/NavbarItem.d.ts +2 -1
- package/dist/components/core/navbar-item/NavbarItem.js +3 -10
- package/dist/components/core/navbar-item/NavbarItem.types.d.ts +4 -1
- package/dist/components/core/navbar-item/NavbarItem.types.js +1 -1
- package/dist/components/core/navbar-item/navbar-item-impl.css +10 -0
- package/dist/components/core/navbar-menu/navbar-menu-impl.css +9 -0
- package/dist/components/core/navbar-switcher/navbar-switcher-impl.css +19 -0
- package/dist/components/core/pagination/Pagination.js +1 -1
- package/dist/components/core/pagination/pagination-impl.css +9 -0
- package/dist/components/core/pagination-dots/PaginationDots.d.ts +1 -1
- package/dist/components/core/pagination-dots/PaginationDots.js +3 -3
- package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +3 -1
- package/dist/components/core/segmented-control/SegmentedControl.css +4 -3
- package/dist/components/core/segmented-control/SegmentedControl.js +17 -3
- package/dist/components/core/segmented-control-item/SegmentedControlItem.css +2 -1
- package/dist/components/core/select/Select.js +3 -3
- package/dist/components/core/select/Select.types.d.ts +11 -1
- package/dist/components/core/select/Select.types.js +1 -1
- package/dist/components/core/tab-list/TabList.js +9 -1
- package/dist/components/core/table-header-cell/TableHeaderCell.d.ts +1 -1
- package/dist/components/core/table-header-cell/TableHeaderCell.js +5 -2
- package/dist/components/core/table-header-cell/TableHeaderCell.types.d.ts +7 -1
- package/dist/components/core/table-header-cell/TableHeaderCell.types.js +1 -1
- package/dist/components/core/text-area/TextArea.d.ts +1 -1
- package/dist/components/core/text-area/TextArea.js +2 -2
- package/dist/components/core/text-area/TextArea.types.d.ts +9 -1
- package/dist/components/core/text-area/TextArea.types.js +1 -1
- package/dist/components/core/text-input/TextInput.d.ts +1 -1
- package/dist/components/core/text-input/TextInput.js +2 -2
- package/dist/components/core/text-input/TextInput.types.d.ts +9 -1
- package/dist/components/core/text-input/TextInput.types.js +1 -1
- package/dist/components/data-table-data.d.ts +33 -0
- package/dist/components/data-table-data.js +12 -0
- package/dist/components/download-banner-data.d.ts +52 -0
- package/dist/components/download-banner-data.js +11 -0
- package/dist/components/index.d.ts +45 -0
- package/dist/components/index.js +32 -0
- package/dist/components/resource-banner-data.d.ts +34 -0
- package/dist/components/resource-banner-data.js +13 -0
- package/dist/components/scene-scroll-data.d.ts +24 -0
- package/dist/components/scene-scroll-data.js +9 -0
- package/dist/components/stats-data.d.ts +28 -0
- package/dist/components/stats-data.js +11 -0
- package/dist/components/steps-data.d.ts +38 -0
- package/dist/components/steps-data.js +13 -0
- package/dist/components/sticky-cards-data.d.ts +43 -0
- package/dist/components/sticky-cards-data.js +12 -0
- package/dist/components/team-grid-data.d.ts +38 -0
- package/dist/components/team-grid-data.js +11 -0
- package/dist/components/tiers-data.d.ts +47 -0
- package/dist/components/tiers-data.js +13 -0
- package/dist/marketing.css +77 -28
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useState } from 'react';
|
|
4
|
+
import './TeamGrid.css';
|
|
5
|
+
import './team-grid-impl.css';
|
|
6
|
+
import '../block-reveal.css';
|
|
7
|
+
import { given } from '../../given.js';
|
|
8
|
+
import { Button } from '../../core/button/Button.js';
|
|
9
|
+
import { Link } from '../../core/link/Link.js';
|
|
10
|
+
import { AspectRatio } from '../../layout/aspect-ratio/AspectRatio.js';
|
|
11
|
+
import { arriving, useReveal } from '../use-reveal.js';
|
|
12
|
+
import { teamGridDefaults, teamGridParts } from './TeamGrid.types.js';
|
|
13
|
+
/**
|
|
14
|
+
* A profile link's accessible name when the page gives none: the visible word, then whose it is.
|
|
15
|
+
*
|
|
16
|
+
* The word first, because it is what a reader using speech sees and says — "click LinkedIn" has to
|
|
17
|
+
* match — and the name after it, because "LinkedIn" alone is the same on every card and a list of
|
|
18
|
+
* links heard out of context would be one word repeated.
|
|
19
|
+
*/
|
|
20
|
+
const profileNameOf = (person) => `${person.profile?.label ?? ''}, ${person.name}`;
|
|
21
|
+
/**
|
|
22
|
+
* Where a person comes in the arrival, capped so a long set does not keep a reader waiting.
|
|
23
|
+
*
|
|
24
|
+
* The heading and the line under it take the first two steps; the people showing follow a step
|
|
25
|
+
* apart up to the fourth, and the button arrives a step after the last of them. FeatureCards' cap,
|
|
26
|
+
* for its reason: block-reveal.css keeps a block's trail inside its own movement.
|
|
27
|
+
*/
|
|
28
|
+
const FIRST_PERSON = 2;
|
|
29
|
+
const LAST_STEP = FIRST_PERSON + 3;
|
|
30
|
+
/**
|
|
31
|
+
* A heading over a grid of people — the reference design's Leaders template, AvatarGrid in its
|
|
32
|
+
* source.
|
|
33
|
+
*
|
|
34
|
+
* Each person is a square photograph over their name, their role and, where they have one, a link
|
|
35
|
+
* to a profile of theirs elsewhere. The people are records rather than cards the page composes: a
|
|
36
|
+
* person here is words and a picture, and the grid is what decides how each one is drawn.
|
|
37
|
+
*
|
|
38
|
+
* The first `collapsedCount` show at once and the rest wait behind a button under the grid. One
|
|
39
|
+
* list rather than the reference's two grids, so a screen reader counts the whole set and the
|
|
40
|
+
* order is the same whether it is open or not; the people past the count are in the markup from
|
|
41
|
+
* the first render and `hidden` until asked for, which takes them out of the accessibility tree
|
|
42
|
+
* and the tab order as well as off the page. The reference marked its hidden half `aria-hidden`
|
|
43
|
+
* only, and left their links in the tab order behind a collapsed row.
|
|
44
|
+
*
|
|
45
|
+
* Pressing the button leaves focus on it. The people it shows sit straight before it in reading
|
|
46
|
+
* order, so they are the next thing a reader moving back meets, and the people it hides take any
|
|
47
|
+
* focus they could have held away with them — which they cannot be holding, the button having it.
|
|
48
|
+
*
|
|
49
|
+
* `'use client'` for the button's state, for the arrival, and for `useId`, which names the region
|
|
50
|
+
* by its heading and gives the button the list's id to control.
|
|
51
|
+
*/
|
|
52
|
+
export function TeamGrid({ ground = teamGridDefaults.ground, title, description, people, collapsedCount = teamGridDefaults.collapsedCount, expandLabel = teamGridDefaults.expandLabel, collapseLabel = teamGridDefaults.collapseLabel, profileName = profileNameOf, externalLabel = teamGridDefaults.externalLabel, }) {
|
|
53
|
+
const titleId = useId();
|
|
54
|
+
const listId = useId();
|
|
55
|
+
const [band, revealed] = useReveal();
|
|
56
|
+
const [expanded, setExpanded] = useState(false);
|
|
57
|
+
// A count below one would hide everyone behind the button; a fraction is not a number of people.
|
|
58
|
+
const shown = Math.max(1, Math.floor(collapsedCount));
|
|
59
|
+
const longer = people.length > shown;
|
|
60
|
+
const inverse = ground === 'strong';
|
|
61
|
+
return (_jsx("section", { className: teamGridParts.root, "data-ground": ground, "aria-labelledby": titleId, children: _jsxs("div", { className: teamGridParts.inner, ref: band, "data-revealed": revealed, children: [_jsxs("div", { className: teamGridParts.header, children: [_jsx("h2", { className: teamGridParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: teamGridParts.description, ...arriving(1), children: description })) : null] }), _jsxs("div", { className: teamGridParts.people, children: [_jsx("ul", { className: teamGridParts.list, id: listId, children: people.map((person, at) => {
|
|
62
|
+
const extra = at >= shown;
|
|
63
|
+
/* A person showing from the start arrives with the block. One past the count arrives
|
|
64
|
+
when the button shows them, by the stylesheet's own animation, a step behind the
|
|
65
|
+
one before — so it carries its place among the extras rather than the block's. */
|
|
66
|
+
const motion = extra
|
|
67
|
+
? {
|
|
68
|
+
'data-extra': '',
|
|
69
|
+
style: { '--dds-teamgrid-extra-at': at - shown },
|
|
70
|
+
}
|
|
71
|
+
: arriving(Math.min(FIRST_PERSON + at, LAST_STEP));
|
|
72
|
+
return (_jsxs("li", { className: teamGridParts.item, hidden: extra && !expanded, ...motion, children: [_jsx("div", { className: teamGridParts.media, children: _jsx(AspectRatio, { ratio: "square", children: _jsx("img", { src: person.image.src, alt: person.image.alt, loading: "lazy", decoding: "async" }) }) }), _jsxs("div", { className: teamGridParts.text, children: [_jsx("h3", { className: teamGridParts.name, children: person.name }), _jsx("p", { className: teamGridParts.role, children: person.role }), person.profile !== undefined ? (_jsx("div", { className: teamGridParts.profile, children: _jsx(Link, { href: person.profile.href, external: true, tone: inverse ? 'inherit' : 'brand', "aria-label": `${profileName(person)} ${externalLabel}`, children: person.profile.label }) })) : null] })] }, `${person.name}-${at}`));
|
|
73
|
+
}) }), longer ? (_jsx("div", { className: teamGridParts.actions, ...arriving(Math.min(FIRST_PERSON + shown, LAST_STEP + 1)), children: _jsx(Button, { variant: "secondary", size: "md", tone: inverse ? 'inverse' : 'default', "aria-expanded": expanded, "aria-controls": listId, onClick: () => setExpanded((open) => !open), children: expanded ? collapseLabel : expandLabel }) })) : null] })] }) }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TeamGridMember } from '../../team-grid-data.js';
|
|
2
|
+
export interface TeamGridOwnProps {
|
|
3
|
+
/**
|
|
4
|
+
* `page` — The default, and the page's own white. Most people sections have nothing to separate themselves from
|
|
5
|
+
* `subtle` — A quiet step off the page — the reference design's slate band — for a section that wants to read as its own. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section; the photographs' placeholder is raised to color.bg.surface-card-raised so it does not vanish into the band
|
|
6
|
+
* `strong` — `color.bg.brand-strong`, the dark band, for a section a reader should arrive at. The heading, the names and the roles turn white, the profile links take the band's ink rather than the brand's, and the button takes Button's inverse tone
|
|
7
|
+
*/
|
|
8
|
+
ground?: 'page' | 'subtle' | 'strong';
|
|
9
|
+
/** Always. Who these people are, in the fewest words — "Meet our leadership team". It names the section as a region, so a screen reader can jump to it and past it. */
|
|
10
|
+
title: string;
|
|
11
|
+
/** A line saying who the set is, when the title alone does not — "The people steering Deriv's products, markets and teams". */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Always. The people, in the order the page wants them read — by seniority or by relevance, the first `collapsedCount` of them showing at once. Each has a name, a role, a picture with its `alt`, and where there is one a `profile` link. The same framing in every photograph: the grid crops each to a square, and a set of faces at different distances reads as a set of different sources. An empty list draws the heading alone. */
|
|
14
|
+
people: TeamGridMember[];
|
|
15
|
+
/** How many people show before the rest are asked for. Four, the default, is one row at full width, so the button sits under a complete row. A list no longer than this has no button at all — there is nothing more to show. */
|
|
16
|
+
collapsedCount?: number;
|
|
17
|
+
/** The button's words while the rest of the list is hidden. Translate it with the page, and keep it parallel to collapseLabel. */
|
|
18
|
+
expandLabel?: string;
|
|
19
|
+
/** The button's words once the whole list is showing, for the reason expandLabel exists. */
|
|
20
|
+
collapseLabel?: string;
|
|
21
|
+
/** The accessible name of a person's profile link, given the person. The visible word is the profile's own — "LinkedIn" — and it is the same on every card, so the link is named for its person too. Without this the name is the profile's label and then the person's, "LinkedIn, Alex Morgan": the visible word first, so a reader using speech can say what they see. Pass it to translate that with the page. */
|
|
22
|
+
profileName?: (person: TeamGridMember) => string;
|
|
23
|
+
/** Said after every profile link's name, because a profile opens in a new tab and a reader should know before they press it — ResourceBanner's words for the same thing. Translate it with the page. */
|
|
24
|
+
externalLabel?: string;
|
|
25
|
+
}
|
|
26
|
+
export type TeamGridProps = TeamGridOwnProps;
|
|
27
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
28
|
+
export declare const teamGridParts: {
|
|
29
|
+
readonly root: "dds-teamgrid";
|
|
30
|
+
readonly inner: "dds-teamgrid__inner";
|
|
31
|
+
readonly header: "dds-teamgrid__header";
|
|
32
|
+
readonly title: "dds-teamgrid__title";
|
|
33
|
+
readonly description: "dds-teamgrid__description";
|
|
34
|
+
readonly people: "dds-teamgrid__people";
|
|
35
|
+
readonly list: "dds-teamgrid__list";
|
|
36
|
+
readonly item: "dds-teamgrid__item";
|
|
37
|
+
readonly media: "dds-teamgrid__media";
|
|
38
|
+
readonly text: "dds-teamgrid__text";
|
|
39
|
+
readonly name: "dds-teamgrid__name";
|
|
40
|
+
readonly role: "dds-teamgrid__role";
|
|
41
|
+
readonly profile: "dds-teamgrid__profile";
|
|
42
|
+
readonly actions: "dds-teamgrid__actions";
|
|
43
|
+
};
|
|
44
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
45
|
+
export declare const teamGridDefaults: {
|
|
46
|
+
readonly ground: "page";
|
|
47
|
+
readonly collapsedCount: 4;
|
|
48
|
+
readonly expandLabel: "View all";
|
|
49
|
+
readonly collapseLabel: "View less";
|
|
50
|
+
readonly externalLabel: "(opens in a new tab)";
|
|
51
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// GENERATED from team-grid.contract.json. Do not edit.
|
|
2
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
3
|
+
export const teamGridParts = {
|
|
4
|
+
root: 'dds-teamgrid',
|
|
5
|
+
inner: 'dds-teamgrid__inner',
|
|
6
|
+
header: 'dds-teamgrid__header',
|
|
7
|
+
title: 'dds-teamgrid__title',
|
|
8
|
+
description: 'dds-teamgrid__description',
|
|
9
|
+
people: 'dds-teamgrid__people',
|
|
10
|
+
list: 'dds-teamgrid__list',
|
|
11
|
+
item: 'dds-teamgrid__item',
|
|
12
|
+
media: 'dds-teamgrid__media',
|
|
13
|
+
text: 'dds-teamgrid__text',
|
|
14
|
+
name: 'dds-teamgrid__name',
|
|
15
|
+
role: 'dds-teamgrid__role',
|
|
16
|
+
profile: 'dds-teamgrid__profile',
|
|
17
|
+
actions: 'dds-teamgrid__actions',
|
|
18
|
+
};
|
|
19
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
20
|
+
export const teamGridDefaults = { ground: 'page', collapsedCount: 4, expandLabel: 'View all', collapseLabel: 'View less', externalLabel: '(opens in a new tab)' };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Scoped to this package, like the generated sheet beside it.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* The header starts on a phone and is centred once the band is wide enough to hold a line.
|
|
7
|
+
*
|
|
8
|
+
* FeatureCards' rule and its reason: centred text starts every line at a different edge, which
|
|
9
|
+
* costs little across a wide measure and a great deal across a narrow one. The header and the line
|
|
10
|
+
* under it are capped narrower than the column, so `margin-inline: auto` is what centres those
|
|
11
|
+
* boxes rather than their words inside a box left at the start edge.
|
|
12
|
+
*
|
|
13
|
+
* `48rem` longhand because `var()` is not permitted in a container query condition, and it is the
|
|
14
|
+
* width the rest of this package folds at.
|
|
15
|
+
*/
|
|
16
|
+
@container (min-width: 48rem) {
|
|
17
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__header {
|
|
18
|
+
align-items: center;
|
|
19
|
+
margin-inline: auto;
|
|
20
|
+
text-align: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__description {
|
|
24
|
+
margin-inline: auto;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* One column on a phone, two from the fold, four at full width — the reference design's grid, so
|
|
30
|
+
* the four people showing at first are one row on a desktop. `minmax(0, 1fr)` rather than `1fr`, so
|
|
31
|
+
* a long unbroken name shrinks to its column instead of pushing the grid past the page.
|
|
32
|
+
*
|
|
33
|
+
* Four from 64rem rather than FeatureCards' 62rem: four photographs and four names a row want the
|
|
34
|
+
* extra width, and below it two a row keeps each face large enough to be one.
|
|
35
|
+
*/
|
|
36
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__list {
|
|
37
|
+
grid-template-columns: minmax(0, 1fr);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@container (min-width: 48rem) {
|
|
41
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__list {
|
|
42
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@container (min-width: 64rem) {
|
|
47
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__list {
|
|
48
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* A person waiting behind the button is not there at all.
|
|
54
|
+
*
|
|
55
|
+
* The item is a flex column by its contract, and an author's `display` beats the browser's own
|
|
56
|
+
* `[hidden] { display: none }` — so without this the attribute would take a person out of the
|
|
57
|
+
* accessibility tree while leaving them drawn on the page.
|
|
58
|
+
*/
|
|
59
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__item[hidden] {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* And arrives when the button shows them: faded up from opacity.hidden and risen
|
|
65
|
+
* motion.translate.reveal, the arrival every block makes, a stagger step behind the person before.
|
|
66
|
+
*
|
|
67
|
+
* A keyframe rather than a transition because the person was `display: none` a moment earlier and
|
|
68
|
+
* a transition has no painted starting state to move from — the same reason a block's entrance is
|
|
69
|
+
* keyframed. `both`, so a person waiting out their delay waits at the start rather than showing
|
|
70
|
+
* and then jumping back to it. The reference also grew the grid's height open; that needs the
|
|
71
|
+
* extras in a second list, and one list a screen reader counts is worth more than the slide.
|
|
72
|
+
*/
|
|
73
|
+
@keyframes dds-teamgrid-arrive {
|
|
74
|
+
from {
|
|
75
|
+
opacity: var(--opacity-hidden);
|
|
76
|
+
translate: var(--motion-translate-reveal);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__item[data-extra]:not([hidden]) {
|
|
81
|
+
animation: dds-teamgrid-arrive var(--motion-duration-reveal) var(--motion-easing-reveal) both;
|
|
82
|
+
animation-delay: calc(var(--dds-teamgrid-extra-at, 0) * var(--motion-duration-stagger));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* A reader who asked for less movement gets the people without the arrival: they are simply there
|
|
87
|
+
* the moment the button is pressed.
|
|
88
|
+
*/
|
|
89
|
+
@media (prefers-reduced-motion: reduce) {
|
|
90
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__item[data-extra]:not([hidden]) {
|
|
91
|
+
animation: none;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* The photograph's frame takes the whole of the card's width; AspectRatio inside it holds the
|
|
97
|
+
* square and the frame's clip rounds the corners of whatever the picture is.
|
|
98
|
+
*/
|
|
99
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__media {
|
|
100
|
+
inline-size: 100%;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
* A profile link is a pointer target, so it is at least size.target.min tall. A standalone Link is
|
|
105
|
+
* as tall as its line, which is under that at the label step — the profile part holds the height
|
|
106
|
+
* and the link fills it, so the whole of the row is the target rather than the words alone.
|
|
107
|
+
*/
|
|
108
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__profile > .dds-link {
|
|
109
|
+
min-block-size: var(--size-target-min);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* The button fills the column on a phone, the rule for a section's actions — the part is a column,
|
|
114
|
+
* which stretches it. From the fold it is its own width, centred under the grid it lengthens.
|
|
115
|
+
*/
|
|
116
|
+
@container (min-width: 48rem) {
|
|
117
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__actions {
|
|
118
|
+
align-items: center;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* A heading with no line under it closes up: its bound step is the distance to the line, and with
|
|
124
|
+
* nothing there it would only add to the step from the header to the people. `:last-child` is the
|
|
125
|
+
* condition itself, DataTable's way, rather than a prop standing in for it.
|
|
126
|
+
*/
|
|
127
|
+
:where([data-dds-context="marketing"]) .dds-teamgrid .dds-teamgrid__title:last-child {
|
|
128
|
+
margin-block-end: 0;
|
|
129
|
+
}
|
|
@@ -17,4 +17,4 @@ export type { TestimonialRowProps };
|
|
|
17
17
|
* `'use client'` for the row's scroll position, the arrival, and `useId`, which names the region
|
|
18
18
|
* by its heading.
|
|
19
19
|
*/
|
|
20
|
-
export declare function TestimonialRow({ ground, title, reviews, summary, ratingLabel, label, previousLabel, nextLabel, }: TestimonialRowProps): import("react").JSX.Element | null;
|
|
20
|
+
export declare function TestimonialRow({ ground, title, reviews, summary, ratingLabel, label, externalLabel, previousLabel, nextLabel, }: TestimonialRowProps): import("react").JSX.Element | null;
|
|
@@ -8,6 +8,7 @@ import "../block-reveal.css";
|
|
|
8
8
|
import { given } from "../../given.js";
|
|
9
9
|
import { ButtonIcon } from "../../core/button-icon/ButtonIcon.js";
|
|
10
10
|
import { Link } from "../../core/link/Link.js";
|
|
11
|
+
import { visuallyHidden } from "../../visually-hidden.js";
|
|
11
12
|
import { arriving, useReveal } from "../use-reveal.js";
|
|
12
13
|
import { testimonialRowDefaults, testimonialRowParts, } from "./TestimonialRow.types.js";
|
|
13
14
|
/**
|
|
@@ -45,7 +46,7 @@ const AT_END = 2;
|
|
|
45
46
|
* `'use client'` for the row's scroll position, the arrival, and `useId`, which names the region
|
|
46
47
|
* by its heading.
|
|
47
48
|
*/
|
|
48
|
-
export function TestimonialRow({ ground = testimonialRowDefaults.ground, title, reviews, summary, ratingLabel, label = testimonialRowDefaults.label, previousLabel = testimonialRowDefaults.previousLabel, nextLabel = testimonialRowDefaults.nextLabel, }) {
|
|
49
|
+
export function TestimonialRow({ ground = testimonialRowDefaults.ground, title, reviews, summary, ratingLabel, label = testimonialRowDefaults.label, externalLabel = testimonialRowDefaults.externalLabel, previousLabel = testimonialRowDefaults.previousLabel, nextLabel = testimonialRowDefaults.nextLabel, }) {
|
|
49
50
|
const titleId = useId();
|
|
50
51
|
const [band, revealed] = useReveal();
|
|
51
52
|
const row = useRef(null);
|
|
@@ -116,13 +117,13 @@ export function TestimonialRow({ ground = testimonialRowDefaults.ground, title,
|
|
|
116
117
|
return (_jsx("section", { className: testimonialRowParts.root, "data-ground": ground, "aria-labelledby": titleId, children: _jsxs("div", { className: testimonialRowParts.inner, ref: band, "data-revealed": revealed, children: [_jsxs("div", { className: testimonialRowParts.header, children: [_jsx("h2", { className: testimonialRowParts.title, id: titleId, ...arriving(0), children: title }), summary === undefined ? null : (_jsx("p", { className: testimonialRowParts.summary, ...arriving(1), children: summary.href !== undefined && given(summary.href) ? (
|
|
117
118
|
// A link on its own line, so the standalone one: brand ink and a mark. On the dark
|
|
118
119
|
// band it takes the band's ink, where coral is the wrong colour for words.
|
|
119
|
-
|
|
120
|
+
_jsxs(Link, { variant: "standalone", tone: ground === "strong" ? "inherit" : "brand", external: true, href: summary.href, children: [summary.label, _jsx("span", { style: visuallyHidden, children: ` ${externalLabel}` })] })) : (_jsx("span", { children: summary.label })) }))] }), _jsxs("div", { className: testimonialRowParts.carousel, children: [_jsx("div", { ref: row, className: testimonialRowParts.track, role: "group", "aria-label": label, tabIndex: 0, onScroll: read, ...arriving(2), children: reviews.map((each, at) => {
|
|
120
121
|
// A review's position is its identity: the set is a list, and nothing reorders it.
|
|
121
122
|
const card = (_jsxs("figure", { className: testimonialRowParts.item, children: [each.rating === undefined ? null : (
|
|
122
123
|
// Named by the score it is drawn at, so a 4.3 is heard as the 4.5 a reader
|
|
123
124
|
// sees rather than as a number the stars do not show.
|
|
124
125
|
_jsx(Rating, { score: each.rating, label: ratingLabel?.(toHalves(each.rating)) ?? `Rated ${toHalves(each.rating)} out of 5` })), _jsx("blockquote", { className: testimonialRowParts.quote, children: each.quote }), _jsx("figcaption", { className: testimonialRowParts.author, children: _jsxs("span", { children: [_jsx("span", { className: testimonialRowParts.name, children: each.name }), given(each.detail) ? (_jsx("span", { className: testimonialRowParts.detail, children: each.detail })) : null] }) })] }, at));
|
|
125
126
|
// The review is on a service the page does not own, so it opens beside the page.
|
|
126
|
-
return given(each.href) ? (
|
|
127
|
+
return given(each.href) ? (_jsxs("a", { className: testimonialRowParts["review-link"], href: each.href, target: "_blank", rel: "noopener noreferrer", children: [card, _jsx("span", { style: visuallyHidden, children: ` ${externalLabel}` })] }, at)) : (card);
|
|
127
128
|
}) }), reviews.length > 1 ? (_jsx("div", { className: testimonialRowParts.footer, ...arriving(3), children: _jsxs("div", { className: testimonialRowParts.nav, hidden: !scrolls, children: [_jsx("span", { ref: back, className: testimonialRowParts["nav-slot"], "data-available": ends.back, inert: !ends.back, children: _jsx(ButtonIcon, { icon: _jsx(ChevronLeftRegularIcon, {}), label: previousLabel, ...arrow, onClick: () => step(-1) }) }), _jsx("span", { ref: on, className: testimonialRowParts["nav-slot"], "data-available": ends.on, inert: !ends.on, children: _jsx(ButtonIcon, { icon: _jsx(ChevronRightRegularIcon, {}), label: nextLabel, ...arrow, onClick: () => step(1) }) })] }) })) : null] })] }) }));
|
|
128
129
|
}
|
|
@@ -20,6 +20,8 @@ export interface TestimonialRowOwnProps {
|
|
|
20
20
|
previousLabel?: string;
|
|
21
21
|
/** The accessible name of the arrow stepping the row on, for the reason previousLabel exists. */
|
|
22
22
|
nextLabel?: string;
|
|
23
|
+
/** Said after the summary link and every linked review, because both open on a service the page does not own, in a new tab, and a reader should know before they press — TeamGrid's and ResourceBanner's words for the same thing. Translate it with the page. */
|
|
24
|
+
externalLabel?: string;
|
|
23
25
|
}
|
|
24
26
|
export type TestimonialRowProps = TestimonialRowOwnProps;
|
|
25
27
|
/** The class each part carries. Apply these — do not retype them. */
|
|
@@ -51,4 +53,5 @@ export declare const testimonialRowDefaults: {
|
|
|
51
53
|
readonly label: "Reviews";
|
|
52
54
|
readonly previousLabel: "Previous reviews";
|
|
53
55
|
readonly nextLabel: "Next reviews";
|
|
56
|
+
readonly externalLabel: "(opens in a new tab)";
|
|
54
57
|
};
|
|
@@ -23,4 +23,4 @@ export const testimonialRowParts = {
|
|
|
23
23
|
'nav-slot': 'dds-testimonialrow__nav-slot',
|
|
24
24
|
};
|
|
25
25
|
/** Generated so defaults cannot drift from the contract. */
|
|
26
|
-
export const testimonialRowDefaults = { ground: 'page', label: 'Reviews', previousLabel: 'Previous reviews', nextLabel: 'Next reviews' };
|
|
26
|
+
export const testimonialRowDefaults = { ground: 'page', label: 'Reviews', previousLabel: 'Previous reviews', nextLabel: 'Next reviews', externalLabel: '(opens in a new tab)' };
|
|
@@ -193,3 +193,11 @@
|
|
|
193
193
|
:where([data-dds-context="marketing"]) .dds-testimonialrow .dds-testimonialrow__nav-slot[data-available='false'] {
|
|
194
194
|
opacity: var(--opacity-disabled);
|
|
195
195
|
}
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
* Previous and next point along the row. A right-to-left row puts previous on the right, so the
|
|
199
|
+
* chevrons are mirrored to point the way each one moves; as they ship, both pointed backwards.
|
|
200
|
+
*/
|
|
201
|
+
:where([data-dds-context="marketing"]) .dds-testimonialrow .dds-testimonialrow__nav-slot svg:dir(rtl) {
|
|
202
|
+
scale: -1 1;
|
|
203
|
+
}
|
|
@@ -19,4 +19,4 @@ export type { TestimonialSliderProps };
|
|
|
19
19
|
* comes into view. `'use client'` for that, for the rotation, and for `useId`, which names the
|
|
20
20
|
* region by its heading.
|
|
21
21
|
*/
|
|
22
|
-
export declare function TestimonialSlider({ ground, title, quotes, imageStart, imageEnd, autoplay, controlsLabel, slideLabel, }: TestimonialSliderProps): import("react").JSX.Element | null;
|
|
22
|
+
export declare function TestimonialSlider({ ground, title, quotes, imageStart, imageEnd, autoplay, controlsLabel, slideLabel, roleDescription, slideRoleDescription, positionLabel, }: TestimonialSliderProps): import("react").JSX.Element | null;
|
|
@@ -25,10 +25,10 @@ import { testimonialSliderDefaults, testimonialSliderParts, } from "./Testimonia
|
|
|
25
25
|
* comes into view. `'use client'` for that, for the rotation, and for `useId`, which names the
|
|
26
26
|
* region by its heading.
|
|
27
27
|
*/
|
|
28
|
-
export function TestimonialSlider({ ground = testimonialSliderDefaults.ground, title, quotes, imageStart, imageEnd, autoplay = testimonialSliderDefaults.autoplay, controlsLabel = testimonialSliderDefaults.controlsLabel, slideLabel = testimonialSliderDefaults.slideLabel, }) {
|
|
28
|
+
export function TestimonialSlider({ ground = testimonialSliderDefaults.ground, title, quotes, imageStart, imageEnd, autoplay = testimonialSliderDefaults.autoplay, controlsLabel = testimonialSliderDefaults.controlsLabel, slideLabel = testimonialSliderDefaults.slideLabel, roleDescription = testimonialSliderDefaults.roleDescription, slideRoleDescription = testimonialSliderDefaults.slideRoleDescription, positionLabel, }) {
|
|
29
29
|
const titleId = useId();
|
|
30
30
|
const [band, revealed] = useReveal();
|
|
31
|
-
const { rotating, root, region, track, item, away, dots } = useRotation(quotes.length, autoplay);
|
|
31
|
+
const { rotating, root, region, track, item, away, dots } = useRotation(quotes.length, autoplay, { roleDescription, slideRoleDescription, positionLabel });
|
|
32
32
|
// Nothing to show is nothing drawn: a named region with no quote in it leads nowhere.
|
|
33
33
|
if (quotes.length === 0)
|
|
34
34
|
return null;
|
|
@@ -20,6 +20,12 @@ export interface TestimonialSliderOwnProps {
|
|
|
20
20
|
controlsLabel?: string;
|
|
21
21
|
/** The word each dot is announced by, before its number — 'Slide 2'. Translate it with the page, as controlsLabel is; a page that translates only the group's name still has every dot announced in English. */
|
|
22
22
|
slideLabel?: string;
|
|
23
|
+
/** What a screen reader calls the set while it rotates — announced in place of a region, as the word for this kind of thing. Translate it with the page: it is read out as given, so an untranslated one announces an English word on every page in every language. */
|
|
24
|
+
roleDescription?: string;
|
|
25
|
+
/** What a screen reader calls each item while the set rotates, for the reason roleDescription exists. Translate it with the page. */
|
|
26
|
+
slideRoleDescription?: string;
|
|
27
|
+
/** The accessible name of each item while the set rotates, given its place in the set counting from one — so a reader hears where they are. Without it each is named "2 of 5"; pass it to translate that with the page, in that language's own order and digits. */
|
|
28
|
+
positionLabel?: (position: number, count: number) => string;
|
|
23
29
|
}
|
|
24
30
|
export type TestimonialSliderProps = TestimonialSliderOwnProps;
|
|
25
31
|
/** The class each part carries. Apply these — do not retype them. */
|
|
@@ -46,4 +52,6 @@ export declare const testimonialSliderDefaults: {
|
|
|
46
52
|
readonly autoplay: false;
|
|
47
53
|
readonly controlsLabel: "Testimonials";
|
|
48
54
|
readonly slideLabel: "Slide";
|
|
55
|
+
readonly roleDescription: "carousel";
|
|
56
|
+
readonly slideRoleDescription: "slide";
|
|
49
57
|
};
|
|
@@ -18,4 +18,4 @@ export const testimonialSliderParts = {
|
|
|
18
18
|
controls: 'dds-testimonialslider__controls',
|
|
19
19
|
};
|
|
20
20
|
/** Generated so defaults cannot drift from the contract. */
|
|
21
|
-
export const testimonialSliderDefaults = { ground: 'page', autoplay: false, controlsLabel: 'Testimonials', slideLabel: 'Slide' };
|
|
21
|
+
export const testimonialSliderDefaults = { ground: 'page', autoplay: false, controlsLabel: 'Testimonials', slideLabel: 'Slide', roleDescription: 'carousel', slideRoleDescription: 'slide' };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* GENERATED from textscroll.contract.json. Do not edit. */
|
|
2
|
+
|
|
3
|
+
:where([data-dds-context="marketing"]) .dds-textscroll {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
container-type: inline-size;
|
|
6
|
+
display: flex;
|
|
7
|
+
inline-size: 100%;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
overflow-wrap: anywhere;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:where([data-dds-context="marketing"]) .dds-textscroll .dds-textscroll__inner {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
inline-size: 100%;
|
|
17
|
+
min-inline-size: 0;
|
|
18
|
+
max-inline-size: var(--size-content-max);
|
|
19
|
+
padding-block: var(--space-section-default);
|
|
20
|
+
padding-inline: var(--space-inset-page);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:where([data-dds-context="marketing"]) .dds-textscroll .dds-textscroll__text {
|
|
24
|
+
margin-block: 0;
|
|
25
|
+
text-wrap: pretty;
|
|
26
|
+
font-family: var(--font-h4-family);
|
|
27
|
+
font-size: var(--font-h4-size);
|
|
28
|
+
font-weight: var(--font-h4-weight);
|
|
29
|
+
letter-spacing: var(--font-h4-letter-spacing);
|
|
30
|
+
line-height: var(--font-h4-line-height);
|
|
31
|
+
max-inline-size: var(--size-measure-hero-heading);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where([data-dds-context="marketing"]) .dds-textscroll .dds-textscroll__word {
|
|
35
|
+
display: inline;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:where([data-dds-context="marketing"]) .dds-textscroll[data-ground="page"] {
|
|
39
|
+
background-color: var(--color-bg-page);
|
|
40
|
+
color: var(--color-fg-default);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:where([data-dds-context="marketing"]) .dds-textscroll[data-ground="strong"] {
|
|
44
|
+
background-color: var(--color-bg-brand-strong);
|
|
45
|
+
color: var(--color-fg-on-brand-strong);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where([data-dds-context="marketing"]) .dds-textscroll[data-ground="subtle"] {
|
|
49
|
+
background-color: var(--color-bg-page);
|
|
50
|
+
background-image: linear-gradient(var(--color-bg-band-subtle), var(--color-bg-band-subtle));
|
|
51
|
+
color: var(--color-fg-default);
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from 'react';
|
|
3
|
+
import './TextScroll.css';
|
|
4
|
+
import './text-scroll-impl.css';
|
|
5
|
+
import { textScrollDefaults, textScrollParts } from './TextScroll.types.js';
|
|
6
|
+
const SPACE = /^\s+$/u;
|
|
7
|
+
/**
|
|
8
|
+
* The sentence in words and the spaces between them, every character kept.
|
|
9
|
+
*
|
|
10
|
+
* By the language's own word boundaries rather than by spaces. Chinese, Japanese and Thai write
|
|
11
|
+
* none between words, so a split on whitespace handed the whole paragraph back as one word and it
|
|
12
|
+
* brightened all at once. `Intl.Segmenter` knows where their words end, in the browser and on the
|
|
13
|
+
* server alike.
|
|
14
|
+
*
|
|
15
|
+
* The spaces are kept as written rather than rebuilt as one ordinary space: French sets a narrow
|
|
16
|
+
* no-break space before `?` and `%`, and turning it into a breaking one let the line wrap before
|
|
17
|
+
* the punctuation it exists to hold on to. Punctuation rides with the word beside it, so a full
|
|
18
|
+
* stop brightens with its word instead of standing lit on its own.
|
|
19
|
+
*/
|
|
20
|
+
function piecesOf(text) {
|
|
21
|
+
const runs = typeof Intl !== 'undefined' && 'Segmenter' in Intl
|
|
22
|
+
? Array.from(new Intl.Segmenter(undefined, { granularity: 'word' }).segment(text), (each) => ({
|
|
23
|
+
text: each.segment,
|
|
24
|
+
wordLike: each.isWordLike === true,
|
|
25
|
+
}))
|
|
26
|
+
: text.split(/(\s+)/u).map((run) => ({ text: run, wordLike: !SPACE.test(run) }));
|
|
27
|
+
const pieces = [];
|
|
28
|
+
// The word being built, and whether it holds a word yet or only punctuation leading into one.
|
|
29
|
+
let open = null;
|
|
30
|
+
const close = () => {
|
|
31
|
+
if (open !== null)
|
|
32
|
+
pieces.push({ word: true, text: open.text });
|
|
33
|
+
open = null;
|
|
34
|
+
};
|
|
35
|
+
for (const run of runs) {
|
|
36
|
+
if (run.text === '')
|
|
37
|
+
continue;
|
|
38
|
+
if (SPACE.test(run.text)) {
|
|
39
|
+
close();
|
|
40
|
+
pieces.push({ word: false, text: run.text });
|
|
41
|
+
}
|
|
42
|
+
else if (run.wordLike && open?.holdsWord) {
|
|
43
|
+
// Two words with nothing between them — the ordinary case in a script that writes no spaces.
|
|
44
|
+
close();
|
|
45
|
+
open = { text: run.text, holdsWord: true };
|
|
46
|
+
}
|
|
47
|
+
else if (open === null) {
|
|
48
|
+
open = { text: run.text, holdsWord: run.wordLike };
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
open = { text: open.text + run.text, holdsWord: open.holdsWord || run.wordLike };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
close();
|
|
55
|
+
return pieces;
|
|
56
|
+
}
|
|
57
|
+
export function TextScroll({ ground = textScrollDefaults.ground, text }) {
|
|
58
|
+
const pieces = piecesOf(text.trim());
|
|
59
|
+
const count = pieces.filter((piece) => piece.word).length;
|
|
60
|
+
// Nothing to say is nothing drawn: an empty band between two sections is a gap, not a pause.
|
|
61
|
+
if (count === 0)
|
|
62
|
+
return null;
|
|
63
|
+
let at = -1;
|
|
64
|
+
return (_jsx("section", { className: textScrollParts.root, "data-ground": ground, children: _jsx("div", { className: textScrollParts.inner, children: _jsx("p", { className: textScrollParts.text, style: { '--dds-textscroll-words': count }, children: pieces.map((piece, index) => {
|
|
65
|
+
// A piece's place is its identity: the sentence is the page's, and nothing reorders it.
|
|
66
|
+
if (!piece.word)
|
|
67
|
+
return _jsx(Fragment, { children: piece.text }, index);
|
|
68
|
+
at += 1;
|
|
69
|
+
return (_jsx("span", { className: textScrollParts.word, style: { '--dds-textscroll-at': at }, children: piece.text }, index));
|
|
70
|
+
}) }) }) }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TextScrollOwnProps {
|
|
2
|
+
/**
|
|
3
|
+
* `page` — The default, and the page's own white. A statement between two sections that need nothing to separate them
|
|
4
|
+
* `subtle` — The grey band, for a statement that should read as a section of its own without changing the page's voice. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section
|
|
5
|
+
* `strong` — `color.bg.brand-strong`, the dark band, for the statement a page is built around — the mission on an About page. The words turn white and dim toward the band
|
|
6
|
+
*/
|
|
7
|
+
ground?: 'page' | 'subtle' | 'strong';
|
|
8
|
+
/** Always. The statement, as plain text — one sentence or two short ones, under about three hundred characters. Split into words on its whitespace, so a run of spaces or a line break is one space in what is drawn. Written so it reads well a word at a time, because that is how the reveal delivers it: "We believe trading should be accessible to everyone, everywhere, with the tools and the trust to do it well." */
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
export type TextScrollProps = TextScrollOwnProps;
|
|
12
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
13
|
+
export declare const textScrollParts: {
|
|
14
|
+
readonly root: "dds-textscroll";
|
|
15
|
+
readonly inner: "dds-textscroll__inner";
|
|
16
|
+
readonly text: "dds-textscroll__text";
|
|
17
|
+
readonly word: "dds-textscroll__word";
|
|
18
|
+
};
|
|
19
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
20
|
+
export declare const textScrollDefaults: {
|
|
21
|
+
readonly ground: "page";
|
|
22
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED from text-scroll.contract.json. Do not edit.
|
|
2
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
3
|
+
export const textScrollParts = {
|
|
4
|
+
root: 'dds-textscroll',
|
|
5
|
+
inner: 'dds-textscroll__inner',
|
|
6
|
+
text: 'dds-textscroll__text',
|
|
7
|
+
word: 'dds-textscroll__word',
|
|
8
|
+
};
|
|
9
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
10
|
+
export const textScrollDefaults = { ground: 'page' };
|