@smicolon/fasla-ui 0.6.0 → 0.8.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/README.md +24 -5
- package/dist/cli/index.js +2983 -648
- package/dist/index.cjs +50 -12
- package/dist/index.js +50 -12
- package/dist/motion/index.cjs +12 -0
- package/dist/motion/index.d.cts +7 -0
- package/dist/motion/index.d.ts +7 -0
- package/dist/motion/index.js +12 -0
- package/dist/registry/index.cjs +4 -4
- package/dist/registry/index.js +4 -4
- package/dist/tokens/index.cjs +34 -8
- package/dist/tokens/index.d.cts +23 -1
- package/dist/tokens/index.d.ts +23 -1
- package/dist/tokens/index.js +34 -8
- package/package.json +6 -1
- package/registry/blocks/about-approach-stack-cards/AboutApproachStackCards.tsx +305 -0
- package/registry/blocks/about-approach-stack-cards/index.ts +9 -0
- package/registry/blocks/about-bento/AboutBento.tsx +442 -0
- package/registry/blocks/about-bento/index.ts +9 -0
- package/registry/blocks/about-collage-hero/AboutCollageHero.tsx +285 -0
- package/registry/blocks/about-collage-hero/index.ts +8 -0
- package/registry/blocks/about-color-bento/AboutColorBento.tsx +235 -0
- package/registry/blocks/about-color-bento/index.ts +6 -0
- package/registry/blocks/about-culture-masonry/AboutCultureMasonry.tsx +305 -0
- package/registry/blocks/about-culture-masonry/index.ts +9 -0
- package/registry/blocks/about-headline-photos/AboutHeadlinePhotos.tsx +270 -0
- package/registry/blocks/about-headline-photos/index.ts +8 -0
- package/registry/blocks/about-impact-stats-band/AboutImpactStatsBand.test.tsx +45 -0
- package/registry/blocks/about-impact-stats-band/AboutImpactStatsBand.tsx +348 -0
- package/registry/blocks/about-impact-stats-band/index.ts +9 -0
- package/registry/blocks/about-letter-card/AboutLetterCard.tsx +403 -0
- package/registry/blocks/about-letter-card/index.ts +9 -0
- package/registry/blocks/about-mission-statement-hero/AboutMissionStatementHero.tsx +282 -0
- package/registry/blocks/about-mission-statement-hero/index.ts +8 -0
- package/registry/blocks/about-numbered-rows/AboutNumberedRows.tsx +289 -0
- package/registry/blocks/about-numbered-rows/index.ts +7 -0
- package/registry/blocks/about-split-panel/AboutSplitPanel.tsx +247 -0
- package/registry/blocks/about-split-panel/index.ts +8 -0
- package/registry/blocks/about-spotlight/AboutSpotlight.tsx +260 -0
- package/registry/blocks/about-spotlight/index.ts +7 -0
- package/registry/blocks/about-statement-band/AboutStatementBand.tsx +236 -0
- package/registry/blocks/about-statement-band/index.ts +7 -0
- package/registry/blocks/about-story-photos/AboutStoryPhotos.test.tsx +12 -0
- package/registry/blocks/about-story-photos/AboutStoryPhotos.tsx +252 -0
- package/registry/blocks/about-story-photos/index.ts +8 -0
- package/registry/blocks/about-story-snapshots/AboutStorySnapshots.tsx +239 -0
- package/registry/blocks/about-story-snapshots/index.ts +8 -0
- package/registry/blocks/about-type-stack/AboutTypeStack.tsx +205 -0
- package/registry/blocks/about-type-stack/index.ts +6 -0
- package/registry/blocks/about-values-connector-list/AboutValuesConnectorList.tsx +316 -0
- package/registry/blocks/about-values-connector-list/index.ts +7 -0
- package/registry/blocks/about-values-focus/AboutValuesFocus.tsx +303 -0
- package/registry/blocks/about-values-focus/index.ts +8 -0
- package/registry/blocks/about-values-spotlight/AboutValuesSpotlight.tsx +265 -0
- package/registry/blocks/about-values-spotlight/index.ts +8 -0
- package/registry/blocks/auth-centered-card-sign-in/AuthCenteredCardSignIn.tsx +373 -0
- package/registry/blocks/auth-centered-card-sign-in/index.ts +9 -0
- package/registry/blocks/auth-check-your-email/AuthCheckYourEmail.tsx +366 -0
- package/registry/blocks/auth-check-your-email/index.ts +7 -0
- package/registry/blocks/auth-forgot-password/AuthForgotPassword.tsx +265 -0
- package/registry/blocks/auth-forgot-password/index.ts +8 -0
- package/registry/blocks/auth-locked-session-re-authenticate/AuthLockedSessionReAuthenticate.tsx +305 -0
- package/registry/blocks/auth-locked-session-re-authenticate/index.ts +8 -0
- package/registry/blocks/auth-magic-link-request/AuthMagicLinkRequest.tsx +336 -0
- package/registry/blocks/auth-magic-link-request/index.ts +8 -0
- package/registry/blocks/auth-minimal-centered-sign-in/AuthMinimalCenteredSignIn.tsx +261 -0
- package/registry/blocks/auth-minimal-centered-sign-in/index.ts +8 -0
- package/registry/blocks/auth-multi-step-sign-up-wizard/AuthMultiStepSignUpWizard.tsx +460 -0
- package/registry/blocks/auth-multi-step-sign-up-wizard/index.ts +8 -0
- package/registry/blocks/auth-otp-verification/AuthOtpVerification.tsx +348 -0
- package/registry/blocks/auth-otp-verification/index.ts +7 -0
- package/registry/blocks/auth-passkey-sign-in/AuthPasskeySignIn.tsx +294 -0
- package/registry/blocks/auth-passkey-sign-in/index.ts +8 -0
- package/registry/blocks/auth-password-changed/AuthPasswordChanged.tsx +239 -0
- package/registry/blocks/auth-password-changed/index.ts +8 -0
- package/registry/blocks/auth-phone-number-sign-in/AuthPhoneNumberSignIn.tsx +402 -0
- package/registry/blocks/auth-phone-number-sign-in/index.ts +10 -0
- package/registry/blocks/auth-photo-background-sign-in/AuthPhotoBackgroundSignIn.tsx +398 -0
- package/registry/blocks/auth-photo-background-sign-in/index.ts +10 -0
- package/registry/blocks/auth-profile-setup-onboarding/AuthProfileSetupOnboarding.tsx +420 -0
- package/registry/blocks/auth-profile-setup-onboarding/index.ts +7 -0
- package/registry/blocks/auth-qr-device-login/AuthQrDeviceLogin.tsx +303 -0
- package/registry/blocks/auth-qr-device-login/index.ts +7 -0
- package/registry/blocks/auth-reset-password/AuthResetPassword.tsx +313 -0
- package/registry/blocks/auth-reset-password/index.ts +7 -0
- package/registry/blocks/auth-sign-in-error-state/AuthSignInErrorState.tsx +419 -0
- package/registry/blocks/auth-sign-in-error-state/index.ts +9 -0
- package/registry/blocks/auth-sign-up-benefits-column/AuthSignUpBenefitsColumn.tsx +481 -0
- package/registry/blocks/auth-sign-up-benefits-column/index.ts +10 -0
- package/registry/blocks/auth-sign-up-password-strength/AuthSignUpPasswordStrength.tsx +511 -0
- package/registry/blocks/auth-sign-up-password-strength/index.ts +8 -0
- package/registry/blocks/auth-social-first-sign-in/AuthSocialFirstSignIn.tsx +337 -0
- package/registry/blocks/auth-social-first-sign-in/index.ts +9 -0
- package/registry/blocks/auth-split-photo-sign-in/AuthSplitPhotoSignIn.tsx +472 -0
- package/registry/blocks/auth-split-photo-sign-in/index.ts +11 -0
- package/registry/blocks/auth-split-testimonial-sign-up/AuthSplitTestimonialSignUp.tsx +548 -0
- package/registry/blocks/auth-split-testimonial-sign-up/index.ts +11 -0
- package/registry/blocks/auth-tabbed-sign-in-sign-up/AuthTabbedSignInSignUp.tsx +566 -0
- package/registry/blocks/auth-tabbed-sign-in-sign-up/index.ts +11 -0
- package/registry/blocks/auth-team-invite-accept/AuthTeamInviteAccept.tsx +372 -0
- package/registry/blocks/auth-team-invite-accept/index.ts +9 -0
- package/registry/blocks/auth-two-factor-authenticator/AuthTwoFactorAuthenticator.tsx +333 -0
- package/registry/blocks/auth-two-factor-authenticator/index.ts +7 -0
- package/registry/blocks/auth-two-factor-method-chooser/AuthTwoFactorMethodChooser.tsx +290 -0
- package/registry/blocks/auth-two-factor-method-chooser/index.ts +8 -0
- package/registry/blocks/auth-waitlist-early-access/AuthWaitlistEarlyAccess.tsx +430 -0
- package/registry/blocks/auth-waitlist-early-access/index.ts +11 -0
- package/registry/blocks/blog-cards-alongside.test.tsx +176 -0
- package/registry/blocks/blog-cards-author-spotlight/BlogCardsAuthorSpotlight.tsx +342 -0
- package/registry/blocks/blog-cards-author-spotlight/index.ts +6 -0
- package/registry/blocks/blog-cards-bento-editorial-grid/BlogCardsBentoEditorialGrid.tsx +345 -0
- package/registry/blocks/blog-cards-bento-editorial-grid/index.ts +7 -0
- package/registry/blocks/blog-cards-browse.test.tsx +367 -0
- package/registry/blocks/blog-cards-classic-3-up-grid/BlogCardsClassic3UpGrid.tsx +287 -0
- package/registry/blocks/blog-cards-classic-3-up-grid/index.ts +7 -0
- package/registry/blocks/blog-cards-compact-4-up-thumb-grid/BlogCardsCompact4UpThumbGrid.tsx +265 -0
- package/registry/blocks/blog-cards-compact-4-up-thumb-grid/index.ts +7 -0
- package/registry/blocks/blog-cards-dark-editorial-feature/BlogCardsDarkEditorialFeature.tsx +287 -0
- package/registry/blocks/blog-cards-dark-editorial-feature/index.ts +6 -0
- package/registry/blocks/blog-cards-featured-two-up/BlogCardsFeaturedTwoUp.tsx +295 -0
- package/registry/blocks/blog-cards-featured-two-up/index.ts +7 -0
- package/registry/blocks/blog-cards-features.test.tsx +183 -0
- package/registry/blocks/blog-cards-filter-sidebar-results/BlogCardsFilterSidebarResults.tsx +582 -0
- package/registry/blocks/blog-cards-filter-sidebar-results/index.ts +6 -0
- package/registry/blocks/blog-cards-grids.test.tsx +110 -0
- package/registry/blocks/blog-cards-hairline-text-index/BlogCardsHairlineTextIndex.tsx +193 -0
- package/registry/blocks/blog-cards-hairline-text-index/index.ts +7 -0
- package/registry/blocks/blog-cards-journal-bento/BlogCardsJournalBento.tsx +409 -0
- package/registry/blocks/blog-cards-journal-bento/index.ts +8 -0
- package/registry/blocks/blog-cards-latest-popular-split/BlogCardsLatestPopularSplit.tsx +258 -0
- package/registry/blocks/blog-cards-latest-popular-split/index.ts +7 -0
- package/registry/blocks/blog-cards-lead-ranked-picks/BlogCardsLeadRankedPicks.tsx +276 -0
- package/registry/blocks/blog-cards-lead-ranked-picks/index.ts +6 -0
- package/registry/blocks/blog-cards-left-thumb-row-cards/BlogCardsLeftThumbRowCards.tsx +288 -0
- package/registry/blocks/blog-cards-left-thumb-row-cards/index.ts +7 -0
- package/registry/blocks/blog-cards-lists.test.tsx +93 -0
- package/registry/blocks/blog-cards-magazine-cover-story/BlogCardsMagazineCoverStory.tsx +326 -0
- package/registry/blocks/blog-cards-magazine-cover-story/index.ts +6 -0
- package/registry/blocks/blog-cards-newsletter-latest/BlogCardsNewsletterLatest.tsx +291 -0
- package/registry/blocks/blog-cards-newsletter-latest/index.ts +6 -0
- package/registry/blocks/blog-cards-numbered-reading-list/BlogCardsNumberedReadingList.tsx +275 -0
- package/registry/blocks/blog-cards-numbered-reading-list/index.ts +7 -0
- package/registry/blocks/blog-cards-overlay-photo-trio/BlogCardsOverlayPhotoTrio.tsx +279 -0
- package/registry/blocks/blog-cards-overlay-photo-trio/index.ts +7 -0
- package/registry/blocks/blog-cards-post-carousel-with-controls/BlogCardsPostCarouselWithControls.tsx +257 -0
- package/registry/blocks/blog-cards-post-carousel-with-controls/index.ts +6 -0
- package/registry/blocks/blog-cards-pull-quote-feature/BlogCardsPullQuoteFeature.tsx +245 -0
- package/registry/blocks/blog-cards-pull-quote-feature/index.ts +6 -0
- package/registry/blocks/blog-cards-related-posts-strip/BlogCardsRelatedPostsStrip.tsx +247 -0
- package/registry/blocks/blog-cards-related-posts-strip/index.ts +6 -0
- package/registry/blocks/blog-cards-search-chips-results/BlogCardsSearchChipsResults.tsx +473 -0
- package/registry/blocks/blog-cards-search-chips-results/index.ts +6 -0
- package/registry/blocks/blog-cards-split-feature/BlogCardsSplitFeature.tsx +214 -0
- package/registry/blocks/blog-cards-split-feature/index.ts +6 -0
- package/registry/blocks/careers/careers.test.tsx +414 -0
- package/registry/blocks/careers/careers.tsx +391 -0
- package/registry/blocks/careers/index.ts +27 -0
- package/registry/blocks/careers-about-card-role-list/CareersAboutCardRoleList.tsx +235 -0
- package/registry/blocks/careers-about-card-role-list/index.ts +8 -0
- package/registry/blocks/careers-featured-role-spotlight/CareersFeaturedRoleSpotlight.tsx +271 -0
- package/registry/blocks/careers-featured-role-spotlight/index.ts +8 -0
- package/registry/blocks/careers-no-open-roles-empty-state/CareersNoOpenRolesEmptyState.tsx +330 -0
- package/registry/blocks/careers-no-open-roles-empty-state/index.ts +8 -0
- package/registry/blocks/careers-open-roles-hairline-list/CareersOpenRolesHairlineList.tsx +320 -0
- package/registry/blocks/careers-open-roles-hairline-list/index.ts +6 -0
- package/registry/blocks/careers-open-vacancies-table/CareersOpenVacanciesTable.tsx +493 -0
- package/registry/blocks/careers-open-vacancies-table/index.ts +6 -0
- package/registry/blocks/careers-perks-benefits/CareersPerksBenefits.tsx +181 -0
- package/registry/blocks/careers-perks-benefits/index.ts +8 -0
- package/registry/blocks/careers-role-cards-grid/CareersRoleCardsGrid.tsx +405 -0
- package/registry/blocks/careers-role-cards-grid/index.ts +6 -0
- package/registry/blocks/careers-role-detail-page/CareersRoleDetailPage.tsx +541 -0
- package/registry/blocks/careers-role-detail-page/index.ts +10 -0
- package/registry/blocks/careers-role-detail-panel/CareersRoleDetailPanel.tsx +255 -0
- package/registry/blocks/careers-role-detail-panel/index.ts +6 -0
- package/registry/blocks/careers-roles-by-department/CareersRolesByDepartment.tsx +242 -0
- package/registry/blocks/careers-roles-by-department/index.ts +7 -0
- package/registry/blocks/careers-roles-list-accordion-expanded/CareersRolesListAccordionExpanded.tsx +558 -0
- package/registry/blocks/careers-roles-list-accordion-expanded/index.ts +9 -0
- package/registry/blocks/contact-us/contact-us.test.tsx +239 -0
- package/registry/blocks/contact-us/contact-us.tsx +989 -0
- package/registry/blocks/contact-us/index.ts +34 -0
- package/registry/blocks/contact-us-bento-grid/ContactUsBentoGrid.tsx +304 -0
- package/registry/blocks/contact-us-bento-grid/index.ts +5 -0
- package/registry/blocks/contact-us-bento-grid.test.tsx +102 -0
- package/registry/blocks/contact-us-book-a-call/ContactUsBookACall.tsx +403 -0
- package/registry/blocks/contact-us-book-a-call/index.ts +7 -0
- package/registry/blocks/contact-us-book-a-call.test.tsx +134 -0
- package/registry/blocks/contact-us-classic-centered/ContactUsClassicCentered.tsx +199 -0
- package/registry/blocks/contact-us-classic-centered/index.ts +6 -0
- package/registry/blocks/contact-us-dark-statement/ContactUsDarkStatement.tsx +218 -0
- package/registry/blocks/contact-us-dark-statement/index.ts +6 -0
- package/registry/blocks/contact-us-editorial-and-address-card/ContactUsEditorialAndAddressCard.tsx +212 -0
- package/registry/blocks/contact-us-editorial-and-address-card/index.ts +6 -0
- package/registry/blocks/contact-us-editorial-statement/ContactUsEditorialStatement.tsx +205 -0
- package/registry/blocks/contact-us-editorial-statement/index.ts +6 -0
- package/registry/blocks/contact-us-form-contact-methods/ContactUsFormContactMethods.tsx +272 -0
- package/registry/blocks/contact-us-form-contact-methods/index.ts +6 -0
- package/registry/blocks/contact-us-form-with-faq/ContactUsFormWithFaq.tsx +383 -0
- package/registry/blocks/contact-us-form-with-faq/index.ts +7 -0
- package/registry/blocks/contact-us-form-with-faq.test.tsx +161 -0
- package/registry/blocks/contact-us-forms.test.tsx +333 -0
- package/registry/blocks/contact-us-global-offices-table/ContactUsGlobalOfficesTable.tsx +301 -0
- package/registry/blocks/contact-us-global-offices-table/index.ts +5 -0
- package/registry/blocks/contact-us-global-offices-table.test.tsx +100 -0
- package/registry/blocks/contact-us-grand-headline-form/ContactUsGrandHeadlineForm.tsx +180 -0
- package/registry/blocks/contact-us-grand-headline-form/index.ts +6 -0
- package/registry/blocks/contact-us-hours-and-availability/ContactUsHoursAndAvailability.tsx +412 -0
- package/registry/blocks/contact-us-hours-and-availability/index.ts +5 -0
- package/registry/blocks/contact-us-hours-and-availability.test.tsx +123 -0
- package/registry/blocks/contact-us-hq-feature/ContactUsHqFeature.tsx +281 -0
- package/registry/blocks/contact-us-hq-feature/index.ts +5 -0
- package/registry/blocks/contact-us-hq-feature.test.tsx +102 -0
- package/registry/blocks/contact-us-map-split/ContactUsMapSplit.tsx +211 -0
- package/registry/blocks/contact-us-map-split/index.ts +5 -0
- package/registry/blocks/contact-us-map-split.test.tsx +70 -0
- package/registry/blocks/contact-us-named-owners/ContactUsNamedOwners.tsx +324 -0
- package/registry/blocks/contact-us-named-owners/index.ts +6 -0
- package/registry/blocks/contact-us-named-owners.test.tsx +116 -0
- package/registry/blocks/contact-us-newsletter-combo/ContactUsNewsletterCombo.tsx +363 -0
- package/registry/blocks/contact-us-newsletter-combo/index.ts +6 -0
- package/registry/blocks/contact-us-newsletter-combo.test.tsx +165 -0
- package/registry/blocks/contact-us-office-cards/ContactUsOfficeCards.tsx +235 -0
- package/registry/blocks/contact-us-office-cards/index.ts +5 -0
- package/registry/blocks/contact-us-office-cards.test.tsx +71 -0
- package/registry/blocks/contact-us-oversized-wordmark/ContactUsOversizedWordmark.tsx +222 -0
- package/registry/blocks/contact-us-oversized-wordmark/index.ts +6 -0
- package/registry/blocks/contact-us-photo-and-dark-panel/ContactUsPhotoAndDarkPanel.tsx +185 -0
- package/registry/blocks/contact-us-photo-and-dark-panel/index.ts +6 -0
- package/registry/blocks/contact-us-photo-forms.test.tsx +311 -0
- package/registry/blocks/contact-us-pitch-and-inquiry/ContactUsPitchAndInquiry.tsx +202 -0
- package/registry/blocks/contact-us-pitch-and-inquiry/index.ts +6 -0
- package/registry/blocks/contact-us-rtl.test.tsx +105 -0
- package/registry/blocks/contact-us-ruled-split-form/ContactUsRuledSplitForm.tsx +214 -0
- package/registry/blocks/contact-us-ruled-split-form/index.ts +6 -0
- package/registry/blocks/contact-us-social-rail/ContactUsSocialRail.tsx +270 -0
- package/registry/blocks/contact-us-social-rail/index.ts +7 -0
- package/registry/blocks/contact-us-social-rail.test.tsx +121 -0
- package/registry/blocks/contact-us-split-photo-dark/ContactUsSplitPhotoDark.tsx +185 -0
- package/registry/blocks/contact-us-split-photo-dark/index.ts +6 -0
- package/registry/blocks/contact-us-support-channels/ContactUsSupportChannels.tsx +267 -0
- package/registry/blocks/contact-us-support-channels/index.ts +5 -0
- package/registry/blocks/contact-us-support-channels.test.tsx +81 -0
- package/registry/blocks/data-table/DataTable.test.tsx +1 -34
- package/registry/blocks/data-table/DataTable.tsx +0 -139
- package/registry/blocks/data-table/index.ts +1 -8
- package/registry/blocks/error-state-access-denied-403/ErrorStateAccessDenied403.tsx +324 -0
- package/registry/blocks/error-state-access-denied-403/index.ts +10 -0
- package/registry/blocks/error-state-broken-trail-breadcrumb/ErrorStateBrokenTrailBreadcrumb.tsx +179 -0
- package/registry/blocks/error-state-broken-trail-breadcrumb/index.ts +8 -0
- package/registry/blocks/error-state-centered-minimal/ErrorStateCenteredMinimal.tsx +119 -0
- package/registry/blocks/error-state-centered-minimal/index.ts +7 -0
- package/registry/blocks/error-state-colour-field-scatter/ErrorStateColourFieldScatter.tsx +144 -0
- package/registry/blocks/error-state-colour-field-scatter/index.ts +7 -0
- package/registry/blocks/error-state-countdown-redirect/ErrorStateCountdownRedirect.tsx +272 -0
- package/registry/blocks/error-state-countdown-redirect/index.ts +7 -0
- package/registry/blocks/error-state-dark-spotlight/ErrorStateDarkSpotlight.tsx +156 -0
- package/registry/blocks/error-state-dark-spotlight/index.ts +7 -0
- package/registry/blocks/error-state-display-numeral-split/ErrorStateDisplayNumeralSplit.tsx +156 -0
- package/registry/blocks/error-state-display-numeral-split/index.ts +8 -0
- package/registry/blocks/error-state-editorial-photo-split/ErrorStateEditorialPhotoSplit.tsx +156 -0
- package/registry/blocks/error-state-editorial-photo-split/index.ts +8 -0
- package/registry/blocks/error-state-empty-search-results/ErrorStateEmptySearchResults.tsx +243 -0
- package/registry/blocks/error-state-empty-search-results/index.ts +6 -0
- package/registry/blocks/error-state-error-card-panel/ErrorStateErrorCardPanel.tsx +213 -0
- package/registry/blocks/error-state-error-card-panel/index.ts +7 -0
- package/registry/blocks/error-state-full-bleed-photo/ErrorStateFullBleedPhoto.tsx +130 -0
- package/registry/blocks/error-state-full-bleed-photo/index.ts +8 -0
- package/registry/blocks/error-state-ghost-watermark/ErrorStateGhostWatermark.tsx +115 -0
- package/registry/blocks/error-state-ghost-watermark/index.ts +7 -0
- package/registry/blocks/error-state-helpful-links-list/ErrorStateHelpfulLinksList.tsx +185 -0
- package/registry/blocks/error-state-helpful-links-list/index.ts +8 -0
- package/registry/blocks/error-state-illustrated-empty-state/ErrorStateIllustratedEmptyState.tsx +201 -0
- package/registry/blocks/error-state-illustrated-empty-state/index.ts +7 -0
- package/registry/blocks/error-state-layered-badge-centered/ErrorStateLayeredBadgeCentered.tsx +138 -0
- package/registry/blocks/error-state-layered-badge-centered/index.ts +7 -0
- package/registry/blocks/error-state-light-editorial/ErrorStateLightEditorial.tsx +126 -0
- package/registry/blocks/error-state-light-editorial/index.ts +7 -0
- package/registry/blocks/error-state-maintenance-mode/ErrorStateMaintenanceMode.tsx +255 -0
- package/registry/blocks/error-state-maintenance-mode/index.ts +6 -0
- package/registry/blocks/error-state-marquee-404-band/ErrorStateMarquee404Band.tsx +316 -0
- package/registry/blocks/error-state-marquee-404-band/index.ts +7 -0
- package/registry/blocks/error-state-offline-state/ErrorStateOfflineState.tsx +188 -0
- package/registry/blocks/error-state-offline-state/index.ts +7 -0
- package/registry/blocks/error-state-photo-collage-scatter/ErrorStatePhotoCollageScatter.tsx +224 -0
- package/registry/blocks/error-state-photo-collage-scatter/index.ts +8 -0
- package/registry/blocks/error-state-popular-pages-grid/ErrorStatePopularPagesGrid.tsx +195 -0
- package/registry/blocks/error-state-popular-pages-grid/index.ts +9 -0
- package/registry/blocks/error-state-recovery-bento/ErrorStateRecoveryBento.tsx +274 -0
- package/registry/blocks/error-state-recovery-bento/index.ts +7 -0
- package/registry/blocks/error-state-search-recovery/ErrorStateSearchRecovery.tsx +173 -0
- package/registry/blocks/error-state-search-recovery/index.ts +7 -0
- package/registry/blocks/error-state-server-error-500/ErrorStateServerError500.tsx +277 -0
- package/registry/blocks/error-state-server-error-500/index.ts +9 -0
- package/registry/blocks/error-state-sitemap-columns/ErrorStateSitemapColumns.tsx +192 -0
- package/registry/blocks/error-state-sitemap-columns/index.ts +9 -0
- package/registry/blocks/error-state-split-colour-panel/ErrorStateSplitColourPanel.tsx +156 -0
- package/registry/blocks/error-state-split-colour-panel/index.ts +7 -0
- package/registry/blocks/error-state-support-escalation-band/ErrorStateSupportEscalationBand.tsx +186 -0
- package/registry/blocks/error-state-support-escalation-band/index.ts +8 -0
- package/registry/blocks/error-state-terminal-console/ErrorStateTerminalConsole.tsx +208 -0
- package/registry/blocks/error-state-terminal-console/index.ts +8 -0
- package/registry/blocks/faq-accent-active-card/FaqAccentActiveCard.tsx +186 -0
- package/registry/blocks/faq-accent-active-card/index.ts +1 -0
- package/registry/blocks/faq-category-chip-grid/FaqCategoryChipGrid.tsx +425 -0
- package/registry/blocks/faq-category-chip-grid/index.ts +8 -0
- package/registry/blocks/faq-category-columns/FaqCategoryColumns.tsx +248 -0
- package/registry/blocks/faq-category-columns/index.ts +8 -0
- package/registry/blocks/faq-chat-thread/FaqChatThread.tsx +273 -0
- package/registry/blocks/faq-chat-thread/index.ts +7 -0
- package/registry/blocks/faq-classic-centered/FaqClassicCentered.tsx +190 -0
- package/registry/blocks/faq-classic-centered/index.ts +7 -0
- package/registry/blocks/faq-contact-cta-card/FaqContactCtaCard.tsx +229 -0
- package/registry/blocks/faq-contact-cta-card/index.ts +9 -0
- package/registry/blocks/faq-dark-panel/FaqDarkPanel.tsx +186 -0
- package/registry/blocks/faq-dark-panel/index.ts +1 -0
- package/registry/blocks/faq-featured-question/FaqFeaturedQuestion.tsx +221 -0
- package/registry/blocks/faq-featured-question/index.ts +1 -0
- package/registry/blocks/faq-grouped-sections/FaqGroupedSections.tsx +266 -0
- package/registry/blocks/faq-grouped-sections/index.ts +8 -0
- package/registry/blocks/faq-help-hero-popular/FaqHelpHeroPopular.tsx +212 -0
- package/registry/blocks/faq-help-hero-popular/index.ts +7 -0
- package/registry/blocks/faq-masonry-cards/FaqMasonryCards.tsx +268 -0
- package/registry/blocks/faq-masonry-cards/index.ts +7 -0
- package/registry/blocks/faq-media-split/FaqMediaSplit.tsx +197 -0
- package/registry/blocks/faq-media-split/index.ts +8 -0
- package/registry/blocks/faq-mono-index/FaqMonoIndex.tsx +247 -0
- package/registry/blocks/faq-mono-index/index.ts +1 -0
- package/registry/blocks/faq-newsletter-close/FaqNewsletterClose.tsx +314 -0
- package/registry/blocks/faq-newsletter-close/index.ts +8 -0
- package/registry/blocks/faq-numbered-editorial/FaqNumberedEditorial.tsx +190 -0
- package/registry/blocks/faq-numbered-editorial/index.ts +1 -0
- package/registry/blocks/faq-search-categories/FaqSearchCategories.tsx +312 -0
- package/registry/blocks/faq-search-categories/index.ts +8 -0
- package/registry/blocks/faq-sidebar-nav/FaqSidebarNav.tsx +390 -0
- package/registry/blocks/faq-sidebar-nav/index.ts +8 -0
- package/registry/blocks/faq-soft-filled-rows/FaqSoftFilledRows.tsx +209 -0
- package/registry/blocks/faq-soft-filled-rows/index.ts +1 -0
- package/registry/blocks/faq-split-sticky/FaqSplitSticky.tsx +205 -0
- package/registry/blocks/faq-split-sticky/index.ts +7 -0
- package/registry/blocks/faq-support-channels/FaqSupportChannels.tsx +266 -0
- package/registry/blocks/faq-support-channels/index.ts +8 -0
- package/registry/blocks/faq-support-stats/FaqSupportStats.tsx +242 -0
- package/registry/blocks/faq-support-stats/index.ts +8 -0
- package/registry/blocks/faq-table-compact/FaqTableCompact.tsx +220 -0
- package/registry/blocks/faq-table-compact/index.ts +1 -0
- package/registry/blocks/faq-tabs-by-topic/FaqTabsByTopic.tsx +347 -0
- package/registry/blocks/faq-tabs-by-topic/index.ts +8 -0
- package/registry/blocks/faq-topic-tiles/FaqTopicTiles.tsx +254 -0
- package/registry/blocks/faq-topic-tiles/index.ts +7 -0
- package/registry/blocks/faq-two-column-grid/FaqTwoColumnGrid.tsx +256 -0
- package/registry/blocks/faq-two-column-grid/index.ts +7 -0
- package/registry/blocks/faq-uppercase-dense/FaqUppercaseDense.tsx +213 -0
- package/registry/blocks/faq-uppercase-dense/index.ts +1 -0
- package/registry/blocks/features/features.tsx +164 -0
- package/registry/blocks/features/index.ts +12 -0
- package/registry/blocks/features-accordion-photo/FeaturesAccordionPhoto.tsx +149 -0
- package/registry/blocks/features-accordion-photo/index.ts +7 -0
- package/registry/blocks/features-colour-block-cards/FeaturesColourBlockCards.tsx +118 -0
- package/registry/blocks/features-colour-block-cards/index.ts +8 -0
- package/registry/blocks/features-compact-icon-index/FeaturesCompactIconIndex.tsx +136 -0
- package/registry/blocks/features-compact-icon-index/index.ts +7 -0
- package/registry/blocks/features-compare-panels-chat-answer/FeaturesComparePanelsChatAnswer.tsx +346 -0
- package/registry/blocks/features-compare-panels-chat-answer/index.ts +8 -0
- package/registry/blocks/features-editorial-photo-split/FeaturesEditorialPhotoSplit.tsx +118 -0
- package/registry/blocks/features-editorial-photo-split/index.ts +7 -0
- package/registry/blocks/features-expanding-step-cards/FeaturesExpandingStepCards.tsx +300 -0
- package/registry/blocks/features-expanding-step-cards/index.ts +7 -0
- package/registry/blocks/features-feature-checklist-columns/FeaturesFeatureChecklistColumns.tsx +158 -0
- package/registry/blocks/features-feature-checklist-columns/index.ts +7 -0
- package/registry/blocks/features-feature-list-thumbnails/FeaturesFeatureListThumbnails.tsx +137 -0
- package/registry/blocks/features-feature-list-thumbnails/index.ts +7 -0
- package/registry/blocks/features-full-bleed-photo-banner/FeaturesFullBleedPhotoBanner.tsx +116 -0
- package/registry/blocks/features-full-bleed-photo-banner/index.ts +7 -0
- package/registry/blocks/features-icon-card-grid/FeaturesIconCardGrid.tsx +120 -0
- package/registry/blocks/features-icon-card-grid/index.ts +7 -0
- package/registry/blocks/features-icon-titled-preview-rail/FeaturesIconTitledPreviewRail.tsx +321 -0
- package/registry/blocks/features-icon-titled-preview-rail/index.ts +7 -0
- package/registry/blocks/features-icons-numbers.test.tsx +107 -0
- package/registry/blocks/features-interactive.test.tsx +175 -0
- package/registry/blocks/features-media-rail/FeaturesMediaRail.tsx +119 -0
- package/registry/blocks/features-media-rail/index.ts +7 -0
- package/registry/blocks/features-notched-photo-columns/FeaturesNotchedPhotoColumns.tsx +209 -0
- package/registry/blocks/features-notched-photo-columns/index.ts +7 -0
- package/registry/blocks/features-numbered-feature-rows/FeaturesNumberedFeatureRows.tsx +107 -0
- package/registry/blocks/features-numbered-feature-rows/index.ts +7 -0
- package/registry/blocks/features-numbered-steps-floating-cards/FeaturesNumberedStepsFloatingCards.tsx +324 -0
- package/registry/blocks/features-numbered-steps-floating-cards/index.ts +8 -0
- package/registry/blocks/features-perforated-photo-strip/FeaturesPerforatedPhotoStrip.tsx +218 -0
- package/registry/blocks/features-perforated-photo-strip/index.ts +7 -0
- package/registry/blocks/features-photo-bento/FeaturesPhotoBento.tsx +160 -0
- package/registry/blocks/features-photo-bento/index.ts +8 -0
- package/registry/blocks/features-photo-carousel/FeaturesPhotoCarousel.tsx +249 -0
- package/registry/blocks/features-photo-carousel/index.ts +7 -0
- package/registry/blocks/features-photo-collage/FeaturesPhotoCollage.tsx +143 -0
- package/registry/blocks/features-photo-collage/index.ts +7 -0
- package/registry/blocks/features-photo-feature-chips/FeaturesPhotoFeatureChips.tsx +125 -0
- package/registry/blocks/features-photo-feature-chips/index.ts +7 -0
- package/registry/blocks/features-photo-led.test.tsx +177 -0
- package/registry/blocks/features-photo-tabs-panel/FeaturesPhotoTabsPanel.tsx +197 -0
- package/registry/blocks/features-photo-tabs-panel/index.ts +7 -0
- package/registry/blocks/features-portrait-photo-cards-link-row/FeaturesPortraitPhotoCardsLinkRow.tsx +202 -0
- package/registry/blocks/features-portrait-photo-cards-link-row/index.ts +7 -0
- package/registry/blocks/features-product-in-use.test.tsx +188 -0
- package/registry/blocks/features-showcase-card-pair/FeaturesShowcaseCardPair.tsx +161 -0
- package/registry/blocks/features-showcase-card-pair/index.ts +7 -0
- package/registry/blocks/features-solid-colour-cards/FeaturesSolidColourCards.tsx +264 -0
- package/registry/blocks/features-solid-colour-cards/index.ts +7 -0
- package/registry/blocks/features-split-photo-icon-list/FeaturesSplitPhotoIconList.tsx +117 -0
- package/registry/blocks/features-split-photo-icon-list/index.ts +7 -0
- package/registry/blocks/features-stat-feature-band/FeaturesStatFeatureBand.tsx +114 -0
- package/registry/blocks/features-stat-feature-band/index.ts +7 -0
- package/registry/blocks/features-tinted-product-bento/FeaturesTintedProductBento.tsx +252 -0
- package/registry/blocks/features-tinted-product-bento/index.ts +7 -0
- package/registry/blocks/footer/footer.test.tsx +253 -0
- package/registry/blocks/footer/footer.tsx +554 -0
- package/registry/blocks/footer/index.ts +31 -0
- package/registry/blocks/footer-accordion-sitemap/FooterAccordionSitemap.tsx +260 -0
- package/registry/blocks/footer-accordion-sitemap/index.ts +7 -0
- package/registry/blocks/footer-bars.test.tsx +121 -0
- package/registry/blocks/footer-bento-tile/FooterBentoTile.tsx +331 -0
- package/registry/blocks/footer-bento-tile/index.ts +6 -0
- package/registry/blocks/footer-big-question-cta/FooterBigQuestionCta.tsx +250 -0
- package/registry/blocks/footer-big-question-cta/index.ts +6 -0
- package/registry/blocks/footer-big-type-link-stack/FooterBigTypeLinkStack.tsx +275 -0
- package/registry/blocks/footer-big-type-link-stack/index.ts +6 -0
- package/registry/blocks/footer-brand-panel-split/FooterBrandPanelSplit.tsx +163 -0
- package/registry/blocks/footer-brand-panel-split/index.ts +6 -0
- package/registry/blocks/footer-brand.test.tsx +608 -0
- package/registry/blocks/footer-careers-culture/FooterCareersCulture.tsx +297 -0
- package/registry/blocks/footer-careers-culture/index.ts +7 -0
- package/registry/blocks/footer-centered-brand-minimal/FooterCenteredBrandMinimal.tsx +121 -0
- package/registry/blocks/footer-centered-brand-minimal/index.ts +6 -0
- package/registry/blocks/footer-commerce.test.tsx +208 -0
- package/registry/blocks/footer-community-events/FooterCommunityEvents.tsx +263 -0
- package/registry/blocks/footer-community-events/index.ts +8 -0
- package/registry/blocks/footer-community.test.tsx +309 -0
- package/registry/blocks/footer-contact-first/FooterContactFirst.tsx +242 -0
- package/registry/blocks/footer-contact-first/index.ts +7 -0
- package/registry/blocks/footer-contact.test.tsx +285 -0
- package/registry/blocks/footer-controls.test.tsx +78 -0
- package/registry/blocks/footer-cta-band-columns/FooterCtaBandColumns.tsx +225 -0
- package/registry/blocks/footer-cta-band-columns/index.ts +6 -0
- package/registry/blocks/footer-cta.test.tsx +445 -0
- package/registry/blocks/footer-dashboard-slim-bar/FooterDashboardSlimBar.tsx +148 -0
- package/registry/blocks/footer-dashboard-slim-bar/index.ts +6 -0
- package/registry/blocks/footer-docs-page/FooterDocsPage.tsx +261 -0
- package/registry/blocks/footer-docs-page/index.ts +9 -0
- package/registry/blocks/footer-ecommerce-mega/FooterEcommerceMega.tsx +289 -0
- package/registry/blocks/footer-ecommerce-mega/index.ts +6 -0
- package/registry/blocks/footer-floating-demo-card/FooterFloatingDemoCard.tsx +261 -0
- package/registry/blocks/footer-floating-demo-card/index.ts +6 -0
- package/registry/blocks/footer-four-column-directory/FooterFourColumnDirectory.tsx +104 -0
- package/registry/blocks/footer-four-column-directory/index.ts +6 -0
- package/registry/blocks/footer-giant-wordmark/FooterGiantWordmark.tsx +252 -0
- package/registry/blocks/footer-giant-wordmark/index.ts +6 -0
- package/registry/blocks/footer-image-backed-brand/FooterImageBackedBrand.tsx +251 -0
- package/registry/blocks/footer-image-backed-brand/index.ts +6 -0
- package/registry/blocks/footer-index-numbered-columns/FooterIndexNumberedColumns.tsx +236 -0
- package/registry/blocks/footer-index-numbered-columns/index.ts +7 -0
- package/registry/blocks/footer-inline-preferences/FooterInlinePreferences.tsx +164 -0
- package/registry/blocks/footer-inline-preferences/index.ts +7 -0
- package/registry/blocks/footer-legal-first-slim/FooterLegalFirstSlim.tsx +174 -0
- package/registry/blocks/footer-legal-first-slim/index.ts +6 -0
- package/registry/blocks/footer-legal.test.tsx +180 -0
- package/registry/blocks/footer-local-business/FooterLocalBusiness.tsx +258 -0
- package/registry/blocks/footer-local-business/index.ts +7 -0
- package/registry/blocks/footer-marquee-strip/FooterMarqueeStrip.tsx +244 -0
- package/registry/blocks/footer-marquee-strip/index.ts +6 -0
- package/registry/blocks/footer-multi-region/FooterMultiRegion.tsx +310 -0
- package/registry/blocks/footer-multi-region/index.ts +7 -0
- package/registry/blocks/footer-newsletter-split/FooterNewsletterSplit.tsx +255 -0
- package/registry/blocks/footer-newsletter-split/index.ts +6 -0
- package/registry/blocks/footer-product-family-switcher/FooterProductFamilySwitcher.tsx +241 -0
- package/registry/blocks/footer-product-family-switcher/index.ts +7 -0
- package/registry/blocks/footer-service-promise-band/FooterServicePromiseBand.tsx +237 -0
- package/registry/blocks/footer-service-promise-band/index.ts +7 -0
- package/registry/blocks/footer-single-line-bar/FooterSingleLineBar.tsx +107 -0
- package/registry/blocks/footer-single-line-bar/index.ts +6 -0
- package/registry/blocks/footer-sitemap.test.tsx +390 -0
- package/registry/blocks/footer-split-hairline-micro/FooterSplitHairlineMicro.tsx +100 -0
- package/registry/blocks/footer-split-hairline-micro/index.ts +6 -0
- package/registry/blocks/footer-support-contact/FooterSupportContact.tsx +204 -0
- package/registry/blocks/footer-support-contact/index.ts +7 -0
- package/registry/blocks/footer-trust-compliance/FooterTrustCompliance.tsx +234 -0
- package/registry/blocks/footer-trust-compliance/index.ts +7 -0
- package/registry/blocks/footer-two-row-compact/FooterTwoRowCompact.tsx +124 -0
- package/registry/blocks/footer-two-row-compact/index.ts +6 -0
- package/registry/blocks/footer-two-tier-doormat/FooterTwoTierDoormat.tsx +232 -0
- package/registry/blocks/footer-two-tier-doormat/index.ts +8 -0
- package/registry/blocks/footer-utility.test.tsx +406 -0
- package/registry/blocks/footer-wide-sitemap-mega/FooterWideSitemapMega.tsx +227 -0
- package/registry/blocks/footer-wide-sitemap-mega/index.ts +6 -0
- package/registry/blocks/gallery-album-cards/GalleryAlbumCards.tsx +243 -0
- package/registry/blocks/gallery-album-cards/index.ts +8 -0
- package/registry/blocks/gallery-before-after-compare/GalleryBeforeAfterCompare.tsx +269 -0
- package/registry/blocks/gallery-before-after-compare/index.ts +7 -0
- package/registry/blocks/gallery-contact-sheet-wall/GalleryContactSheetWall.tsx +299 -0
- package/registry/blocks/gallery-contact-sheet-wall/index.ts +9 -0
- package/registry/blocks/gallery-culture-photo-grid/GalleryCulturePhotoGrid.tsx +203 -0
- package/registry/blocks/gallery-culture-photo-grid/index.ts +8 -0
- package/registry/blocks/gallery-dark-cinema-wall/GalleryDarkCinemaWall.tsx +180 -0
- package/registry/blocks/gallery-dark-cinema-wall/index.ts +7 -0
- package/registry/blocks/gallery-editorial-split/GalleryEditorialSplit.tsx +188 -0
- package/registry/blocks/gallery-editorial-split/index.ts +8 -0
- package/registry/blocks/gallery-featured-thumb-rail/GalleryFeaturedThumbRail.tsx +397 -0
- package/registry/blocks/gallery-featured-thumb-rail/index.ts +8 -0
- package/registry/blocks/gallery-filmstrip-carousel/GalleryFilmstripCarousel.tsx +384 -0
- package/registry/blocks/gallery-filmstrip-carousel/index.ts +8 -0
- package/registry/blocks/gallery-filter-tabs-grid/GalleryFilterTabsGrid.tsx +403 -0
- package/registry/blocks/gallery-filter-tabs-grid/index.ts +9 -0
- package/registry/blocks/gallery-full-bleed-slider/GalleryFullBleedSlider.tsx +309 -0
- package/registry/blocks/gallery-full-bleed-slider/index.ts +8 -0
- package/registry/blocks/gallery-horizontal-scroll-rail/GalleryHorizontalScrollRail.tsx +300 -0
- package/registry/blocks/gallery-horizontal-scroll-rail/index.ts +8 -0
- package/registry/blocks/gallery-hover-caption-cards/GalleryHoverCaptionCards.tsx +244 -0
- package/registry/blocks/gallery-hover-caption-cards/index.ts +8 -0
- package/registry/blocks/gallery-hover-index-list/GalleryHoverIndexList.tsx +253 -0
- package/registry/blocks/gallery-hover-index-list/index.ts +9 -0
- package/registry/blocks/gallery-lightbox-viewer/GalleryLightboxViewer.tsx +329 -0
- package/registry/blocks/gallery-lightbox-viewer/index.ts +7 -0
- package/registry/blocks/gallery-masonry-gallery/GalleryMasonryGallery.tsx +251 -0
- package/registry/blocks/gallery-masonry-gallery/index.ts +7 -0
- package/registry/blocks/gallery-mosaic-statement-tile/GalleryMosaicStatementTile.tsx +159 -0
- package/registry/blocks/gallery-mosaic-statement-tile/index.ts +8 -0
- package/registry/blocks/gallery-photo-bento/GalleryPhotoBento.tsx +166 -0
- package/registry/blocks/gallery-photo-bento/index.ts +8 -0
- package/registry/blocks/gallery-photo-spotlight/GalleryPhotoSpotlight.tsx +305 -0
- package/registry/blocks/gallery-photo-spotlight/index.ts +9 -0
- package/registry/blocks/gallery-scattered-collage/GalleryScatteredCollage.tsx +286 -0
- package/registry/blocks/gallery-scattered-collage/index.ts +9 -0
- package/registry/blocks/gallery-social-feed-strip/GallerySocialFeedStrip.tsx +288 -0
- package/registry/blocks/gallery-social-feed-strip/index.ts +8 -0
- package/registry/blocks/gallery-timeline-gallery/GalleryTimelineGallery.tsx +243 -0
- package/registry/blocks/gallery-timeline-gallery/index.ts +8 -0
- package/registry/blocks/gallery-video-gallery/GalleryVideoGallery.tsx +362 -0
- package/registry/blocks/gallery-video-gallery/index.ts +8 -0
- package/registry/blocks/hero-annotated-object/HeroAnnotatedObject.tsx +310 -0
- package/registry/blocks/hero-annotated-object/index.ts +10 -0
- package/registry/blocks/hero-arc-gallery/HeroArcGallery.tsx +244 -0
- package/registry/blocks/hero-arc-gallery/index.ts +9 -0
- package/registry/blocks/hero-bento-split/HeroBentoSplit.tsx +266 -0
- package/registry/blocks/hero-bento-split/index.ts +9 -0
- package/registry/blocks/hero-brand-color-block/HeroBrandColorBlock.tsx +259 -0
- package/registry/blocks/hero-brand-color-block/index.ts +8 -0
- package/registry/blocks/hero-colour-stage/HeroColourStage.tsx +292 -0
- package/registry/blocks/hero-colour-stage/index.ts +8 -0
- package/registry/blocks/hero-component-pattern/HeroComponentPattern.tsx +248 -0
- package/registry/blocks/hero-component-pattern/index.ts +9 -0
- package/registry/blocks/hero-curved-media-rail/HeroCurvedMediaRail.tsx +272 -0
- package/registry/blocks/hero-curved-media-rail/index.ts +8 -0
- package/registry/blocks/hero-duo-card/HeroDuoCard.tsx +210 -0
- package/registry/blocks/hero-duo-card/index.ts +8 -0
- package/registry/blocks/hero-editorial-arch/HeroEditorialArch.tsx +226 -0
- package/registry/blocks/hero-editorial-arch/index.ts +10 -0
- package/registry/blocks/hero-editorial-triptych/HeroEditorialTriptych.tsx +252 -0
- package/registry/blocks/hero-editorial-triptych/index.ts +9 -0
- package/registry/blocks/hero-floating-integrations/HeroFloatingIntegrations.tsx +454 -0
- package/registry/blocks/hero-floating-integrations/index.ts +11 -0
- package/registry/blocks/hero-framed-full-bleed/HeroFramedFullBleed.tsx +226 -0
- package/registry/blocks/hero-framed-full-bleed/index.ts +9 -0
- package/registry/blocks/hero-framed-studio-card/HeroFramedStudioCard.tsx +264 -0
- package/registry/blocks/hero-framed-studio-card/index.ts +8 -0
- package/registry/blocks/hero-illustration-split/HeroIllustrationSplit.tsx +200 -0
- package/registry/blocks/hero-illustration-split/index.ts +8 -0
- package/registry/blocks/hero-inline-image-headline/HeroInlineImageHeadline.tsx +305 -0
- package/registry/blocks/hero-inline-image-headline/index.ts +8 -0
- package/registry/blocks/hero-masked-type-reveal/HeroMaskedTypeReveal.tsx +323 -0
- package/registry/blocks/hero-masked-type-reveal/index.ts +9 -0
- package/registry/blocks/hero-motion.test.tsx +185 -0
- package/registry/blocks/hero-orbit-track/HeroOrbitTrack.tsx +238 -0
- package/registry/blocks/hero-orbit-track/index.ts +8 -0
- package/registry/blocks/hero-photo-constellation/HeroPhotoConstellation.tsx +228 -0
- package/registry/blocks/hero-photo-constellation/index.ts +8 -0
- package/registry/blocks/hero-scramble-decode/HeroScrambleDecode.tsx +381 -0
- package/registry/blocks/hero-scramble-decode/index.ts +7 -0
- package/registry/blocks/hero-shape-grid/HeroShapeGrid.tsx +279 -0
- package/registry/blocks/hero-shape-grid/index.ts +8 -0
- package/registry/blocks/hero-split-band-headline/HeroSplitBandHeadline.tsx +242 -0
- package/registry/blocks/hero-split-band-headline/index.ts +8 -0
- package/registry/blocks/hero-split-flap-counters/HeroSplitFlapCounters.tsx +323 -0
- package/registry/blocks/hero-split-flap-counters/index.ts +8 -0
- package/registry/blocks/hero-studio-mosaic/HeroStudioMosaic.tsx +332 -0
- package/registry/blocks/hero-studio-mosaic/index.ts +9 -0
- package/registry/blocks/hero-textured-dark/HeroTexturedDark.tsx +378 -0
- package/registry/blocks/hero-textured-dark/index.ts +10 -0
- package/registry/blocks/hero-twin-panels/HeroTwinPanels.tsx +231 -0
- package/registry/blocks/hero-twin-panels/index.ts +9 -0
- package/registry/blocks/hero-velocity-marquee/HeroVelocityMarquee.tsx +392 -0
- package/registry/blocks/hero-velocity-marquee/index.ts +8 -0
- package/registry/blocks/hero-video-in-type/HeroVideoInType.tsx +374 -0
- package/registry/blocks/hero-video-in-type/index.ts +9 -0
- package/registry/blocks/hero-video-spotlight/HeroVideoSpotlight.tsx +292 -0
- package/registry/blocks/hero-video-spotlight/index.ts +9 -0
- package/registry/blocks/hero-wave-band/HeroWaveBand.tsx +309 -0
- package/registry/blocks/hero-wave-band/index.ts +8 -0
- package/registry/blocks/how-it-works/how-it-works.tsx +330 -0
- package/registry/blocks/how-it-works/index.ts +28 -0
- package/registry/blocks/how-it-works-accordion-steps/HowItWorksAccordionSteps.tsx +213 -0
- package/registry/blocks/how-it-works-accordion-steps/index.ts +7 -0
- package/registry/blocks/how-it-works-center-spine-timeline/HowItWorksCenterSpineTimeline.tsx +200 -0
- package/registry/blocks/how-it-works-center-spine-timeline/index.ts +7 -0
- package/registry/blocks/how-it-works-column-panels/HowItWorksColumnPanels.tsx +299 -0
- package/registry/blocks/how-it-works-column-panels/index.ts +6 -0
- package/registry/blocks/how-it-works-contained-timeline/HowItWorksContainedTimeline.tsx +223 -0
- package/registry/blocks/how-it-works-contained-timeline/index.ts +8 -0
- package/registry/blocks/how-it-works-diagonal-photo-collage/HowItWorksDiagonalPhotoCollage.tsx +170 -0
- package/registry/blocks/how-it-works-diagonal-photo-collage/index.ts +7 -0
- package/registry/blocks/how-it-works-fanned-dissolve-cards/HowItWorksFannedDissolveCards.tsx +258 -0
- package/registry/blocks/how-it-works-fanned-dissolve-cards/index.ts +8 -0
- package/registry/blocks/how-it-works-full-bleed-step-panel/HowItWorksFullBleedStepPanel.tsx +319 -0
- package/registry/blocks/how-it-works-full-bleed-step-panel/index.ts +7 -0
- package/registry/blocks/how-it-works-grids.test.tsx +292 -0
- package/registry/blocks/how-it-works-interactive.test.tsx +262 -0
- package/registry/blocks/how-it-works-numbered-editorial-rows/HowItWorksNumberedEditorialRows.tsx +177 -0
- package/registry/blocks/how-it-works-numbered-editorial-rows/index.ts +7 -0
- package/registry/blocks/how-it-works-numbered-instruction-panels/HowItWorksNumberedInstructionPanels.tsx +226 -0
- package/registry/blocks/how-it-works-numbered-instruction-panels/index.ts +6 -0
- package/registry/blocks/how-it-works-numbered-photo-steps/HowItWorksNumberedPhotoSteps.tsx +150 -0
- package/registry/blocks/how-it-works-numbered-photo-steps/index.ts +7 -0
- package/registry/blocks/how-it-works-orbital-process/HowItWorksOrbitalProcess.tsx +317 -0
- package/registry/blocks/how-it-works-orbital-process/index.ts +7 -0
- package/registry/blocks/how-it-works-overlay-numeral-cards/HowItWorksOverlayNumeralCards.tsx +145 -0
- package/registry/blocks/how-it-works-overlay-numeral-cards/index.ts +7 -0
- package/registry/blocks/how-it-works-phase-line/HowItWorksPhaseLine.tsx +147 -0
- package/registry/blocks/how-it-works-phase-line/index.ts +8 -0
- package/registry/blocks/how-it-works-photo-bento-steps/HowItWorksPhotoBentoSteps.tsx +178 -0
- package/registry/blocks/how-it-works-photo-bento-steps/index.ts +7 -0
- package/registry/blocks/how-it-works-photo-rail-progress/HowItWorksPhotoRailProgress.tsx +231 -0
- package/registry/blocks/how-it-works-photo-rail-progress/index.ts +7 -0
- package/registry/blocks/how-it-works-pill-timeline/HowItWorksPillTimeline.tsx +181 -0
- package/registry/blocks/how-it-works-pill-timeline/index.ts +8 -0
- package/registry/blocks/how-it-works-rounded-cards-device-crop/HowItWorksRoundedCardsDeviceCrop.tsx +286 -0
- package/registry/blocks/how-it-works-rounded-cards-device-crop/index.ts +6 -0
- package/registry/blocks/how-it-works-six-card-grid/HowItWorksSixCardGrid.tsx +385 -0
- package/registry/blocks/how-it-works-six-card-grid/index.ts +6 -0
- package/registry/blocks/how-it-works-split-feature-band/HowItWorksSplitFeatureBand.tsx +316 -0
- package/registry/blocks/how-it-works-split-feature-band/index.ts +6 -0
- package/registry/blocks/how-it-works-split-photo-step-list/HowItWorksSplitPhotoStepList.tsx +179 -0
- package/registry/blocks/how-it-works-split-photo-step-list/index.ts +7 -0
- package/registry/blocks/how-it-works-stacked-media-text/HowItWorksStackedMediaText.tsx +310 -0
- package/registry/blocks/how-it-works-stacked-media-text/index.ts +8 -0
- package/registry/blocks/how-it-works-step-tabs-photo-panel/HowItWorksStepTabsPhotoPanel.tsx +245 -0
- package/registry/blocks/how-it-works-step-tabs-photo-panel/index.ts +7 -0
- package/registry/blocks/how-it-works-tall-visual-rail/HowItWorksTallVisualRail.tsx +349 -0
- package/registry/blocks/how-it-works-tall-visual-rail/index.ts +7 -0
- package/registry/blocks/how-it-works-timelines.test.tsx +265 -0
- package/registry/blocks/how-it-works-vertical-timeline-card/HowItWorksVerticalTimelineCard.tsx +169 -0
- package/registry/blocks/how-it-works-vertical-timeline-card/index.ts +8 -0
- package/registry/blocks/how-it-works-visual-steps.test.tsx +368 -0
- package/registry/blocks/logo-cloud-bento-logo-wall/LogoCloudBentoLogoWall.tsx +159 -0
- package/registry/blocks/logo-cloud-bento-logo-wall/index.ts +7 -0
- package/registry/blocks/logo-cloud-bordered-cell-grid/LogoCloudBorderedCellGrid.tsx +132 -0
- package/registry/blocks/logo-cloud-bordered-cell-grid/index.ts +7 -0
- package/registry/blocks/logo-cloud-centered-caption-row/LogoCloudCenteredCaptionRow.tsx +141 -0
- package/registry/blocks/logo-cloud-centered-caption-row/index.ts +7 -0
- package/registry/blocks/logo-cloud-contained-card/LogoCloudContainedCard.tsx +106 -0
- package/registry/blocks/logo-cloud-contained-card/index.ts +7 -0
- package/registry/blocks/logo-cloud-dark-logo-wall/LogoCloudDarkLogoWall.tsx +123 -0
- package/registry/blocks/logo-cloud-dark-logo-wall/index.ts +7 -0
- package/registry/blocks/logo-cloud-divider-rule-row/LogoCloudDividerRuleRow.tsx +156 -0
- package/registry/blocks/logo-cloud-divider-rule-row/index.ts +7 -0
- package/registry/blocks/logo-cloud-editorial-eyebrow-split/LogoCloudEditorialEyebrowSplit.tsx +121 -0
- package/registry/blocks/logo-cloud-editorial-eyebrow-split/index.ts +7 -0
- package/registry/blocks/logo-cloud-enterprise-wall/LogoCloudEnterpriseWall.tsx +114 -0
- package/registry/blocks/logo-cloud-enterprise-wall/index.ts +7 -0
- package/registry/blocks/logo-cloud-flanked-heading/LogoCloudFlankedHeading.tsx +162 -0
- package/registry/blocks/logo-cloud-flanked-heading/index.ts +8 -0
- package/registry/blocks/logo-cloud-grid-closing-cta/LogoCloudGridClosingCta.tsx +173 -0
- package/registry/blocks/logo-cloud-grid-closing-cta/index.ts +8 -0
- package/registry/blocks/logo-cloud-heading-grid/LogoCloudHeadingGrid.tsx +124 -0
- package/registry/blocks/logo-cloud-heading-grid/index.ts +7 -0
- package/registry/blocks/logo-cloud-heading-left-grid-right/LogoCloudHeadingLeftGridRight.tsx +172 -0
- package/registry/blocks/logo-cloud-heading-left-grid-right/index.ts +8 -0
- package/registry/blocks/logo-cloud-logo-metric-tiles/LogoCloudLogoMetricTiles.tsx +159 -0
- package/registry/blocks/logo-cloud-logo-metric-tiles/index.ts +8 -0
- package/registry/blocks/logo-cloud-logo-quote-cards/LogoCloudLogoQuoteCards.tsx +162 -0
- package/registry/blocks/logo-cloud-logo-quote-cards/index.ts +8 -0
- package/registry/blocks/logo-cloud-logo-testimonial-strip/LogoCloudLogoTestimonialStrip.tsx +159 -0
- package/registry/blocks/logo-cloud-logo-testimonial-strip/index.ts +8 -0
- package/registry/blocks/logo-cloud-named-integration-cards/LogoCloudNamedIntegrationCards.tsx +186 -0
- package/registry/blocks/logo-cloud-named-integration-cards/index.ts +9 -0
- package/registry/blocks/logo-cloud-pyramid-row-stack/LogoCloudPyramidRowStack.tsx +156 -0
- package/registry/blocks/logo-cloud-pyramid-row-stack/index.ts +7 -0
- package/registry/blocks/logo-cloud-quote-panel-grid/LogoCloudQuotePanelGrid.tsx +177 -0
- package/registry/blocks/logo-cloud-quote-panel-grid/index.ts +8 -0
- package/registry/blocks/logo-cloud-scatter-field/LogoCloudScatterField.tsx +190 -0
- package/registry/blocks/logo-cloud-scatter-field/index.ts +8 -0
- package/registry/blocks/logo-cloud-side-label-rail/LogoCloudSideLabelRail.tsx +139 -0
- package/registry/blocks/logo-cloud-side-label-rail/index.ts +7 -0
- package/registry/blocks/logo-cloud-stat-rail-above-grid/LogoCloudStatRailAboveGrid.tsx +173 -0
- package/registry/blocks/logo-cloud-stat-rail-above-grid/index.ts +8 -0
- package/registry/blocks/logo-cloud-tabbed-industry-filter/LogoCloudTabbedIndustryFilter.tsx +200 -0
- package/registry/blocks/logo-cloud-tabbed-industry-filter/index.ts +8 -0
- package/registry/blocks/navbar/index.ts +22 -11
- package/registry/blocks/navbar/navbar.test.tsx +42 -0
- package/registry/blocks/navbar/navbar.tsx +455 -0
- package/registry/blocks/navbar-active-action-with-border/NavbarActiveActionWithBorder.tsx +120 -0
- package/registry/blocks/navbar-active-action-with-border/index.ts +6 -0
- package/registry/blocks/navbar-active-action-with-round/NavbarActiveActionWithRound.tsx +120 -0
- package/registry/blocks/navbar-active-action-with-round/index.ts +6 -0
- package/registry/blocks/navbar-centered-floating/NavbarCenteredFloating.tsx +121 -0
- package/registry/blocks/navbar-centered-floating/index.ts +6 -0
- package/registry/blocks/navbar-default/NavbarDefault.tsx +102 -0
- package/registry/blocks/navbar-default/index.ts +6 -0
- package/registry/blocks/navbar-e-commerce/NavbarECommerce.tsx +212 -0
- package/registry/blocks/navbar-e-commerce/index.ts +6 -0
- package/registry/blocks/navbar-extended-at-top/NavbarExtendedAtTop.tsx +155 -0
- package/registry/blocks/navbar-extended-at-top/index.ts +7 -0
- package/registry/blocks/navbar-multiple-action/NavbarMultipleAction.tsx +133 -0
- package/registry/blocks/navbar-multiple-action/index.ts +6 -0
- package/registry/blocks/navbar-multiple-icon/NavbarMultipleIcon.tsx +132 -0
- package/registry/blocks/navbar-multiple-icon/index.ts +6 -0
- package/registry/blocks/navbar-rounded/NavbarRounded.tsx +140 -0
- package/registry/blocks/navbar-rounded/index.ts +6 -0
- package/registry/blocks/navbar-rounded-small/NavbarRoundedSmall.tsx +143 -0
- package/registry/blocks/navbar-rounded-small/index.ts +6 -0
- package/registry/blocks/navbar-small/NavbarSmall.tsx +142 -0
- package/registry/blocks/navbar-small/index.ts +6 -0
- package/registry/blocks/navbar-with-centered-logo/NavbarWithCenteredLogo.tsx +118 -0
- package/registry/blocks/navbar-with-centered-logo/index.ts +6 -0
- package/registry/blocks/navbar-with-cta-button/NavbarWithCtaButton.tsx +131 -0
- package/registry/blocks/navbar-with-cta-button/index.ts +6 -0
- package/registry/blocks/navbar-with-dropdown/NavbarWithDropdown.tsx +118 -0
- package/registry/blocks/navbar-with-dropdown/index.ts +6 -0
- package/registry/blocks/navbar-with-icon-indicator-avatar/NavbarWithIconIndicatorAvatar.tsx +85 -0
- package/registry/blocks/navbar-with-icon-indicator-avatar/index.ts +6 -0
- package/registry/blocks/navbar-with-search-input/NavbarWithSearchInput.tsx +90 -0
- package/registry/blocks/navbar-with-search-input/index.ts +6 -0
- package/registry/blocks/navbar-with-submenu/NavbarWithSubmenu.tsx +156 -0
- package/registry/blocks/navbar-with-submenu/index.ts +6 -0
- package/registry/blocks/page-header/PageHeader.tsx +5 -9
- package/registry/blocks/pricing/index.ts +20 -0
- package/registry/blocks/pricing/pricing.test.tsx +82 -0
- package/registry/blocks/pricing/pricing.tsx +343 -0
- package/registry/blocks/pricing-comparison-matrix/PricingComparisonMatrix.tsx +336 -0
- package/registry/blocks/pricing-comparison-matrix/index.ts +10 -0
- package/registry/blocks/pricing-plan-picker-summary/PricingPlanPickerSummary.tsx +399 -0
- package/registry/blocks/pricing-plan-picker-summary/index.ts +8 -0
- package/registry/blocks/pricing-plan-spotlight/PricingPlanSpotlight.tsx +281 -0
- package/registry/blocks/pricing-plan-spotlight/index.ts +7 -0
- package/registry/blocks/pricing-tiers/PricingTiers.tsx +264 -0
- package/registry/blocks/pricing-tiers/index.ts +8 -0
- package/registry/blocks/pricing-two-plan-compare/PricingTwoPlanCompare.tsx +187 -0
- package/registry/blocks/pricing-two-plan-compare/index.ts +7 -0
- package/registry/blocks/social-proof/index.ts +36 -0
- package/registry/blocks/social-proof/social-proof.test.tsx +504 -0
- package/registry/blocks/social-proof/social-proof.tsx +759 -0
- package/registry/blocks/social-proof-app-review-row/SocialProofAppReviewRow.tsx +168 -0
- package/registry/blocks/social-proof-app-review-row/index.ts +7 -0
- package/registry/blocks/social-proof-arch-portrait-quote/SocialProofArchPortraitQuote.tsx +114 -0
- package/registry/blocks/social-proof-arch-portrait-quote/index.ts +6 -0
- package/registry/blocks/social-proof-avatar-selector-row/SocialProofAvatarSelectorRow.tsx +276 -0
- package/registry/blocks/social-proof-avatar-selector-row/index.ts +7 -0
- package/registry/blocks/social-proof-benefit-title-cards/SocialProofBenefitTitleCards.tsx +182 -0
- package/registry/blocks/social-proof-benefit-title-cards/index.ts +8 -0
- package/registry/blocks/social-proof-bracket-framed-quote/SocialProofBracketFramedQuote.tsx +120 -0
- package/registry/blocks/social-proof-bracket-framed-quote/index.ts +6 -0
- package/registry/blocks/social-proof-case-study-row/SocialProofCaseStudyRow.tsx +210 -0
- package/registry/blocks/social-proof-case-study-row/index.ts +7 -0
- package/registry/blocks/social-proof-centered-logo-spotlight/SocialProofCenteredLogoSpotlight.tsx +103 -0
- package/registry/blocks/social-proof-centered-logo-spotlight/index.ts +6 -0
- package/registry/blocks/social-proof-classic-trio-grid/SocialProofClassicTrioGrid.tsx +138 -0
- package/registry/blocks/social-proof-classic-trio-grid/index.ts +7 -0
- package/registry/blocks/social-proof-dark-editorial-split/SocialProofDarkEditorialSplit.tsx +111 -0
- package/registry/blocks/social-proof-dark-editorial-split/index.ts +6 -0
- package/registry/blocks/social-proof-giant-quote-duo/SocialProofGiantQuoteDuo.tsx +156 -0
- package/registry/blocks/social-proof-giant-quote-duo/index.ts +8 -0
- package/registry/blocks/social-proof-highlighted-phrase-spotlight/SocialProofHighlightedPhraseSpotlight.tsx +130 -0
- package/registry/blocks/social-proof-highlighted-phrase-spotlight/index.ts +7 -0
- package/registry/blocks/social-proof-overlay-photo-quote/SocialProofOverlayPhotoQuote.tsx +111 -0
- package/registry/blocks/social-proof-overlay-photo-quote/index.ts +7 -0
- package/registry/blocks/social-proof-persona-pillars/SocialProofPersonaPillars.tsx +253 -0
- package/registry/blocks/social-proof-persona-pillars/index.ts +7 -0
- package/registry/blocks/social-proof-progress-carousel/SocialProofProgressCarousel.tsx +249 -0
- package/registry/blocks/social-proof-progress-carousel/index.ts +8 -0
- package/registry/blocks/social-proof-proof-bento/SocialProofProofBento.tsx +218 -0
- package/registry/blocks/social-proof-proof-bento/index.ts +6 -0
- package/registry/blocks/social-proof-spotlight-carousel/SocialProofSpotlightCarousel.tsx +212 -0
- package/registry/blocks/social-proof-spotlight-carousel/index.ts +7 -0
- package/registry/blocks/social-proof-staggered-columns/SocialProofStaggeredColumns.tsx +227 -0
- package/registry/blocks/social-proof-staggered-columns/index.ts +7 -0
- package/registry/blocks/social-proof-stat-story-cards/SocialProofStatStoryCards.tsx +218 -0
- package/registry/blocks/social-proof-stat-story-cards/index.ts +7 -0
- package/registry/blocks/social-proof-tinted-panel-portrait/SocialProofTintedPanelPortrait.tsx +130 -0
- package/registry/blocks/social-proof-tinted-panel-portrait/index.ts +7 -0
- package/registry/blocks/social-proof-tweet-wall/SocialProofTweetWall.tsx +179 -0
- package/registry/blocks/social-proof-tweet-wall/index.ts +7 -0
- package/registry/blocks/social-proof-vertical-column-wall/SocialProofVerticalColumnWall.tsx +220 -0
- package/registry/blocks/social-proof-vertical-column-wall/index.ts +7 -0
- package/registry/blocks/social-proof-video-story-cards/SocialProofVideoStoryCards.tsx +177 -0
- package/registry/blocks/social-proof-video-story-cards/index.ts +7 -0
- package/registry/blocks/social-proof-workplace-portrait-stories/SocialProofWorkplacePortraitStories.tsx +320 -0
- package/registry/blocks/social-proof-workplace-portrait-stories/index.ts +7 -0
- package/registry/blocks/team/index.ts +21 -0
- package/registry/blocks/team/team-classes.test.ts +35 -0
- package/registry/blocks/team/team.tsx +315 -0
- package/registry/blocks/team-annotated-portrait-lineup/TeamAnnotatedPortraitLineup.tsx +310 -0
- package/registry/blocks/team-annotated-portrait-lineup/index.ts +8 -0
- package/registry/blocks/team-annotated-portrait-lineup/team-annotated-portrait-lineup.test.tsx +71 -0
- package/registry/blocks/team-compact-directory-rows/TeamCompactDirectoryRows.tsx +190 -0
- package/registry/blocks/team-compact-directory-rows/compact-directory-rows.test.tsx +40 -0
- package/registry/blocks/team-compact-directory-rows/index.ts +7 -0
- package/registry/blocks/team-department-tabs-directory/TeamDepartmentTabsDirectory.tsx +414 -0
- package/registry/blocks/team-department-tabs-directory/department-tabs-directory.test.tsx +87 -0
- package/registry/blocks/team-department-tabs-directory/index.ts +10 -0
- package/registry/blocks/team-founder-spotlight/TeamFounderSpotlight.tsx +176 -0
- package/registry/blocks/team-founder-spotlight/index.ts +8 -0
- package/registry/blocks/team-founder-spotlight/team-founder-spotlight.test.tsx +70 -0
- package/registry/blocks/team-grid-backdrop-roster/TeamGridBackdropRoster.tsx +234 -0
- package/registry/blocks/team-grid-backdrop-roster/grid-backdrop-roster.test.tsx +72 -0
- package/registry/blocks/team-grid-backdrop-roster/index.ts +8 -0
- package/registry/blocks/team-hairline-grid/TeamHairlineGrid.tsx +190 -0
- package/registry/blocks/team-hairline-grid/hairline-grid.test.tsx +49 -0
- package/registry/blocks/team-hairline-grid/index.ts +1 -0
- package/registry/blocks/team-leadership-grid/TeamLeadershipGrid.tsx +183 -0
- package/registry/blocks/team-leadership-grid/index.ts +7 -0
- package/registry/blocks/team-leadership-grid/team-leadership-grid.test.tsx +38 -0
- package/registry/blocks/team-member-carousel/TeamMemberCarousel.tsx +178 -0
- package/registry/blocks/team-member-carousel/index.ts +7 -0
- package/registry/blocks/team-member-carousel/team-member-carousel.test.tsx +58 -0
- package/registry/blocks/team-member-table/TeamMemberTable.tsx +184 -0
- package/registry/blocks/team-member-table/index.ts +7 -0
- package/registry/blocks/team-member-table/member-table.test.tsx +43 -0
- package/registry/blocks/team-meta-profile-cards/TeamMetaProfileCards.tsx +155 -0
- package/registry/blocks/team-meta-profile-cards/index.ts +7 -0
- package/registry/blocks/team-meta-profile-cards/team-meta-profile-cards.test.tsx +29 -0
- package/registry/blocks/team-overlay-portrait-cards/TeamOverlayPortraitCards.tsx +145 -0
- package/registry/blocks/team-overlay-portrait-cards/index.ts +7 -0
- package/registry/blocks/team-overlay-portrait-cards/team-overlay-portrait-cards.test.tsx +49 -0
- package/registry/blocks/team-portrait-wall/TeamPortraitWall.tsx +131 -0
- package/registry/blocks/team-portrait-wall/index.ts +1 -0
- package/registry/blocks/team-portrait-wall/portrait-wall.test.tsx +45 -0
- package/registry/blocks/team-split-intro-member-rail/TeamSplitIntroMemberRail.tsx +203 -0
- package/registry/blocks/team-split-intro-member-rail/index.ts +7 -0
- package/registry/blocks/team-split-intro-member-rail/split-intro-member-rail.test.tsx +52 -0
- package/registry/blocks/team-we-re-hiring-cta/TeamWeReHiringCta.tsx +192 -0
- package/registry/blocks/team-we-re-hiring-cta/index.ts +7 -0
- package/registry/blocks/team-we-re-hiring-cta/we-re-hiring-cta.test.tsx +44 -0
- package/registry/blocks/video-call/index.ts +53 -0
- package/registry/blocks/video-call/video-call-around.test.tsx +427 -0
- package/registry/blocks/video-call/video-call-before.test.tsx +438 -0
- package/registry/blocks/video-call/video-call-hero.test.tsx +236 -0
- package/registry/blocks/video-call/video-call-stages.test.tsx +477 -0
- package/registry/blocks/video-call/video-call.test.tsx +794 -0
- package/registry/blocks/video-call/video-call.tsx +1282 -0
- package/registry/blocks/video-call-ai-meeting-recap/VideoCallAiMeetingRecap.tsx +466 -0
- package/registry/blocks/video-call-ai-meeting-recap/index.ts +9 -0
- package/registry/blocks/video-call-call-controls-dock/VideoCallCallControlsDock.tsx +211 -0
- package/registry/blocks/video-call-call-controls-dock/index.ts +6 -0
- package/registry/blocks/video-call-call-ended-summary/VideoCallCallEndedSummary.tsx +464 -0
- package/registry/blocks/video-call-call-ended-summary/index.ts +10 -0
- package/registry/blocks/video-call-call-with-side-panel/VideoCallCallWithSidePanel.tsx +357 -0
- package/registry/blocks/video-call-call-with-side-panel/index.ts +6 -0
- package/registry/blocks/video-call-captions-reactions/VideoCallCaptionsReactions.tsx +467 -0
- package/registry/blocks/video-call-captions-reactions/index.ts +10 -0
- package/registry/blocks/video-call-grid-gallery-call/VideoCallGridGalleryCall.tsx +312 -0
- package/registry/blocks/video-call-grid-gallery-call/index.ts +6 -0
- package/registry/blocks/video-call-hero-call-mockup/VideoCallHeroCallMockup.tsx +349 -0
- package/registry/blocks/video-call-hero-call-mockup/index.ts +7 -0
- package/registry/blocks/video-call-incoming-call/VideoCallIncomingCall.tsx +336 -0
- package/registry/blocks/video-call-incoming-call/index.ts +7 -0
- package/registry/blocks/video-call-join-link-dial-in/VideoCallJoinLinkDialIn.tsx +409 -0
- package/registry/blocks/video-call-join-link-dial-in/index.ts +9 -0
- package/registry/blocks/video-call-pre-join-lobby/VideoCallPreJoinLobby.tsx +345 -0
- package/registry/blocks/video-call-pre-join-lobby/index.ts +7 -0
- package/registry/blocks/video-call-screen-share-stage/VideoCallScreenShareStage.tsx +379 -0
- package/registry/blocks/video-call-screen-share-stage/index.ts +7 -0
- package/registry/blocks/video-call-speaker-filmstrip/VideoCallSpeakerFilmstrip.tsx +303 -0
- package/registry/blocks/video-call-speaker-filmstrip/index.ts +6 -0
- package/registry/blocks/video-call-upcoming-meetings/VideoCallUpcomingMeetings.tsx +349 -0
- package/registry/blocks/video-call-upcoming-meetings/index.ts +7 -0
- package/registry/blocks/video-call-waiting-room-knock/VideoCallWaitingRoomKnock.tsx +412 -0
- package/registry/blocks/video-call-waiting-room-knock/index.ts +7 -0
- package/registry/text-props-undefined.test.tsx +3 -26
- package/registry/ui/accordion/accordion.test.tsx +320 -0
- package/registry/ui/accordion/accordion.tsx +353 -0
- package/registry/ui/accordion/index.ts +15 -0
- package/registry/ui/alert/alert.test.tsx +325 -0
- package/registry/ui/alert/alert.tsx +384 -0
- package/registry/ui/alert/index.ts +10 -0
- package/registry/ui/alert-dialog/alert-dialog.test.tsx +396 -0
- package/registry/ui/alert-dialog/alert-dialog.tsx +423 -0
- package/registry/ui/alert-dialog/index.ts +21 -0
- package/registry/ui/avatar/avatar.test.tsx +2 -2
- package/registry/ui/avatar/avatar.tsx +3 -7
- package/registry/ui/breadcrumb/breadcrumb.test.tsx +305 -0
- package/registry/ui/breadcrumb/breadcrumb.tsx +205 -0
- package/registry/ui/breadcrumb/index.ts +11 -0
- package/registry/ui/button/button.test.tsx +353 -40
- package/registry/ui/button/button.tsx +304 -30
- package/registry/ui/calendar/calendar.test.tsx +192 -0
- package/registry/ui/calendar/calendar.tsx +405 -0
- package/registry/ui/calendar/index.ts +1 -0
- package/registry/ui/carousel/carousel.test.tsx +720 -0
- package/registry/ui/carousel/carousel.tsx +554 -0
- package/registry/ui/carousel/index.ts +11 -0
- package/registry/ui/checkbox/checkbox.test.tsx +318 -2
- package/registry/ui/checkbox/checkbox.tsx +253 -67
- package/registry/ui/checkbox/index.ts +1 -1
- package/registry/ui/content-carousel/content-carousel.test.tsx +303 -0
- package/registry/ui/content-carousel/content-carousel.tsx +180 -0
- package/registry/ui/content-carousel/index.ts +8 -0
- package/registry/ui/date-picker/date-picker.test.tsx +216 -0
- package/registry/ui/date-picker/date-picker.tsx +461 -0
- package/registry/ui/date-picker/index.ts +8 -0
- package/registry/ui/dialog/dialog.test.tsx +438 -0
- package/registry/ui/dialog/dialog.tsx +533 -0
- package/registry/ui/dialog/index.ts +20 -0
- package/registry/ui/dropdown-menu/dropdown-menu.test.tsx +502 -0
- package/registry/ui/dropdown-menu/dropdown-menu.tsx +627 -0
- package/registry/ui/dropdown-menu/index.ts +32 -0
- package/registry/ui/fasla-logo/fasla-logo.tsx +101 -0
- package/registry/ui/fasla-logo/index.ts +1 -0
- package/registry/ui/input/index.ts +10 -1
- package/registry/ui/input/input.test.tsx +251 -44
- package/registry/ui/input/input.tsx +311 -40
- package/registry/ui/input-number/index.ts +1 -0
- package/registry/ui/input-number/input-number.test.tsx +302 -0
- package/registry/ui/input-number/input-number.tsx +398 -0
- package/registry/ui/input-otp/index.ts +7 -0
- package/registry/ui/input-otp/input-otp.test.tsx +299 -0
- package/registry/ui/input-otp/input-otp.tsx +279 -0
- package/registry/ui/pagination/index.ts +12 -0
- package/registry/ui/pagination/pagination.test.tsx +330 -0
- package/registry/ui/pagination/pagination.tsx +336 -0
- package/registry/ui/progress/index.ts +8 -0
- package/registry/ui/progress/progress.test.tsx +168 -0
- package/registry/ui/progress/progress.tsx +165 -0
- package/registry/ui/select/index.ts +12 -1
- package/registry/ui/select/select.test.tsx +505 -0
- package/registry/ui/select/select.tsx +658 -56
- package/registry/ui/sheet/index.ts +19 -0
- package/registry/ui/sheet/sheet.test.tsx +411 -0
- package/registry/ui/sheet/sheet.tsx +496 -0
- package/registry/{blocks → ui}/sidebar/index.ts +10 -5
- package/registry/ui/sidebar/sidebar.test.tsx +224 -0
- package/registry/ui/sidebar/sidebar.tsx +754 -0
- package/registry/ui/slider/index.ts +8 -0
- package/registry/ui/slider/slider.test.tsx +231 -0
- package/registry/ui/slider/slider.tsx +319 -0
- package/registry/ui/social-icons/index.ts +1 -0
- package/registry/ui/social-icons/social-icons.tsx +284 -0
- package/registry/ui/steps/index.ts +16 -0
- package/registry/ui/steps/steps.test.tsx +435 -0
- package/registry/ui/steps/steps.tsx +571 -0
- package/registry/ui/switch/switch.tsx +3 -2
- package/registry/ui/tabs/index.ts +4 -0
- package/registry/ui/tabs/tabs.test.tsx +576 -0
- package/registry/ui/tabs/tabs.tsx +324 -112
- package/registry/ui/textarea/index.ts +1 -1
- package/registry/ui/textarea/textarea.test.tsx +294 -0
- package/registry/ui/textarea/textarea.tsx +329 -56
- package/registry/ui/tooltip/index.ts +14 -0
- package/registry/ui/tooltip/tooltip.test.tsx +301 -0
- package/registry/ui/tooltip/tooltip.tsx +409 -0
- package/registry.json +9702 -139
- package/tailwind-preset.ts +2 -2
- package/registry/blocks/navbar/Navbar.test.tsx +0 -12
- package/registry/blocks/navbar/Navbar.tsx +0 -175
- package/registry/blocks/sidebar/Sidebar.test.tsx +0 -39
- package/registry/blocks/sidebar/Sidebar.tsx +0 -212
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check } from "lucide-react"
|
|
5
|
+
import { cn } from "../../../src/lib/utils"
|
|
6
|
+
import { badgeVariants } from "../../ui/badge/badge"
|
|
7
|
+
import { Button, type ButtonProps } from "../../ui/button/button"
|
|
8
|
+
import { Checkbox } from "../../ui/checkbox/checkbox"
|
|
9
|
+
import { DatePicker } from "../../ui/date-picker/date-picker"
|
|
10
|
+
import { Input } from "../../ui/input/input"
|
|
11
|
+
import { Select } from "../../ui/select/select"
|
|
12
|
+
import { SOCIAL_PLATFORM_NAMES, SocialIcon, type SocialPlatform } from "../../ui/social-icons/social-icons"
|
|
13
|
+
import { Textarea } from "../../ui/textarea/textarea"
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Figma: `Contact Us` (42334:293941). The parts every `contact-us-*` block is
|
|
17
|
+
* built from, one per thing the Figma set repeats: the section and its
|
|
18
|
+
* 1248px column, the header, the hairline, the contact form (its fields, its
|
|
19
|
+
* state machine and its three outcomes), the single-choice chips, the
|
|
20
|
+
* social links, and the small motion helper the entrances use.
|
|
21
|
+
*
|
|
22
|
+
* Mobile first, and sized by the block's own width, not the window's, as the
|
|
23
|
+
* Careers, Pricing and Gallery blocks are: the section is a query container,
|
|
24
|
+
* so a block lays out for the room it is given, such as a docs preview
|
|
25
|
+
* narrower than the screen. The base classes are Figma's Mobile (360);
|
|
26
|
+
* `[@container(min-width:40rem)]:` is its Tablet, from 640px of content
|
|
27
|
+
* (Figma's Tablet, 768 less its padding), and `[@container(min-width:64rem)]:`
|
|
28
|
+
* its Desktop, from 1024px of content. The section's own padding still
|
|
29
|
+
* follows the window, since an element can't query itself. The page's `dir`
|
|
30
|
+
* sets the direction; nothing here takes one.
|
|
31
|
+
*
|
|
32
|
+
* Forms: there is no backend. A form validates on the client, then calls the
|
|
33
|
+
* block's `onSubmit(values)`: resolve to show the thank-you panel, reject (or
|
|
34
|
+
* throw) to show the send error.
|
|
35
|
+
*
|
|
36
|
+
* Required fields: a required text field, select or textarea shows the word
|
|
37
|
+
* `requiredLabel` ("Required", «مطلوب») and nothing else, in the atom's own Top
|
|
38
|
+
* Right label slot (`labelEnd`), at the end of the label row. A field with no
|
|
39
|
+
* visible label (`hideLabel`) shows no word and no extra row; it is still
|
|
40
|
+
* required in behaviour. No label or placeholder carries an asterisk,
|
|
41
|
+
* anywhere. `aria-required` and the error on submit are the behaviour. Consent and terms checkboxes are required too, with neither
|
|
42
|
+
* the word nor an asterisk.
|
|
43
|
+
*
|
|
44
|
+
* STAND-INS. Figma components this family has no code atom for. Each is
|
|
45
|
+
* replaced by plain markup inside this file, so it can be swapped for the
|
|
46
|
+
* real atom, in one place, once the atom exists. Rating is used by no Type.
|
|
47
|
+
*
|
|
48
|
+
* - Toggle (15088:228441): the 12 time-slot buttons of Book a Call.
|
|
49
|
+
* Stand-in: `aria-pressed` buttons styled with `buttonVariants`. Used by
|
|
50
|
+
* Book a Call.
|
|
51
|
+
* - Table Cell (39961:4559): the 30 cells of Global Offices Table.
|
|
52
|
+
* Stand-in: native `<table>` markup, a `th` or `td` styled like the cell.
|
|
53
|
+
* Used by Global Offices Table.
|
|
54
|
+
* - Separator (14860:69243): the 3 dividing lines of Editorial & Address Card.
|
|
55
|
+
* Stand-in: `ContactUsRule`, a 1px `bg-border` line. Used by Editorial &
|
|
56
|
+
* Address Card, and by Ruled Split Form and Oversized Wordmark, where Figma
|
|
57
|
+
* draws a plain frame.
|
|
58
|
+
*
|
|
59
|
+
* Tints: `soft-warning-5`, `soft-warning-30` and `soft-success-30` are
|
|
60
|
+
* Figma's `opacity/warning/5`, `opacity/warning/30` and `opacity/success/30`.
|
|
61
|
+
* They live in the theme with the other soft tints.
|
|
62
|
+
*
|
|
63
|
+
* Motion uses the repo's presets, copied in (120, 180 and 260ms,
|
|
64
|
+
* `cubic-bezier(0.2, 0, 0, 1)`), as the Web Animations API, once, when the
|
|
65
|
+
* block is a fifth in view, and never when the reader prefers reduced motion:
|
|
66
|
+
* then a block only fades.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export type ContactUsLocale = "en" | "ar"
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Splits a block's props into its copy (every key the default copy has, with
|
|
73
|
+
* the default where the prop is left out) and the rest.
|
|
74
|
+
*/
|
|
75
|
+
export function splitCopy<C extends object, P extends object>(defaults: C, props: P) {
|
|
76
|
+
const copy: Record<string, unknown> = { ...(defaults as Record<string, unknown>) }
|
|
77
|
+
const rest: Record<string, unknown> = {}
|
|
78
|
+
for (const [key, value] of Object.entries(props)) {
|
|
79
|
+
if (key in defaults) {
|
|
80
|
+
if (value !== undefined) copy[key] = value
|
|
81
|
+
} else rest[key] = value
|
|
82
|
+
}
|
|
83
|
+
return { copy: copy as C, rest: rest as Omit<P, keyof C> }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The props every form block takes, besides its copy. */
|
|
87
|
+
export interface ContactUsFormBlockProps extends Omit<React.HTMLAttributes<HTMLElement>, "title" | "onSubmit"> {
|
|
88
|
+
/** Which language the default copy is in. The page's `dir` sets the direction. */
|
|
89
|
+
locale?: ContactUsLocale
|
|
90
|
+
/**
|
|
91
|
+
* Called with the checked fields, by name. Resolve to show the thank-you
|
|
92
|
+
* panel; reject, or throw, to show the send error. The block sends nothing
|
|
93
|
+
* itself.
|
|
94
|
+
*/
|
|
95
|
+
onSubmit?: (values: ContactUsValues) => void | Promise<void>
|
|
96
|
+
/** Turns every field and the button off. */
|
|
97
|
+
disabled?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ───────────────────────────── Motion ───────────────────────────── */
|
|
101
|
+
|
|
102
|
+
const STANDARD = "cubic-bezier(0.2, 0, 0, 1)"
|
|
103
|
+
const FAST_MS = 120
|
|
104
|
+
const BASE_MS = 180
|
|
105
|
+
const SLOW_MS = 260
|
|
106
|
+
const STAGGER_MS = 50
|
|
107
|
+
const MAX_STAGGER_MS = 300
|
|
108
|
+
|
|
109
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect
|
|
110
|
+
|
|
111
|
+
/** Whether the reader asked the system for less motion. */
|
|
112
|
+
export function prefersReducedMotion() {
|
|
113
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Plays one entrance on each node, in order. What a node does is named by its
|
|
118
|
+
* `data-reveal`: `rise` (the default: fade up 12px), `fade`, `word` (fade up
|
|
119
|
+
* 14px, for a sentence set word by word), `draw-x` and `draw-y` (a rule drawn
|
|
120
|
+
* from its start or its top; set its `transform-origin` in CSS) and `wipe`
|
|
121
|
+
* (opens from the start edge while it scales in). A node's own
|
|
122
|
+
* `data-reveal-delay` (ms) replaces its place in the stagger.
|
|
123
|
+
*/
|
|
124
|
+
export function playReveal(nodes: HTMLElement[]) {
|
|
125
|
+
const reduced = prefersReducedMotion()
|
|
126
|
+
return nodes
|
|
127
|
+
.filter((node) => typeof node.animate === "function")
|
|
128
|
+
.map((node, index) => {
|
|
129
|
+
const kind = node.dataset.reveal || "rise"
|
|
130
|
+
const own = node.dataset.revealDelay === undefined ? Number.NaN : Number(node.dataset.revealDelay)
|
|
131
|
+
const delay = reduced ? 0 : Number.isFinite(own) ? own : Math.min(index * STAGGER_MS, MAX_STAGGER_MS)
|
|
132
|
+
const rtl = getComputedStyle(node).direction === "rtl"
|
|
133
|
+
let frames: Keyframe[]
|
|
134
|
+
if (reduced || kind === "fade") frames = [{ opacity: 0 }, { opacity: 1 }]
|
|
135
|
+
else if (kind === "draw-x") frames = [{ transform: "scaleX(0)" }, { transform: "scaleX(1)" }]
|
|
136
|
+
else if (kind === "draw-y") frames = [{ transform: "scaleY(0)" }, { transform: "scaleY(1)" }]
|
|
137
|
+
else if (kind === "pan") frames = [{ transform: "scale(1.08)" }, { transform: "none" }]
|
|
138
|
+
else if (kind === "drop")
|
|
139
|
+
// The pin: falls 24px, overshoots by 3px and settles, one keyframe of overshoot.
|
|
140
|
+
frames = [
|
|
141
|
+
{ opacity: 0, transform: "translateY(-24px)" },
|
|
142
|
+
{ opacity: 1, transform: "translateY(3px)", offset: 0.7 },
|
|
143
|
+
{ opacity: 1, transform: "none" },
|
|
144
|
+
]
|
|
145
|
+
else if (kind === "ring") frames = [{ opacity: 0.5, transform: "scale(0.6)" }, { opacity: 0, transform: "scale(1.8)" }]
|
|
146
|
+
else if (kind === "settle") frames = [{ opacity: 0, transform: "scale(1.04)" }, { opacity: 1, transform: "none" }]
|
|
147
|
+
else if (kind === "slide") {
|
|
148
|
+
// Travels 16px to its place, from the end side (the default) or the start side, whichever way the page reads.
|
|
149
|
+
const side = (node.dataset.revealFrom === "start" ? -1 : 1) * (rtl ? -1 : 1)
|
|
150
|
+
frames = [{ opacity: 0, transform: `translateX(${16 * side}px)` }, { opacity: 1, transform: "none" }]
|
|
151
|
+
} else if (kind === "wipe")
|
|
152
|
+
frames = [
|
|
153
|
+
{ clipPath: rtl ? "inset(0 0 0 100%)" : "inset(0 100% 0 0)", opacity: 0, transform: "scale(0.94)" },
|
|
154
|
+
{ clipPath: "inset(0 0 0 0)", opacity: 1, transform: "none" },
|
|
155
|
+
]
|
|
156
|
+
else frames = [{ opacity: 0, transform: `translateY(${kind === "word" ? 14 : 12}px)` }, { opacity: 1, transform: "none" }]
|
|
157
|
+
return node.animate(frames, {
|
|
158
|
+
duration: reduced ? FAST_MS : ["wipe", "settle", "slide", "pan", "drop", "ring"].includes(kind) ? SLOW_MS : BASE_MS,
|
|
159
|
+
delay,
|
|
160
|
+
easing: STANDARD,
|
|
161
|
+
fill: "backwards",
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Plays `playReveal` once on the `[data-reveal]` nodes inside the returned
|
|
168
|
+
* ref's element, when a fifth of it is in view. Before then, and without
|
|
169
|
+
* script, everything is simply there.
|
|
170
|
+
*/
|
|
171
|
+
export function useReveal<T extends HTMLElement>() {
|
|
172
|
+
const ref = React.useRef<T>(null)
|
|
173
|
+
useIsomorphicLayoutEffect(() => {
|
|
174
|
+
const node = ref.current
|
|
175
|
+
if (!node || typeof IntersectionObserver === "undefined") return
|
|
176
|
+
let animations: Animation[] = []
|
|
177
|
+
const observer = new IntersectionObserver(
|
|
178
|
+
(entries) => {
|
|
179
|
+
if (!entries.some((entry) => entry.isIntersecting)) return
|
|
180
|
+
observer.disconnect()
|
|
181
|
+
animations = playReveal(Array.from(node.querySelectorAll<HTMLElement>("[data-reveal]")))
|
|
182
|
+
},
|
|
183
|
+
{ threshold: 0.2 }
|
|
184
|
+
)
|
|
185
|
+
observer.observe(node)
|
|
186
|
+
return () => {
|
|
187
|
+
observer.disconnect()
|
|
188
|
+
animations.forEach((animation) => animation.cancel())
|
|
189
|
+
}
|
|
190
|
+
}, [])
|
|
191
|
+
return ref
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A sentence set word by word, each word a `data-reveal="word"` span that
|
|
196
|
+
* rises 30ms after the one before. The words stay one run of text for
|
|
197
|
+
* assistive technology.
|
|
198
|
+
*/
|
|
199
|
+
export function ContactUsWords({ children, className }: { children: string; className?: string }) {
|
|
200
|
+
const words = children.split(/\s+/).filter(Boolean)
|
|
201
|
+
return (
|
|
202
|
+
<span className={className}>
|
|
203
|
+
{words.map((word, index) => (
|
|
204
|
+
<React.Fragment key={index}>
|
|
205
|
+
{index > 0 ? " " : null}
|
|
206
|
+
<span data-reveal="word" data-reveal-delay={Math.min(index * 30, 360)} className="inline-block">
|
|
207
|
+
{word}
|
|
208
|
+
</span>
|
|
209
|
+
</React.Fragment>
|
|
210
|
+
))}
|
|
211
|
+
</span>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ───────────────────────────── Live time ───────────────────────────── */
|
|
216
|
+
|
|
217
|
+
/** One day's opening hours, "09:00" to "18:00" in the place's own time, or `null` when it is closed. */
|
|
218
|
+
export type ContactUsHours = { open: string; close: string } | null
|
|
219
|
+
|
|
220
|
+
/** A week of opening hours, Sunday first, as `Date#getDay` counts. */
|
|
221
|
+
export type ContactUsWeek = readonly [ContactUsHours, ContactUsHours, ContactUsHours, ContactUsHours, ContactUsHours, ContactUsHours, ContactUsHours]
|
|
222
|
+
|
|
223
|
+
const WEEKDAYS: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }
|
|
224
|
+
const partFormatters = new Map<string, Intl.DateTimeFormat>()
|
|
225
|
+
|
|
226
|
+
/** The weekday (0 is Sunday) and the minutes since midnight at `date` in `timeZone`. */
|
|
227
|
+
export function zoneParts(date: Date, timeZone: string) {
|
|
228
|
+
let format = partFormatters.get(timeZone)
|
|
229
|
+
if (!format) {
|
|
230
|
+
format = new Intl.DateTimeFormat("en-US", { timeZone, weekday: "short", hour: "2-digit", minute: "2-digit", hourCycle: "h23" })
|
|
231
|
+
partFormatters.set(timeZone, format)
|
|
232
|
+
}
|
|
233
|
+
const parts = format.formatToParts(date)
|
|
234
|
+
const get = (type: string) => parts.find((part) => part.type === type)?.value ?? ""
|
|
235
|
+
return { day: WEEKDAYS[get("weekday")] ?? 0, minutes: Number(get("hour")) * 60 + Number(get("minute")) }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** "14:20": the time at `date` in `timeZone`, 24-hour, always with Western digits, in any language. */
|
|
239
|
+
export function formatClock(date: Date, timeZone: string) {
|
|
240
|
+
return new Intl.DateTimeFormat("en-GB", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(date)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const toMinutes = (time: string) => Number(time.slice(0, 2)) * 60 + Number(time.slice(3, 5))
|
|
244
|
+
|
|
245
|
+
export interface ContactUsOpenState {
|
|
246
|
+
open: boolean
|
|
247
|
+
/** The weekday at `now` in the place's time zone, 0 is Sunday. */
|
|
248
|
+
day: number
|
|
249
|
+
/** While open: the minutes until it closes. */
|
|
250
|
+
closesInMinutes?: number
|
|
251
|
+
/** While closed: when it opens next, `daysAhead` days on (0 is later today) at `time`. */
|
|
252
|
+
next?: { daysAhead: number; day: number; time: string }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Whether a place on `week` is open at `now`, and when that next changes. */
|
|
256
|
+
export function openState(week: ContactUsWeek, timeZone: string, now: Date): ContactUsOpenState {
|
|
257
|
+
const { day, minutes } = zoneParts(now, timeZone)
|
|
258
|
+
const today = week[day]
|
|
259
|
+
if (today && minutes >= toMinutes(today.open) && minutes < toMinutes(today.close)) return { open: true, day, closesInMinutes: toMinutes(today.close) - minutes }
|
|
260
|
+
if (today && minutes < toMinutes(today.open)) return { open: false, day, next: { daysAhead: 0, day, time: today.open } }
|
|
261
|
+
for (let ahead = 1; ahead <= 7; ahead++) {
|
|
262
|
+
const d = (day + ahead) % 7
|
|
263
|
+
const hours = week[d]
|
|
264
|
+
if (hours) return { open: false, day, next: { daysAhead: ahead, day: d, time: hours.open } }
|
|
265
|
+
}
|
|
266
|
+
return { open: false, day }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 220 minutes becomes { hours: 3, minutes: 40 }. */
|
|
270
|
+
export const splitMinutes = (total: number) => ({ hours: Math.floor(total / 60), minutes: total % 60 })
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The time a live block shows. Give it `now` and it is frozen there, the same
|
|
274
|
+
* on the server and in the browser. Without it the first render is `null`, so
|
|
275
|
+
* the server and the browser agree, then it is the real time, ticking on every
|
|
276
|
+
* minute and paused while the tab is hidden.
|
|
277
|
+
*/
|
|
278
|
+
export function useLiveNow(now?: Date | number | string): Date | null {
|
|
279
|
+
const frozen = now === undefined ? null : new Date(now)
|
|
280
|
+
const frozenAt = frozen ? frozen.getTime() : null
|
|
281
|
+
const [live, setLive] = React.useState<Date | null>(null)
|
|
282
|
+
React.useEffect(() => {
|
|
283
|
+
if (frozenAt !== null) return
|
|
284
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
285
|
+
const tick = () => {
|
|
286
|
+
setLive(new Date())
|
|
287
|
+
const at = new Date()
|
|
288
|
+
timer = setTimeout(tick, 60_000 - (at.getSeconds() * 1000 + at.getMilliseconds()))
|
|
289
|
+
}
|
|
290
|
+
const sync = () => {
|
|
291
|
+
clearTimeout(timer)
|
|
292
|
+
if (!document.hidden) tick()
|
|
293
|
+
}
|
|
294
|
+
sync()
|
|
295
|
+
document.addEventListener("visibilitychange", sync)
|
|
296
|
+
return () => {
|
|
297
|
+
clearTimeout(timer)
|
|
298
|
+
document.removeEventListener("visibilitychange", sync)
|
|
299
|
+
}
|
|
300
|
+
}, [frozenAt])
|
|
301
|
+
return frozen ?? live
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Counts `target` up from zero once, the first time half of the element is in
|
|
306
|
+
* view, over the slow preset. Reduced motion, and a browser without
|
|
307
|
+
* IntersectionObserver, show the target at once. The first render is the target.
|
|
308
|
+
*/
|
|
309
|
+
export function useCountUp<T extends HTMLElement>(target: number) {
|
|
310
|
+
const ref = React.useRef<T>(null)
|
|
311
|
+
const [value, setValue] = React.useState(target)
|
|
312
|
+
React.useEffect(() => {
|
|
313
|
+
setValue(target)
|
|
314
|
+
const node = ref.current
|
|
315
|
+
if (!node || typeof IntersectionObserver === "undefined" || prefersReducedMotion()) return
|
|
316
|
+
let frame = 0
|
|
317
|
+
const observer = new IntersectionObserver(
|
|
318
|
+
(entries) => {
|
|
319
|
+
if (!entries.some((entry) => entry.isIntersecting)) return
|
|
320
|
+
observer.disconnect()
|
|
321
|
+
const start = performance.now()
|
|
322
|
+
const step = (at: number) => {
|
|
323
|
+
const t = Math.min(1, (at - start) / SLOW_MS)
|
|
324
|
+
setValue(Math.round(target * (1 - (1 - t) * (1 - t))))
|
|
325
|
+
if (t < 1) frame = requestAnimationFrame(step)
|
|
326
|
+
}
|
|
327
|
+
setValue(0)
|
|
328
|
+
frame = requestAnimationFrame(step)
|
|
329
|
+
},
|
|
330
|
+
{ threshold: 0.5 }
|
|
331
|
+
)
|
|
332
|
+
observer.observe(node)
|
|
333
|
+
return () => {
|
|
334
|
+
observer.disconnect()
|
|
335
|
+
cancelAnimationFrame(frame)
|
|
336
|
+
}
|
|
337
|
+
}, [target])
|
|
338
|
+
return [ref, value] as const
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* ─────────────────────────── The dark band ─────────────────────────── */
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Which theme scope a dark band takes: `dark` on a light page and `light` on a
|
|
345
|
+
* dark one, so the band is the page's opposite, as Figma's `background-inverse`
|
|
346
|
+
* panels are. A light page is assumed until the browser says otherwise.
|
|
347
|
+
*/
|
|
348
|
+
export function useInverseScope(): "dark" | "light" {
|
|
349
|
+
const [scope, setScope] = React.useState<"dark" | "light">("dark")
|
|
350
|
+
useIsomorphicLayoutEffect(() => {
|
|
351
|
+
const root = document.documentElement
|
|
352
|
+
const read = () => setScope(root.classList.contains("dark") ? "light" : "dark")
|
|
353
|
+
read()
|
|
354
|
+
const observer = new MutationObserver(read)
|
|
355
|
+
observer.observe(root, { attributes: true, attributeFilter: ["class"] })
|
|
356
|
+
return () => observer.disconnect()
|
|
357
|
+
}, [])
|
|
358
|
+
return scope
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* A panel on the page's opposite theme. Everything inside it, the atoms
|
|
363
|
+
* included, draws with the other mode's tokens, so a field on it is dark with no
|
|
364
|
+
* colour of ours. Inside, use the plain tokens (`text-foreground`,
|
|
365
|
+
* `text-muted-foreground`): they already mean what Figma's `*-inverse` tokens do.
|
|
366
|
+
*/
|
|
367
|
+
export function ContactUsInverseBand({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
368
|
+
const scope = useInverseScope()
|
|
369
|
+
return (
|
|
370
|
+
<div className={cn(scope, "bg-background text-foreground", className)} {...props}>
|
|
371
|
+
{children}
|
|
372
|
+
</div>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ──────────────────────────── Page parts ──────────────────────────── */
|
|
377
|
+
|
|
378
|
+
export interface ContactUsSectionProps extends React.HTMLAttributes<HTMLElement> {
|
|
379
|
+
/** The id of the heading that names the section. */
|
|
380
|
+
labelledBy?: string
|
|
381
|
+
/** Classes for the 1248px column inside the padding. */
|
|
382
|
+
contentClassName?: string
|
|
383
|
+
/** The padding around the column: 96px on Desktop (`lg`) or 64px (`md`), as Figma sets each Type. */
|
|
384
|
+
padding?: "lg" | "md"
|
|
385
|
+
/** The space between the column's parts: 48px (`lg`), 40px (`md`) or none. */
|
|
386
|
+
gap?: "lg" | "md" | "none"
|
|
387
|
+
/** The section's ground: `background`, or `muted`. */
|
|
388
|
+
tone?: "background" | "muted"
|
|
389
|
+
/** Something drawn behind the column and across the whole section, such as Grand Headline Form's grid. */
|
|
390
|
+
backdrop?: React.ReactNode
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Figma's section: padding 96 on Desktop, 64 × 24 on Tablet and 32 × 16 on
|
|
395
|
+
* phones (64 on Desktop for `md`), and a 1248px column.
|
|
396
|
+
*/
|
|
397
|
+
export function ContactUsSection({ labelledBy, className, contentClassName, padding = "lg", gap = "lg", tone = "background", backdrop, children, ...props }: ContactUsSectionProps) {
|
|
398
|
+
return (
|
|
399
|
+
<section
|
|
400
|
+
aria-labelledby={labelledBy}
|
|
401
|
+
className={cn(
|
|
402
|
+
"[container-type:inline-size] px-4 py-8 md:px-6 md:py-16",
|
|
403
|
+
padding === "lg" ? "lg:p-24" : "lg:p-16",
|
|
404
|
+
tone === "muted" ? "bg-muted" : "bg-background",
|
|
405
|
+
backdrop ? "relative overflow-hidden" : undefined,
|
|
406
|
+
className
|
|
407
|
+
)}
|
|
408
|
+
{...props}
|
|
409
|
+
>
|
|
410
|
+
{backdrop}
|
|
411
|
+
<div className={cn("mx-auto flex w-full max-w-[1248px] flex-col", backdrop ? "relative" : undefined, gap === "lg" ? "gap-12" : gap === "md" ? "gap-10" : "gap-0", contentClassName)}>{children}</div>
|
|
412
|
+
</section>
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export interface ContactUsHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
417
|
+
/** The id the section's `aria-labelledby` points at. */
|
|
418
|
+
headingId: string
|
|
419
|
+
/** The small line above the heading. Hidden when empty. */
|
|
420
|
+
eyebrow?: React.ReactNode
|
|
421
|
+
/** The section's heading, an `h2`. */
|
|
422
|
+
heading: React.ReactNode
|
|
423
|
+
/** The line under the heading. Hidden when empty. */
|
|
424
|
+
description?: React.ReactNode
|
|
425
|
+
/** Where the header's lines sit: at the start, or centred. */
|
|
426
|
+
align?: "start" | "center"
|
|
427
|
+
/** Replaces the eyebrow's classes. */
|
|
428
|
+
eyebrowClassName?: string
|
|
429
|
+
/** Replaces the heading's classes. */
|
|
430
|
+
headingClassName?: string
|
|
431
|
+
/** Replaces the description's classes. */
|
|
432
|
+
descriptionClassName?: string
|
|
433
|
+
/** Lets the heading rise into place once, when the block is in view. */
|
|
434
|
+
reveal?: boolean
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** The header: an optional eyebrow, the heading and its description in `muted-foreground`, 12px apart. */
|
|
438
|
+
export function ContactUsHeader({
|
|
439
|
+
headingId,
|
|
440
|
+
eyebrow,
|
|
441
|
+
heading,
|
|
442
|
+
description,
|
|
443
|
+
align = "start",
|
|
444
|
+
eyebrowClassName,
|
|
445
|
+
headingClassName,
|
|
446
|
+
descriptionClassName,
|
|
447
|
+
reveal,
|
|
448
|
+
className,
|
|
449
|
+
children,
|
|
450
|
+
...props
|
|
451
|
+
}: ContactUsHeaderProps) {
|
|
452
|
+
return (
|
|
453
|
+
<header className={cn("flex w-full flex-col gap-3", align === "center" ? "items-center text-center" : "items-start", className)} {...props}>
|
|
454
|
+
{eyebrow ? <p className={cn("text-sm font-semibold text-primary", eyebrowClassName)}>{eyebrow}</p> : null}
|
|
455
|
+
<h2 id={headingId} data-reveal={reveal ? "rise" : undefined} className={cn("text-4xl font-semibold text-foreground", headingClassName)}>
|
|
456
|
+
{heading}
|
|
457
|
+
</h2>
|
|
458
|
+
{description ? <p className={cn("text-base text-muted-foreground", descriptionClassName)}>{description}</p> : null}
|
|
459
|
+
{children}
|
|
460
|
+
</header>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** The plain hairline: a 1px `border` line, across (the default) or down. Stand-in for Separator. */
|
|
465
|
+
export function ContactUsRule({ orientation = "horizontal", className, ...props }: React.HTMLAttributes<HTMLDivElement> & { orientation?: "horizontal" | "vertical" }) {
|
|
466
|
+
return <div aria-hidden className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "w-px self-stretch", className)} {...props} />
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* A photo a block shows. No block ships with one: pass your own. Alt is empty
|
|
471
|
+
* ("") for a decorative photo. The slot crops the photo to fill it, around its
|
|
472
|
+
* middle; `focus` moves that middle, as Figma's image crops do.
|
|
473
|
+
*/
|
|
474
|
+
export interface ContactUsPhoto {
|
|
475
|
+
src: string
|
|
476
|
+
alt: string
|
|
477
|
+
/**
|
|
478
|
+
* Where the slot's crop is centred, as CSS `object-position` values such as
|
|
479
|
+
* "50% 81%": on a phone (`base`), from Tablet (`tablet`) and from Desktop
|
|
480
|
+
* (`desktop`). Each falls back to the one before, and all to the middle.
|
|
481
|
+
*/
|
|
482
|
+
focus?: { base?: string; tablet?: string; desktop?: string }
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* A photo slot: the box Figma calls Image Slot, which crops its photo to fill
|
|
487
|
+
* it. A `muted` ground shows without a photo. `photoReveal` puts a `data-reveal`
|
|
488
|
+
* on the photo itself, so a `settle` stays inside the box's clip.
|
|
489
|
+
*/
|
|
490
|
+
export function ContactUsPhotoSlot({ photo, photoReveal, className, children, ...props }: React.HTMLAttributes<HTMLDivElement> & { photo?: ContactUsPhoto; photoReveal?: string }) {
|
|
491
|
+
const focus = photo?.focus
|
|
492
|
+
const style = focus
|
|
493
|
+
? ({ "--cu-pos": focus.base, "--cu-pos-t": focus.tablet, "--cu-pos-d": focus.desktop } as React.CSSProperties)
|
|
494
|
+
: undefined
|
|
495
|
+
return (
|
|
496
|
+
<div className={cn("relative overflow-hidden bg-muted", className)} {...props}>
|
|
497
|
+
{photo ? (
|
|
498
|
+
<img
|
|
499
|
+
src={photo.src}
|
|
500
|
+
alt={photo.alt}
|
|
501
|
+
loading="lazy"
|
|
502
|
+
decoding="async"
|
|
503
|
+
data-reveal={photoReveal}
|
|
504
|
+
style={style}
|
|
505
|
+
className="absolute inset-0 size-full object-cover [object-position:var(--cu-pos,50%_50%)] [@container(min-width:40rem)]:[object-position:var(--cu-pos-t,var(--cu-pos,50%_50%))] [@container(min-width:64rem)]:[object-position:var(--cu-pos-d,var(--cu-pos-t,var(--cu-pos,50%_50%)))]"
|
|
506
|
+
/>
|
|
507
|
+
) : null}
|
|
508
|
+
{children}
|
|
509
|
+
</div>
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export interface ContactUsSocialLink {
|
|
514
|
+
/** Which brand's mark. */
|
|
515
|
+
platform: SocialPlatform
|
|
516
|
+
/** Where it goes. */
|
|
517
|
+
href: string
|
|
518
|
+
/** Names the link for assistive technology. The brand's own name without it. */
|
|
519
|
+
label?: string
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** A row of brand marks, each a link, 12px apart. The marks come from the Social Icons atom. */
|
|
523
|
+
export function ContactUsSocialLinks({ links, className }: { links: ContactUsSocialLink[]; className?: string }) {
|
|
524
|
+
return (
|
|
525
|
+
<ul className={cn("flex items-center gap-3", className)}>
|
|
526
|
+
{links.map((link) => (
|
|
527
|
+
<li key={link.platform} className="flex">
|
|
528
|
+
<a
|
|
529
|
+
href={link.href}
|
|
530
|
+
aria-label={link.label ?? SOCIAL_PLATFORM_NAMES[link.platform]}
|
|
531
|
+
className="inline-flex rounded-sm focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
532
|
+
>
|
|
533
|
+
<SocialIcon platform={link.platform} className="size-6" />
|
|
534
|
+
</a>
|
|
535
|
+
</li>
|
|
536
|
+
))}
|
|
537
|
+
</ul>
|
|
538
|
+
)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* ───────────────────────────── The form ───────────────────────────── */
|
|
542
|
+
|
|
543
|
+
export type ContactUsValue = string | boolean
|
|
544
|
+
/** What `onSubmit` receives: every field by name. Text and choices are strings (trimmed), a checkbox is a boolean. */
|
|
545
|
+
export type ContactUsValues = Record<string, ContactUsValue>
|
|
546
|
+
export type ContactUsStatus = "idle" | "pending" | "success" | "error"
|
|
547
|
+
|
|
548
|
+
export interface ContactUsOption {
|
|
549
|
+
value: string
|
|
550
|
+
label: string
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export interface ContactUsFieldDef {
|
|
554
|
+
/** The key in `onSubmit`'s values. */
|
|
555
|
+
name: string
|
|
556
|
+
kind: "text" | "email" | "tel" | "date" | "select" | "textarea" | "checkbox"
|
|
557
|
+
/** The label, as Figma writes it. A required field gets no asterisk: the atom shows the word `requiredLabel` at the end of its label row. */
|
|
558
|
+
label: string
|
|
559
|
+
/** Keeps the label for assistive technology but draws none, as Editorial Statement's fields do. */
|
|
560
|
+
hideLabel?: boolean
|
|
561
|
+
placeholder?: string
|
|
562
|
+
required?: boolean
|
|
563
|
+
autoComplete?: string
|
|
564
|
+
/** A select's choices. */
|
|
565
|
+
options?: readonly ContactUsOption[]
|
|
566
|
+
/** Size of the field or checkbox: `sm`, `md` (the default) or `lg`. Fields are 32, 36 and 40px tall. */
|
|
567
|
+
size?: "sm" | "md" | "lg"
|
|
568
|
+
/** Corner shape of a text field or select: Figma's 8px `standard` (the default), or a `rounded` pill. */
|
|
569
|
+
radius?: "standard" | "rounded"
|
|
570
|
+
/** An icon after a text field's value. Sized to 16px by the atom. */
|
|
571
|
+
endIcon?: React.ReactNode
|
|
572
|
+
/** Classes for the field group, such as the min height Figma gives a Textarea. */
|
|
573
|
+
groupClassName?: string
|
|
574
|
+
/** A textarea's limit. The counter shows from 80% of it. */
|
|
575
|
+
maxLength?: number
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** What a block says about the outcomes of a send. Default copy comes from `contactUsFormMessages`. */
|
|
579
|
+
export interface ContactUsFormMessages {
|
|
580
|
+
/** Shown on the submit button while the message sends. */
|
|
581
|
+
pendingLabel: string
|
|
582
|
+
/** Said once the message is sent. */
|
|
583
|
+
success: React.ReactNode
|
|
584
|
+
/** Said when sending failed. */
|
|
585
|
+
error: React.ReactNode
|
|
586
|
+
/** Said under a required field left empty. */
|
|
587
|
+
requiredError: string
|
|
588
|
+
/** Said under an email field that is not an address. */
|
|
589
|
+
emailError: string
|
|
590
|
+
/** Said under a required checkbox left clear. */
|
|
591
|
+
consentError: string
|
|
592
|
+
/** The word at the end of a required field's label row, in the atom's Top Right label slot. */
|
|
593
|
+
requiredLabel: string
|
|
594
|
+
/** The thank-you panel's button, which brings the form back. */
|
|
595
|
+
againLabel: string
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export const contactUsFormMessages: Record<ContactUsLocale, ContactUsFormMessages> = {
|
|
599
|
+
en: {
|
|
600
|
+
pendingLabel: "Sending",
|
|
601
|
+
success: "Thank you. Your message is on its way to the team.",
|
|
602
|
+
error: "We could not send that. Please try again.",
|
|
603
|
+
requiredError: "This field is required.",
|
|
604
|
+
emailError: "Enter a valid email address.",
|
|
605
|
+
consentError: "Accept this to continue.",
|
|
606
|
+
requiredLabel: "Required",
|
|
607
|
+
againLabel: "Send another message",
|
|
608
|
+
},
|
|
609
|
+
ar: {
|
|
610
|
+
pendingLabel: "جارٍ الإرسال",
|
|
611
|
+
success: "شكرًا لك. وصلت رسالتك إلى الفريق.",
|
|
612
|
+
error: "تعذّر الإرسال. حاول مرة أخرى.",
|
|
613
|
+
requiredError: "هذا الحقل مطلوب.",
|
|
614
|
+
emailError: "اكتب بريدًا إلكترونيًا صحيحًا.",
|
|
615
|
+
consentError: "وافق للمتابعة.",
|
|
616
|
+
requiredLabel: "مطلوب",
|
|
617
|
+
againLabel: "أرسل رسالة أخرى",
|
|
618
|
+
},
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** The messages for a language, with any the block was given in place of the defaults. */
|
|
622
|
+
export function pickFormMessages(locale: ContactUsLocale, given: Partial<ContactUsFormMessages>): ContactUsFormMessages {
|
|
623
|
+
const defaults = contactUsFormMessages[locale]
|
|
624
|
+
return {
|
|
625
|
+
pendingLabel: given.pendingLabel ?? defaults.pendingLabel,
|
|
626
|
+
success: given.success ?? defaults.success,
|
|
627
|
+
error: given.error ?? defaults.error,
|
|
628
|
+
requiredError: given.requiredError ?? defaults.requiredError,
|
|
629
|
+
emailError: given.emailError ?? defaults.emailError,
|
|
630
|
+
consentError: given.consentError ?? defaults.consentError,
|
|
631
|
+
requiredLabel: given.requiredLabel ?? defaults.requiredLabel,
|
|
632
|
+
againLabel: given.againLabel ?? defaults.againLabel,
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
637
|
+
|
|
638
|
+
export interface ContactUsFormState {
|
|
639
|
+
values: ContactUsValues
|
|
640
|
+
errors: Record<string, string>
|
|
641
|
+
status: ContactUsStatus
|
|
642
|
+
/** True while sending, or when the block is disabled. */
|
|
643
|
+
disabled: boolean
|
|
644
|
+
messages: ContactUsFormMessages
|
|
645
|
+
formRef: React.MutableRefObject<HTMLFormElement | null>
|
|
646
|
+
setValue: (name: string, value: ContactUsValue) => void
|
|
647
|
+
submit: (event: React.FormEvent) => void
|
|
648
|
+
reset: () => void
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export interface UseContactUsFormOptions {
|
|
652
|
+
fields: readonly ContactUsFieldDef[]
|
|
653
|
+
messages: ContactUsFormMessages
|
|
654
|
+
onSubmit?: (values: ContactUsValues) => void | Promise<void>
|
|
655
|
+
disabled?: boolean
|
|
656
|
+
/** What the fields hold at first, and again after "send another". */
|
|
657
|
+
defaultValues?: ContactUsValues
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* The form's state machine: idle, pending, success or error. Submitting
|
|
662
|
+
* checks every field, puts the reader's focus on the first one that fails,
|
|
663
|
+
* and only then calls `onSubmit`.
|
|
664
|
+
*/
|
|
665
|
+
export function useContactUsForm({ fields, messages, onSubmit, disabled = false, defaultValues }: UseContactUsFormOptions): ContactUsFormState {
|
|
666
|
+
const initial = React.useRef<ContactUsValues>(defaultValues ?? {})
|
|
667
|
+
const [values, setValues] = React.useState<ContactUsValues>(initial.current)
|
|
668
|
+
const [errors, setErrors] = React.useState<Record<string, string>>({})
|
|
669
|
+
const [status, setStatus] = React.useState<ContactUsStatus>("idle")
|
|
670
|
+
const formRef = React.useRef<HTMLFormElement | null>(null)
|
|
671
|
+
const focusFirst = React.useRef(false)
|
|
672
|
+
const alive = React.useRef(true)
|
|
673
|
+
React.useEffect(() => {
|
|
674
|
+
alive.current = true
|
|
675
|
+
return () => {
|
|
676
|
+
alive.current = false
|
|
677
|
+
}
|
|
678
|
+
}, [])
|
|
679
|
+
|
|
680
|
+
const setValue = React.useCallback((name: string, value: ContactUsValue) => {
|
|
681
|
+
setValues((current) => ({ ...current, [name]: value }))
|
|
682
|
+
setErrors((current) => {
|
|
683
|
+
if (!(name in current)) return current
|
|
684
|
+
const rest = { ...current }
|
|
685
|
+
delete rest[name]
|
|
686
|
+
return rest
|
|
687
|
+
})
|
|
688
|
+
setStatus((current) => (current === "error" ? "idle" : current))
|
|
689
|
+
}, [])
|
|
690
|
+
|
|
691
|
+
const complete = (): ContactUsValues =>
|
|
692
|
+
Object.fromEntries(
|
|
693
|
+
fields.map((field) => {
|
|
694
|
+
const value = values[field.name]
|
|
695
|
+
if (field.kind === "checkbox") return [field.name, value === true]
|
|
696
|
+
return [field.name, typeof value === "string" ? value.trim() : ""]
|
|
697
|
+
})
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
const validate = (clean: ContactUsValues) => {
|
|
701
|
+
const next: Record<string, string> = {}
|
|
702
|
+
for (const field of fields) {
|
|
703
|
+
const value = clean[field.name]
|
|
704
|
+
if (field.kind === "checkbox") {
|
|
705
|
+
if (field.required && value !== true) next[field.name] = messages.consentError
|
|
706
|
+
} else if (value === "") {
|
|
707
|
+
if (field.required) next[field.name] = messages.requiredError
|
|
708
|
+
} else if (field.kind === "email" && !EMAIL.test(String(value))) {
|
|
709
|
+
next[field.name] = messages.emailError
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return next
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const submit = async (event: React.FormEvent) => {
|
|
716
|
+
event.preventDefault()
|
|
717
|
+
if (status === "pending" || disabled) return
|
|
718
|
+
const clean = complete()
|
|
719
|
+
const next = validate(clean)
|
|
720
|
+
setErrors(next)
|
|
721
|
+
if (Object.keys(next).length > 0) {
|
|
722
|
+
focusFirst.current = true
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
setStatus("pending")
|
|
726
|
+
try {
|
|
727
|
+
await onSubmit?.(clean)
|
|
728
|
+
if (alive.current) setStatus("success")
|
|
729
|
+
} catch {
|
|
730
|
+
if (alive.current) setStatus("error")
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// After a failed check, the first field that failed takes focus.
|
|
735
|
+
React.useEffect(() => {
|
|
736
|
+
if (!focusFirst.current) return
|
|
737
|
+
focusFirst.current = false
|
|
738
|
+
formRef.current?.querySelector<HTMLElement>("[data-invalid] input, [data-invalid] textarea, [data-invalid] button")?.focus()
|
|
739
|
+
}, [errors])
|
|
740
|
+
|
|
741
|
+
const reset = React.useCallback(() => {
|
|
742
|
+
setValues(initial.current)
|
|
743
|
+
setErrors({})
|
|
744
|
+
setStatus("idle")
|
|
745
|
+
}, [])
|
|
746
|
+
|
|
747
|
+
return { values, errors, status, disabled: disabled || status === "pending", messages, formRef, setValue, submit, reset }
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const ContactUsFormContext = React.createContext<ContactUsFormState | null>(null)
|
|
751
|
+
|
|
752
|
+
function useFormContext() {
|
|
753
|
+
const form = React.useContext(ContactUsFormContext)
|
|
754
|
+
if (!form) throw new Error("A Contact Us field must sit inside a ContactUsForm.")
|
|
755
|
+
return form
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export interface ContactUsFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
759
|
+
/** The state from `useContactUsForm`. */
|
|
760
|
+
form: ContactUsFormState
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* The `<form>`, which checks its own fields. Once the message is sent it shows
|
|
765
|
+
* the thank-you panel in place of its children; when sending fails it adds the
|
|
766
|
+
* error under them.
|
|
767
|
+
*/
|
|
768
|
+
export function ContactUsForm({ form, className, children, ...props }: ContactUsFormProps) {
|
|
769
|
+
return (
|
|
770
|
+
<ContactUsFormContext.Provider value={form}>
|
|
771
|
+
<form ref={form.formRef} noValidate onSubmit={form.submit} aria-busy={form.status === "pending" || undefined} className={className} {...props}>
|
|
772
|
+
{form.status === "success" ? (
|
|
773
|
+
<div role="status" className="flex flex-col items-start gap-4">
|
|
774
|
+
<span className="flex size-10 items-center justify-center rounded-full bg-soft-success text-success">
|
|
775
|
+
<Check aria-hidden size={20} strokeWidth={1.5} />
|
|
776
|
+
</span>
|
|
777
|
+
<p className="text-base font-medium text-foreground">{form.messages.success}</p>
|
|
778
|
+
<Button type="button" variant="outline" size="md" onClick={form.reset}>
|
|
779
|
+
{form.messages.againLabel}
|
|
780
|
+
</Button>
|
|
781
|
+
</div>
|
|
782
|
+
) : (
|
|
783
|
+
<>
|
|
784
|
+
{children}
|
|
785
|
+
{form.status === "error" ? (
|
|
786
|
+
<p role="alert" className="text-sm text-destructive">
|
|
787
|
+
{form.messages.error}
|
|
788
|
+
</p>
|
|
789
|
+
) : null}
|
|
790
|
+
</>
|
|
791
|
+
)}
|
|
792
|
+
</form>
|
|
793
|
+
</ContactUsFormContext.Provider>
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** The submit button: it shows the pending label while the message sends. */
|
|
798
|
+
export function ContactUsSubmit({ children, ...props }: Omit<ButtonProps, "type" | "loading" | "loadingLabel">) {
|
|
799
|
+
const form = useFormContext()
|
|
800
|
+
return (
|
|
801
|
+
<Button type="submit" loading={form.status === "pending"} loadingLabel={form.messages.pendingLabel} disabled={form.disabled && form.status !== "pending"} {...props}>
|
|
802
|
+
{children}
|
|
803
|
+
</Button>
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Textarea's own box is 60px, with its label row 88px: a group can ask for
|
|
808
|
+
// more, as Figma's fixed 104px Textarea does. Tailwind reads classes as written.
|
|
809
|
+
export const CONTACT_US_TEXTAREA_88 = "min-h-[88px]"
|
|
810
|
+
export const CONTACT_US_TEXTAREA_104 = "min-h-[104px]"
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* A phone field. Browsers give `type=tel` a left-to-right direction of its own,
|
|
814
|
+
* so in an Arabic page its value and placeholder would sit on the left while
|
|
815
|
+
* every other field's sit on the right. The direction stays left-to-right, so
|
|
816
|
+
* "+971 50 123 4567" never reverses, and only the alignment follows the page.
|
|
817
|
+
*/
|
|
818
|
+
const TEL_ALIGN = "rtl:[&_[data-slot=input]]:text-right"
|
|
819
|
+
|
|
820
|
+
/** A date field keeps its value as `yyyy-mm-dd`, the same text a native date input sends. */
|
|
821
|
+
const isoDate = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`
|
|
822
|
+
const parseIsoDate = (text: string): Date | null => {
|
|
823
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text)
|
|
824
|
+
return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : null
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** One field of the form, drawn with the atom its `kind` names. */
|
|
828
|
+
export function ContactUsField({ field, className, reveal }: { field: ContactUsFieldDef; className?: string; reveal?: boolean }) {
|
|
829
|
+
const form = useFormContext()
|
|
830
|
+
const errorId = React.useId()
|
|
831
|
+
const error = form.errors[field.name]
|
|
832
|
+
const value = form.values[field.name]
|
|
833
|
+
const text = typeof value === "string" ? value : ""
|
|
834
|
+
const wrap = { "data-contact-field": field.name, "data-invalid": error ? "" : undefined, "data-reveal": reveal ? "rise" : undefined, className: cn("min-w-0", className) }
|
|
835
|
+
const aria = { "aria-required": field.required || undefined, "aria-label": field.hideLabel ? field.label : undefined }
|
|
836
|
+
// The word sits in the atom's Top Right label slot, so a field with no visible label has no slot and shows nothing. It is still required.
|
|
837
|
+
const requiredWord = field.required && !field.hideLabel ? form.messages.requiredLabel : undefined
|
|
838
|
+
const label = field.hideLabel ? undefined : field.label
|
|
839
|
+
|
|
840
|
+
if (field.kind === "select")
|
|
841
|
+
return (
|
|
842
|
+
<div {...wrap}>
|
|
843
|
+
<Select
|
|
844
|
+
name={field.name}
|
|
845
|
+
size={field.size ?? "md"}
|
|
846
|
+
radius={field.radius}
|
|
847
|
+
label={label}
|
|
848
|
+
labelEnd={requiredWord}
|
|
849
|
+
placeholder={field.placeholder}
|
|
850
|
+
items={field.options ?? []}
|
|
851
|
+
value={text === "" ? null : text}
|
|
852
|
+
onValueChange={(next) => form.setValue(field.name, next ?? "")}
|
|
853
|
+
disabled={form.disabled}
|
|
854
|
+
description={error ? <span className="text-destructive">{error}</span> : undefined}
|
|
855
|
+
{...aria}
|
|
856
|
+
/>
|
|
857
|
+
</div>
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
if (field.kind === "textarea") {
|
|
861
|
+
const max = field.maxLength
|
|
862
|
+
const near = max !== undefined && text.length >= max * 0.8
|
|
863
|
+
return (
|
|
864
|
+
<div {...wrap}>
|
|
865
|
+
<Textarea
|
|
866
|
+
name={field.name}
|
|
867
|
+
label={label}
|
|
868
|
+
labelEnd={requiredWord}
|
|
869
|
+
placeholder={field.placeholder}
|
|
870
|
+
maxLength={max}
|
|
871
|
+
disabled={form.disabled}
|
|
872
|
+
value={text}
|
|
873
|
+
onChange={(event) => form.setValue(field.name, event.target.value)}
|
|
874
|
+
invalid={Boolean(error)}
|
|
875
|
+
error={error}
|
|
876
|
+
descriptionEnd={
|
|
877
|
+
near && max !== undefined ? (
|
|
878
|
+
<span className={text.length >= max ? "text-destructive" : text.length >= max * 0.9 ? "text-warning" : undefined}>
|
|
879
|
+
{text.length}/{max}
|
|
880
|
+
</span>
|
|
881
|
+
) : undefined
|
|
882
|
+
}
|
|
883
|
+
className={field.groupClassName}
|
|
884
|
+
{...aria}
|
|
885
|
+
/>
|
|
886
|
+
</div>
|
|
887
|
+
)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (field.kind === "checkbox")
|
|
891
|
+
return (
|
|
892
|
+
<div {...wrap}>
|
|
893
|
+
<Checkbox
|
|
894
|
+
name={field.name}
|
|
895
|
+
size={field.size ?? "md"}
|
|
896
|
+
label={field.label}
|
|
897
|
+
checked={value === true}
|
|
898
|
+
disabled={form.disabled}
|
|
899
|
+
onChange={(event) => form.setValue(field.name, event.target.checked)}
|
|
900
|
+
aria-invalid={error ? true : undefined}
|
|
901
|
+
aria-describedby={error ? errorId : undefined}
|
|
902
|
+
{...aria}
|
|
903
|
+
/>
|
|
904
|
+
{error ? (
|
|
905
|
+
<p id={errorId} className="mt-1 text-xs text-destructive">
|
|
906
|
+
{error}
|
|
907
|
+
</p>
|
|
908
|
+
) : null}
|
|
909
|
+
</div>
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
if (field.kind === "date")
|
|
913
|
+
return (
|
|
914
|
+
<div {...wrap}>
|
|
915
|
+
<DatePicker
|
|
916
|
+
size={field.size ?? "md"}
|
|
917
|
+
label={label}
|
|
918
|
+
labelEnd={requiredWord}
|
|
919
|
+
placeholder={field.placeholder}
|
|
920
|
+
disabled={form.disabled}
|
|
921
|
+
value={parseIsoDate(text)}
|
|
922
|
+
onValueChange={(next) => form.setValue(field.name, next ? isoDate(next) : "")}
|
|
923
|
+
description={error ? <span className="text-destructive">{error}</span> : undefined}
|
|
924
|
+
aria-label={field.hideLabel ? field.label : undefined}
|
|
925
|
+
/>
|
|
926
|
+
</div>
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
return (
|
|
930
|
+
<div {...wrap}>
|
|
931
|
+
<Input
|
|
932
|
+
name={field.name}
|
|
933
|
+
type={field.kind}
|
|
934
|
+
size={field.size ?? "md"}
|
|
935
|
+
radius={field.radius}
|
|
936
|
+
endIcon={field.endIcon}
|
|
937
|
+
label={label}
|
|
938
|
+
labelEnd={requiredWord}
|
|
939
|
+
className={cn(field.kind === "tel" && TEL_ALIGN, field.groupClassName)}
|
|
940
|
+
placeholder={field.placeholder}
|
|
941
|
+
autoComplete={field.autoComplete}
|
|
942
|
+
disabled={form.disabled}
|
|
943
|
+
value={text}
|
|
944
|
+
onChange={(event) => form.setValue(field.name, event.target.value)}
|
|
945
|
+
invalid={Boolean(error)}
|
|
946
|
+
error={error}
|
|
947
|
+
{...aria}
|
|
948
|
+
/>
|
|
949
|
+
</div>
|
|
950
|
+
)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* A single choice, as chips: Figma's Badge, Solid Success when chosen and
|
|
955
|
+
* Outline otherwise. Each chip is an `aria-pressed` button; pressing the chosen one
|
|
956
|
+
* clears the choice. The chosen chip's `value` is the field's value.
|
|
957
|
+
*/
|
|
958
|
+
export function ContactUsChoiceGroup({ name, label, options, className }: { name: string; label: string; options: readonly ContactUsOption[]; className?: string }) {
|
|
959
|
+
const form = useFormContext()
|
|
960
|
+
const labelId = React.useId()
|
|
961
|
+
const chosen = form.values[name]
|
|
962
|
+
return (
|
|
963
|
+
<div role="group" aria-labelledby={labelId} className={cn("flex min-w-0 flex-col gap-3", className)}>
|
|
964
|
+
<p id={labelId} className="text-sm font-medium text-foreground">
|
|
965
|
+
{label}
|
|
966
|
+
</p>
|
|
967
|
+
<div className="flex flex-wrap gap-2">
|
|
968
|
+
{options.map((option) => {
|
|
969
|
+
const pressed = chosen === option.value
|
|
970
|
+
return (
|
|
971
|
+
<button
|
|
972
|
+
key={option.value}
|
|
973
|
+
type="button"
|
|
974
|
+
aria-pressed={pressed}
|
|
975
|
+
disabled={form.disabled}
|
|
976
|
+
onClick={() => form.setValue(name, pressed ? "" : option.value)}
|
|
977
|
+
className={cn(
|
|
978
|
+
badgeVariants({ variant: pressed ? "solid" : "outline", tone: "primary", size: "lg", radius: "rounded" }),
|
|
979
|
+
"cursor-pointer focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 motion-safe:transition-[transform,background-color,color] motion-safe:duration-[120ms] active:motion-safe:scale-[0.97]"
|
|
980
|
+
)}
|
|
981
|
+
>
|
|
982
|
+
{option.label}
|
|
983
|
+
</button>
|
|
984
|
+
)
|
|
985
|
+
})}
|
|
986
|
+
</div>
|
|
987
|
+
</div>
|
|
988
|
+
)
|
|
989
|
+
}
|