@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.
Files changed (962) hide show
  1. package/README.md +24 -5
  2. package/dist/cli/index.js +2983 -648
  3. package/dist/index.cjs +50 -12
  4. package/dist/index.js +50 -12
  5. package/dist/motion/index.cjs +12 -0
  6. package/dist/motion/index.d.cts +7 -0
  7. package/dist/motion/index.d.ts +7 -0
  8. package/dist/motion/index.js +12 -0
  9. package/dist/registry/index.cjs +4 -4
  10. package/dist/registry/index.js +4 -4
  11. package/dist/tokens/index.cjs +34 -8
  12. package/dist/tokens/index.d.cts +23 -1
  13. package/dist/tokens/index.d.ts +23 -1
  14. package/dist/tokens/index.js +34 -8
  15. package/package.json +6 -1
  16. package/registry/blocks/about-approach-stack-cards/AboutApproachStackCards.tsx +305 -0
  17. package/registry/blocks/about-approach-stack-cards/index.ts +9 -0
  18. package/registry/blocks/about-bento/AboutBento.tsx +442 -0
  19. package/registry/blocks/about-bento/index.ts +9 -0
  20. package/registry/blocks/about-collage-hero/AboutCollageHero.tsx +285 -0
  21. package/registry/blocks/about-collage-hero/index.ts +8 -0
  22. package/registry/blocks/about-color-bento/AboutColorBento.tsx +235 -0
  23. package/registry/blocks/about-color-bento/index.ts +6 -0
  24. package/registry/blocks/about-culture-masonry/AboutCultureMasonry.tsx +305 -0
  25. package/registry/blocks/about-culture-masonry/index.ts +9 -0
  26. package/registry/blocks/about-headline-photos/AboutHeadlinePhotos.tsx +270 -0
  27. package/registry/blocks/about-headline-photos/index.ts +8 -0
  28. package/registry/blocks/about-impact-stats-band/AboutImpactStatsBand.test.tsx +45 -0
  29. package/registry/blocks/about-impact-stats-band/AboutImpactStatsBand.tsx +348 -0
  30. package/registry/blocks/about-impact-stats-band/index.ts +9 -0
  31. package/registry/blocks/about-letter-card/AboutLetterCard.tsx +403 -0
  32. package/registry/blocks/about-letter-card/index.ts +9 -0
  33. package/registry/blocks/about-mission-statement-hero/AboutMissionStatementHero.tsx +282 -0
  34. package/registry/blocks/about-mission-statement-hero/index.ts +8 -0
  35. package/registry/blocks/about-numbered-rows/AboutNumberedRows.tsx +289 -0
  36. package/registry/blocks/about-numbered-rows/index.ts +7 -0
  37. package/registry/blocks/about-split-panel/AboutSplitPanel.tsx +247 -0
  38. package/registry/blocks/about-split-panel/index.ts +8 -0
  39. package/registry/blocks/about-spotlight/AboutSpotlight.tsx +260 -0
  40. package/registry/blocks/about-spotlight/index.ts +7 -0
  41. package/registry/blocks/about-statement-band/AboutStatementBand.tsx +236 -0
  42. package/registry/blocks/about-statement-band/index.ts +7 -0
  43. package/registry/blocks/about-story-photos/AboutStoryPhotos.test.tsx +12 -0
  44. package/registry/blocks/about-story-photos/AboutStoryPhotos.tsx +252 -0
  45. package/registry/blocks/about-story-photos/index.ts +8 -0
  46. package/registry/blocks/about-story-snapshots/AboutStorySnapshots.tsx +239 -0
  47. package/registry/blocks/about-story-snapshots/index.ts +8 -0
  48. package/registry/blocks/about-type-stack/AboutTypeStack.tsx +205 -0
  49. package/registry/blocks/about-type-stack/index.ts +6 -0
  50. package/registry/blocks/about-values-connector-list/AboutValuesConnectorList.tsx +316 -0
  51. package/registry/blocks/about-values-connector-list/index.ts +7 -0
  52. package/registry/blocks/about-values-focus/AboutValuesFocus.tsx +303 -0
  53. package/registry/blocks/about-values-focus/index.ts +8 -0
  54. package/registry/blocks/about-values-spotlight/AboutValuesSpotlight.tsx +265 -0
  55. package/registry/blocks/about-values-spotlight/index.ts +8 -0
  56. package/registry/blocks/auth-centered-card-sign-in/AuthCenteredCardSignIn.tsx +373 -0
  57. package/registry/blocks/auth-centered-card-sign-in/index.ts +9 -0
  58. package/registry/blocks/auth-check-your-email/AuthCheckYourEmail.tsx +366 -0
  59. package/registry/blocks/auth-check-your-email/index.ts +7 -0
  60. package/registry/blocks/auth-forgot-password/AuthForgotPassword.tsx +265 -0
  61. package/registry/blocks/auth-forgot-password/index.ts +8 -0
  62. package/registry/blocks/auth-locked-session-re-authenticate/AuthLockedSessionReAuthenticate.tsx +305 -0
  63. package/registry/blocks/auth-locked-session-re-authenticate/index.ts +8 -0
  64. package/registry/blocks/auth-magic-link-request/AuthMagicLinkRequest.tsx +336 -0
  65. package/registry/blocks/auth-magic-link-request/index.ts +8 -0
  66. package/registry/blocks/auth-minimal-centered-sign-in/AuthMinimalCenteredSignIn.tsx +261 -0
  67. package/registry/blocks/auth-minimal-centered-sign-in/index.ts +8 -0
  68. package/registry/blocks/auth-multi-step-sign-up-wizard/AuthMultiStepSignUpWizard.tsx +460 -0
  69. package/registry/blocks/auth-multi-step-sign-up-wizard/index.ts +8 -0
  70. package/registry/blocks/auth-otp-verification/AuthOtpVerification.tsx +348 -0
  71. package/registry/blocks/auth-otp-verification/index.ts +7 -0
  72. package/registry/blocks/auth-passkey-sign-in/AuthPasskeySignIn.tsx +294 -0
  73. package/registry/blocks/auth-passkey-sign-in/index.ts +8 -0
  74. package/registry/blocks/auth-password-changed/AuthPasswordChanged.tsx +239 -0
  75. package/registry/blocks/auth-password-changed/index.ts +8 -0
  76. package/registry/blocks/auth-phone-number-sign-in/AuthPhoneNumberSignIn.tsx +402 -0
  77. package/registry/blocks/auth-phone-number-sign-in/index.ts +10 -0
  78. package/registry/blocks/auth-photo-background-sign-in/AuthPhotoBackgroundSignIn.tsx +398 -0
  79. package/registry/blocks/auth-photo-background-sign-in/index.ts +10 -0
  80. package/registry/blocks/auth-profile-setup-onboarding/AuthProfileSetupOnboarding.tsx +420 -0
  81. package/registry/blocks/auth-profile-setup-onboarding/index.ts +7 -0
  82. package/registry/blocks/auth-qr-device-login/AuthQrDeviceLogin.tsx +303 -0
  83. package/registry/blocks/auth-qr-device-login/index.ts +7 -0
  84. package/registry/blocks/auth-reset-password/AuthResetPassword.tsx +313 -0
  85. package/registry/blocks/auth-reset-password/index.ts +7 -0
  86. package/registry/blocks/auth-sign-in-error-state/AuthSignInErrorState.tsx +419 -0
  87. package/registry/blocks/auth-sign-in-error-state/index.ts +9 -0
  88. package/registry/blocks/auth-sign-up-benefits-column/AuthSignUpBenefitsColumn.tsx +481 -0
  89. package/registry/blocks/auth-sign-up-benefits-column/index.ts +10 -0
  90. package/registry/blocks/auth-sign-up-password-strength/AuthSignUpPasswordStrength.tsx +511 -0
  91. package/registry/blocks/auth-sign-up-password-strength/index.ts +8 -0
  92. package/registry/blocks/auth-social-first-sign-in/AuthSocialFirstSignIn.tsx +337 -0
  93. package/registry/blocks/auth-social-first-sign-in/index.ts +9 -0
  94. package/registry/blocks/auth-split-photo-sign-in/AuthSplitPhotoSignIn.tsx +472 -0
  95. package/registry/blocks/auth-split-photo-sign-in/index.ts +11 -0
  96. package/registry/blocks/auth-split-testimonial-sign-up/AuthSplitTestimonialSignUp.tsx +548 -0
  97. package/registry/blocks/auth-split-testimonial-sign-up/index.ts +11 -0
  98. package/registry/blocks/auth-tabbed-sign-in-sign-up/AuthTabbedSignInSignUp.tsx +566 -0
  99. package/registry/blocks/auth-tabbed-sign-in-sign-up/index.ts +11 -0
  100. package/registry/blocks/auth-team-invite-accept/AuthTeamInviteAccept.tsx +372 -0
  101. package/registry/blocks/auth-team-invite-accept/index.ts +9 -0
  102. package/registry/blocks/auth-two-factor-authenticator/AuthTwoFactorAuthenticator.tsx +333 -0
  103. package/registry/blocks/auth-two-factor-authenticator/index.ts +7 -0
  104. package/registry/blocks/auth-two-factor-method-chooser/AuthTwoFactorMethodChooser.tsx +290 -0
  105. package/registry/blocks/auth-two-factor-method-chooser/index.ts +8 -0
  106. package/registry/blocks/auth-waitlist-early-access/AuthWaitlistEarlyAccess.tsx +430 -0
  107. package/registry/blocks/auth-waitlist-early-access/index.ts +11 -0
  108. package/registry/blocks/blog-cards-alongside.test.tsx +176 -0
  109. package/registry/blocks/blog-cards-author-spotlight/BlogCardsAuthorSpotlight.tsx +342 -0
  110. package/registry/blocks/blog-cards-author-spotlight/index.ts +6 -0
  111. package/registry/blocks/blog-cards-bento-editorial-grid/BlogCardsBentoEditorialGrid.tsx +345 -0
  112. package/registry/blocks/blog-cards-bento-editorial-grid/index.ts +7 -0
  113. package/registry/blocks/blog-cards-browse.test.tsx +367 -0
  114. package/registry/blocks/blog-cards-classic-3-up-grid/BlogCardsClassic3UpGrid.tsx +287 -0
  115. package/registry/blocks/blog-cards-classic-3-up-grid/index.ts +7 -0
  116. package/registry/blocks/blog-cards-compact-4-up-thumb-grid/BlogCardsCompact4UpThumbGrid.tsx +265 -0
  117. package/registry/blocks/blog-cards-compact-4-up-thumb-grid/index.ts +7 -0
  118. package/registry/blocks/blog-cards-dark-editorial-feature/BlogCardsDarkEditorialFeature.tsx +287 -0
  119. package/registry/blocks/blog-cards-dark-editorial-feature/index.ts +6 -0
  120. package/registry/blocks/blog-cards-featured-two-up/BlogCardsFeaturedTwoUp.tsx +295 -0
  121. package/registry/blocks/blog-cards-featured-two-up/index.ts +7 -0
  122. package/registry/blocks/blog-cards-features.test.tsx +183 -0
  123. package/registry/blocks/blog-cards-filter-sidebar-results/BlogCardsFilterSidebarResults.tsx +582 -0
  124. package/registry/blocks/blog-cards-filter-sidebar-results/index.ts +6 -0
  125. package/registry/blocks/blog-cards-grids.test.tsx +110 -0
  126. package/registry/blocks/blog-cards-hairline-text-index/BlogCardsHairlineTextIndex.tsx +193 -0
  127. package/registry/blocks/blog-cards-hairline-text-index/index.ts +7 -0
  128. package/registry/blocks/blog-cards-journal-bento/BlogCardsJournalBento.tsx +409 -0
  129. package/registry/blocks/blog-cards-journal-bento/index.ts +8 -0
  130. package/registry/blocks/blog-cards-latest-popular-split/BlogCardsLatestPopularSplit.tsx +258 -0
  131. package/registry/blocks/blog-cards-latest-popular-split/index.ts +7 -0
  132. package/registry/blocks/blog-cards-lead-ranked-picks/BlogCardsLeadRankedPicks.tsx +276 -0
  133. package/registry/blocks/blog-cards-lead-ranked-picks/index.ts +6 -0
  134. package/registry/blocks/blog-cards-left-thumb-row-cards/BlogCardsLeftThumbRowCards.tsx +288 -0
  135. package/registry/blocks/blog-cards-left-thumb-row-cards/index.ts +7 -0
  136. package/registry/blocks/blog-cards-lists.test.tsx +93 -0
  137. package/registry/blocks/blog-cards-magazine-cover-story/BlogCardsMagazineCoverStory.tsx +326 -0
  138. package/registry/blocks/blog-cards-magazine-cover-story/index.ts +6 -0
  139. package/registry/blocks/blog-cards-newsletter-latest/BlogCardsNewsletterLatest.tsx +291 -0
  140. package/registry/blocks/blog-cards-newsletter-latest/index.ts +6 -0
  141. package/registry/blocks/blog-cards-numbered-reading-list/BlogCardsNumberedReadingList.tsx +275 -0
  142. package/registry/blocks/blog-cards-numbered-reading-list/index.ts +7 -0
  143. package/registry/blocks/blog-cards-overlay-photo-trio/BlogCardsOverlayPhotoTrio.tsx +279 -0
  144. package/registry/blocks/blog-cards-overlay-photo-trio/index.ts +7 -0
  145. package/registry/blocks/blog-cards-post-carousel-with-controls/BlogCardsPostCarouselWithControls.tsx +257 -0
  146. package/registry/blocks/blog-cards-post-carousel-with-controls/index.ts +6 -0
  147. package/registry/blocks/blog-cards-pull-quote-feature/BlogCardsPullQuoteFeature.tsx +245 -0
  148. package/registry/blocks/blog-cards-pull-quote-feature/index.ts +6 -0
  149. package/registry/blocks/blog-cards-related-posts-strip/BlogCardsRelatedPostsStrip.tsx +247 -0
  150. package/registry/blocks/blog-cards-related-posts-strip/index.ts +6 -0
  151. package/registry/blocks/blog-cards-search-chips-results/BlogCardsSearchChipsResults.tsx +473 -0
  152. package/registry/blocks/blog-cards-search-chips-results/index.ts +6 -0
  153. package/registry/blocks/blog-cards-split-feature/BlogCardsSplitFeature.tsx +214 -0
  154. package/registry/blocks/blog-cards-split-feature/index.ts +6 -0
  155. package/registry/blocks/careers/careers.test.tsx +414 -0
  156. package/registry/blocks/careers/careers.tsx +391 -0
  157. package/registry/blocks/careers/index.ts +27 -0
  158. package/registry/blocks/careers-about-card-role-list/CareersAboutCardRoleList.tsx +235 -0
  159. package/registry/blocks/careers-about-card-role-list/index.ts +8 -0
  160. package/registry/blocks/careers-featured-role-spotlight/CareersFeaturedRoleSpotlight.tsx +271 -0
  161. package/registry/blocks/careers-featured-role-spotlight/index.ts +8 -0
  162. package/registry/blocks/careers-no-open-roles-empty-state/CareersNoOpenRolesEmptyState.tsx +330 -0
  163. package/registry/blocks/careers-no-open-roles-empty-state/index.ts +8 -0
  164. package/registry/blocks/careers-open-roles-hairline-list/CareersOpenRolesHairlineList.tsx +320 -0
  165. package/registry/blocks/careers-open-roles-hairline-list/index.ts +6 -0
  166. package/registry/blocks/careers-open-vacancies-table/CareersOpenVacanciesTable.tsx +493 -0
  167. package/registry/blocks/careers-open-vacancies-table/index.ts +6 -0
  168. package/registry/blocks/careers-perks-benefits/CareersPerksBenefits.tsx +181 -0
  169. package/registry/blocks/careers-perks-benefits/index.ts +8 -0
  170. package/registry/blocks/careers-role-cards-grid/CareersRoleCardsGrid.tsx +405 -0
  171. package/registry/blocks/careers-role-cards-grid/index.ts +6 -0
  172. package/registry/blocks/careers-role-detail-page/CareersRoleDetailPage.tsx +541 -0
  173. package/registry/blocks/careers-role-detail-page/index.ts +10 -0
  174. package/registry/blocks/careers-role-detail-panel/CareersRoleDetailPanel.tsx +255 -0
  175. package/registry/blocks/careers-role-detail-panel/index.ts +6 -0
  176. package/registry/blocks/careers-roles-by-department/CareersRolesByDepartment.tsx +242 -0
  177. package/registry/blocks/careers-roles-by-department/index.ts +7 -0
  178. package/registry/blocks/careers-roles-list-accordion-expanded/CareersRolesListAccordionExpanded.tsx +558 -0
  179. package/registry/blocks/careers-roles-list-accordion-expanded/index.ts +9 -0
  180. package/registry/blocks/contact-us/contact-us.test.tsx +239 -0
  181. package/registry/blocks/contact-us/contact-us.tsx +989 -0
  182. package/registry/blocks/contact-us/index.ts +34 -0
  183. package/registry/blocks/contact-us-bento-grid/ContactUsBentoGrid.tsx +304 -0
  184. package/registry/blocks/contact-us-bento-grid/index.ts +5 -0
  185. package/registry/blocks/contact-us-bento-grid.test.tsx +102 -0
  186. package/registry/blocks/contact-us-book-a-call/ContactUsBookACall.tsx +403 -0
  187. package/registry/blocks/contact-us-book-a-call/index.ts +7 -0
  188. package/registry/blocks/contact-us-book-a-call.test.tsx +134 -0
  189. package/registry/blocks/contact-us-classic-centered/ContactUsClassicCentered.tsx +199 -0
  190. package/registry/blocks/contact-us-classic-centered/index.ts +6 -0
  191. package/registry/blocks/contact-us-dark-statement/ContactUsDarkStatement.tsx +218 -0
  192. package/registry/blocks/contact-us-dark-statement/index.ts +6 -0
  193. package/registry/blocks/contact-us-editorial-and-address-card/ContactUsEditorialAndAddressCard.tsx +212 -0
  194. package/registry/blocks/contact-us-editorial-and-address-card/index.ts +6 -0
  195. package/registry/blocks/contact-us-editorial-statement/ContactUsEditorialStatement.tsx +205 -0
  196. package/registry/blocks/contact-us-editorial-statement/index.ts +6 -0
  197. package/registry/blocks/contact-us-form-contact-methods/ContactUsFormContactMethods.tsx +272 -0
  198. package/registry/blocks/contact-us-form-contact-methods/index.ts +6 -0
  199. package/registry/blocks/contact-us-form-with-faq/ContactUsFormWithFaq.tsx +383 -0
  200. package/registry/blocks/contact-us-form-with-faq/index.ts +7 -0
  201. package/registry/blocks/contact-us-form-with-faq.test.tsx +161 -0
  202. package/registry/blocks/contact-us-forms.test.tsx +333 -0
  203. package/registry/blocks/contact-us-global-offices-table/ContactUsGlobalOfficesTable.tsx +301 -0
  204. package/registry/blocks/contact-us-global-offices-table/index.ts +5 -0
  205. package/registry/blocks/contact-us-global-offices-table.test.tsx +100 -0
  206. package/registry/blocks/contact-us-grand-headline-form/ContactUsGrandHeadlineForm.tsx +180 -0
  207. package/registry/blocks/contact-us-grand-headline-form/index.ts +6 -0
  208. package/registry/blocks/contact-us-hours-and-availability/ContactUsHoursAndAvailability.tsx +412 -0
  209. package/registry/blocks/contact-us-hours-and-availability/index.ts +5 -0
  210. package/registry/blocks/contact-us-hours-and-availability.test.tsx +123 -0
  211. package/registry/blocks/contact-us-hq-feature/ContactUsHqFeature.tsx +281 -0
  212. package/registry/blocks/contact-us-hq-feature/index.ts +5 -0
  213. package/registry/blocks/contact-us-hq-feature.test.tsx +102 -0
  214. package/registry/blocks/contact-us-map-split/ContactUsMapSplit.tsx +211 -0
  215. package/registry/blocks/contact-us-map-split/index.ts +5 -0
  216. package/registry/blocks/contact-us-map-split.test.tsx +70 -0
  217. package/registry/blocks/contact-us-named-owners/ContactUsNamedOwners.tsx +324 -0
  218. package/registry/blocks/contact-us-named-owners/index.ts +6 -0
  219. package/registry/blocks/contact-us-named-owners.test.tsx +116 -0
  220. package/registry/blocks/contact-us-newsletter-combo/ContactUsNewsletterCombo.tsx +363 -0
  221. package/registry/blocks/contact-us-newsletter-combo/index.ts +6 -0
  222. package/registry/blocks/contact-us-newsletter-combo.test.tsx +165 -0
  223. package/registry/blocks/contact-us-office-cards/ContactUsOfficeCards.tsx +235 -0
  224. package/registry/blocks/contact-us-office-cards/index.ts +5 -0
  225. package/registry/blocks/contact-us-office-cards.test.tsx +71 -0
  226. package/registry/blocks/contact-us-oversized-wordmark/ContactUsOversizedWordmark.tsx +222 -0
  227. package/registry/blocks/contact-us-oversized-wordmark/index.ts +6 -0
  228. package/registry/blocks/contact-us-photo-and-dark-panel/ContactUsPhotoAndDarkPanel.tsx +185 -0
  229. package/registry/blocks/contact-us-photo-and-dark-panel/index.ts +6 -0
  230. package/registry/blocks/contact-us-photo-forms.test.tsx +311 -0
  231. package/registry/blocks/contact-us-pitch-and-inquiry/ContactUsPitchAndInquiry.tsx +202 -0
  232. package/registry/blocks/contact-us-pitch-and-inquiry/index.ts +6 -0
  233. package/registry/blocks/contact-us-rtl.test.tsx +105 -0
  234. package/registry/blocks/contact-us-ruled-split-form/ContactUsRuledSplitForm.tsx +214 -0
  235. package/registry/blocks/contact-us-ruled-split-form/index.ts +6 -0
  236. package/registry/blocks/contact-us-social-rail/ContactUsSocialRail.tsx +270 -0
  237. package/registry/blocks/contact-us-social-rail/index.ts +7 -0
  238. package/registry/blocks/contact-us-social-rail.test.tsx +121 -0
  239. package/registry/blocks/contact-us-split-photo-dark/ContactUsSplitPhotoDark.tsx +185 -0
  240. package/registry/blocks/contact-us-split-photo-dark/index.ts +6 -0
  241. package/registry/blocks/contact-us-support-channels/ContactUsSupportChannels.tsx +267 -0
  242. package/registry/blocks/contact-us-support-channels/index.ts +5 -0
  243. package/registry/blocks/contact-us-support-channels.test.tsx +81 -0
  244. package/registry/blocks/data-table/DataTable.test.tsx +1 -34
  245. package/registry/blocks/data-table/DataTable.tsx +0 -139
  246. package/registry/blocks/data-table/index.ts +1 -8
  247. package/registry/blocks/error-state-access-denied-403/ErrorStateAccessDenied403.tsx +324 -0
  248. package/registry/blocks/error-state-access-denied-403/index.ts +10 -0
  249. package/registry/blocks/error-state-broken-trail-breadcrumb/ErrorStateBrokenTrailBreadcrumb.tsx +179 -0
  250. package/registry/blocks/error-state-broken-trail-breadcrumb/index.ts +8 -0
  251. package/registry/blocks/error-state-centered-minimal/ErrorStateCenteredMinimal.tsx +119 -0
  252. package/registry/blocks/error-state-centered-minimal/index.ts +7 -0
  253. package/registry/blocks/error-state-colour-field-scatter/ErrorStateColourFieldScatter.tsx +144 -0
  254. package/registry/blocks/error-state-colour-field-scatter/index.ts +7 -0
  255. package/registry/blocks/error-state-countdown-redirect/ErrorStateCountdownRedirect.tsx +272 -0
  256. package/registry/blocks/error-state-countdown-redirect/index.ts +7 -0
  257. package/registry/blocks/error-state-dark-spotlight/ErrorStateDarkSpotlight.tsx +156 -0
  258. package/registry/blocks/error-state-dark-spotlight/index.ts +7 -0
  259. package/registry/blocks/error-state-display-numeral-split/ErrorStateDisplayNumeralSplit.tsx +156 -0
  260. package/registry/blocks/error-state-display-numeral-split/index.ts +8 -0
  261. package/registry/blocks/error-state-editorial-photo-split/ErrorStateEditorialPhotoSplit.tsx +156 -0
  262. package/registry/blocks/error-state-editorial-photo-split/index.ts +8 -0
  263. package/registry/blocks/error-state-empty-search-results/ErrorStateEmptySearchResults.tsx +243 -0
  264. package/registry/blocks/error-state-empty-search-results/index.ts +6 -0
  265. package/registry/blocks/error-state-error-card-panel/ErrorStateErrorCardPanel.tsx +213 -0
  266. package/registry/blocks/error-state-error-card-panel/index.ts +7 -0
  267. package/registry/blocks/error-state-full-bleed-photo/ErrorStateFullBleedPhoto.tsx +130 -0
  268. package/registry/blocks/error-state-full-bleed-photo/index.ts +8 -0
  269. package/registry/blocks/error-state-ghost-watermark/ErrorStateGhostWatermark.tsx +115 -0
  270. package/registry/blocks/error-state-ghost-watermark/index.ts +7 -0
  271. package/registry/blocks/error-state-helpful-links-list/ErrorStateHelpfulLinksList.tsx +185 -0
  272. package/registry/blocks/error-state-helpful-links-list/index.ts +8 -0
  273. package/registry/blocks/error-state-illustrated-empty-state/ErrorStateIllustratedEmptyState.tsx +201 -0
  274. package/registry/blocks/error-state-illustrated-empty-state/index.ts +7 -0
  275. package/registry/blocks/error-state-layered-badge-centered/ErrorStateLayeredBadgeCentered.tsx +138 -0
  276. package/registry/blocks/error-state-layered-badge-centered/index.ts +7 -0
  277. package/registry/blocks/error-state-light-editorial/ErrorStateLightEditorial.tsx +126 -0
  278. package/registry/blocks/error-state-light-editorial/index.ts +7 -0
  279. package/registry/blocks/error-state-maintenance-mode/ErrorStateMaintenanceMode.tsx +255 -0
  280. package/registry/blocks/error-state-maintenance-mode/index.ts +6 -0
  281. package/registry/blocks/error-state-marquee-404-band/ErrorStateMarquee404Band.tsx +316 -0
  282. package/registry/blocks/error-state-marquee-404-band/index.ts +7 -0
  283. package/registry/blocks/error-state-offline-state/ErrorStateOfflineState.tsx +188 -0
  284. package/registry/blocks/error-state-offline-state/index.ts +7 -0
  285. package/registry/blocks/error-state-photo-collage-scatter/ErrorStatePhotoCollageScatter.tsx +224 -0
  286. package/registry/blocks/error-state-photo-collage-scatter/index.ts +8 -0
  287. package/registry/blocks/error-state-popular-pages-grid/ErrorStatePopularPagesGrid.tsx +195 -0
  288. package/registry/blocks/error-state-popular-pages-grid/index.ts +9 -0
  289. package/registry/blocks/error-state-recovery-bento/ErrorStateRecoveryBento.tsx +274 -0
  290. package/registry/blocks/error-state-recovery-bento/index.ts +7 -0
  291. package/registry/blocks/error-state-search-recovery/ErrorStateSearchRecovery.tsx +173 -0
  292. package/registry/blocks/error-state-search-recovery/index.ts +7 -0
  293. package/registry/blocks/error-state-server-error-500/ErrorStateServerError500.tsx +277 -0
  294. package/registry/blocks/error-state-server-error-500/index.ts +9 -0
  295. package/registry/blocks/error-state-sitemap-columns/ErrorStateSitemapColumns.tsx +192 -0
  296. package/registry/blocks/error-state-sitemap-columns/index.ts +9 -0
  297. package/registry/blocks/error-state-split-colour-panel/ErrorStateSplitColourPanel.tsx +156 -0
  298. package/registry/blocks/error-state-split-colour-panel/index.ts +7 -0
  299. package/registry/blocks/error-state-support-escalation-band/ErrorStateSupportEscalationBand.tsx +186 -0
  300. package/registry/blocks/error-state-support-escalation-band/index.ts +8 -0
  301. package/registry/blocks/error-state-terminal-console/ErrorStateTerminalConsole.tsx +208 -0
  302. package/registry/blocks/error-state-terminal-console/index.ts +8 -0
  303. package/registry/blocks/faq-accent-active-card/FaqAccentActiveCard.tsx +186 -0
  304. package/registry/blocks/faq-accent-active-card/index.ts +1 -0
  305. package/registry/blocks/faq-category-chip-grid/FaqCategoryChipGrid.tsx +425 -0
  306. package/registry/blocks/faq-category-chip-grid/index.ts +8 -0
  307. package/registry/blocks/faq-category-columns/FaqCategoryColumns.tsx +248 -0
  308. package/registry/blocks/faq-category-columns/index.ts +8 -0
  309. package/registry/blocks/faq-chat-thread/FaqChatThread.tsx +273 -0
  310. package/registry/blocks/faq-chat-thread/index.ts +7 -0
  311. package/registry/blocks/faq-classic-centered/FaqClassicCentered.tsx +190 -0
  312. package/registry/blocks/faq-classic-centered/index.ts +7 -0
  313. package/registry/blocks/faq-contact-cta-card/FaqContactCtaCard.tsx +229 -0
  314. package/registry/blocks/faq-contact-cta-card/index.ts +9 -0
  315. package/registry/blocks/faq-dark-panel/FaqDarkPanel.tsx +186 -0
  316. package/registry/blocks/faq-dark-panel/index.ts +1 -0
  317. package/registry/blocks/faq-featured-question/FaqFeaturedQuestion.tsx +221 -0
  318. package/registry/blocks/faq-featured-question/index.ts +1 -0
  319. package/registry/blocks/faq-grouped-sections/FaqGroupedSections.tsx +266 -0
  320. package/registry/blocks/faq-grouped-sections/index.ts +8 -0
  321. package/registry/blocks/faq-help-hero-popular/FaqHelpHeroPopular.tsx +212 -0
  322. package/registry/blocks/faq-help-hero-popular/index.ts +7 -0
  323. package/registry/blocks/faq-masonry-cards/FaqMasonryCards.tsx +268 -0
  324. package/registry/blocks/faq-masonry-cards/index.ts +7 -0
  325. package/registry/blocks/faq-media-split/FaqMediaSplit.tsx +197 -0
  326. package/registry/blocks/faq-media-split/index.ts +8 -0
  327. package/registry/blocks/faq-mono-index/FaqMonoIndex.tsx +247 -0
  328. package/registry/blocks/faq-mono-index/index.ts +1 -0
  329. package/registry/blocks/faq-newsletter-close/FaqNewsletterClose.tsx +314 -0
  330. package/registry/blocks/faq-newsletter-close/index.ts +8 -0
  331. package/registry/blocks/faq-numbered-editorial/FaqNumberedEditorial.tsx +190 -0
  332. package/registry/blocks/faq-numbered-editorial/index.ts +1 -0
  333. package/registry/blocks/faq-search-categories/FaqSearchCategories.tsx +312 -0
  334. package/registry/blocks/faq-search-categories/index.ts +8 -0
  335. package/registry/blocks/faq-sidebar-nav/FaqSidebarNav.tsx +390 -0
  336. package/registry/blocks/faq-sidebar-nav/index.ts +8 -0
  337. package/registry/blocks/faq-soft-filled-rows/FaqSoftFilledRows.tsx +209 -0
  338. package/registry/blocks/faq-soft-filled-rows/index.ts +1 -0
  339. package/registry/blocks/faq-split-sticky/FaqSplitSticky.tsx +205 -0
  340. package/registry/blocks/faq-split-sticky/index.ts +7 -0
  341. package/registry/blocks/faq-support-channels/FaqSupportChannels.tsx +266 -0
  342. package/registry/blocks/faq-support-channels/index.ts +8 -0
  343. package/registry/blocks/faq-support-stats/FaqSupportStats.tsx +242 -0
  344. package/registry/blocks/faq-support-stats/index.ts +8 -0
  345. package/registry/blocks/faq-table-compact/FaqTableCompact.tsx +220 -0
  346. package/registry/blocks/faq-table-compact/index.ts +1 -0
  347. package/registry/blocks/faq-tabs-by-topic/FaqTabsByTopic.tsx +347 -0
  348. package/registry/blocks/faq-tabs-by-topic/index.ts +8 -0
  349. package/registry/blocks/faq-topic-tiles/FaqTopicTiles.tsx +254 -0
  350. package/registry/blocks/faq-topic-tiles/index.ts +7 -0
  351. package/registry/blocks/faq-two-column-grid/FaqTwoColumnGrid.tsx +256 -0
  352. package/registry/blocks/faq-two-column-grid/index.ts +7 -0
  353. package/registry/blocks/faq-uppercase-dense/FaqUppercaseDense.tsx +213 -0
  354. package/registry/blocks/faq-uppercase-dense/index.ts +1 -0
  355. package/registry/blocks/features/features.tsx +164 -0
  356. package/registry/blocks/features/index.ts +12 -0
  357. package/registry/blocks/features-accordion-photo/FeaturesAccordionPhoto.tsx +149 -0
  358. package/registry/blocks/features-accordion-photo/index.ts +7 -0
  359. package/registry/blocks/features-colour-block-cards/FeaturesColourBlockCards.tsx +118 -0
  360. package/registry/blocks/features-colour-block-cards/index.ts +8 -0
  361. package/registry/blocks/features-compact-icon-index/FeaturesCompactIconIndex.tsx +136 -0
  362. package/registry/blocks/features-compact-icon-index/index.ts +7 -0
  363. package/registry/blocks/features-compare-panels-chat-answer/FeaturesComparePanelsChatAnswer.tsx +346 -0
  364. package/registry/blocks/features-compare-panels-chat-answer/index.ts +8 -0
  365. package/registry/blocks/features-editorial-photo-split/FeaturesEditorialPhotoSplit.tsx +118 -0
  366. package/registry/blocks/features-editorial-photo-split/index.ts +7 -0
  367. package/registry/blocks/features-expanding-step-cards/FeaturesExpandingStepCards.tsx +300 -0
  368. package/registry/blocks/features-expanding-step-cards/index.ts +7 -0
  369. package/registry/blocks/features-feature-checklist-columns/FeaturesFeatureChecklistColumns.tsx +158 -0
  370. package/registry/blocks/features-feature-checklist-columns/index.ts +7 -0
  371. package/registry/blocks/features-feature-list-thumbnails/FeaturesFeatureListThumbnails.tsx +137 -0
  372. package/registry/blocks/features-feature-list-thumbnails/index.ts +7 -0
  373. package/registry/blocks/features-full-bleed-photo-banner/FeaturesFullBleedPhotoBanner.tsx +116 -0
  374. package/registry/blocks/features-full-bleed-photo-banner/index.ts +7 -0
  375. package/registry/blocks/features-icon-card-grid/FeaturesIconCardGrid.tsx +120 -0
  376. package/registry/blocks/features-icon-card-grid/index.ts +7 -0
  377. package/registry/blocks/features-icon-titled-preview-rail/FeaturesIconTitledPreviewRail.tsx +321 -0
  378. package/registry/blocks/features-icon-titled-preview-rail/index.ts +7 -0
  379. package/registry/blocks/features-icons-numbers.test.tsx +107 -0
  380. package/registry/blocks/features-interactive.test.tsx +175 -0
  381. package/registry/blocks/features-media-rail/FeaturesMediaRail.tsx +119 -0
  382. package/registry/blocks/features-media-rail/index.ts +7 -0
  383. package/registry/blocks/features-notched-photo-columns/FeaturesNotchedPhotoColumns.tsx +209 -0
  384. package/registry/blocks/features-notched-photo-columns/index.ts +7 -0
  385. package/registry/blocks/features-numbered-feature-rows/FeaturesNumberedFeatureRows.tsx +107 -0
  386. package/registry/blocks/features-numbered-feature-rows/index.ts +7 -0
  387. package/registry/blocks/features-numbered-steps-floating-cards/FeaturesNumberedStepsFloatingCards.tsx +324 -0
  388. package/registry/blocks/features-numbered-steps-floating-cards/index.ts +8 -0
  389. package/registry/blocks/features-perforated-photo-strip/FeaturesPerforatedPhotoStrip.tsx +218 -0
  390. package/registry/blocks/features-perforated-photo-strip/index.ts +7 -0
  391. package/registry/blocks/features-photo-bento/FeaturesPhotoBento.tsx +160 -0
  392. package/registry/blocks/features-photo-bento/index.ts +8 -0
  393. package/registry/blocks/features-photo-carousel/FeaturesPhotoCarousel.tsx +249 -0
  394. package/registry/blocks/features-photo-carousel/index.ts +7 -0
  395. package/registry/blocks/features-photo-collage/FeaturesPhotoCollage.tsx +143 -0
  396. package/registry/blocks/features-photo-collage/index.ts +7 -0
  397. package/registry/blocks/features-photo-feature-chips/FeaturesPhotoFeatureChips.tsx +125 -0
  398. package/registry/blocks/features-photo-feature-chips/index.ts +7 -0
  399. package/registry/blocks/features-photo-led.test.tsx +177 -0
  400. package/registry/blocks/features-photo-tabs-panel/FeaturesPhotoTabsPanel.tsx +197 -0
  401. package/registry/blocks/features-photo-tabs-panel/index.ts +7 -0
  402. package/registry/blocks/features-portrait-photo-cards-link-row/FeaturesPortraitPhotoCardsLinkRow.tsx +202 -0
  403. package/registry/blocks/features-portrait-photo-cards-link-row/index.ts +7 -0
  404. package/registry/blocks/features-product-in-use.test.tsx +188 -0
  405. package/registry/blocks/features-showcase-card-pair/FeaturesShowcaseCardPair.tsx +161 -0
  406. package/registry/blocks/features-showcase-card-pair/index.ts +7 -0
  407. package/registry/blocks/features-solid-colour-cards/FeaturesSolidColourCards.tsx +264 -0
  408. package/registry/blocks/features-solid-colour-cards/index.ts +7 -0
  409. package/registry/blocks/features-split-photo-icon-list/FeaturesSplitPhotoIconList.tsx +117 -0
  410. package/registry/blocks/features-split-photo-icon-list/index.ts +7 -0
  411. package/registry/blocks/features-stat-feature-band/FeaturesStatFeatureBand.tsx +114 -0
  412. package/registry/blocks/features-stat-feature-band/index.ts +7 -0
  413. package/registry/blocks/features-tinted-product-bento/FeaturesTintedProductBento.tsx +252 -0
  414. package/registry/blocks/features-tinted-product-bento/index.ts +7 -0
  415. package/registry/blocks/footer/footer.test.tsx +253 -0
  416. package/registry/blocks/footer/footer.tsx +554 -0
  417. package/registry/blocks/footer/index.ts +31 -0
  418. package/registry/blocks/footer-accordion-sitemap/FooterAccordionSitemap.tsx +260 -0
  419. package/registry/blocks/footer-accordion-sitemap/index.ts +7 -0
  420. package/registry/blocks/footer-bars.test.tsx +121 -0
  421. package/registry/blocks/footer-bento-tile/FooterBentoTile.tsx +331 -0
  422. package/registry/blocks/footer-bento-tile/index.ts +6 -0
  423. package/registry/blocks/footer-big-question-cta/FooterBigQuestionCta.tsx +250 -0
  424. package/registry/blocks/footer-big-question-cta/index.ts +6 -0
  425. package/registry/blocks/footer-big-type-link-stack/FooterBigTypeLinkStack.tsx +275 -0
  426. package/registry/blocks/footer-big-type-link-stack/index.ts +6 -0
  427. package/registry/blocks/footer-brand-panel-split/FooterBrandPanelSplit.tsx +163 -0
  428. package/registry/blocks/footer-brand-panel-split/index.ts +6 -0
  429. package/registry/blocks/footer-brand.test.tsx +608 -0
  430. package/registry/blocks/footer-careers-culture/FooterCareersCulture.tsx +297 -0
  431. package/registry/blocks/footer-careers-culture/index.ts +7 -0
  432. package/registry/blocks/footer-centered-brand-minimal/FooterCenteredBrandMinimal.tsx +121 -0
  433. package/registry/blocks/footer-centered-brand-minimal/index.ts +6 -0
  434. package/registry/blocks/footer-commerce.test.tsx +208 -0
  435. package/registry/blocks/footer-community-events/FooterCommunityEvents.tsx +263 -0
  436. package/registry/blocks/footer-community-events/index.ts +8 -0
  437. package/registry/blocks/footer-community.test.tsx +309 -0
  438. package/registry/blocks/footer-contact-first/FooterContactFirst.tsx +242 -0
  439. package/registry/blocks/footer-contact-first/index.ts +7 -0
  440. package/registry/blocks/footer-contact.test.tsx +285 -0
  441. package/registry/blocks/footer-controls.test.tsx +78 -0
  442. package/registry/blocks/footer-cta-band-columns/FooterCtaBandColumns.tsx +225 -0
  443. package/registry/blocks/footer-cta-band-columns/index.ts +6 -0
  444. package/registry/blocks/footer-cta.test.tsx +445 -0
  445. package/registry/blocks/footer-dashboard-slim-bar/FooterDashboardSlimBar.tsx +148 -0
  446. package/registry/blocks/footer-dashboard-slim-bar/index.ts +6 -0
  447. package/registry/blocks/footer-docs-page/FooterDocsPage.tsx +261 -0
  448. package/registry/blocks/footer-docs-page/index.ts +9 -0
  449. package/registry/blocks/footer-ecommerce-mega/FooterEcommerceMega.tsx +289 -0
  450. package/registry/blocks/footer-ecommerce-mega/index.ts +6 -0
  451. package/registry/blocks/footer-floating-demo-card/FooterFloatingDemoCard.tsx +261 -0
  452. package/registry/blocks/footer-floating-demo-card/index.ts +6 -0
  453. package/registry/blocks/footer-four-column-directory/FooterFourColumnDirectory.tsx +104 -0
  454. package/registry/blocks/footer-four-column-directory/index.ts +6 -0
  455. package/registry/blocks/footer-giant-wordmark/FooterGiantWordmark.tsx +252 -0
  456. package/registry/blocks/footer-giant-wordmark/index.ts +6 -0
  457. package/registry/blocks/footer-image-backed-brand/FooterImageBackedBrand.tsx +251 -0
  458. package/registry/blocks/footer-image-backed-brand/index.ts +6 -0
  459. package/registry/blocks/footer-index-numbered-columns/FooterIndexNumberedColumns.tsx +236 -0
  460. package/registry/blocks/footer-index-numbered-columns/index.ts +7 -0
  461. package/registry/blocks/footer-inline-preferences/FooterInlinePreferences.tsx +164 -0
  462. package/registry/blocks/footer-inline-preferences/index.ts +7 -0
  463. package/registry/blocks/footer-legal-first-slim/FooterLegalFirstSlim.tsx +174 -0
  464. package/registry/blocks/footer-legal-first-slim/index.ts +6 -0
  465. package/registry/blocks/footer-legal.test.tsx +180 -0
  466. package/registry/blocks/footer-local-business/FooterLocalBusiness.tsx +258 -0
  467. package/registry/blocks/footer-local-business/index.ts +7 -0
  468. package/registry/blocks/footer-marquee-strip/FooterMarqueeStrip.tsx +244 -0
  469. package/registry/blocks/footer-marquee-strip/index.ts +6 -0
  470. package/registry/blocks/footer-multi-region/FooterMultiRegion.tsx +310 -0
  471. package/registry/blocks/footer-multi-region/index.ts +7 -0
  472. package/registry/blocks/footer-newsletter-split/FooterNewsletterSplit.tsx +255 -0
  473. package/registry/blocks/footer-newsletter-split/index.ts +6 -0
  474. package/registry/blocks/footer-product-family-switcher/FooterProductFamilySwitcher.tsx +241 -0
  475. package/registry/blocks/footer-product-family-switcher/index.ts +7 -0
  476. package/registry/blocks/footer-service-promise-band/FooterServicePromiseBand.tsx +237 -0
  477. package/registry/blocks/footer-service-promise-band/index.ts +7 -0
  478. package/registry/blocks/footer-single-line-bar/FooterSingleLineBar.tsx +107 -0
  479. package/registry/blocks/footer-single-line-bar/index.ts +6 -0
  480. package/registry/blocks/footer-sitemap.test.tsx +390 -0
  481. package/registry/blocks/footer-split-hairline-micro/FooterSplitHairlineMicro.tsx +100 -0
  482. package/registry/blocks/footer-split-hairline-micro/index.ts +6 -0
  483. package/registry/blocks/footer-support-contact/FooterSupportContact.tsx +204 -0
  484. package/registry/blocks/footer-support-contact/index.ts +7 -0
  485. package/registry/blocks/footer-trust-compliance/FooterTrustCompliance.tsx +234 -0
  486. package/registry/blocks/footer-trust-compliance/index.ts +7 -0
  487. package/registry/blocks/footer-two-row-compact/FooterTwoRowCompact.tsx +124 -0
  488. package/registry/blocks/footer-two-row-compact/index.ts +6 -0
  489. package/registry/blocks/footer-two-tier-doormat/FooterTwoTierDoormat.tsx +232 -0
  490. package/registry/blocks/footer-two-tier-doormat/index.ts +8 -0
  491. package/registry/blocks/footer-utility.test.tsx +406 -0
  492. package/registry/blocks/footer-wide-sitemap-mega/FooterWideSitemapMega.tsx +227 -0
  493. package/registry/blocks/footer-wide-sitemap-mega/index.ts +6 -0
  494. package/registry/blocks/gallery-album-cards/GalleryAlbumCards.tsx +243 -0
  495. package/registry/blocks/gallery-album-cards/index.ts +8 -0
  496. package/registry/blocks/gallery-before-after-compare/GalleryBeforeAfterCompare.tsx +269 -0
  497. package/registry/blocks/gallery-before-after-compare/index.ts +7 -0
  498. package/registry/blocks/gallery-contact-sheet-wall/GalleryContactSheetWall.tsx +299 -0
  499. package/registry/blocks/gallery-contact-sheet-wall/index.ts +9 -0
  500. package/registry/blocks/gallery-culture-photo-grid/GalleryCulturePhotoGrid.tsx +203 -0
  501. package/registry/blocks/gallery-culture-photo-grid/index.ts +8 -0
  502. package/registry/blocks/gallery-dark-cinema-wall/GalleryDarkCinemaWall.tsx +180 -0
  503. package/registry/blocks/gallery-dark-cinema-wall/index.ts +7 -0
  504. package/registry/blocks/gallery-editorial-split/GalleryEditorialSplit.tsx +188 -0
  505. package/registry/blocks/gallery-editorial-split/index.ts +8 -0
  506. package/registry/blocks/gallery-featured-thumb-rail/GalleryFeaturedThumbRail.tsx +397 -0
  507. package/registry/blocks/gallery-featured-thumb-rail/index.ts +8 -0
  508. package/registry/blocks/gallery-filmstrip-carousel/GalleryFilmstripCarousel.tsx +384 -0
  509. package/registry/blocks/gallery-filmstrip-carousel/index.ts +8 -0
  510. package/registry/blocks/gallery-filter-tabs-grid/GalleryFilterTabsGrid.tsx +403 -0
  511. package/registry/blocks/gallery-filter-tabs-grid/index.ts +9 -0
  512. package/registry/blocks/gallery-full-bleed-slider/GalleryFullBleedSlider.tsx +309 -0
  513. package/registry/blocks/gallery-full-bleed-slider/index.ts +8 -0
  514. package/registry/blocks/gallery-horizontal-scroll-rail/GalleryHorizontalScrollRail.tsx +300 -0
  515. package/registry/blocks/gallery-horizontal-scroll-rail/index.ts +8 -0
  516. package/registry/blocks/gallery-hover-caption-cards/GalleryHoverCaptionCards.tsx +244 -0
  517. package/registry/blocks/gallery-hover-caption-cards/index.ts +8 -0
  518. package/registry/blocks/gallery-hover-index-list/GalleryHoverIndexList.tsx +253 -0
  519. package/registry/blocks/gallery-hover-index-list/index.ts +9 -0
  520. package/registry/blocks/gallery-lightbox-viewer/GalleryLightboxViewer.tsx +329 -0
  521. package/registry/blocks/gallery-lightbox-viewer/index.ts +7 -0
  522. package/registry/blocks/gallery-masonry-gallery/GalleryMasonryGallery.tsx +251 -0
  523. package/registry/blocks/gallery-masonry-gallery/index.ts +7 -0
  524. package/registry/blocks/gallery-mosaic-statement-tile/GalleryMosaicStatementTile.tsx +159 -0
  525. package/registry/blocks/gallery-mosaic-statement-tile/index.ts +8 -0
  526. package/registry/blocks/gallery-photo-bento/GalleryPhotoBento.tsx +166 -0
  527. package/registry/blocks/gallery-photo-bento/index.ts +8 -0
  528. package/registry/blocks/gallery-photo-spotlight/GalleryPhotoSpotlight.tsx +305 -0
  529. package/registry/blocks/gallery-photo-spotlight/index.ts +9 -0
  530. package/registry/blocks/gallery-scattered-collage/GalleryScatteredCollage.tsx +286 -0
  531. package/registry/blocks/gallery-scattered-collage/index.ts +9 -0
  532. package/registry/blocks/gallery-social-feed-strip/GallerySocialFeedStrip.tsx +288 -0
  533. package/registry/blocks/gallery-social-feed-strip/index.ts +8 -0
  534. package/registry/blocks/gallery-timeline-gallery/GalleryTimelineGallery.tsx +243 -0
  535. package/registry/blocks/gallery-timeline-gallery/index.ts +8 -0
  536. package/registry/blocks/gallery-video-gallery/GalleryVideoGallery.tsx +362 -0
  537. package/registry/blocks/gallery-video-gallery/index.ts +8 -0
  538. package/registry/blocks/hero-annotated-object/HeroAnnotatedObject.tsx +310 -0
  539. package/registry/blocks/hero-annotated-object/index.ts +10 -0
  540. package/registry/blocks/hero-arc-gallery/HeroArcGallery.tsx +244 -0
  541. package/registry/blocks/hero-arc-gallery/index.ts +9 -0
  542. package/registry/blocks/hero-bento-split/HeroBentoSplit.tsx +266 -0
  543. package/registry/blocks/hero-bento-split/index.ts +9 -0
  544. package/registry/blocks/hero-brand-color-block/HeroBrandColorBlock.tsx +259 -0
  545. package/registry/blocks/hero-brand-color-block/index.ts +8 -0
  546. package/registry/blocks/hero-colour-stage/HeroColourStage.tsx +292 -0
  547. package/registry/blocks/hero-colour-stage/index.ts +8 -0
  548. package/registry/blocks/hero-component-pattern/HeroComponentPattern.tsx +248 -0
  549. package/registry/blocks/hero-component-pattern/index.ts +9 -0
  550. package/registry/blocks/hero-curved-media-rail/HeroCurvedMediaRail.tsx +272 -0
  551. package/registry/blocks/hero-curved-media-rail/index.ts +8 -0
  552. package/registry/blocks/hero-duo-card/HeroDuoCard.tsx +210 -0
  553. package/registry/blocks/hero-duo-card/index.ts +8 -0
  554. package/registry/blocks/hero-editorial-arch/HeroEditorialArch.tsx +226 -0
  555. package/registry/blocks/hero-editorial-arch/index.ts +10 -0
  556. package/registry/blocks/hero-editorial-triptych/HeroEditorialTriptych.tsx +252 -0
  557. package/registry/blocks/hero-editorial-triptych/index.ts +9 -0
  558. package/registry/blocks/hero-floating-integrations/HeroFloatingIntegrations.tsx +454 -0
  559. package/registry/blocks/hero-floating-integrations/index.ts +11 -0
  560. package/registry/blocks/hero-framed-full-bleed/HeroFramedFullBleed.tsx +226 -0
  561. package/registry/blocks/hero-framed-full-bleed/index.ts +9 -0
  562. package/registry/blocks/hero-framed-studio-card/HeroFramedStudioCard.tsx +264 -0
  563. package/registry/blocks/hero-framed-studio-card/index.ts +8 -0
  564. package/registry/blocks/hero-illustration-split/HeroIllustrationSplit.tsx +200 -0
  565. package/registry/blocks/hero-illustration-split/index.ts +8 -0
  566. package/registry/blocks/hero-inline-image-headline/HeroInlineImageHeadline.tsx +305 -0
  567. package/registry/blocks/hero-inline-image-headline/index.ts +8 -0
  568. package/registry/blocks/hero-masked-type-reveal/HeroMaskedTypeReveal.tsx +323 -0
  569. package/registry/blocks/hero-masked-type-reveal/index.ts +9 -0
  570. package/registry/blocks/hero-motion.test.tsx +185 -0
  571. package/registry/blocks/hero-orbit-track/HeroOrbitTrack.tsx +238 -0
  572. package/registry/blocks/hero-orbit-track/index.ts +8 -0
  573. package/registry/blocks/hero-photo-constellation/HeroPhotoConstellation.tsx +228 -0
  574. package/registry/blocks/hero-photo-constellation/index.ts +8 -0
  575. package/registry/blocks/hero-scramble-decode/HeroScrambleDecode.tsx +381 -0
  576. package/registry/blocks/hero-scramble-decode/index.ts +7 -0
  577. package/registry/blocks/hero-shape-grid/HeroShapeGrid.tsx +279 -0
  578. package/registry/blocks/hero-shape-grid/index.ts +8 -0
  579. package/registry/blocks/hero-split-band-headline/HeroSplitBandHeadline.tsx +242 -0
  580. package/registry/blocks/hero-split-band-headline/index.ts +8 -0
  581. package/registry/blocks/hero-split-flap-counters/HeroSplitFlapCounters.tsx +323 -0
  582. package/registry/blocks/hero-split-flap-counters/index.ts +8 -0
  583. package/registry/blocks/hero-studio-mosaic/HeroStudioMosaic.tsx +332 -0
  584. package/registry/blocks/hero-studio-mosaic/index.ts +9 -0
  585. package/registry/blocks/hero-textured-dark/HeroTexturedDark.tsx +378 -0
  586. package/registry/blocks/hero-textured-dark/index.ts +10 -0
  587. package/registry/blocks/hero-twin-panels/HeroTwinPanels.tsx +231 -0
  588. package/registry/blocks/hero-twin-panels/index.ts +9 -0
  589. package/registry/blocks/hero-velocity-marquee/HeroVelocityMarquee.tsx +392 -0
  590. package/registry/blocks/hero-velocity-marquee/index.ts +8 -0
  591. package/registry/blocks/hero-video-in-type/HeroVideoInType.tsx +374 -0
  592. package/registry/blocks/hero-video-in-type/index.ts +9 -0
  593. package/registry/blocks/hero-video-spotlight/HeroVideoSpotlight.tsx +292 -0
  594. package/registry/blocks/hero-video-spotlight/index.ts +9 -0
  595. package/registry/blocks/hero-wave-band/HeroWaveBand.tsx +309 -0
  596. package/registry/blocks/hero-wave-band/index.ts +8 -0
  597. package/registry/blocks/how-it-works/how-it-works.tsx +330 -0
  598. package/registry/blocks/how-it-works/index.ts +28 -0
  599. package/registry/blocks/how-it-works-accordion-steps/HowItWorksAccordionSteps.tsx +213 -0
  600. package/registry/blocks/how-it-works-accordion-steps/index.ts +7 -0
  601. package/registry/blocks/how-it-works-center-spine-timeline/HowItWorksCenterSpineTimeline.tsx +200 -0
  602. package/registry/blocks/how-it-works-center-spine-timeline/index.ts +7 -0
  603. package/registry/blocks/how-it-works-column-panels/HowItWorksColumnPanels.tsx +299 -0
  604. package/registry/blocks/how-it-works-column-panels/index.ts +6 -0
  605. package/registry/blocks/how-it-works-contained-timeline/HowItWorksContainedTimeline.tsx +223 -0
  606. package/registry/blocks/how-it-works-contained-timeline/index.ts +8 -0
  607. package/registry/blocks/how-it-works-diagonal-photo-collage/HowItWorksDiagonalPhotoCollage.tsx +170 -0
  608. package/registry/blocks/how-it-works-diagonal-photo-collage/index.ts +7 -0
  609. package/registry/blocks/how-it-works-fanned-dissolve-cards/HowItWorksFannedDissolveCards.tsx +258 -0
  610. package/registry/blocks/how-it-works-fanned-dissolve-cards/index.ts +8 -0
  611. package/registry/blocks/how-it-works-full-bleed-step-panel/HowItWorksFullBleedStepPanel.tsx +319 -0
  612. package/registry/blocks/how-it-works-full-bleed-step-panel/index.ts +7 -0
  613. package/registry/blocks/how-it-works-grids.test.tsx +292 -0
  614. package/registry/blocks/how-it-works-interactive.test.tsx +262 -0
  615. package/registry/blocks/how-it-works-numbered-editorial-rows/HowItWorksNumberedEditorialRows.tsx +177 -0
  616. package/registry/blocks/how-it-works-numbered-editorial-rows/index.ts +7 -0
  617. package/registry/blocks/how-it-works-numbered-instruction-panels/HowItWorksNumberedInstructionPanels.tsx +226 -0
  618. package/registry/blocks/how-it-works-numbered-instruction-panels/index.ts +6 -0
  619. package/registry/blocks/how-it-works-numbered-photo-steps/HowItWorksNumberedPhotoSteps.tsx +150 -0
  620. package/registry/blocks/how-it-works-numbered-photo-steps/index.ts +7 -0
  621. package/registry/blocks/how-it-works-orbital-process/HowItWorksOrbitalProcess.tsx +317 -0
  622. package/registry/blocks/how-it-works-orbital-process/index.ts +7 -0
  623. package/registry/blocks/how-it-works-overlay-numeral-cards/HowItWorksOverlayNumeralCards.tsx +145 -0
  624. package/registry/blocks/how-it-works-overlay-numeral-cards/index.ts +7 -0
  625. package/registry/blocks/how-it-works-phase-line/HowItWorksPhaseLine.tsx +147 -0
  626. package/registry/blocks/how-it-works-phase-line/index.ts +8 -0
  627. package/registry/blocks/how-it-works-photo-bento-steps/HowItWorksPhotoBentoSteps.tsx +178 -0
  628. package/registry/blocks/how-it-works-photo-bento-steps/index.ts +7 -0
  629. package/registry/blocks/how-it-works-photo-rail-progress/HowItWorksPhotoRailProgress.tsx +231 -0
  630. package/registry/blocks/how-it-works-photo-rail-progress/index.ts +7 -0
  631. package/registry/blocks/how-it-works-pill-timeline/HowItWorksPillTimeline.tsx +181 -0
  632. package/registry/blocks/how-it-works-pill-timeline/index.ts +8 -0
  633. package/registry/blocks/how-it-works-rounded-cards-device-crop/HowItWorksRoundedCardsDeviceCrop.tsx +286 -0
  634. package/registry/blocks/how-it-works-rounded-cards-device-crop/index.ts +6 -0
  635. package/registry/blocks/how-it-works-six-card-grid/HowItWorksSixCardGrid.tsx +385 -0
  636. package/registry/blocks/how-it-works-six-card-grid/index.ts +6 -0
  637. package/registry/blocks/how-it-works-split-feature-band/HowItWorksSplitFeatureBand.tsx +316 -0
  638. package/registry/blocks/how-it-works-split-feature-band/index.ts +6 -0
  639. package/registry/blocks/how-it-works-split-photo-step-list/HowItWorksSplitPhotoStepList.tsx +179 -0
  640. package/registry/blocks/how-it-works-split-photo-step-list/index.ts +7 -0
  641. package/registry/blocks/how-it-works-stacked-media-text/HowItWorksStackedMediaText.tsx +310 -0
  642. package/registry/blocks/how-it-works-stacked-media-text/index.ts +8 -0
  643. package/registry/blocks/how-it-works-step-tabs-photo-panel/HowItWorksStepTabsPhotoPanel.tsx +245 -0
  644. package/registry/blocks/how-it-works-step-tabs-photo-panel/index.ts +7 -0
  645. package/registry/blocks/how-it-works-tall-visual-rail/HowItWorksTallVisualRail.tsx +349 -0
  646. package/registry/blocks/how-it-works-tall-visual-rail/index.ts +7 -0
  647. package/registry/blocks/how-it-works-timelines.test.tsx +265 -0
  648. package/registry/blocks/how-it-works-vertical-timeline-card/HowItWorksVerticalTimelineCard.tsx +169 -0
  649. package/registry/blocks/how-it-works-vertical-timeline-card/index.ts +8 -0
  650. package/registry/blocks/how-it-works-visual-steps.test.tsx +368 -0
  651. package/registry/blocks/logo-cloud-bento-logo-wall/LogoCloudBentoLogoWall.tsx +159 -0
  652. package/registry/blocks/logo-cloud-bento-logo-wall/index.ts +7 -0
  653. package/registry/blocks/logo-cloud-bordered-cell-grid/LogoCloudBorderedCellGrid.tsx +132 -0
  654. package/registry/blocks/logo-cloud-bordered-cell-grid/index.ts +7 -0
  655. package/registry/blocks/logo-cloud-centered-caption-row/LogoCloudCenteredCaptionRow.tsx +141 -0
  656. package/registry/blocks/logo-cloud-centered-caption-row/index.ts +7 -0
  657. package/registry/blocks/logo-cloud-contained-card/LogoCloudContainedCard.tsx +106 -0
  658. package/registry/blocks/logo-cloud-contained-card/index.ts +7 -0
  659. package/registry/blocks/logo-cloud-dark-logo-wall/LogoCloudDarkLogoWall.tsx +123 -0
  660. package/registry/blocks/logo-cloud-dark-logo-wall/index.ts +7 -0
  661. package/registry/blocks/logo-cloud-divider-rule-row/LogoCloudDividerRuleRow.tsx +156 -0
  662. package/registry/blocks/logo-cloud-divider-rule-row/index.ts +7 -0
  663. package/registry/blocks/logo-cloud-editorial-eyebrow-split/LogoCloudEditorialEyebrowSplit.tsx +121 -0
  664. package/registry/blocks/logo-cloud-editorial-eyebrow-split/index.ts +7 -0
  665. package/registry/blocks/logo-cloud-enterprise-wall/LogoCloudEnterpriseWall.tsx +114 -0
  666. package/registry/blocks/logo-cloud-enterprise-wall/index.ts +7 -0
  667. package/registry/blocks/logo-cloud-flanked-heading/LogoCloudFlankedHeading.tsx +162 -0
  668. package/registry/blocks/logo-cloud-flanked-heading/index.ts +8 -0
  669. package/registry/blocks/logo-cloud-grid-closing-cta/LogoCloudGridClosingCta.tsx +173 -0
  670. package/registry/blocks/logo-cloud-grid-closing-cta/index.ts +8 -0
  671. package/registry/blocks/logo-cloud-heading-grid/LogoCloudHeadingGrid.tsx +124 -0
  672. package/registry/blocks/logo-cloud-heading-grid/index.ts +7 -0
  673. package/registry/blocks/logo-cloud-heading-left-grid-right/LogoCloudHeadingLeftGridRight.tsx +172 -0
  674. package/registry/blocks/logo-cloud-heading-left-grid-right/index.ts +8 -0
  675. package/registry/blocks/logo-cloud-logo-metric-tiles/LogoCloudLogoMetricTiles.tsx +159 -0
  676. package/registry/blocks/logo-cloud-logo-metric-tiles/index.ts +8 -0
  677. package/registry/blocks/logo-cloud-logo-quote-cards/LogoCloudLogoQuoteCards.tsx +162 -0
  678. package/registry/blocks/logo-cloud-logo-quote-cards/index.ts +8 -0
  679. package/registry/blocks/logo-cloud-logo-testimonial-strip/LogoCloudLogoTestimonialStrip.tsx +159 -0
  680. package/registry/blocks/logo-cloud-logo-testimonial-strip/index.ts +8 -0
  681. package/registry/blocks/logo-cloud-named-integration-cards/LogoCloudNamedIntegrationCards.tsx +186 -0
  682. package/registry/blocks/logo-cloud-named-integration-cards/index.ts +9 -0
  683. package/registry/blocks/logo-cloud-pyramid-row-stack/LogoCloudPyramidRowStack.tsx +156 -0
  684. package/registry/blocks/logo-cloud-pyramid-row-stack/index.ts +7 -0
  685. package/registry/blocks/logo-cloud-quote-panel-grid/LogoCloudQuotePanelGrid.tsx +177 -0
  686. package/registry/blocks/logo-cloud-quote-panel-grid/index.ts +8 -0
  687. package/registry/blocks/logo-cloud-scatter-field/LogoCloudScatterField.tsx +190 -0
  688. package/registry/blocks/logo-cloud-scatter-field/index.ts +8 -0
  689. package/registry/blocks/logo-cloud-side-label-rail/LogoCloudSideLabelRail.tsx +139 -0
  690. package/registry/blocks/logo-cloud-side-label-rail/index.ts +7 -0
  691. package/registry/blocks/logo-cloud-stat-rail-above-grid/LogoCloudStatRailAboveGrid.tsx +173 -0
  692. package/registry/blocks/logo-cloud-stat-rail-above-grid/index.ts +8 -0
  693. package/registry/blocks/logo-cloud-tabbed-industry-filter/LogoCloudTabbedIndustryFilter.tsx +200 -0
  694. package/registry/blocks/logo-cloud-tabbed-industry-filter/index.ts +8 -0
  695. package/registry/blocks/navbar/index.ts +22 -11
  696. package/registry/blocks/navbar/navbar.test.tsx +42 -0
  697. package/registry/blocks/navbar/navbar.tsx +455 -0
  698. package/registry/blocks/navbar-active-action-with-border/NavbarActiveActionWithBorder.tsx +120 -0
  699. package/registry/blocks/navbar-active-action-with-border/index.ts +6 -0
  700. package/registry/blocks/navbar-active-action-with-round/NavbarActiveActionWithRound.tsx +120 -0
  701. package/registry/blocks/navbar-active-action-with-round/index.ts +6 -0
  702. package/registry/blocks/navbar-centered-floating/NavbarCenteredFloating.tsx +121 -0
  703. package/registry/blocks/navbar-centered-floating/index.ts +6 -0
  704. package/registry/blocks/navbar-default/NavbarDefault.tsx +102 -0
  705. package/registry/blocks/navbar-default/index.ts +6 -0
  706. package/registry/blocks/navbar-e-commerce/NavbarECommerce.tsx +212 -0
  707. package/registry/blocks/navbar-e-commerce/index.ts +6 -0
  708. package/registry/blocks/navbar-extended-at-top/NavbarExtendedAtTop.tsx +155 -0
  709. package/registry/blocks/navbar-extended-at-top/index.ts +7 -0
  710. package/registry/blocks/navbar-multiple-action/NavbarMultipleAction.tsx +133 -0
  711. package/registry/blocks/navbar-multiple-action/index.ts +6 -0
  712. package/registry/blocks/navbar-multiple-icon/NavbarMultipleIcon.tsx +132 -0
  713. package/registry/blocks/navbar-multiple-icon/index.ts +6 -0
  714. package/registry/blocks/navbar-rounded/NavbarRounded.tsx +140 -0
  715. package/registry/blocks/navbar-rounded/index.ts +6 -0
  716. package/registry/blocks/navbar-rounded-small/NavbarRoundedSmall.tsx +143 -0
  717. package/registry/blocks/navbar-rounded-small/index.ts +6 -0
  718. package/registry/blocks/navbar-small/NavbarSmall.tsx +142 -0
  719. package/registry/blocks/navbar-small/index.ts +6 -0
  720. package/registry/blocks/navbar-with-centered-logo/NavbarWithCenteredLogo.tsx +118 -0
  721. package/registry/blocks/navbar-with-centered-logo/index.ts +6 -0
  722. package/registry/blocks/navbar-with-cta-button/NavbarWithCtaButton.tsx +131 -0
  723. package/registry/blocks/navbar-with-cta-button/index.ts +6 -0
  724. package/registry/blocks/navbar-with-dropdown/NavbarWithDropdown.tsx +118 -0
  725. package/registry/blocks/navbar-with-dropdown/index.ts +6 -0
  726. package/registry/blocks/navbar-with-icon-indicator-avatar/NavbarWithIconIndicatorAvatar.tsx +85 -0
  727. package/registry/blocks/navbar-with-icon-indicator-avatar/index.ts +6 -0
  728. package/registry/blocks/navbar-with-search-input/NavbarWithSearchInput.tsx +90 -0
  729. package/registry/blocks/navbar-with-search-input/index.ts +6 -0
  730. package/registry/blocks/navbar-with-submenu/NavbarWithSubmenu.tsx +156 -0
  731. package/registry/blocks/navbar-with-submenu/index.ts +6 -0
  732. package/registry/blocks/page-header/PageHeader.tsx +5 -9
  733. package/registry/blocks/pricing/index.ts +20 -0
  734. package/registry/blocks/pricing/pricing.test.tsx +82 -0
  735. package/registry/blocks/pricing/pricing.tsx +343 -0
  736. package/registry/blocks/pricing-comparison-matrix/PricingComparisonMatrix.tsx +336 -0
  737. package/registry/blocks/pricing-comparison-matrix/index.ts +10 -0
  738. package/registry/blocks/pricing-plan-picker-summary/PricingPlanPickerSummary.tsx +399 -0
  739. package/registry/blocks/pricing-plan-picker-summary/index.ts +8 -0
  740. package/registry/blocks/pricing-plan-spotlight/PricingPlanSpotlight.tsx +281 -0
  741. package/registry/blocks/pricing-plan-spotlight/index.ts +7 -0
  742. package/registry/blocks/pricing-tiers/PricingTiers.tsx +264 -0
  743. package/registry/blocks/pricing-tiers/index.ts +8 -0
  744. package/registry/blocks/pricing-two-plan-compare/PricingTwoPlanCompare.tsx +187 -0
  745. package/registry/blocks/pricing-two-plan-compare/index.ts +7 -0
  746. package/registry/blocks/social-proof/index.ts +36 -0
  747. package/registry/blocks/social-proof/social-proof.test.tsx +504 -0
  748. package/registry/blocks/social-proof/social-proof.tsx +759 -0
  749. package/registry/blocks/social-proof-app-review-row/SocialProofAppReviewRow.tsx +168 -0
  750. package/registry/blocks/social-proof-app-review-row/index.ts +7 -0
  751. package/registry/blocks/social-proof-arch-portrait-quote/SocialProofArchPortraitQuote.tsx +114 -0
  752. package/registry/blocks/social-proof-arch-portrait-quote/index.ts +6 -0
  753. package/registry/blocks/social-proof-avatar-selector-row/SocialProofAvatarSelectorRow.tsx +276 -0
  754. package/registry/blocks/social-proof-avatar-selector-row/index.ts +7 -0
  755. package/registry/blocks/social-proof-benefit-title-cards/SocialProofBenefitTitleCards.tsx +182 -0
  756. package/registry/blocks/social-proof-benefit-title-cards/index.ts +8 -0
  757. package/registry/blocks/social-proof-bracket-framed-quote/SocialProofBracketFramedQuote.tsx +120 -0
  758. package/registry/blocks/social-proof-bracket-framed-quote/index.ts +6 -0
  759. package/registry/blocks/social-proof-case-study-row/SocialProofCaseStudyRow.tsx +210 -0
  760. package/registry/blocks/social-proof-case-study-row/index.ts +7 -0
  761. package/registry/blocks/social-proof-centered-logo-spotlight/SocialProofCenteredLogoSpotlight.tsx +103 -0
  762. package/registry/blocks/social-proof-centered-logo-spotlight/index.ts +6 -0
  763. package/registry/blocks/social-proof-classic-trio-grid/SocialProofClassicTrioGrid.tsx +138 -0
  764. package/registry/blocks/social-proof-classic-trio-grid/index.ts +7 -0
  765. package/registry/blocks/social-proof-dark-editorial-split/SocialProofDarkEditorialSplit.tsx +111 -0
  766. package/registry/blocks/social-proof-dark-editorial-split/index.ts +6 -0
  767. package/registry/blocks/social-proof-giant-quote-duo/SocialProofGiantQuoteDuo.tsx +156 -0
  768. package/registry/blocks/social-proof-giant-quote-duo/index.ts +8 -0
  769. package/registry/blocks/social-proof-highlighted-phrase-spotlight/SocialProofHighlightedPhraseSpotlight.tsx +130 -0
  770. package/registry/blocks/social-proof-highlighted-phrase-spotlight/index.ts +7 -0
  771. package/registry/blocks/social-proof-overlay-photo-quote/SocialProofOverlayPhotoQuote.tsx +111 -0
  772. package/registry/blocks/social-proof-overlay-photo-quote/index.ts +7 -0
  773. package/registry/blocks/social-proof-persona-pillars/SocialProofPersonaPillars.tsx +253 -0
  774. package/registry/blocks/social-proof-persona-pillars/index.ts +7 -0
  775. package/registry/blocks/social-proof-progress-carousel/SocialProofProgressCarousel.tsx +249 -0
  776. package/registry/blocks/social-proof-progress-carousel/index.ts +8 -0
  777. package/registry/blocks/social-proof-proof-bento/SocialProofProofBento.tsx +218 -0
  778. package/registry/blocks/social-proof-proof-bento/index.ts +6 -0
  779. package/registry/blocks/social-proof-spotlight-carousel/SocialProofSpotlightCarousel.tsx +212 -0
  780. package/registry/blocks/social-proof-spotlight-carousel/index.ts +7 -0
  781. package/registry/blocks/social-proof-staggered-columns/SocialProofStaggeredColumns.tsx +227 -0
  782. package/registry/blocks/social-proof-staggered-columns/index.ts +7 -0
  783. package/registry/blocks/social-proof-stat-story-cards/SocialProofStatStoryCards.tsx +218 -0
  784. package/registry/blocks/social-proof-stat-story-cards/index.ts +7 -0
  785. package/registry/blocks/social-proof-tinted-panel-portrait/SocialProofTintedPanelPortrait.tsx +130 -0
  786. package/registry/blocks/social-proof-tinted-panel-portrait/index.ts +7 -0
  787. package/registry/blocks/social-proof-tweet-wall/SocialProofTweetWall.tsx +179 -0
  788. package/registry/blocks/social-proof-tweet-wall/index.ts +7 -0
  789. package/registry/blocks/social-proof-vertical-column-wall/SocialProofVerticalColumnWall.tsx +220 -0
  790. package/registry/blocks/social-proof-vertical-column-wall/index.ts +7 -0
  791. package/registry/blocks/social-proof-video-story-cards/SocialProofVideoStoryCards.tsx +177 -0
  792. package/registry/blocks/social-proof-video-story-cards/index.ts +7 -0
  793. package/registry/blocks/social-proof-workplace-portrait-stories/SocialProofWorkplacePortraitStories.tsx +320 -0
  794. package/registry/blocks/social-proof-workplace-portrait-stories/index.ts +7 -0
  795. package/registry/blocks/team/index.ts +21 -0
  796. package/registry/blocks/team/team-classes.test.ts +35 -0
  797. package/registry/blocks/team/team.tsx +315 -0
  798. package/registry/blocks/team-annotated-portrait-lineup/TeamAnnotatedPortraitLineup.tsx +310 -0
  799. package/registry/blocks/team-annotated-portrait-lineup/index.ts +8 -0
  800. package/registry/blocks/team-annotated-portrait-lineup/team-annotated-portrait-lineup.test.tsx +71 -0
  801. package/registry/blocks/team-compact-directory-rows/TeamCompactDirectoryRows.tsx +190 -0
  802. package/registry/blocks/team-compact-directory-rows/compact-directory-rows.test.tsx +40 -0
  803. package/registry/blocks/team-compact-directory-rows/index.ts +7 -0
  804. package/registry/blocks/team-department-tabs-directory/TeamDepartmentTabsDirectory.tsx +414 -0
  805. package/registry/blocks/team-department-tabs-directory/department-tabs-directory.test.tsx +87 -0
  806. package/registry/blocks/team-department-tabs-directory/index.ts +10 -0
  807. package/registry/blocks/team-founder-spotlight/TeamFounderSpotlight.tsx +176 -0
  808. package/registry/blocks/team-founder-spotlight/index.ts +8 -0
  809. package/registry/blocks/team-founder-spotlight/team-founder-spotlight.test.tsx +70 -0
  810. package/registry/blocks/team-grid-backdrop-roster/TeamGridBackdropRoster.tsx +234 -0
  811. package/registry/blocks/team-grid-backdrop-roster/grid-backdrop-roster.test.tsx +72 -0
  812. package/registry/blocks/team-grid-backdrop-roster/index.ts +8 -0
  813. package/registry/blocks/team-hairline-grid/TeamHairlineGrid.tsx +190 -0
  814. package/registry/blocks/team-hairline-grid/hairline-grid.test.tsx +49 -0
  815. package/registry/blocks/team-hairline-grid/index.ts +1 -0
  816. package/registry/blocks/team-leadership-grid/TeamLeadershipGrid.tsx +183 -0
  817. package/registry/blocks/team-leadership-grid/index.ts +7 -0
  818. package/registry/blocks/team-leadership-grid/team-leadership-grid.test.tsx +38 -0
  819. package/registry/blocks/team-member-carousel/TeamMemberCarousel.tsx +178 -0
  820. package/registry/blocks/team-member-carousel/index.ts +7 -0
  821. package/registry/blocks/team-member-carousel/team-member-carousel.test.tsx +58 -0
  822. package/registry/blocks/team-member-table/TeamMemberTable.tsx +184 -0
  823. package/registry/blocks/team-member-table/index.ts +7 -0
  824. package/registry/blocks/team-member-table/member-table.test.tsx +43 -0
  825. package/registry/blocks/team-meta-profile-cards/TeamMetaProfileCards.tsx +155 -0
  826. package/registry/blocks/team-meta-profile-cards/index.ts +7 -0
  827. package/registry/blocks/team-meta-profile-cards/team-meta-profile-cards.test.tsx +29 -0
  828. package/registry/blocks/team-overlay-portrait-cards/TeamOverlayPortraitCards.tsx +145 -0
  829. package/registry/blocks/team-overlay-portrait-cards/index.ts +7 -0
  830. package/registry/blocks/team-overlay-portrait-cards/team-overlay-portrait-cards.test.tsx +49 -0
  831. package/registry/blocks/team-portrait-wall/TeamPortraitWall.tsx +131 -0
  832. package/registry/blocks/team-portrait-wall/index.ts +1 -0
  833. package/registry/blocks/team-portrait-wall/portrait-wall.test.tsx +45 -0
  834. package/registry/blocks/team-split-intro-member-rail/TeamSplitIntroMemberRail.tsx +203 -0
  835. package/registry/blocks/team-split-intro-member-rail/index.ts +7 -0
  836. package/registry/blocks/team-split-intro-member-rail/split-intro-member-rail.test.tsx +52 -0
  837. package/registry/blocks/team-we-re-hiring-cta/TeamWeReHiringCta.tsx +192 -0
  838. package/registry/blocks/team-we-re-hiring-cta/index.ts +7 -0
  839. package/registry/blocks/team-we-re-hiring-cta/we-re-hiring-cta.test.tsx +44 -0
  840. package/registry/blocks/video-call/index.ts +53 -0
  841. package/registry/blocks/video-call/video-call-around.test.tsx +427 -0
  842. package/registry/blocks/video-call/video-call-before.test.tsx +438 -0
  843. package/registry/blocks/video-call/video-call-hero.test.tsx +236 -0
  844. package/registry/blocks/video-call/video-call-stages.test.tsx +477 -0
  845. package/registry/blocks/video-call/video-call.test.tsx +794 -0
  846. package/registry/blocks/video-call/video-call.tsx +1282 -0
  847. package/registry/blocks/video-call-ai-meeting-recap/VideoCallAiMeetingRecap.tsx +466 -0
  848. package/registry/blocks/video-call-ai-meeting-recap/index.ts +9 -0
  849. package/registry/blocks/video-call-call-controls-dock/VideoCallCallControlsDock.tsx +211 -0
  850. package/registry/blocks/video-call-call-controls-dock/index.ts +6 -0
  851. package/registry/blocks/video-call-call-ended-summary/VideoCallCallEndedSummary.tsx +464 -0
  852. package/registry/blocks/video-call-call-ended-summary/index.ts +10 -0
  853. package/registry/blocks/video-call-call-with-side-panel/VideoCallCallWithSidePanel.tsx +357 -0
  854. package/registry/blocks/video-call-call-with-side-panel/index.ts +6 -0
  855. package/registry/blocks/video-call-captions-reactions/VideoCallCaptionsReactions.tsx +467 -0
  856. package/registry/blocks/video-call-captions-reactions/index.ts +10 -0
  857. package/registry/blocks/video-call-grid-gallery-call/VideoCallGridGalleryCall.tsx +312 -0
  858. package/registry/blocks/video-call-grid-gallery-call/index.ts +6 -0
  859. package/registry/blocks/video-call-hero-call-mockup/VideoCallHeroCallMockup.tsx +349 -0
  860. package/registry/blocks/video-call-hero-call-mockup/index.ts +7 -0
  861. package/registry/blocks/video-call-incoming-call/VideoCallIncomingCall.tsx +336 -0
  862. package/registry/blocks/video-call-incoming-call/index.ts +7 -0
  863. package/registry/blocks/video-call-join-link-dial-in/VideoCallJoinLinkDialIn.tsx +409 -0
  864. package/registry/blocks/video-call-join-link-dial-in/index.ts +9 -0
  865. package/registry/blocks/video-call-pre-join-lobby/VideoCallPreJoinLobby.tsx +345 -0
  866. package/registry/blocks/video-call-pre-join-lobby/index.ts +7 -0
  867. package/registry/blocks/video-call-screen-share-stage/VideoCallScreenShareStage.tsx +379 -0
  868. package/registry/blocks/video-call-screen-share-stage/index.ts +7 -0
  869. package/registry/blocks/video-call-speaker-filmstrip/VideoCallSpeakerFilmstrip.tsx +303 -0
  870. package/registry/blocks/video-call-speaker-filmstrip/index.ts +6 -0
  871. package/registry/blocks/video-call-upcoming-meetings/VideoCallUpcomingMeetings.tsx +349 -0
  872. package/registry/blocks/video-call-upcoming-meetings/index.ts +7 -0
  873. package/registry/blocks/video-call-waiting-room-knock/VideoCallWaitingRoomKnock.tsx +412 -0
  874. package/registry/blocks/video-call-waiting-room-knock/index.ts +7 -0
  875. package/registry/text-props-undefined.test.tsx +3 -26
  876. package/registry/ui/accordion/accordion.test.tsx +320 -0
  877. package/registry/ui/accordion/accordion.tsx +353 -0
  878. package/registry/ui/accordion/index.ts +15 -0
  879. package/registry/ui/alert/alert.test.tsx +325 -0
  880. package/registry/ui/alert/alert.tsx +384 -0
  881. package/registry/ui/alert/index.ts +10 -0
  882. package/registry/ui/alert-dialog/alert-dialog.test.tsx +396 -0
  883. package/registry/ui/alert-dialog/alert-dialog.tsx +423 -0
  884. package/registry/ui/alert-dialog/index.ts +21 -0
  885. package/registry/ui/avatar/avatar.test.tsx +2 -2
  886. package/registry/ui/avatar/avatar.tsx +3 -7
  887. package/registry/ui/breadcrumb/breadcrumb.test.tsx +305 -0
  888. package/registry/ui/breadcrumb/breadcrumb.tsx +205 -0
  889. package/registry/ui/breadcrumb/index.ts +11 -0
  890. package/registry/ui/button/button.test.tsx +353 -40
  891. package/registry/ui/button/button.tsx +304 -30
  892. package/registry/ui/calendar/calendar.test.tsx +192 -0
  893. package/registry/ui/calendar/calendar.tsx +405 -0
  894. package/registry/ui/calendar/index.ts +1 -0
  895. package/registry/ui/carousel/carousel.test.tsx +720 -0
  896. package/registry/ui/carousel/carousel.tsx +554 -0
  897. package/registry/ui/carousel/index.ts +11 -0
  898. package/registry/ui/checkbox/checkbox.test.tsx +318 -2
  899. package/registry/ui/checkbox/checkbox.tsx +253 -67
  900. package/registry/ui/checkbox/index.ts +1 -1
  901. package/registry/ui/content-carousel/content-carousel.test.tsx +303 -0
  902. package/registry/ui/content-carousel/content-carousel.tsx +180 -0
  903. package/registry/ui/content-carousel/index.ts +8 -0
  904. package/registry/ui/date-picker/date-picker.test.tsx +216 -0
  905. package/registry/ui/date-picker/date-picker.tsx +461 -0
  906. package/registry/ui/date-picker/index.ts +8 -0
  907. package/registry/ui/dialog/dialog.test.tsx +438 -0
  908. package/registry/ui/dialog/dialog.tsx +533 -0
  909. package/registry/ui/dialog/index.ts +20 -0
  910. package/registry/ui/dropdown-menu/dropdown-menu.test.tsx +502 -0
  911. package/registry/ui/dropdown-menu/dropdown-menu.tsx +627 -0
  912. package/registry/ui/dropdown-menu/index.ts +32 -0
  913. package/registry/ui/fasla-logo/fasla-logo.tsx +101 -0
  914. package/registry/ui/fasla-logo/index.ts +1 -0
  915. package/registry/ui/input/index.ts +10 -1
  916. package/registry/ui/input/input.test.tsx +251 -44
  917. package/registry/ui/input/input.tsx +311 -40
  918. package/registry/ui/input-number/index.ts +1 -0
  919. package/registry/ui/input-number/input-number.test.tsx +302 -0
  920. package/registry/ui/input-number/input-number.tsx +398 -0
  921. package/registry/ui/input-otp/index.ts +7 -0
  922. package/registry/ui/input-otp/input-otp.test.tsx +299 -0
  923. package/registry/ui/input-otp/input-otp.tsx +279 -0
  924. package/registry/ui/pagination/index.ts +12 -0
  925. package/registry/ui/pagination/pagination.test.tsx +330 -0
  926. package/registry/ui/pagination/pagination.tsx +336 -0
  927. package/registry/ui/progress/index.ts +8 -0
  928. package/registry/ui/progress/progress.test.tsx +168 -0
  929. package/registry/ui/progress/progress.tsx +165 -0
  930. package/registry/ui/select/index.ts +12 -1
  931. package/registry/ui/select/select.test.tsx +505 -0
  932. package/registry/ui/select/select.tsx +658 -56
  933. package/registry/ui/sheet/index.ts +19 -0
  934. package/registry/ui/sheet/sheet.test.tsx +411 -0
  935. package/registry/ui/sheet/sheet.tsx +496 -0
  936. package/registry/{blocks → ui}/sidebar/index.ts +10 -5
  937. package/registry/ui/sidebar/sidebar.test.tsx +224 -0
  938. package/registry/ui/sidebar/sidebar.tsx +754 -0
  939. package/registry/ui/slider/index.ts +8 -0
  940. package/registry/ui/slider/slider.test.tsx +231 -0
  941. package/registry/ui/slider/slider.tsx +319 -0
  942. package/registry/ui/social-icons/index.ts +1 -0
  943. package/registry/ui/social-icons/social-icons.tsx +284 -0
  944. package/registry/ui/steps/index.ts +16 -0
  945. package/registry/ui/steps/steps.test.tsx +435 -0
  946. package/registry/ui/steps/steps.tsx +571 -0
  947. package/registry/ui/switch/switch.tsx +3 -2
  948. package/registry/ui/tabs/index.ts +4 -0
  949. package/registry/ui/tabs/tabs.test.tsx +576 -0
  950. package/registry/ui/tabs/tabs.tsx +324 -112
  951. package/registry/ui/textarea/index.ts +1 -1
  952. package/registry/ui/textarea/textarea.test.tsx +294 -0
  953. package/registry/ui/textarea/textarea.tsx +329 -56
  954. package/registry/ui/tooltip/index.ts +14 -0
  955. package/registry/ui/tooltip/tooltip.test.tsx +301 -0
  956. package/registry/ui/tooltip/tooltip.tsx +409 -0
  957. package/registry.json +9702 -139
  958. package/tailwind-preset.ts +2 -2
  959. package/registry/blocks/navbar/Navbar.test.tsx +0 -12
  960. package/registry/blocks/navbar/Navbar.tsx +0 -175
  961. package/registry/blocks/sidebar/Sidebar.test.tsx +0 -39
  962. package/registry/blocks/sidebar/Sidebar.tsx +0 -212
@@ -0,0 +1,1282 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Ellipsis, Hand, MessageSquare, Mic, MicOff, PhoneOff, Pin, ScreenShare, Search, Smile, Star, UserPlus, Video, VideoOff, X } from "lucide-react"
5
+ import { cn } from "../../../src/lib/utils"
6
+ import { Badge } from "../../ui/badge/badge"
7
+ import { Avatar } from "../../ui/avatar/avatar"
8
+ import { Button } from "../../ui/button/button"
9
+ import { Input } from "../../ui/input/input"
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs/tabs"
11
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../ui/dropdown-menu/dropdown-menu"
12
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip/tooltip"
13
+
14
+ /*
15
+ * Figma: `Video Call` (42869:1033). The parts every `video-call-*` block is
16
+ * built from, one per thing the Figma set repeats: the section and its 1248px
17
+ * column, the heading, the stage bar, the participant tile, the controls dock
18
+ * (compact and labelled, with a Leave that needs a hold), and the small helpers
19
+ * that keep Latin words and figures whole inside Arabic.
20
+ *
21
+ * Mobile first, and sized by the block's own width, not the window's, as the
22
+ * Careers and Pricing blocks are: the section is a query container, so a block
23
+ * lays out for the room it is given. The base classes are Figma's Mobile (360);
24
+ * `[@container(min-width:40rem)]:` is its Tablet, from 640px of content
25
+ * (Figma's Tablet, 768 less its padding), and `[@container(min-width:64rem)]:`
26
+ * its Desktop, from 1024px of content. The section's own padding still follows
27
+ * the window, since an element can't query itself. The page's `dir` sets the
28
+ * direction; nothing here takes one.
29
+ *
30
+ * STAND-INS. The code library has no Toast, Rating or Separator yet, so this
31
+ * file carries three small ones, named here so they are easy to swap:
32
+ * - `VideoCallToast` (and `useVideoCallToast`): a polite live region that
33
+ * disappears on its own. Replace it with the Toast atom when there is one.
34
+ * - `StarRow`: five toggle stars that fill up to the pointer. Replace it with
35
+ * the Rating atom.
36
+ * - `VideoCallSeparator`: a 1px `border` line. Replace it with Separator.
37
+ * The real atoms come in a later PR, and none of the shared atoms in
38
+ * `registry/ui` is changed here.
39
+ *
40
+ * Stills only: a tile shows a photo, never a video element, and nothing here
41
+ * asks for the camera or the microphone. Every real action (leave, mute, share,
42
+ * react) is a callback; what the controls show (pressed, open, hovered) is state
43
+ * the block keeps itself.
44
+ */
45
+
46
+ export type VideoCallLocale = "en" | "ar"
47
+
48
+ /* ───────────────────────── Latin inside Arabic ───────────────────────── */
49
+
50
+ const ARABIC = /[؀-ۿ]/
51
+
52
+ /** Whether a string has no Arabic letters. */
53
+ export const isLatin = (text: React.ReactNode) => typeof text === "string" && !ARABIC.test(text)
54
+
55
+ /** A run of Latin letters, figures and the marks that join them: `Fasla`, `⌘ D`, `fasla.to/room`, `+44 20 7946 0212`, `2,100` (a comma only between digits, so `Karim, Mazen` still splits). */
56
+ const LATIN_RUN = /([A-Za-z0-9⌘](?:[A-Za-z0-9⌘ .:@/+'’&_–-]|,(?=\d))*[A-Za-z0-9⌘]|[A-Za-z0-9⌘])/
57
+
58
+ /** One Latin run kept whole and left-to-right inside any sentence, as Figma's LRI…PDI marks do. */
59
+ export function Latin({ children, className }: { children: React.ReactNode; className?: string }) {
60
+ return (
61
+ <bdi dir="ltr" className={cn("whitespace-nowrap", className)}>
62
+ {children}
63
+ </bdi>
64
+ )
65
+ }
66
+
67
+ /**
68
+ * Text with each Latin run or figure set in its own left-to-right isolate, so
69
+ * a name, a shortcut or a number inside an Arabic sentence keeps its order and
70
+ * never glues to the Arabic word beside it. A string with no Arabic, or
71
+ * anything that is not a string, passes through untouched.
72
+ */
73
+ export function Figures({ children }: { children: React.ReactNode }) {
74
+ if (typeof children !== "string" || !ARABIC.test(children)) return <>{children}</>
75
+ const parts = children.split(LATIN_RUN)
76
+ if (parts.length === 1) return <>{children}</>
77
+ return (
78
+ <>
79
+ {parts.map((part, index) =>
80
+ index % 2 === 1 ? (
81
+ <Latin key={index}>{part}</Latin>
82
+ ) : (
83
+ <React.Fragment key={index}>{part}</React.Fragment>
84
+ )
85
+ )}
86
+ </>
87
+ )
88
+ }
89
+
90
+ /* ─────────────────────────────── Motion ─────────────────────────────── */
91
+
92
+ /**
93
+ * The motion presets, copied in because a block must not import `src/motion`:
94
+ * those files do not resolve after a CLI install. 120ms for a fade, 180ms for
95
+ * a move, 60ms between siblings, and the standard ease.
96
+ */
97
+ export const FAST_MS = 120
98
+ export const BASE_MS = 180
99
+ export const STAGGER_MS = 60
100
+ export const STANDARD = "cubic-bezier(0.2, 0, 0, 1)"
101
+
102
+ /** Whether the visitor asked for less motion. False where there is no `matchMedia`, as in a test. */
103
+ export function prefersReducedMotion() {
104
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
105
+ }
106
+
107
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect
108
+
109
+ /**
110
+ * Fades the nodes in together, or in a staggered rise of 8px. With reduced
111
+ * motion every node fades in together in 120ms and nothing moves. Returns the
112
+ * running animations so a caller can cancel them.
113
+ */
114
+ export function playEntrance(nodes: HTMLElement[], { rise = 8, stagger = true }: { rise?: number; stagger?: boolean } = {}) {
115
+ const reduced = prefersReducedMotion()
116
+ return nodes
117
+ .filter((node) => typeof node.animate === "function")
118
+ .map((node, index) =>
119
+ node.animate(reduced || !rise ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 0, transform: `translateY(${rise}px)` }, { opacity: 1, transform: "none" }], {
120
+ duration: reduced ? FAST_MS : BASE_MS,
121
+ delay: reduced || !stagger ? 0 : index * STAGGER_MS,
122
+ easing: STANDARD,
123
+ fill: "backwards",
124
+ })
125
+ )
126
+ }
127
+
128
+ /**
129
+ * Plays `playEntrance` once, the first time the element scrolls into view, on
130
+ * every child marked `data-reveal`. Content that is already on screen when
131
+ * there is no IntersectionObserver is simply shown.
132
+ */
133
+ export function useRevealOnce<T extends HTMLElement>() {
134
+ const ref = React.useRef<T>(null)
135
+ useIsomorphicLayoutEffect(() => {
136
+ const node = ref.current
137
+ if (!node || typeof IntersectionObserver === "undefined") return
138
+ let animations: Animation[] = []
139
+ const observer = new IntersectionObserver(
140
+ (entries) => {
141
+ if (!entries.some((entry) => entry.isIntersecting)) return
142
+ observer.disconnect()
143
+ animations = playEntrance(Array.from(node.querySelectorAll<HTMLElement>("[data-reveal]")))
144
+ },
145
+ { threshold: 0.2 }
146
+ )
147
+ observer.observe(node)
148
+ return () => {
149
+ observer.disconnect()
150
+ animations.forEach((animation) => animation.cancel())
151
+ }
152
+ }, [])
153
+ return ref
154
+ }
155
+
156
+ /* ─────────────────────── Section, heading, rules ─────────────────────── */
157
+
158
+ export interface VideoCallSectionProps extends React.HTMLAttributes<HTMLElement> {
159
+ /** The id of the heading that names the section. */
160
+ labelledBy?: string
161
+ /** Classes for the 1248px column inside the padding. */
162
+ contentClassName?: string
163
+ /** The space between the parts in the column: 48px (`lg`) or 16px (`sm`), as Figma sets each Type. */
164
+ gap?: "lg" | "sm"
165
+ }
166
+
167
+ /**
168
+ * Figma's section: `theme/background`, padding 32 on phones, 64 on Tablet and
169
+ * 96 on Desktop, and a 1248px column.
170
+ */
171
+ export function VideoCallSection({ labelledBy, className, contentClassName, gap = "lg", children, ...props }: VideoCallSectionProps) {
172
+ return (
173
+ <section aria-labelledby={labelledBy} data-video-call="" className={cn("[container-type:inline-size] bg-background p-8 md:p-16 lg:p-24", className)} {...props}>
174
+ <div className={cn("mx-auto flex w-full max-w-[1248px] flex-col", gap === "lg" ? "gap-12" : "gap-4", contentClassName)}>{children}</div>
175
+ </section>
176
+ )
177
+ }
178
+
179
+ export interface VideoCallHeadingProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
180
+ /** The id the section's `aria-labelledby` points at. */
181
+ headingId: string
182
+ /** The small line above the heading, in `primary`. */
183
+ eyebrow?: React.ReactNode
184
+ /** The section's heading, an `h2`. */
185
+ heading: React.ReactNode
186
+ /** The line under the heading. */
187
+ description?: React.ReactNode
188
+ }
189
+
190
+ /** The start-aligned heading: an eyebrow, an `h2` (2xl on phones, 3xl from Tablet) and a line in `muted-foreground`, 12px apart. */
191
+ export function VideoCallHeading({ headingId, eyebrow, heading, description, className, ...props }: VideoCallHeadingProps) {
192
+ return (
193
+ <header className={cn("flex w-full flex-col items-start gap-3", className)} {...props}>
194
+ {eyebrow ? <p className="text-xs font-medium text-primary">{eyebrow}</p> : null}
195
+ <h2 id={headingId} className="text-xl font-semibold text-foreground [@container(min-width:40rem)]:text-3xl">
196
+ {heading}
197
+ </h2>
198
+ {description ? <p className="max-w-[720px] text-base text-muted-foreground">{description}</p> : null}
199
+ </header>
200
+ )
201
+ }
202
+
203
+ /** Figma's `Separator`: a 1px `border` line across its container. A stand-in until the library has the atom. */
204
+ export function VideoCallSeparator({ className }: { className?: string }) {
205
+ return <div role="separator" aria-orientation="horizontal" className={cn("h-px w-full shrink-0 bg-border", className)} />
206
+ }
207
+
208
+ /* ───────────────────────────── Toast (stand-in) ───────────────────────────── */
209
+
210
+ /**
211
+ * Holds a short message for `ms` (1.6s by default) and then clears it. A
212
+ * stand-in for the Toast atom: the dock uses it where a phone has no hover and
213
+ * a tooltip becomes a toast above the dock.
214
+ */
215
+ export function useVideoCallToast(ms = 1600) {
216
+ const [message, setMessage] = React.useState<React.ReactNode>(null)
217
+ const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)
218
+ const show = React.useCallback(
219
+ (next: React.ReactNode) => {
220
+ setMessage(next)
221
+ clearTimeout(timer.current)
222
+ timer.current = setTimeout(() => setMessage(null), ms)
223
+ },
224
+ [ms]
225
+ )
226
+ React.useEffect(() => () => clearTimeout(timer.current), [])
227
+ return { message, show }
228
+ }
229
+
230
+ /** The toast itself: a pill in `foreground` that rises 8px as it appears. Renders nothing without a message. */
231
+ export function VideoCallToast({ message, className }: { message: React.ReactNode; className?: string }) {
232
+ const ref = React.useRef<HTMLDivElement>(null)
233
+ useIsomorphicLayoutEffect(() => {
234
+ if (message && ref.current) playEntrance([ref.current], { stagger: false })
235
+ }, [message])
236
+ return (
237
+ <div role="status" aria-live="polite" className="contents">
238
+ {message ? (
239
+ <div ref={ref} className={cn("pointer-events-none rounded-lg bg-foreground px-3 py-1.5 text-center text-xs text-background", className)}>
240
+ {message}
241
+ </div>
242
+ ) : null}
243
+ </div>
244
+ )
245
+ }
246
+
247
+ /* ──────────────────────────── Star row (stand-in) ──────────────────────────── */
248
+
249
+ export interface StarRowProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
250
+ /** How many stars are filled, 0 to `count`. */
251
+ value?: number
252
+ /** Where the row starts when `value` is not passed. */
253
+ defaultValue?: number
254
+ /** Called with the new number when a star is chosen. */
255
+ onValueChange?: (value: number) => void
256
+ /** The number of stars. */
257
+ count?: number
258
+ /** Names the group for assistive technology. */
259
+ label: string
260
+ /** Names star `n`: `Rate 3 of 5`. */
261
+ starLabel?: (n: number, count: number) => string
262
+ /** The star's drawn size in pixels: Figma's rating row is 28, the stand-in's default 20. */
263
+ starSize?: number
264
+ /** The gap between one star's fill and the next while hovering: 60ms, and 40 on a phone. */
265
+ cascadeMs?: number
266
+ }
267
+
268
+ /**
269
+ * Five toggle stars. The chosen ones are drawn in `warning` outline, the rest
270
+ * in `soft-warning-20` (the amber at 20%, as the Rating atom's unfilled star); hovering fills every star up to the pointer, on a
271
+ * 60ms cascade, and choosing one keeps it. The row is a plain flex row, so it
272
+ * fills from the start in either direction. A stand-in for the Rating atom.
273
+ */
274
+ export function StarRow({ value, defaultValue = 0, onValueChange, count = 5, label, starLabel, starSize = 20, cascadeMs = 60, className, ...props }: StarRowProps) {
275
+ const [own, setOwn] = React.useState(defaultValue)
276
+ const [hover, setHover] = React.useState<number | null>(null)
277
+ const current = value ?? own
278
+ const shown = hover ?? current
279
+ const choose = (n: number) => {
280
+ setOwn(n)
281
+ onValueChange?.(n)
282
+ }
283
+ return (
284
+ <div role="radiogroup" aria-label={label} className={cn("flex items-center gap-1", className)} onPointerLeave={() => setHover(null)} {...props}>
285
+ {Array.from({ length: count }, (_, index) => {
286
+ const n = index + 1
287
+ const filled = n <= shown
288
+ return (
289
+ <button
290
+ key={n}
291
+ type="button"
292
+ role="radio"
293
+ aria-checked={n === current}
294
+ aria-label={starLabel ? starLabel(n, count) : `${n} / ${count}`}
295
+ data-filled={filled ? "" : undefined}
296
+ onPointerEnter={() => setHover(n)}
297
+ onClick={() => choose(n)}
298
+ onKeyDown={(event) => {
299
+ const step = event.key === "ArrowRight" || event.key === "ArrowUp" ? 1 : event.key === "ArrowLeft" || event.key === "ArrowDown" ? -1 : 0
300
+ if (!step) return
301
+ event.preventDefault()
302
+ const dir = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
303
+ choose(Math.min(count, Math.max(1, (current || 1) + step * dir)))
304
+ }}
305
+ style={{ transitionDelay: hover !== null ? `${index * cascadeMs}ms` : undefined, width: starSize + 4, height: starSize + 4 }}
306
+ className={cn(
307
+ "inline-flex items-center justify-center rounded-sm outline-none transition-[color] duration-150 focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
308
+ filled ? "text-warning" : "text-soft-warning-20"
309
+ )}
310
+ >
311
+ <Star size={starSize} strokeWidth={1.5} absoluteStrokeWidth aria-hidden className={cn("transition-[fill] duration-150 motion-reduce:transition-none", filled && hover !== null ? "fill-current" : "fill-transparent")} />
312
+ </button>
313
+ )
314
+ })}
315
+ </div>
316
+ )
317
+ }
318
+
319
+ /* ────────────────────────────── Stage bar ────────────────────────────── */
320
+
321
+ export interface StageBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
322
+ /** Something before the name: the REC badge. */
323
+ leading?: React.ReactNode
324
+ /** The meeting's name. */
325
+ title: React.ReactNode
326
+ /** The elapsed time, `24:18`, set in `muted-foreground` beside the name. */
327
+ time?: React.ReactNode
328
+ /** Live state beside the name: a REC badge, a connection badge. */
329
+ state?: React.ReactNode
330
+ /** View switcher, participant count and fullscreen, at the end. */
331
+ actions?: React.ReactNode
332
+ }
333
+
334
+ /** Figma's Stage Bar: the meeting name and its live state at the start, the view controls at the end, wrapping to a second row on phones. */
335
+ export function StageBar({ leading, title, time, state, actions, className, ...props }: StageBarProps) {
336
+ return (
337
+ <div className={cn("flex w-full flex-wrap items-center justify-between gap-x-4 gap-y-2", className)} {...props}>
338
+ <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
339
+ {leading}
340
+ <p className="min-w-0 truncate text-sm font-semibold text-foreground">{title}</p>
341
+ {time ? <p className="text-sm text-muted-foreground tabular-nums">{time}</p> : null}
342
+ {state}
343
+ </div>
344
+ {actions ? <div className="flex items-center gap-2">{actions}</div> : null}
345
+ </div>
346
+ )
347
+ }
348
+
349
+ /* ──────────────────────────── Participant tile ──────────────────────────── */
350
+
351
+ export interface VideoCallPhoto {
352
+ src: string
353
+ /** Describes the photo. Pass "" for a purely decorative one. */
354
+ alt: string
355
+ /** The focal point of a crop, as a CSS `object-position` such as `"46% 12%"`: the point of the photo that stays put whatever the tile's shape. Without one a block uses its own default. */
356
+ position?: string
357
+ }
358
+
359
+ export interface ParticipantTileProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
360
+ /** The participant's name, shown in the name chip. */
361
+ name: string
362
+ /** The camera still. Without one the tile shows `initials` on `muted`, as Figma does for a camera that is off. */
363
+ photo?: VideoCallPhoto
364
+ /** One or two letters for a camera that is off. */
365
+ initials?: string
366
+ /** Shows the mic-off mark on the name chip. */
367
+ muted?: boolean
368
+ /** Draws the speaking ring. */
369
+ speaking?: boolean
370
+ /** Where the name chip sits: bottom start, bottom end, or not at all. */
371
+ chip?: "start" | "end" | "none"
372
+ /** Extra classes for the name chip, such as one that hides it on phones. */
373
+ chipClassName?: string
374
+ /** Whether the tile is pinned. A pinned tile keeps the pin on show; an unpinned one shows it on hover or focus, and always on a touch screen. */
375
+ pinned?: boolean
376
+ /** Called when the pin is pressed; without it the tile has no pin. */
377
+ onPinnedChange?: (pinned: boolean) => void
378
+ /** Names the pin button: `Pin Adam`. */
379
+ pinLabel?: string
380
+ }
381
+
382
+ /**
383
+ * A camera tile: a photo (or initials) filling a rounded box, a name chip in
384
+ * `primary` at the bottom, an optional speaking ring that fades in over 90ms,
385
+ * and an optional pin. Anything passed as children sits on top, such as a
386
+ * caption bar or a reaction.
387
+ */
388
+ export function ParticipantTile({
389
+ name,
390
+ photo,
391
+ initials,
392
+ muted = false,
393
+ speaking = false,
394
+ chip = "start",
395
+ chipClassName,
396
+ pinned = false,
397
+ onPinnedChange,
398
+ pinLabel,
399
+ className,
400
+ children,
401
+ ...props
402
+ }: ParticipantTileProps) {
403
+ return (
404
+ <div
405
+ data-speaking={speaking ? "" : undefined}
406
+ className={cn(
407
+ "group/tile relative isolate overflow-hidden rounded-lg bg-muted ring-2 ring-inset ring-transparent transition-shadow duration-100 motion-reduce:transition-none",
408
+ speaking && "ring-border",
409
+ className
410
+ )}
411
+ {...props}
412
+ >
413
+ {photo ? (
414
+ <img src={photo.src} alt={photo.alt} loading="lazy" decoding="async" style={photo.position ? { objectPosition: photo.position } : undefined} className="absolute inset-0 -z-10 size-full object-cover object-[50%_42%]" />
415
+ ) : initials ? (
416
+ <span aria-hidden className="absolute inset-0 -z-10 flex items-center justify-center pb-10 text-xl leading-none font-semibold text-foreground [@container(min-width:40rem)]:pb-6 [@container(min-width:40rem)]:text-3xl">
417
+ {initials}
418
+ </span>
419
+ ) : null}
420
+ {onPinnedChange ? (
421
+ <Button
422
+ type="button"
423
+ size="icon-xs"
424
+ radius="rounded"
425
+ aria-label={pinLabel ?? `Pin ${name}`}
426
+ aria-pressed={pinned}
427
+ onClick={() => onPinnedChange(!pinned)}
428
+ className={cn(
429
+ "absolute start-3 top-3 transition-[transform,opacity] duration-100 motion-reduce:transition-none",
430
+ pinned ? "rotate-[15deg]" : "opacity-0 focus-visible:opacity-100 group-focus-within/tile:opacity-100 group-hover/tile:opacity-100 [@media(pointer:coarse)]:opacity-100"
431
+ )}
432
+ >
433
+ <Pin aria-hidden />
434
+ </Button>
435
+ ) : null}
436
+ {chip !== "none" ? (
437
+ <Badge
438
+ variant="solid"
439
+ tone="primary"
440
+ size="lg"
441
+ radius="standard"
442
+ icon={muted ? <MicOff /> : <Mic />}
443
+ className={cn("absolute bottom-3", chip === "start" ? "start-3" : "end-3", chipClassName)}
444
+ >
445
+ <Figures>{name}</Figures>
446
+ </Badge>
447
+ ) : null}
448
+ {children}
449
+ </div>
450
+ )
451
+ }
452
+
453
+ /* ─────────────────────────────── Controls dock ─────────────────────────────── */
454
+
455
+ export interface DockControl {
456
+ id: string
457
+ /** The glyph, from your icon set. */
458
+ icon: React.ElementType
459
+ /** The glyph while pressed, such as a crossed-out mic. It cross-fades with `icon` over 100ms. */
460
+ pressedIcon?: React.ElementType
461
+ /** The button's name and its tooltip: "Mute". */
462
+ label: string
463
+ /** The name while pressed: "Unmute". */
464
+ pressedLabel?: string
465
+ /** What the tooltip says while pressed, when it says more: "You're muted. Press ⌘ D to talk". */
466
+ pressedTip?: string
467
+ /** The short line under the button in the labelled dock. Defaults to `label`. */
468
+ caption?: string
469
+ /** The caption while pressed. */
470
+ pressedCaption?: string
471
+ /** `off` controls (mic, camera) turn destructive when pressed; `on` controls (hand, captions) turn secondary. */
472
+ kind?: "off" | "on"
473
+ /** Makes it a toggle that keeps its own pressed state. Without it the control only calls back. */
474
+ toggle?: boolean
475
+ /** Where it starts. */
476
+ defaultPressed?: boolean
477
+ /** Leaves it out of the phone layout, as Figma trims the dock to five controls. */
478
+ hideOnPhone?: boolean
479
+ /** Items for a menu that opens from the control: the More menu. */
480
+ menu?: { id: string; label: string }[]
481
+ }
482
+
483
+ /** The dock's controls are the Icon Button atom's own sizes: `md` (36px) or `lg` (40px). */
484
+ export type DockSize = "md" | "lg"
485
+
486
+ export interface ControlsDockProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
487
+ controls: DockControl[]
488
+ /** `compact` is the round buttons alone; `labelled` stacks a short caption under each. */
489
+ density?: "compact" | "labelled"
490
+ /** The control size from Tablet up: the Icon Button's `md` (36px) or `lg` (40px). */
491
+ size?: DockSize
492
+ /** The control size on phones. Defaults to `size`; a stage dock sets `md` here and keeps `lg` from Tablet. */
493
+ phoneSize?: DockSize
494
+ /** Names the Leave button and is its text: "Leave". */
495
+ leaveLabel: string
496
+ /** What the Leave button says while it waits for the second press: "Press again to leave". */
497
+ leaveConfirmLabel: string
498
+ /** A line for the first mouse click on Leave: "Hold to leave". */
499
+ leaveHint: string
500
+ /** How long Leave must be held, in ms. */
501
+ holdMs?: number
502
+ /** Draws Leave's corners as a pill (`rounded`) or 8px (`standard`), as the compact and labelled rows differ. */
503
+ leaveRadius?: "rounded" | "standard"
504
+ /** Keeps Leave labelled on phones too, as the labelled dock does. */
505
+ leaveLabelled?: boolean
506
+ /** Keeps Leave out of the dock, for a row that is only controls. */
507
+ hideLeave?: boolean
508
+ /** The accessible name of the dock. */
509
+ label?: string
510
+ /** Called when a control is pressed; toggles also say whether they are now pressed. */
511
+ onControl?: (id: string, pressed: boolean) => void
512
+ /** Called with the id of the chosen More-menu item. */
513
+ onMenuAction?: (id: string) => void
514
+ /** Called once Leave is held for `holdMs`, or confirmed with a second press from the keyboard. */
515
+ onLeave?: () => void
516
+ }
517
+
518
+ /** Whether the device can hover. Phones can't, so a tooltip becomes a toast. False before the first read. */
519
+ function useCanHover() {
520
+ const [can, setCan] = React.useState(true)
521
+ React.useEffect(() => {
522
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return
523
+ setCan(window.matchMedia("(hover: hover)").matches)
524
+ }, [])
525
+ return can
526
+ }
527
+
528
+ interface DockButtonProps {
529
+ control: DockControl
530
+ size: DockSize
531
+ pressed: boolean
532
+ canHover: boolean
533
+ onPress: () => void
534
+ onMenuAction?: (id: string) => void
535
+ onHint: (text: string) => void
536
+ }
537
+
538
+ function DockButton({ control, size, pressed, canHover, onPress, onMenuAction, onHint }: DockButtonProps) {
539
+ const Icon = control.icon
540
+ const PressedIcon = control.pressedIcon
541
+ const name = pressed ? (control.pressedLabel ?? control.label) : control.label
542
+ const tip = pressed ? (control.pressedTip ?? name) : name
543
+ const tone = pressed ? (control.kind === "on" ? "secondary" : "destructive") : "primary"
544
+ const button = (
545
+ <Button
546
+ type="button"
547
+ size={size === "md" ? "icon-md" : "icon-lg"}
548
+ radius="rounded"
549
+ tone={tone}
550
+ aria-label={name}
551
+ aria-pressed={control.toggle ? pressed : undefined}
552
+ data-control={control.id}
553
+ onClick={() => {
554
+ onPress()
555
+ if (!canHover) onHint(tip)
556
+ }}
557
+ className={cn("transition-[transform,background-color] duration-150 motion-reduce:transition-none", canHover && "hover:motion-safe:scale-[1.06]", "active:motion-safe:scale-[0.96]")}
558
+ >
559
+ <span className="grid size-4 place-items-center" aria-hidden>
560
+ <Icon className={cn("col-start-1 row-start-1 transition-opacity duration-100 motion-reduce:transition-none", PressedIcon && pressed && "opacity-0")} />
561
+ {PressedIcon ? <PressedIcon className={cn("col-start-1 row-start-1 transition-opacity duration-100 motion-reduce:transition-none", !pressed && "opacity-0")} /> : null}
562
+ </span>
563
+ </Button>
564
+ )
565
+ if (control.menu) {
566
+ return (
567
+ <DropdownMenu>
568
+ <DropdownMenuTrigger
569
+ render={
570
+ <Button
571
+ type="button"
572
+ size={size === "md" ? "icon-md" : "icon-lg"}
573
+ radius="rounded"
574
+ aria-label={name}
575
+ data-control={control.id}
576
+ className={cn("transition-[transform,background-color] duration-150 motion-reduce:transition-none hover:motion-safe:scale-[1.06] active:motion-safe:scale-[0.96]")}
577
+ />
578
+ }
579
+ >
580
+ <Icon aria-hidden />
581
+ </DropdownMenuTrigger>
582
+ <DropdownMenuContent side="top" align="center">
583
+ {control.menu.map((item) => (
584
+ <DropdownMenuItem key={item.id} onClick={() => onMenuAction?.(item.id)}>
585
+ {item.label}
586
+ </DropdownMenuItem>
587
+ ))}
588
+ </DropdownMenuContent>
589
+ </DropdownMenu>
590
+ )
591
+ }
592
+ if (!canHover) return button
593
+ return (
594
+ <Tooltip>
595
+ <TooltipTrigger delay={400} render={button} />
596
+ <TooltipContent side="top">
597
+ <Figures>{tip}</Figures>
598
+ </TooltipContent>
599
+ </Tooltip>
600
+ )
601
+ }
602
+
603
+ /**
604
+ * Leave, held. A mouse or finger has to stay down for `holdMs` (400ms) before
605
+ * `onLeave` is called, with a fill that grows across the button; a quick click
606
+ * only shows the hint. From the keyboard a hold is unreliable, so the first
607
+ * press arms the button ("Press again to leave") and a second press within
608
+ * three seconds leaves. On phones it is the Icon Button's `icon-lg` (40px),
609
+ * icon-only; from Tablet the Button's labelled `lg` (40px).
610
+ */
611
+ export function LeaveButton({
612
+ label,
613
+ confirmLabel,
614
+ hint,
615
+ holdMs = 400,
616
+ radius = "rounded",
617
+ alwaysLabelled = false,
618
+ onLeave,
619
+ onHint,
620
+ className,
621
+ }: {
622
+ label: string
623
+ confirmLabel: string
624
+ hint: string
625
+ holdMs?: number
626
+ radius?: "rounded" | "standard"
627
+ /** Keeps the label on phones too, as Figma's labelled dock does; otherwise a phone gets an icon-only button. */
628
+ alwaysLabelled?: boolean
629
+ onLeave?: () => void
630
+ onHint?: (text: string) => void
631
+ className?: string
632
+ }) {
633
+ const [armed, setArmed] = React.useState(false)
634
+ const [buttonRef, tier, measured] = useContainerTier<HTMLButtonElement>()
635
+ const fill = React.useRef<HTMLSpanElement>(null)
636
+ const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)
637
+ const armTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)
638
+ const animation = React.useRef<Animation>(undefined)
639
+ const held = React.useRef(false)
640
+
641
+ const stop = () => {
642
+ clearTimeout(timer.current)
643
+ animation.current?.cancel()
644
+ }
645
+ React.useEffect(() => () => { stop(); clearTimeout(armTimer.current) }, [])
646
+
647
+ const start = () => {
648
+ held.current = false
649
+ stop()
650
+ if (fill.current && typeof fill.current.animate === "function" && !prefersReducedMotion()) {
651
+ animation.current = fill.current.animate([{ width: "0%" }, { width: "100%" }], { duration: holdMs, easing: "linear", fill: "forwards" })
652
+ }
653
+ timer.current = setTimeout(() => {
654
+ held.current = true
655
+ onLeave?.()
656
+ }, holdMs)
657
+ }
658
+
659
+ return (
660
+ <Button
661
+ ref={buttonRef}
662
+ type="button"
663
+ variant="destructive"
664
+ size={!alwaysLabelled && measured && tier === "mobile" ? "icon-lg" : "lg"}
665
+ radius={radius}
666
+ aria-label={armed ? confirmLabel : label}
667
+ data-control="leave"
668
+ data-armed={armed ? "" : undefined}
669
+ onPointerDown={(event) => {
670
+ if (event.pointerType === "mouse" && event.button !== 0) return
671
+ start()
672
+ }}
673
+ onPointerUp={stop}
674
+ onPointerLeave={stop}
675
+ onPointerCancel={stop}
676
+ onClick={(event) => {
677
+ // A real pointer click has `detail >= 1`; Enter and Space report 0.
678
+ if (event.detail > 0) {
679
+ if (!held.current) onHint?.(hint)
680
+ return
681
+ }
682
+ if (armed) {
683
+ clearTimeout(armTimer.current)
684
+ setArmed(false)
685
+ onLeave?.()
686
+ } else {
687
+ setArmed(true)
688
+ onHint?.(confirmLabel)
689
+ armTimer.current = setTimeout(() => setArmed(false), 3000)
690
+ }
691
+ }}
692
+ // An icon-only phone button and a labelled Tablet one are different atom sizes: wait for the first reading before showing either.
693
+ // Until measured the button is the labelled `lg`; its box is narrowed with the container query so the room it reserves is already the phone's 40px or Tablet's labelled width.
694
+ className={cn("relative overflow-hidden", !alwaysLabelled && !measured && "invisible [@container(max-width:39.99rem)]:w-10 [@container(max-width:39.99rem)]:px-0", className)}
695
+ >
696
+ <span ref={fill} aria-hidden className="pointer-events-none absolute inset-y-0 start-0 w-0 bg-destructive-foreground opacity-20" />
697
+ <PhoneOff aria-hidden className="relative" />
698
+ <span className={cn("relative", !alwaysLabelled && "hidden [@container(min-width:40rem)]:inline")}>{armed ? confirmLabel : label}</span>
699
+ </Button>
700
+ )
701
+ }
702
+
703
+ /**
704
+ * Figma's controls dock. Toggles keep their own pressed state; every press is
705
+ * reported with `onControl`. A mic or camera that is off turns `destructive`
706
+ * over 160ms while its glyph cross-fades in 100ms; hovering lifts a control to
707
+ * 1.06 and pressing settles it to 0.96; a tooltip opens after 400ms. On a phone
708
+ * there is no hover: the controls only settle on press, a tooltip becomes a
709
+ * 1.6s toast above the dock, and the dock drops the controls marked
710
+ * `hideOnPhone`. In RTL the whole dock mirrors, so Leave stays on the side the
711
+ * eye leaves from.
712
+ */
713
+ export function ControlsDock({
714
+ controls,
715
+ density = "compact",
716
+ size = "lg",
717
+ phoneSize,
718
+ leaveLabel,
719
+ leaveConfirmLabel,
720
+ leaveHint,
721
+ holdMs,
722
+ leaveRadius = "rounded",
723
+ leaveLabelled = false,
724
+ hideLeave = false,
725
+ label,
726
+ onControl,
727
+ onMenuAction,
728
+ onLeave,
729
+ className,
730
+ ...props
731
+ }: ControlsDockProps) {
732
+ const [pressed, setPressed] = React.useState<Record<string, boolean>>(() => Object.fromEntries(controls.map((c) => [c.id, Boolean(c.defaultPressed)])))
733
+ const canHover = useCanHover()
734
+ const toast = useVideoCallToast()
735
+ const [dockRef, tier, measured] = useContainerTier<HTMLDivElement>()
736
+ const buttonSize = tier === "mobile" ? (phoneSize ?? size) : size
737
+ // Only a dock whose size changes with the tier waits for its first reading.
738
+ const waiting = !measured && phoneSize !== undefined && phoneSize !== size
739
+ const isPressed = (c: DockControl) => pressed[c.id] ?? Boolean(c.defaultPressed)
740
+ return (
741
+ <div ref={dockRef} className={cn("relative", className)} {...props}>
742
+ <div role="group" aria-label={label} className={cn("flex items-start justify-center", density === "labelled" ? "gap-2 [@container(min-width:40rem)]:gap-5" : "gap-2", waiting && "invisible")}>
743
+ {controls.map((control) => {
744
+ const on = isPressed(control)
745
+ const button = (
746
+ <DockButton
747
+ control={control}
748
+ size={buttonSize}
749
+ pressed={on}
750
+ canHover={canHover}
751
+ onHint={toast.show}
752
+ onMenuAction={onMenuAction}
753
+ onPress={() => {
754
+ const next = control.toggle ? !on : false
755
+ if (control.toggle) setPressed((state) => ({ ...state, [control.id]: next }))
756
+ onControl?.(control.id, next)
757
+ }}
758
+ />
759
+ )
760
+ return (
761
+ <div
762
+ key={control.id}
763
+ className={cn(
764
+ density === "labelled" ? "flex flex-col items-center gap-1.5" : "block",
765
+ control.hideOnPhone && (density === "labelled" ? "hidden [@container(min-width:40rem)]:flex" : "hidden [@container(min-width:40rem)]:block")
766
+ )}
767
+ >
768
+ {button}
769
+ {density === "labelled" ? <span className="text-xs text-muted-foreground">{on ? (control.pressedCaption ?? control.caption ?? control.label) : (control.caption ?? control.label)}</span> : null}
770
+ </div>
771
+ )
772
+ })}
773
+ {hideLeave ? null : (
774
+ <LeaveButton label={leaveLabel} confirmLabel={leaveConfirmLabel} hint={leaveHint} holdMs={holdMs} radius={leaveRadius} alwaysLabelled={leaveLabelled} onLeave={onLeave} onHint={toast.show} />
775
+ )}
776
+ </div>
777
+ <VideoCallToast message={toast.message} className="absolute inset-x-0 bottom-full mx-auto mb-2 w-fit max-w-full" />
778
+ </div>
779
+ )
780
+ }
781
+
782
+ /* ═══════════════════════════ Call stages ═══════════════════════════
783
+ * What the four call-stage Types share: the stage card, the people on it, the
784
+ * stage dock's controls, the container tier a script can read, the FLIP that
785
+ * moves tiles when people come and go, and the side panel.
786
+ */
787
+
788
+ /** Fills `{name}`-style holes in a copy string. */
789
+ export const fillTemplate = (template: string, values: Record<string, string | number>) =>
790
+ template.replace(/\{(\w+)\}/g, (_, key: string) => String(values[key] ?? ""))
791
+
792
+ /** The surface of a card in Figma's Video Call set: `card`, a 1px `border`, a 16px radius. */
793
+ export const VIDEO_CALL_CARD = "rounded-2xl border border-border bg-card"
794
+
795
+ /** The copy props a caller passed, so they win over the defaults. `keys` are the copy's own keys. */
796
+ export function passedCopy(keys: readonly string[], props: Record<string, unknown>) {
797
+ return Object.fromEntries(Object.entries(props).filter(([key, value]) => keys.includes(key) && value !== undefined))
798
+ }
799
+
800
+ /** The props that are not copy, for the element the block renders. */
801
+ export function restOf<T extends Record<string, unknown>>(keys: readonly string[], props: T) {
802
+ return Object.fromEntries(Object.entries(props).filter(([key]) => !keys.includes(key))) as Partial<T>
803
+ }
804
+
805
+ /** A person on a call stage. Their photo comes from the block's `photos`, by `id`. */
806
+ export interface VideoCallParticipant {
807
+ /** Keys the person, and picks their photo. */
808
+ id: string
809
+ /** The name on the chip: `Karim Nasser`. */
810
+ name: string
811
+ /** The short form a phone or a strip shows: `Adam F.`. */
812
+ shortName?: string
813
+ /** One or two letters for a camera that is off. A person with no photo and no initials shows a plain `muted` tile. */
814
+ initials?: string
815
+ /** Shows the mic-off mark on their chip. */
816
+ muted?: boolean
817
+ }
818
+
819
+ /** The photos a stage shows, by participant `id`. A person without one shows their initials. */
820
+ export type VideoCallPhotos = Record<string, VideoCallPhoto>
821
+
822
+ /**
823
+ * Figma's Call Stage: a `card` surface, 16px radius, 1px `border`, 16px apart
824
+ * inside. A phone's stage hugs its tiles, 4px at the sides.
825
+ */
826
+ export function CallStage({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
827
+ return (
828
+ <div
829
+ data-call-stage=""
830
+ className={cn("flex w-full flex-col gap-4 rounded-2xl border border-border bg-card px-1 py-4 [@container(min-width:40rem)]:p-4", className)}
831
+ {...props}
832
+ />
833
+ )
834
+ }
835
+
836
+ /* ───────────────────────────── Stage dock ───────────────────────────── */
837
+
838
+ export interface StageDockCopy {
839
+ /** Names the dock. */
840
+ label: string
841
+ mic: { label: string; pressedLabel: string; tip: string }
842
+ video: { label: string; pressedLabel: string }
843
+ share: string
844
+ chat: string
845
+ reactions: string
846
+ hand: { label: string; pressedLabel: string }
847
+ more: string
848
+ menu: { id: string; label: string }[]
849
+ /** Leave's label, and its two lines of help. */
850
+ leave: string
851
+ leaveConfirm: string
852
+ leaveHint: string
853
+ }
854
+
855
+ /** The dock every call stage carries, English and Arabic. */
856
+ export const STAGE_DOCK_COPY: Record<VideoCallLocale, StageDockCopy> = {
857
+ en: {
858
+ label: "Call controls",
859
+ mic: { label: "Mute", pressedLabel: "Unmute", tip: "You're muted. Press ⌘ D to talk" },
860
+ video: { label: "Stop video", pressedLabel: "Start video" },
861
+ share: "Share screen",
862
+ chat: "Chat",
863
+ reactions: "Reactions",
864
+ hand: { label: "Raise hand", pressedLabel: "Lower hand" },
865
+ more: "More",
866
+ menu: [
867
+ { id: "settings", label: "Settings" },
868
+ { id: "report", label: "Report a problem" },
869
+ ],
870
+ leave: "Leave",
871
+ leaveConfirm: "Press again to leave",
872
+ leaveHint: "Hold to leave",
873
+ },
874
+ ar: {
875
+ label: "عناصر التحكم في المكالمة",
876
+ mic: { label: "كتم", pressedLabel: "إلغاء الكتم", tip: "الميكروفون مكتوم. اضغط ⌘ D للتحدث" },
877
+ video: { label: "إيقاف الفيديو", pressedLabel: "تشغيل الفيديو" },
878
+ share: "مشاركة الشاشة",
879
+ chat: "الدردشة",
880
+ reactions: "التفاعلات",
881
+ hand: { label: "رفع اليد", pressedLabel: "خفض اليد" },
882
+ more: "المزيد",
883
+ menu: [
884
+ { id: "settings", label: "الإعدادات" },
885
+ { id: "report", label: "الإبلاغ عن مشكلة" },
886
+ ],
887
+ leave: "خروج",
888
+ leaveConfirm: "اضغط مرة أخرى للخروج",
889
+ leaveHint: "اضغط مطولًا للخروج",
890
+ },
891
+ }
892
+
893
+ /**
894
+ * The seven controls of a call stage's dock, in Figma's order: mic, camera,
895
+ * share, chat, reactions, hand and More. A phone keeps five: share and
896
+ * reactions drop out. `chat: "toggle"` makes Chat a toggle that keeps its own
897
+ * pressed state, for a stage whose chat opens a panel.
898
+ */
899
+ export function stageDockControls(d: StageDockCopy, { chat = "button" }: { chat?: "button" | "toggle" } = {}): DockControl[] {
900
+ return [
901
+ { id: "mic", icon: Mic, pressedIcon: MicOff, label: d.mic.label, pressedLabel: d.mic.pressedLabel, pressedTip: d.mic.tip, toggle: true },
902
+ { id: "video", icon: Video, pressedIcon: VideoOff, label: d.video.label, pressedLabel: d.video.pressedLabel, toggle: true },
903
+ { id: "screen-share", icon: ScreenShare, label: d.share, hideOnPhone: true },
904
+ { id: "chat", icon: MessageSquare, label: d.chat, kind: "on", toggle: chat === "toggle" },
905
+ { id: "reactions", icon: Smile, label: d.reactions, hideOnPhone: true },
906
+ { id: "hand", icon: Hand, label: d.hand.label, pressedLabel: d.hand.pressedLabel, kind: "on", toggle: true },
907
+ { id: "more", icon: Ellipsis, label: d.more, menu: d.menu },
908
+ ]
909
+ }
910
+
911
+ /* ───────────────────── Container tier, FLIP and debounce ───────────────────── */
912
+
913
+ export type ContainerTier = "mobile" | "tablet" | "desktop"
914
+
915
+ /**
916
+ * Which of Figma's three breakpoints the block's own width is at, the same
917
+ * 640px and 1024px of content the container queries use. A script reads this
918
+ * where a class can't act: which way a wipe runs, which axis a panel opens on.
919
+ */
920
+ export function containerTier(node: Element | null): ContainerTier {
921
+ const root = node?.closest<HTMLElement>("[data-video-call]")
922
+ if (!root || typeof getComputedStyle !== "function") return "mobile"
923
+ const style = getComputedStyle(root)
924
+ const width = root.clientWidth - (parseFloat(style.paddingInlineStart) || 0) - (parseFloat(style.paddingInlineEnd) || 0)
925
+ return width >= 1024 ? "desktop" : width >= 640 ? "tablet" : "mobile"
926
+ }
927
+
928
+ /**
929
+ * The tier of the section a node is in, kept up to date as the section resizes.
930
+ * The third value is `false` until the first reading: the layout effect sets
931
+ * it before the browser paints, so on the client nothing wrong is ever drawn,
932
+ * but server-rendered HTML is painted before React hydrates. A part whose atom
933
+ * size depends on the tier hides itself until then (`invisible`), so a reader
934
+ * on Tablet never sees the phone size flash first.
935
+ */
936
+ export function useContainerTier<T extends HTMLElement>() {
937
+ const ref = React.useRef<T>(null)
938
+ const [tier, setTier] = React.useState<ContainerTier>("mobile")
939
+ const [measured, setMeasured] = React.useState(false)
940
+ useIsomorphicLayoutEffect(() => {
941
+ const node = ref.current
942
+ if (!node) return
943
+ const read = () => {
944
+ setTier(containerTier(node))
945
+ setMeasured(true)
946
+ }
947
+ read()
948
+ const root = node.closest("[data-video-call]")
949
+ if (!root || typeof ResizeObserver === "undefined") return
950
+ const observer = new ResizeObserver(read)
951
+ observer.observe(root)
952
+ return () => observer.disconnect()
953
+ }, [])
954
+ return [ref, tier, measured] as const
955
+ }
956
+
957
+ /**
958
+ * FLIP for a list: every child marked `data-flip="<id>"` slides from where it
959
+ * was to where it is, over 260ms ease-out, whenever `key` changes, so the
960
+ * tiles that stay travel instead of jumping when someone joins or leaves. A
961
+ * tile that is new fades in. Reduced motion skips it. It measures, so it works
962
+ * in either direction.
963
+ */
964
+ export function useFlip<T extends HTMLElement>(key: string, duration = 260) {
965
+ const ref = React.useRef<T>(null)
966
+ const rects = React.useRef(new Map<string, DOMRect>())
967
+ const last = React.useRef<string | undefined>(undefined)
968
+ useIsomorphicLayoutEffect(() => {
969
+ const node = ref.current
970
+ if (!node) return
971
+ const items = Array.from(node.querySelectorAll<HTMLElement>("[data-flip]"))
972
+ const next = new Map<string, DOMRect>()
973
+ for (const el of items) next.set(el.dataset.flip!, el.getBoundingClientRect())
974
+ if (last.current !== undefined && last.current !== key && !prefersReducedMotion()) {
975
+ for (const el of items) {
976
+ if (typeof el.animate !== "function") continue
977
+ const before = rects.current.get(el.dataset.flip!)
978
+ const after = next.get(el.dataset.flip!)!
979
+ if (!before) {
980
+ el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: BASE_MS, easing: STANDARD })
981
+ continue
982
+ }
983
+ const dx = before.left - after.left
984
+ const dy = before.top - after.top
985
+ if (Math.abs(dx) < 1 && Math.abs(dy) < 1) continue
986
+ el.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "none" }], { duration, easing: "ease-out" })
987
+ }
988
+ }
989
+ rects.current = next
990
+ last.current = key
991
+ }, [key, duration])
992
+ return ref
993
+ }
994
+
995
+ /** A value that follows `value` after it has held still for `ms`: the active-speaker ring never strobes between two voices. */
996
+ export function useDebouncedValue<T>(value: T, ms: number): T {
997
+ const [held, setHeld] = React.useState(value)
998
+ React.useEffect(() => {
999
+ if (Object.is(value, held)) return
1000
+ const timer = setTimeout(() => setHeld(value), ms)
1001
+ return () => clearTimeout(timer)
1002
+ }, [value, ms, held])
1003
+ return held
1004
+ }
1005
+
1006
+ /* ───────────────────────────── Side panel ───────────────────────────── */
1007
+
1008
+ export interface SidePanelPerson {
1009
+ id: string
1010
+ name: string
1011
+ /** Their role: Host, Guest. */
1012
+ role?: string
1013
+ photo?: VideoCallPhoto
1014
+ initials?: string
1015
+ /** Shows the mic as off, in `destructive`. */
1016
+ muted?: boolean
1017
+ }
1018
+
1019
+ export interface SidePanelMessage {
1020
+ id: string
1021
+ author: string
1022
+ text: string
1023
+ }
1024
+
1025
+ export interface SidePanelCopy {
1026
+ /** The panel's heading, and its name. */
1027
+ title: string
1028
+ /** Names the close button. */
1029
+ close: string
1030
+ /** Names the tab list. */
1031
+ tabsLabel: string
1032
+ participants: string
1033
+ chat: string
1034
+ /** Names the search field and is its placeholder. */
1035
+ search: string
1036
+ /** The line above the list; `{count}` is filled in. */
1037
+ inCall: string
1038
+ /** What the list says when the search finds no one. */
1039
+ noResults: string
1040
+ /** What the Chat tab says with no messages. */
1041
+ noMessages: string
1042
+ /** Names the Chat tab's dot. */
1043
+ unread: string
1044
+ invite: string
1045
+ /** Names a person's menu button; `{name}` is filled in. */
1046
+ more: string
1047
+ /** Names the mic mark of someone who is muted. */
1048
+ muted: string
1049
+ /** Names the handle that resizes the sheet on a phone. */
1050
+ handle: string
1051
+ }
1052
+
1053
+ export type SidePanelSnap = "60" | "92"
1054
+
1055
+ export interface SidePanelProps extends Omit<React.HTMLAttributes<HTMLElement>, "title" | "onChange"> {
1056
+ copy: SidePanelCopy
1057
+ people: SidePanelPerson[]
1058
+ /** The Chat tab's messages. */
1059
+ messages?: SidePanelMessage[]
1060
+ /** Puts the dot on the Chat tab, scaling in from nothing, until the Chat tab is opened. */
1061
+ unread?: boolean
1062
+ /** The open tab, when you control it. */
1063
+ tab?: "participants" | "chat"
1064
+ defaultTab?: "participants" | "chat"
1065
+ onTabChange?: (tab: "participants" | "chat") => void
1066
+ /** Makes the panel a bottom sheet on a phone: a handle on top, and a height of 60% that a drag or a press steps to 92%. From Tablet it is a plain card. */
1067
+ sheet?: boolean
1068
+ /** The sheet's height step, when you control it. */
1069
+ snap?: SidePanelSnap
1070
+ defaultSnap?: SidePanelSnap
1071
+ onSnapChange?: (snap: SidePanelSnap) => void
1072
+ /** Called when the close button is pressed; without it the panel has none. */
1073
+ onClose?: () => void
1074
+ /** Called when Invite is pressed. */
1075
+ onInvite?: () => void
1076
+ /** Called with a person's id when their menu button is pressed. */
1077
+ onPersonMenu?: (id: string) => void
1078
+ }
1079
+
1080
+ function useControlled<T>(value: T | undefined, initial: T, onChange?: (next: T) => void) {
1081
+ const [own, setOwn] = React.useState(initial)
1082
+ const current = value ?? own
1083
+ const set = (next: T) => {
1084
+ if (value === undefined) setOwn(next)
1085
+ onChange?.(next)
1086
+ }
1087
+ return [current, set] as const
1088
+ }
1089
+
1090
+ /** The sheet's grab handle: a press steps between the two heights, and a drag up or down of 24px picks one. */
1091
+ function SheetHandle({ label, snap, onSnap }: { label: string; snap: SidePanelSnap; onSnap: (snap: SidePanelSnap) => void }) {
1092
+ const start = React.useRef<number | null>(null)
1093
+ const dragged = React.useRef(false)
1094
+ return (
1095
+ <button
1096
+ type="button"
1097
+ aria-label={label}
1098
+ aria-expanded={snap === "92"}
1099
+ onPointerDown={(event) => {
1100
+ start.current = event.clientY
1101
+ dragged.current = false
1102
+ }}
1103
+ onPointerUp={(event) => {
1104
+ if (start.current === null) return
1105
+ const dy = event.clientY - start.current
1106
+ start.current = null
1107
+ if (Math.abs(dy) >= 24) {
1108
+ dragged.current = true
1109
+ onSnap(dy < 0 ? "92" : "60")
1110
+ }
1111
+ }}
1112
+ onPointerCancel={() => (start.current = null)}
1113
+ onClick={() => {
1114
+ if (dragged.current) {
1115
+ dragged.current = false
1116
+ return
1117
+ }
1118
+ onSnap(snap === "92" ? "60" : "92")
1119
+ }}
1120
+ className="-mt-2 flex h-6 w-full shrink-0 items-center justify-center rounded-md outline-none [touch-action:none] focus-visible:ring-2 focus-visible:ring-ring [@container(min-width:40rem)]:hidden"
1121
+ >
1122
+ <span aria-hidden className="h-1 w-9 rounded-full bg-muted-foreground" />
1123
+ </button>
1124
+ )
1125
+ }
1126
+
1127
+ /** A person's row. A row that arrives after the first render grows in over 180ms, height and opacity together. */
1128
+ function PersonRow({ person, copy, isNew, onMenu }: { person: SidePanelPerson; copy: SidePanelCopy; isNew: boolean; onMenu?: (id: string) => void }) {
1129
+ const ref = React.useRef<HTMLLIElement>(null)
1130
+ useIsomorphicLayoutEffect(() => {
1131
+ const node = ref.current
1132
+ if (!isNew || !node || typeof node.animate !== "function") return
1133
+ const height = node.offsetHeight
1134
+ node.animate(prefersReducedMotion() ? [{ opacity: 0 }, { opacity: 1 }] : [{ height: "0px", opacity: 0, overflow: "hidden" }, { height: `${height}px`, opacity: 1, overflow: "hidden" }], {
1135
+ duration: BASE_MS,
1136
+ easing: STANDARD,
1137
+ })
1138
+ }, [])
1139
+ const Mark = person.muted ? MicOff : Mic
1140
+ return (
1141
+ <li ref={ref} data-person={person.id} className="flex items-center gap-3 rounded-lg p-2">
1142
+ <Avatar size="32" radius="standard" variant={person.photo ? "image" : "initials"} src={person.photo?.src} name={person.name} />
1143
+ <div className="flex min-w-0 flex-1 flex-col">
1144
+ <span className="truncate text-sm font-medium text-foreground">
1145
+ <Figures>{person.name}</Figures>
1146
+ </span>
1147
+ {person.role ? <span className="truncate text-xs text-muted-foreground">{person.role}</span> : null}
1148
+ </div>
1149
+ <Mark aria-hidden className={cn("size-4 shrink-0", person.muted ? "text-destructive" : "text-muted-foreground")} />
1150
+ {person.muted ? <span className="sr-only">{copy.muted}</span> : null}
1151
+ <Button type="button" variant="ghost" tone="secondary" size="icon-xs" aria-label={fillTemplate(copy.more, { name: person.name })} onClick={() => onMenu?.(person.id)}>
1152
+ <Ellipsis aria-hidden />
1153
+ </Button>
1154
+ </li>
1155
+ )
1156
+ }
1157
+
1158
+ /**
1159
+ * Figma's side panel: a heading with a close button, a Participants / Chat
1160
+ * tab pair, a people search and the list, and Invite at the foot. The search
1161
+ * filters for real; the tabs cross-fade their content in 120ms; the Chat tab's
1162
+ * dot scales in from 0; a row that arrives grows in. As a sheet on a phone it
1163
+ * is 60% tall with a handle that steps it to 92%. A card, 360px wide beside the
1164
+ * stage on Desktop and full width below it from Tablet.
1165
+ */
1166
+ export function SidePanel({
1167
+ copy,
1168
+ people,
1169
+ messages = [],
1170
+ unread = false,
1171
+ tab,
1172
+ defaultTab = "participants",
1173
+ onTabChange,
1174
+ sheet = false,
1175
+ snap,
1176
+ defaultSnap = "60",
1177
+ onSnapChange,
1178
+ onClose,
1179
+ onInvite,
1180
+ onPersonMenu,
1181
+ className,
1182
+ ...props
1183
+ }: SidePanelProps) {
1184
+ const [current, setTab] = useControlled(tab, defaultTab, onTabChange)
1185
+ const [size, setSize] = useControlled(snap, defaultSnap, onSnapChange)
1186
+ const [query, setQuery] = React.useState("")
1187
+ const content = React.useRef<HTMLDivElement>(null)
1188
+ const dot = React.useRef<HTMLSpanElement>(null)
1189
+ const seen = React.useRef<Set<string> | null>(null)
1190
+ const firstTab = React.useRef(true)
1191
+ const shown = people.filter((person) => person.name.toLowerCase().includes(query.trim().toLowerCase()))
1192
+ const fresh = new Set(seen.current ? people.filter((person) => !seen.current!.has(person.id)).map((person) => person.id) : [])
1193
+ React.useEffect(() => {
1194
+ seen.current = new Set(people.map((person) => person.id))
1195
+ })
1196
+ React.useEffect(() => {
1197
+ if (firstTab.current) {
1198
+ firstTab.current = false
1199
+ return
1200
+ }
1201
+ if (content.current) playEntrance([content.current], { rise: 0, stagger: false })
1202
+ }, [current])
1203
+ React.useEffect(() => {
1204
+ const node = dot.current
1205
+ if (!unread || !node || typeof node.animate !== "function") return
1206
+ node.animate(prefersReducedMotion() ? [{ opacity: 0 }, { opacity: 1 }] : [{ transform: "scale(0)" }, { transform: "scale(1)" }], { duration: BASE_MS, easing: STANDARD })
1207
+ }, [unread, current])
1208
+
1209
+ return (
1210
+ <aside
1211
+ aria-label={copy.title}
1212
+ data-side-panel=""
1213
+ data-snap={sheet ? size : undefined}
1214
+ className={cn(
1215
+ "flex min-h-0 w-full flex-col gap-4 rounded-2xl border border-border bg-card p-5",
1216
+ sheet && "h-[468px] transition-[height] duration-200 motion-reduce:transition-none data-[snap=92]:h-[600px] [@container(min-width:40rem)]:h-auto",
1217
+ className
1218
+ )}
1219
+ {...props}
1220
+ >
1221
+ {sheet ? <SheetHandle label={copy.handle} snap={size} onSnap={setSize} /> : null}
1222
+ <div className="flex items-center justify-between gap-3">
1223
+ <h3 className="text-base font-semibold text-foreground">{copy.title}</h3>
1224
+ {onClose ? (
1225
+ <Button type="button" variant="ghost" tone="secondary" size="icon-xs" aria-label={copy.close} onClick={onClose}>
1226
+ <X aria-hidden />
1227
+ </Button>
1228
+ ) : null}
1229
+ </div>
1230
+ <Tabs value={current} onValueChange={(value) => setTab(value as "participants" | "chat")} className="flex min-h-0 flex-1 flex-col gap-4">
1231
+ <TabsList variant="bordered" aria-label={copy.tabsLabel}>
1232
+ <TabsTrigger value="participants">{copy.participants}</TabsTrigger>
1233
+ <TabsTrigger value="chat">
1234
+ <span className="relative">
1235
+ {copy.chat}
1236
+ {unread && current !== "chat" ? <span ref={dot} role="img" aria-label={copy.unread} className="absolute -top-1 -end-2 size-2 rounded-full bg-destructive" /> : null}
1237
+ </span>
1238
+ </TabsTrigger>
1239
+ </TabsList>
1240
+ <div ref={content} className="flex min-h-0 flex-1 flex-col gap-4">
1241
+ <TabsContent value="participants" className="flex min-h-0 flex-1 flex-col gap-4">
1242
+ <Input size="lg" aria-label={copy.search} placeholder={copy.search} startIcon={<Search aria-hidden />} value={query} onChange={(event) => setQuery(event.target.value)} />
1243
+ <div className="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto [@container(min-width:40rem)]:max-h-[22rem]">
1244
+ <p className="text-xs font-medium text-muted-foreground">
1245
+ <Figures>{fillTemplate(copy.inCall, { count: people.length })}</Figures>
1246
+ </p>
1247
+ {shown.length ? (
1248
+ <ul className="flex flex-col gap-1">
1249
+ {shown.map((person) => (
1250
+ <PersonRow key={person.id} person={person} copy={copy} isNew={fresh.has(person.id)} onMenu={onPersonMenu} />
1251
+ ))}
1252
+ </ul>
1253
+ ) : (
1254
+ <p role="status" className="py-4 text-sm text-muted-foreground">{copy.noResults}</p>
1255
+ )}
1256
+ </div>
1257
+ </TabsContent>
1258
+ <TabsContent value="chat" className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto">
1259
+ {messages.length ? (
1260
+ messages.map((message) => (
1261
+ <p key={message.id} className="flex flex-col gap-0.5 text-sm">
1262
+ <span className="font-semibold text-foreground">
1263
+ <Figures>{message.author}</Figures>
1264
+ </span>
1265
+ <span className="text-muted-foreground">
1266
+ <Figures>{message.text}</Figures>
1267
+ </span>
1268
+ </p>
1269
+ ))
1270
+ ) : (
1271
+ <p className="py-4 text-sm text-muted-foreground">{copy.noMessages}</p>
1272
+ )}
1273
+ </TabsContent>
1274
+ </div>
1275
+ </Tabs>
1276
+ <Button type="button" size="lg" className="w-full" onClick={onInvite}>
1277
+ <UserPlus aria-hidden />
1278
+ {copy.invite}
1279
+ </Button>
1280
+ </aside>
1281
+ )
1282
+ }