@ghxstship/xos-ui 0.0.0-stage → 4.16.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 (487) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -2
  3. package/package.json +76 -4
  4. package/scripts/generate-stories.mjs +85 -0
  5. package/src/AIDisclosureBadge/README.md +7 -0
  6. package/src/AccessGridMatrix/README.md +3 -0
  7. package/src/Accordion/README.md +7 -0
  8. package/src/ActivityFeedItem/README.md +3 -0
  9. package/src/AgencySlate/README.md +3 -0
  10. package/src/AppShell/README.md +5 -0
  11. package/src/ApplicationForm/README.md +3 -0
  12. package/src/AssertionRankBadge/README.md +5 -0
  13. package/src/AvailabilityCalendar/README.md +3 -0
  14. package/src/Avatar/Avatar.tsx +56 -0
  15. package/src/Avatar/README.md +3 -0
  16. package/src/Avatar/avatar-class.ts +19 -0
  17. package/src/AvatarStack/README.md +3 -0
  18. package/src/Badge/README.md +3 -0
  19. package/src/Banner/README.md +3 -0
  20. package/src/BidSheet/README.md +3 -0
  21. package/src/Board/README.md +6 -0
  22. package/src/BrandMark/BrandMark.tsx +95 -0
  23. package/src/BrandMark/README.md +7 -0
  24. package/src/BrandSlot/README.md +6 -0
  25. package/src/Breadcrumbs/README.md +3 -0
  26. package/src/BudgetGrid/README.md +10 -0
  27. package/src/BulkActionBar/README.md +3 -0
  28. package/src/Button/Button.tsx +100 -0
  29. package/src/Button/README.md +10 -0
  30. package/src/Button/button-class.ts +54 -0
  31. package/src/ButtonLink/README.md +7 -0
  32. package/src/Calendar/README.md +3 -0
  33. package/src/Card/README.md +3 -0
  34. package/src/Chart/README.md +9 -0
  35. package/src/Charts/Calendar.tsx +228 -0
  36. package/src/Charts/Chart.tsx +547 -0
  37. package/src/Charts/Charts.tsx +20 -0
  38. package/src/Charts/Charts.types.ts +40 -0
  39. package/src/Charts/Gallery.tsx +25 -0
  40. package/src/Charts/Heatmap.tsx +116 -0
  41. package/src/Charts/OrgChart.tsx +53 -0
  42. package/src/Charts/ResourceSchedule.tsx +91 -0
  43. package/src/Charts/Sparkline.tsx +59 -0
  44. package/src/Charts/Timeline.tsx +777 -0
  45. package/src/Charts/categories.ts +21 -0
  46. package/src/Charts/timeline-plan.ts +191 -0
  47. package/src/Checkbox/README.md +6 -0
  48. package/src/ChecklistRunner/README.md +3 -0
  49. package/src/Chips/Badge.tsx +27 -0
  50. package/src/Chips/Chips.shared.tsx +34 -0
  51. package/src/Chips/Chips.tsx +21 -0
  52. package/src/Chips/Chips.types.ts +35 -0
  53. package/src/Chips/DepartmentChip.tsx +16 -0
  54. package/src/Chips/PhaseChip.tsx +16 -0
  55. package/src/Chips/PropertyChip.tsx +15 -0
  56. package/src/Chips/RecordKindChip.tsx +14 -0
  57. package/src/Chips/StateChip.tsx +19 -0
  58. package/src/Chips/Tag.tsx +47 -0
  59. package/src/Chips/URIDChip.tsx +14 -0
  60. package/src/Chips/chipClass.tsx +8 -0
  61. package/src/Chips/codeClass.tsx +6 -0
  62. package/src/ClockButton/README.md +6 -0
  63. package/src/CodeSample/README.md +7 -0
  64. package/src/ColorPicker/README.md +5 -0
  65. package/src/Combobox/README.md +7 -0
  66. package/src/CommandMenu/CommandMenu.tsx +179 -0
  67. package/src/CommandMenu/README.md +5 -0
  68. package/src/ComparisonTable/README.md +9 -0
  69. package/src/Compass/ChecklistRunner.tsx +128 -0
  70. package/src/Compass/ClockButton.tsx +57 -0
  71. package/src/Compass/Compass.shared.tsx +58 -0
  72. package/src/Compass/Compass.tsx +26 -0
  73. package/src/Compass/Compass.types.ts +79 -0
  74. package/src/Compass/DaySheetView.tsx +104 -0
  75. package/src/Compass/ForceUpdateScreen.tsx +29 -0
  76. package/src/Compass/LiveActivity.tsx +60 -0
  77. package/src/Compass/OfflineBanner.tsx +31 -0
  78. package/src/Compass/PhotoCapture.tsx +69 -0
  79. package/src/Compass/QuickIncidentSheet.tsx +46 -0
  80. package/src/Compass/ScanSheet.tsx +83 -0
  81. package/src/Compass/ShiftCard.tsx +55 -0
  82. package/src/Compass/SignaturePad.tsx +71 -0
  83. package/src/Compass/SyncConflictSheet.tsx +60 -0
  84. package/src/ConsentBanner/ConsentBanner.tsx +210 -0
  85. package/src/ConsentBanner/ConsentBannerStatic.tsx +163 -0
  86. package/src/ConsentBanner/ConsentSettings.tsx +213 -0
  87. package/src/ConsentBanner/README.md +101 -0
  88. package/src/ConsentBanner/consent.ts +238 -0
  89. package/src/ConsentBanner/index.ts +40 -0
  90. package/src/ConsentBanner/static.ts +11 -0
  91. package/src/ConsentBanner/useConsent.ts +160 -0
  92. package/src/ContextMenu/README.md +3 -0
  93. package/src/CoordinateMatrix/README.md +6 -0
  94. package/src/CredentialBadge/README.md +7 -0
  95. package/src/CurrencyInput/README.md +7 -0
  96. package/src/DataTable/DataTable.tsx +1034 -0
  97. package/src/DataTable/DataTableResizer.tsx +72 -0
  98. package/src/DataTable/DataTableVirtualizer.tsx +58 -0
  99. package/src/DataTable/README.md +9 -0
  100. package/src/DatePicker/README.md +5 -0
  101. package/src/DateRangePicker/README.md +3 -0
  102. package/src/DaySheetView/README.md +3 -0
  103. package/src/DepartmentChip/README.md +3 -0
  104. package/src/DepartmentGlyph/README.md +18 -0
  105. package/src/Dialog/README.md +3 -0
  106. package/src/DiffViewer/README.md +3 -0
  107. package/src/Divider/README.md +3 -0
  108. package/src/Drawer/README.md +3 -0
  109. package/src/DropdownMenu/README.md +3 -0
  110. package/src/EPKViewer/README.md +3 -0
  111. package/src/Editing/ColorPicker.tsx +125 -0
  112. package/src/Editing/DiffViewer.tsx +49 -0
  113. package/src/Editing/Editing.tsx +29 -0
  114. package/src/Editing/Editing.types.ts +71 -0
  115. package/src/Editing/FilterBuilder.tsx +72 -0
  116. package/src/Editing/QuickAdd.tsx +102 -0
  117. package/src/Editing/QuickAddChips.tsx +180 -0
  118. package/src/Editing/contrastRatio.tsx +11 -0
  119. package/src/Editing/parseQuickAdd.tsx +187 -0
  120. package/src/Editing/quickAddCore.ts +204 -0
  121. package/src/Editing/timeShorthand.ts +157 -0
  122. package/src/Editing/useQuickAdd.ts +201 -0
  123. package/src/EmergencyCodeCard/README.md +10 -0
  124. package/src/EmptyState/README.md +5 -0
  125. package/src/EngagementTimeline/README.md +3 -0
  126. package/src/ErrorState/README.md +3 -0
  127. package/src/FAQ/README.md +7 -0
  128. package/src/FeatureGrid/README.md +7 -0
  129. package/src/Feedback/Banner.tsx +40 -0
  130. package/src/Feedback/EmptyState.tsx +32 -0
  131. package/src/Feedback/ErrorState.tsx +32 -0
  132. package/src/Feedback/Feedback.shared.tsx +38 -0
  133. package/src/Feedback/Feedback.tsx +18 -0
  134. package/src/Feedback/Feedback.types.ts +40 -0
  135. package/src/Feedback/InlineAlert.tsx +19 -0
  136. package/src/Feedback/RefusalNotice.tsx +46 -0
  137. package/src/Feedback/RegionBoundary.tsx +102 -0
  138. package/src/Feedback/Toast.tsx +31 -0
  139. package/src/Feedback/ToastRegion.tsx +26 -0
  140. package/src/Feedback/UndoToast.tsx +97 -0
  141. package/src/FeedbackWidget/README.md +3 -0
  142. package/src/Field/Field.tsx +81 -0
  143. package/src/FileDrop/README.md +3 -0
  144. package/src/FilePreviewer/README.md +3 -0
  145. package/src/FilterBuilder/README.md +3 -0
  146. package/src/ForceUpdateScreen/README.md +3 -0
  147. package/src/Forms/Checkbox.tsx +14 -0
  148. package/src/Forms/CurrencyInput.tsx +40 -0
  149. package/src/Forms/Forms.shared.tsx +5 -0
  150. package/src/Forms/Forms.tsx +20 -0
  151. package/src/Forms/Forms.types.ts +33 -0
  152. package/src/Forms/Input.tsx +28 -0
  153. package/src/Forms/Money.tsx +15 -0
  154. package/src/Forms/NumberInput.tsx +85 -0
  155. package/src/Forms/Radio.tsx +31 -0
  156. package/src/Forms/Select.tsx +54 -0
  157. package/src/Forms/Switch.tsx +14 -0
  158. package/src/Forms/Textarea.tsx +30 -0
  159. package/src/Gallery/README.md +5 -0
  160. package/src/GateLifecycleRail/README.md +7 -0
  161. package/src/GateReadinessPanel/README.md +8 -0
  162. package/src/Gateway/AgencySlate.tsx +76 -0
  163. package/src/Gateway/ApplicationForm.tsx +48 -0
  164. package/src/Gateway/AvailabilityCalendar.tsx +82 -0
  165. package/src/Gateway/EPKViewer.tsx +77 -0
  166. package/src/Gateway/EngagementTimeline.tsx +43 -0
  167. package/src/Gateway/Gateway.shared.tsx +5 -0
  168. package/src/Gateway/Gateway.tsx +30 -0
  169. package/src/Gateway/Gateway.types.ts +69 -0
  170. package/src/Gateway/OnboardingPacket.tsx +61 -0
  171. package/src/Gateway/OpportunityCard.tsx +73 -0
  172. package/src/Gateway/OpportunityFilters.tsx +64 -0
  173. package/src/Gateway/PaymentTracker.tsx +71 -0
  174. package/src/Gateway/PayoutDetailsForm.tsx +62 -0
  175. package/src/Gateway/ProfileEditor.tsx +45 -0
  176. package/src/Gateway/ProgressBar.tsx +30 -0
  177. package/src/Gateway/RatingDialog.tsx +83 -0
  178. package/src/Gateway/UpNextCard.tsx +76 -0
  179. package/src/Glyphs/Glyphs.tsx +66 -0
  180. package/src/Grids/BidSheet.tsx +63 -0
  181. package/src/Grids/BudgetGrid.tsx +164 -0
  182. package/src/Grids/Grids.shared.tsx +5 -0
  183. package/src/Grids/Grids.tsx +11 -0
  184. package/src/Grids/Grids.types.ts +38 -0
  185. package/src/Grids/SpreadsheetGrid.tsx +445 -0
  186. package/src/Grids/columnTotal.tsx +14 -0
  187. package/src/Grids/glForCode.tsx +11 -0
  188. package/src/Grids/parseCell.tsx +16 -0
  189. package/src/Help/FeedbackWidget.tsx +89 -0
  190. package/src/Help/Help.tsx +14 -0
  191. package/src/Help/Help.types.ts +51 -0
  192. package/src/Help/HelpPanel.tsx +116 -0
  193. package/src/Help/NotificationCenter.tsx +100 -0
  194. package/src/Help/OnboardingChecklist.tsx +57 -0
  195. package/src/Help/ShortcutEditor.tsx +81 -0
  196. package/src/Help/WhatsNew.tsx +33 -0
  197. package/src/HelpPanel/README.md +5 -0
  198. package/src/HoverCard/README.md +3 -0
  199. package/src/Icon/Icon.tsx +47 -0
  200. package/src/Icon/README.md +9 -0
  201. package/src/IconButton/README.md +6 -0
  202. package/src/Identity/BrandSlot.tsx +20 -0
  203. package/src/Identity/BrandSlotFrame.tsx +38 -0
  204. package/src/Identity/CredentialBadge.tsx +92 -0
  205. package/src/Identity/Identity.tsx +14 -0
  206. package/src/Identity/Identity.types.ts +25 -0
  207. package/src/Identity/PresenceIndicator.tsx +37 -0
  208. package/src/Identity/QRCode.tsx +22 -0
  209. package/src/Identity/WhiteLabelPreview.tsx +33 -0
  210. package/src/Identity/qrPath.tsx +16 -0
  211. package/src/Identity/tokenVariable.tsx +9 -0
  212. package/src/InlineAlert/README.md +3 -0
  213. package/src/Input/README.md +8 -0
  214. package/src/Kbd/Kbd.tsx +30 -0
  215. package/src/Kbd/README.md +6 -0
  216. package/src/Layout/AppShell.tsx +151 -0
  217. package/src/Layout/Layout.tsx +14 -0
  218. package/src/Layout/Layout.types.ts +43 -0
  219. package/src/Layout/PageHeader.tsx +22 -0
  220. package/src/Layout/ResizablePanel.tsx +81 -0
  221. package/src/Layout/ScrollRegion.tsx +107 -0
  222. package/src/Layout/SplitView.tsx +78 -0
  223. package/src/Layout/Stepper.tsx +39 -0
  224. package/src/LegalText/LegalText.tsx +289 -0
  225. package/src/LegalText/README.md +10 -0
  226. package/src/LegalText/index.ts +29 -0
  227. package/src/LegalText/legal-markdown.ts +581 -0
  228. package/src/Link/Link.tsx +25 -0
  229. package/src/Link/README.md +5 -0
  230. package/src/Link/link-class.ts +10 -0
  231. package/src/LiveActivity/README.md +3 -0
  232. package/src/LogoSlotWall/README.md +7 -0
  233. package/src/MapView/README.md +5 -0
  234. package/src/Marketing/AIDisclosureBadge.tsx +31 -0
  235. package/src/Marketing/ButtonLink.tsx +32 -0
  236. package/src/Marketing/CodeSample.tsx +47 -0
  237. package/src/Marketing/ComparisonScroll.tsx +7 -0
  238. package/src/Marketing/ComparisonTable.tsx +104 -0
  239. package/src/Marketing/FAQ.tsx +26 -0
  240. package/src/Marketing/FeatureGrid.tsx +65 -0
  241. package/src/Marketing/GateLifecycleRail.tsx +52 -0
  242. package/src/Marketing/LogoSlotWall.tsx +53 -0
  243. package/src/Marketing/Marketing.tsx +49 -0
  244. package/src/Marketing/Marketing.types.ts +64 -0
  245. package/src/Marketing/MarketingFooter.tsx +88 -0
  246. package/src/Marketing/MarketingHero.tsx +45 -0
  247. package/src/Marketing/MarketingNav.tsx +76 -0
  248. package/src/Marketing/MarketingPoints.tsx +16 -0
  249. package/src/Marketing/MarketingSection.tsx +52 -0
  250. package/src/Marketing/PersonaSwitcher.tsx +137 -0
  251. package/src/Marketing/PriceDisplay.tsx +56 -0
  252. package/src/Marketing/PricingTable.tsx +122 -0
  253. package/src/Marketing/ProofStatStrip.tsx +35 -0
  254. package/src/Marketing/ScreenshotFrame.tsx +56 -0
  255. package/src/Marketing/SurfaceTriptych.tsx +43 -0
  256. package/src/Marketing/Testimonial.tsx +45 -0
  257. package/src/Marketing/marketing-copy.ts +25 -0
  258. package/src/Marketing/provided.tsx +36 -0
  259. package/src/MarketingFooter/README.md +7 -0
  260. package/src/MarketingHero/README.md +7 -0
  261. package/src/MarketingNav/README.md +7 -0
  262. package/src/MarketingSection/README.md +7 -0
  263. package/src/Media/FilePreviewer.tsx +86 -0
  264. package/src/Media/MapView.tsx +200 -0
  265. package/src/Media/Media.tsx +11 -0
  266. package/src/Media/Media.types.ts +47 -0
  267. package/src/Media/RichTextEditor.tsx +198 -0
  268. package/src/Media/toLngLat.tsx +13 -0
  269. package/src/Menus/ContextMenu.tsx +106 -0
  270. package/src/Menus/DropdownMenu.tsx +51 -0
  271. package/src/Menus/Menus.shared.tsx +67 -0
  272. package/src/Menus/Menus.tsx +13 -0
  273. package/src/Menus/Menus.types.ts +49 -0
  274. package/src/Menus/OrgSwitcher.tsx +69 -0
  275. package/src/Menus/compactTriggerClass.tsx +7 -0
  276. package/src/Menus/menuClass.tsx +4 -0
  277. package/src/Menus/menuItemClass.tsx +7 -0
  278. package/src/Menus/orgSwitcherTriggerClass.tsx +7 -0
  279. package/src/Money/README.md +6 -0
  280. package/src/MultiSelect/README.md +3 -0
  281. package/src/Navigation/Accordion.tsx +59 -0
  282. package/src/Navigation/Breadcrumbs.tsx +59 -0
  283. package/src/Navigation/MoreFields.tsx +42 -0
  284. package/src/Navigation/Navigation.shared.tsx +32 -0
  285. package/src/Navigation/Navigation.tsx +21 -0
  286. package/src/Navigation/Navigation.types.ts +63 -0
  287. package/src/Navigation/Pagination.tsx +30 -0
  288. package/src/Navigation/SegmentedControl.tsx +87 -0
  289. package/src/Navigation/Sidebar.tsx +106 -0
  290. package/src/Navigation/TabBar.tsx +61 -0
  291. package/src/Navigation/Tabs.tsx +79 -0
  292. package/src/Navigation/TopNav.tsx +86 -0
  293. package/src/Navigation/sidebar-classes.ts +18 -0
  294. package/src/NotificationCenter/README.md +3 -0
  295. package/src/NumberInput/README.md +3 -0
  296. package/src/OfflineBanner/README.md +3 -0
  297. package/src/OnboardingChecklist/README.md +3 -0
  298. package/src/OnboardingPacket/README.md +3 -0
  299. package/src/OpportunityCard/README.md +5 -0
  300. package/src/OpportunityFilters/README.md +3 -0
  301. package/src/OrgChart/README.md +5 -0
  302. package/src/OrgSwitcher/README.md +3 -0
  303. package/src/Overlays/Dialog.tsx +54 -0
  304. package/src/Overlays/Drawer.tsx +62 -0
  305. package/src/Overlays/HoverCard.tsx +59 -0
  306. package/src/Overlays/Overlays.shared.tsx +12 -0
  307. package/src/Overlays/Overlays.tsx +20 -0
  308. package/src/Overlays/Overlays.types.ts +66 -0
  309. package/src/Overlays/Popover.tsx +70 -0
  310. package/src/Overlays/PropertyList.tsx +19 -0
  311. package/src/Overlays/ShortcutSheet.tsx +62 -0
  312. package/src/Overlays/SidePeek.tsx +117 -0
  313. package/src/Overlays/Tooltip.tsx +73 -0
  314. package/src/Overlays/panelClass.tsx +6 -0
  315. package/src/Overlays/popoverPanelClass.tsx +6 -0
  316. package/src/PageHeader/README.md +3 -0
  317. package/src/Pagination/README.md +3 -0
  318. package/src/PaymentTracker/README.md +3 -0
  319. package/src/PayoutDetailsForm/README.md +3 -0
  320. package/src/PersonaSwitcher/README.md +7 -0
  321. package/src/PhaseChip/README.md +5 -0
  322. package/src/PhaseGlyph/README.md +17 -0
  323. package/src/PhotoCapture/README.md +3 -0
  324. package/src/Pickers/Combobox.tsx +91 -0
  325. package/src/Pickers/DatePicker.tsx +60 -0
  326. package/src/Pickers/DateRangePicker.tsx +78 -0
  327. package/src/Pickers/FileDrop.tsx +84 -0
  328. package/src/Pickers/MultiSelect.tsx +118 -0
  329. package/src/Pickers/Pickers.shared.tsx +337 -0
  330. package/src/Pickers/Pickers.tsx +17 -0
  331. package/src/Pickers/Pickers.types.ts +47 -0
  332. package/src/Pickers/Slider.tsx +59 -0
  333. package/src/Pickers/TimePicker.tsx +68 -0
  334. package/src/Pickers/rankOptions.tsx +12 -0
  335. package/src/Popover/README.md +3 -0
  336. package/src/PresenceIndicator/README.md +3 -0
  337. package/src/PriceDisplay/README.md +7 -0
  338. package/src/PricingTable/README.md +9 -0
  339. package/src/ProfileEditor/README.md +3 -0
  340. package/src/ProgressBar/README.md +3 -0
  341. package/src/ProofStatStrip/README.md +7 -0
  342. package/src/PropertyChip/README.md +3 -0
  343. package/src/ProvenanceBadge/README.md +3 -0
  344. package/src/QRCode/README.md +6 -0
  345. package/src/QuickAdd/README.md +5 -0
  346. package/src/QuickIncidentSheet/README.md +3 -0
  347. package/src/Radio/README.md +3 -0
  348. package/src/RadioChannelTable/README.md +7 -0
  349. package/src/RatingDialog/README.md +3 -0
  350. package/src/ReconciliationTable/README.md +3 -0
  351. package/src/RecordKindChip/README.md +3 -0
  352. package/src/RecordKindGlyph/README.md +34 -0
  353. package/src/RecordRow/README.md +7 -0
  354. package/src/RefusalNotice/README.md +5 -0
  355. package/src/ResizablePanel/README.md +3 -0
  356. package/src/ResourceSchedule/README.md +6 -0
  357. package/src/RichTextEditor/README.md +3 -0
  358. package/src/RichTextEditor/extensions.ts +217 -0
  359. package/src/RunOfShowLive/README.md +5 -0
  360. package/src/ScanSheet/README.md +3 -0
  361. package/src/ScreenshotFrame/README.md +7 -0
  362. package/src/ScrollRegion/README.md +9 -0
  363. package/src/SegmentedControl/README.md +5 -0
  364. package/src/Select/README.md +5 -0
  365. package/src/ShiftCard/README.md +3 -0
  366. package/src/ShortcutEditor/README.md +3 -0
  367. package/src/ShortcutSheet/README.md +3 -0
  368. package/src/SidePeek/README.md +6 -0
  369. package/src/Sidebar/README.md +5 -0
  370. package/src/SignaturePad/README.md +3 -0
  371. package/src/Skeleton/README.md +3 -0
  372. package/src/Slider/README.md +3 -0
  373. package/src/SplitView/README.md +3 -0
  374. package/src/SpreadsheetGrid/README.md +9 -0
  375. package/src/StalenessIndicator/README.md +3 -0
  376. package/src/StatTile/README.md +6 -0
  377. package/src/StateChip/README.md +6 -0
  378. package/src/StateIcon/README.md +6 -0
  379. package/src/StateIcon/StateIcon.tsx +127 -0
  380. package/src/StatusHeatmap/README.md +15 -0
  381. package/src/Stepper/README.md +3 -0
  382. package/src/Surface/Skeleton.tsx +21 -0
  383. package/src/Surface/Surface.tsx +63 -0
  384. package/src/SurfaceTriptych/README.md +7 -0
  385. package/src/Switch/README.md +5 -0
  386. package/src/SyncConflictSheet/README.md +3 -0
  387. package/src/TabBar/README.md +7 -0
  388. package/src/Tabs/README.md +6 -0
  389. package/src/Tag/README.md +3 -0
  390. package/src/TemplateAuth/README.md +6 -0
  391. package/src/TemplateCollection/README.md +6 -0
  392. package/src/TemplateDashboard/README.md +8 -0
  393. package/src/TemplateDocument/README.md +5 -0
  394. package/src/TemplateGatewayShell/README.md +5 -0
  395. package/src/TemplateGridEditor/README.md +3 -0
  396. package/src/TemplateRecord/README.md +3 -0
  397. package/src/TemplateSettings/README.md +5 -0
  398. package/src/TemplateSplitView/README.md +3 -0
  399. package/src/TemplateSystem/README.md +5 -0
  400. package/src/TemplateWizard/README.md +3 -0
  401. package/src/Templates/TemplateAuth.tsx +16 -0
  402. package/src/Templates/TemplateCollection.tsx +35 -0
  403. package/src/Templates/TemplateDashboard.tsx +200 -0
  404. package/src/Templates/TemplateDocument.tsx +24 -0
  405. package/src/Templates/TemplateGatewayShell.tsx +36 -0
  406. package/src/Templates/TemplateGridEditor.tsx +17 -0
  407. package/src/Templates/TemplateRecord.tsx +31 -0
  408. package/src/Templates/TemplateSettings.tsx +26 -0
  409. package/src/Templates/TemplateSplitView.tsx +27 -0
  410. package/src/Templates/TemplateSystem.tsx +19 -0
  411. package/src/Templates/TemplateSystemLink.tsx +55 -0
  412. package/src/Templates/TemplateWizard.tsx +43 -0
  413. package/src/Templates/Templates.tsx +29 -0
  414. package/src/Templates/Templates.types.ts +153 -0
  415. package/src/Testimonial/README.md +7 -0
  416. package/src/Textarea/README.md +3 -0
  417. package/src/TimePicker/README.md +5 -0
  418. package/src/Timeline/README.md +6 -0
  419. package/src/Toast/README.md +3 -0
  420. package/src/Tooltip/README.md +3 -0
  421. package/src/TopNav/README.md +5 -0
  422. package/src/TreeView/README.md +3 -0
  423. package/src/URIDChip/README.md +5 -0
  424. package/src/UndoToast/README.md +3 -0
  425. package/src/UpNextCard/README.md +6 -0
  426. package/src/Views/ActivityFeedItem.tsx +52 -0
  427. package/src/Views/Board.tsx +229 -0
  428. package/src/Views/BulkActionBar.tsx +76 -0
  429. package/src/Views/RecordList.tsx +146 -0
  430. package/src/Views/RecordRow.tsx +132 -0
  431. package/src/Views/StatTile.tsx +88 -0
  432. package/src/Views/TreeView.tsx +161 -0
  433. package/src/Views/Views.tsx +18 -0
  434. package/src/Views/Views.types.ts +99 -0
  435. package/src/WhatsNew/README.md +3 -0
  436. package/src/WhiteLabelPreview/README.md +8 -0
  437. package/src/XOS/AccessGridMatrix.tsx +111 -0
  438. package/src/XOS/AssertionRankBadge.tsx +27 -0
  439. package/src/XOS/CoordinateMatrix.tsx +123 -0
  440. package/src/XOS/EmergencyCodeCard.tsx +109 -0
  441. package/src/XOS/GateReadinessPanel.tsx +100 -0
  442. package/src/XOS/ProvenanceBadge.tsx +19 -0
  443. package/src/XOS/RadioChannelTable.tsx +50 -0
  444. package/src/XOS/ReconciliationTable.tsx +64 -0
  445. package/src/XOS/RunOfShowLive.tsx +97 -0
  446. package/src/XOS/StalenessIndicator.tsx +31 -0
  447. package/src/XOS/StatusHeatmap.tsx +200 -0
  448. package/src/XOS/XOS.shared.tsx +90 -0
  449. package/src/XOS/XOS.tsx +29 -0
  450. package/src/XOS/XOS.types.ts +99 -0
  451. package/src/XOS/resolveStep.tsx +16 -0
  452. package/src/brand/defaults/README.md +13 -0
  453. package/src/brand/defaults/app-icon.svg +6 -0
  454. package/src/brand/defaults/favicon.svg +6 -0
  455. package/src/brand/defaults/logo-dark.svg +6 -0
  456. package/src/brand/defaults/logo-light.svg +6 -0
  457. package/src/brand/defaults/splash.svg +6 -0
  458. package/src/index.ts +606 -0
  459. package/src/lib/context.tsx +156 -0
  460. package/src/lib/cx.ts +4 -0
  461. package/src/lib/format.ts +200 -0
  462. package/src/lib/hooks.ts +187 -0
  463. package/src/lib/i18n.ts +97 -0
  464. package/src/lib/icons.ts +45 -0
  465. package/src/lib/live.ts +92 -0
  466. package/src/lib/preferences.ts +242 -0
  467. package/src/lib/route-transition.tsx +22 -0
  468. package/src/lib/selection.ts +92 -0
  469. package/src/lib/shortcut-targets.ts +125 -0
  470. package/src/lib/theme.ts +50 -0
  471. package/src/lib/undo-stack.ts +125 -0
  472. package/src/marketing.ts +33 -0
  473. package/src/pwa/action-outcome.tsx +81 -0
  474. package/src/pwa/analytics.ts +236 -0
  475. package/src/pwa/browser-push.tsx +132 -0
  476. package/src/pwa/client.ts +126 -0
  477. package/src/pwa/index.ts +40 -0
  478. package/src/pwa/offline-key.ts +18 -0
  479. package/src/pwa/service-worker.ts +229 -0
  480. package/src/static.ts +10 -0
  481. package/src/styles/base.css +208 -0
  482. package/src/styles/components.css +453 -0
  483. package/src/styles/index.css +16 -0
  484. package/src/styles/marketing.css +1260 -0
  485. package/src/styles/reference.css +5792 -0
  486. package/src/types/reference.d.ts +1608 -0
  487. package/tailwind.config.cjs +3 -0
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import { type ReactElement } from "react";
4
+ import { useLocale, useMessages } from "../lib/context.tsx";
5
+ import { AIDisclosureBadge as StaticAIDisclosureBadge } from "./AIDisclosureBadge.tsx";
6
+ import { ComparisonTable as StaticComparisonTable } from "./ComparisonTable.tsx";
7
+ import { FeatureGrid as StaticFeatureGrid } from "./FeatureGrid.tsx";
8
+ import { GateLifecycleRail as StaticGateLifecycleRail } from "./GateLifecycleRail.tsx";
9
+ import { LogoSlotWall as StaticLogoSlotWall } from "./LogoSlotWall.tsx";
10
+ import { PriceDisplay as StaticPriceDisplay } from "./PriceDisplay.tsx";
11
+ import { ProofStatStrip as StaticProofStatStrip } from "./ProofStatStrip.tsx";
12
+ import { type MarketingLocaleProps } from "./Marketing.types.ts";
13
+
14
+ /**
15
+ * The Marketing components that print a shared string or format a number, bound to the nearest
16
+ * XOSProvider. `@xos/ui` exports these, so inside a product shell they follow its locale like every
17
+ * other component. `@xos/ui/marketing` exports the plain ones, which a server component renders
18
+ * with the page's `locale` and `messages`.
19
+ */
20
+ function provided<P extends MarketingLocaleProps>(
21
+ Component: (props: P) => ReactElement | null,
22
+ ): (props: P) => ReactElement | null {
23
+ return function Provided(props: P): ReactElement | null {
24
+ const locale = useLocale();
25
+ const messages = useMessages();
26
+ return <Component {...({ locale, messages, ...props } as P)} />;
27
+ };
28
+ }
29
+
30
+ export const ProofStatStrip = provided(StaticProofStatStrip);
31
+ export const GateLifecycleRail = provided(StaticGateLifecycleRail);
32
+ export const FeatureGrid = provided(StaticFeatureGrid);
33
+ export const ComparisonTable = provided(StaticComparisonTable);
34
+ export const PriceDisplay = provided(StaticPriceDisplay);
35
+ export const LogoSlotWall = provided(StaticLogoSlotWall);
36
+ export const AIDisclosureBadge = provided(StaticAIDisclosureBadge);
@@ -0,0 +1,7 @@
1
+ # MarketingFooter
2
+
3
+ MarketingFooter closes a public page with link columns, the legal line, and the language and theme controls.
4
+
5
+ Link columns sit in two columns on phones and in one row from bp-sm; every link stays visible at every width. The language control lists only the locales of open territories, so a closed territory's language is never offered. The theme control switches Dark, Light and Sunlight.
6
+
7
+ The consumer provides a BrandMark, the columns, the legal line and the open locales.
@@ -0,0 +1,7 @@
1
+ # MarketingHero
2
+
3
+ MarketingHero opens a public page with the display heading, a lead, at most two calls to action and a product image.
4
+
5
+ The default variant sets the heading in display-64 beside a ScreenshotFrame and stacks below bp-lg. The `compact` variant centers a display-48 heading with no media and closes a page. A page has one default hero, and it carries the only `h1`.
6
+
7
+ `note` holds terms that must sit beside the buttons, such as trial length and what happens when the trial ends. The consumer provides `title`, `lead`, ButtonLink elements as `actions`, and a ScreenshotFrame as `media`.
@@ -0,0 +1,7 @@
1
+ # MarketingNav
2
+
3
+ MarketingNav is the sticky header of a public page: brand, section links and the primary call to action.
4
+
5
+ A skip link comes first in the tab order and targets `#main`. From bp-lg the section links sit in the bar; below it they move into a menu panel opened by a labeled toggle with `aria-expanded`. `actions` stays visible at every width, so the primary call to action is never more than one tap away. `secondaryActions` hides below bp-sm and repeats inside the panel.
6
+
7
+ The consumer provides a BrandMark as `brand`, the links, and ButtonLink elements for the actions. Mark the current page with `current`.
@@ -0,0 +1,7 @@
1
+ # MarketingSection
2
+
3
+ MarketingSection is one band of a public page: an eyebrow, a display-40 heading, a lead and the section content.
4
+
5
+ Sections alternate on `bg-canvas` and, with `tone="surface"`, on `bg-surface`, separated by a hairline. Vertical rhythm is space-64, and space-96 from bp-lg. The heading is labeled by `id`, so in-page links and the navigation land on it.
6
+
7
+ Write `lead` as a direct answer of 40 to 60 words that names the product and states one verifiable fact; search and answer engines quote this passage. The consumer provides `id`, `title`, `lead` and the content.
@@ -0,0 +1,86 @@
1
+ import { useState, type ReactElement } from "react";
2
+ import { cx } from "../lib/cx.ts";
3
+ import { useT } from "../lib/context.tsx";
4
+ import { Icon } from "../Icon/Icon.tsx";
5
+ import { IconButton } from "../Button/Button.tsx";
6
+ import { type FilePreviewerProps } from "./Media.types.ts";
7
+
8
+ /**
9
+ * A file with page thumbnails, zoom and download; the page area holds the rendered page. A file of
10
+ * one page (an image, a video) has no thumbnail rail.
11
+ */
12
+ export function FilePreviewer({
13
+ name,
14
+ meta,
15
+ pages,
16
+ page = 0,
17
+ zoom = "100%",
18
+ icon,
19
+ children,
20
+ onPage,
21
+ onZoomIn,
22
+ onZoomOut,
23
+ onDownload,
24
+ zoomable = true,
25
+ downloadable = true,
26
+ }: FilePreviewerProps): ReactElement {
27
+ const t = useT();
28
+ const [current, setCurrent] = useState(page);
29
+ return (
30
+ <section aria-label={name} className="overflow-hidden rounded-card border border-border-subtle">
31
+ <div className="flex flex-wrap items-center gap-8 min-h-(--ui-shell-head) ps-12 pe-8 bg-bg-surface border-b border-border-subtle">
32
+ <Icon name={icon ?? "FileText"} color="var(--color-text-secondary)" />
33
+ <strong className="font-semibold">{name}</strong>
34
+ <span className="text-text-secondary">{meta}</span>
35
+ <span className="flex items-center gap-2 ms-auto">
36
+ {zoomable ? (
37
+ <>
38
+ <IconButton icon="ZoomOut" label={t("file.zoomOut")} onClick={onZoomOut} />
39
+ <span className="text-text-secondary tabular-nums" aria-live="polite">
40
+ {zoom}
41
+ </span>
42
+ <IconButton icon="ZoomIn" label={t("file.zoomIn")} onClick={onZoomIn} />
43
+ </>
44
+ ) : null}
45
+ {downloadable ? (
46
+ <IconButton icon="Download" label={t("action.download")} onClick={onDownload} />
47
+ ) : null}
48
+ </span>
49
+ </div>
50
+ <div className="flex h-(--ui-listbox-max) max-sm:flex-col">
51
+ {pages > 1 ? (
52
+ <div className="flex flex-col max-sm:flex-row gap-8 w-(--space-64) max-sm:w-full p-8 overflow-auto bg-bg-surface border-e border-border-subtle">
53
+ {Array.from({ length: pages }, (_, i) => (
54
+ <button
55
+ key={i}
56
+ type="button"
57
+ aria-label={t("file.page", { n: i + 1 })}
58
+ aria-pressed={i === current}
59
+ onClick={() => {
60
+ setCurrent(i);
61
+ onPage?.(i);
62
+ }}
63
+ className={cx(
64
+ "flex-none h-(--space-64) max-sm:w-(--space-48) rounded-chip border border-border-subtle bg-bg-raised text-text-11 text-text-tertiary cursor-pointer tabular-nums",
65
+ "aria-pressed:border-accent aria-pressed:text-text-primary",
66
+ )}
67
+ >
68
+ {i + 1}
69
+ </button>
70
+ ))}
71
+ </div>
72
+ ) : null}
73
+ {/* Safe centering keeps a zoomed page scrollable to its start edge. The region takes focus so a
74
+ keyboard can scroll a page larger than the frame. */}
75
+ <div
76
+ role="region"
77
+ aria-label={t("file.previewArea", { name })}
78
+ tabIndex={0}
79
+ className="flex flex-1 justify-center-safe p-24 overflow-auto bg-bg-surface"
80
+ >
81
+ {children}
82
+ </div>
83
+ </div>
84
+ </section>
85
+ );
86
+ }
@@ -0,0 +1,200 @@
1
+ import { useEffect, useRef, useState, type ReactElement } from "react";
2
+ import { cx } from "../lib/cx.ts";
3
+ import { useT } from "../lib/context.tsx";
4
+ import { type MapViewProps } from "./Media.types.ts";
5
+ import { toLngLat } from "./toLngLat.tsx";
6
+
7
+ const PIN_FILL: Record<NonNullable<MapViewProps["pins"][number]["tone"]>, string> = {
8
+ accent: cx("fill-accent"),
9
+ danger: cx("fill-danger"),
10
+ warning: cx("fill-warning"),
11
+ };
12
+
13
+ const PIN_VAR: Record<NonNullable<MapViewProps["pins"][number]["tone"]>, string> = {
14
+ accent: "--color-accent-default",
15
+ danger: "--color-danger",
16
+ warning: "--color-warning",
17
+ };
18
+
19
+ function pinFill(tone: MapViewProps["pins"][number]["tone"]): string {
20
+ return PIN_FILL[tone ?? "accent"];
21
+ }
22
+
23
+ function cssColor(name: string): string {
24
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
25
+ }
26
+
27
+ /**
28
+ * Venues, zones, geofences and incident pins. The numbered legend is the text alternative. With
29
+ * `geo`, the same shapes render on MapLibre tiles; without WebGL the drawing stays.
30
+ */
31
+ export function MapView({
32
+ label,
33
+ width,
34
+ height,
35
+ zones,
36
+ pins,
37
+ geofence,
38
+ geo,
39
+ }: MapViewProps): ReactElement {
40
+ const t = useT();
41
+ const container = useRef<HTMLDivElement>(null);
42
+ const [tiles, setTiles] = useState(false);
43
+ useEffect(() => {
44
+ if (!geo || !container.current) return undefined;
45
+ let disposed = false;
46
+ let map: { remove: () => void } | null = null;
47
+ const el = container.current;
48
+ import("maplibre-gl")
49
+ .then(({ Map: MapLibre }) => {
50
+ if (disposed) return;
51
+ const ll = (x: number, y: number) => toLngLat(x, y, width, height, geo.bounds);
52
+ const instance = new MapLibre({
53
+ container: el,
54
+ style: geo.styleUrl,
55
+ bounds: geo.bounds,
56
+ attributionControl: { compact: true },
57
+ });
58
+ map = instance;
59
+ instance.on("load", () => {
60
+ instance.addSource("zones", {
61
+ type: "geojson",
62
+ data: {
63
+ type: "FeatureCollection",
64
+ features: zones.map((z) => ({
65
+ type: "Feature",
66
+ properties: { label: z.label },
67
+ geometry: {
68
+ type: "Polygon",
69
+ coordinates: [
70
+ [
71
+ ll(z.x, z.y),
72
+ ll(z.x + z.w, z.y),
73
+ ll(z.x + z.w, z.y + z.h),
74
+ ll(z.x, z.y + z.h),
75
+ ll(z.x, z.y),
76
+ ],
77
+ ],
78
+ },
79
+ })),
80
+ },
81
+ });
82
+ instance.addLayer({
83
+ id: "zones",
84
+ type: "line",
85
+ source: "zones",
86
+ paint: { "line-color": cssColor("--color-border-strong"), "line-width": 1 },
87
+ });
88
+ instance.addSource("pins", {
89
+ type: "geojson",
90
+ data: {
91
+ type: "FeatureCollection",
92
+ features: pins.map((p, i) => ({
93
+ type: "Feature",
94
+ properties: { n: String(i + 1), color: cssColor(PIN_VAR[p.tone ?? "accent"]) },
95
+ geometry: { type: "Point", coordinates: ll(p.x, p.y) },
96
+ })),
97
+ },
98
+ });
99
+ instance.addLayer({
100
+ id: "pins",
101
+ type: "circle",
102
+ source: "pins",
103
+ paint: {
104
+ "circle-radius": 9,
105
+ "circle-color": ["get", "color"],
106
+ "circle-stroke-width": 2,
107
+ "circle-stroke-color": cssColor("--color-bg-canvas"),
108
+ },
109
+ });
110
+ setTiles(true);
111
+ });
112
+ })
113
+ .catch((error: unknown) => {
114
+ if (!disposed) setTiles(false);
115
+ if (!(error instanceof Error)) throw error;
116
+ });
117
+ return () => {
118
+ disposed = true;
119
+ map?.remove();
120
+ };
121
+ }, [geo, zones, pins, width, height]);
122
+ return (
123
+ <figure className="grid grid-cols-[minmax(0,1fr)_var(--ui-settings-nav)] max-md:grid-cols-[minmax(0,1fr)] gap-16 m-0">
124
+ <div className="relative">
125
+ {geo ? (
126
+ <div
127
+ ref={container}
128
+ aria-hidden="true"
129
+ inert
130
+ className={cx("absolute inset-0 rounded-card overflow-hidden", !tiles && "hidden")}
131
+ />
132
+ ) : null}
133
+ <svg
134
+ viewBox={`0 0 ${width} ${height}`}
135
+ role="img"
136
+ aria-label={label}
137
+ className={cx(
138
+ "w-full h-auto rounded-card border border-border-subtle bg-bg-surface",
139
+ tiles && "opacity-0",
140
+ )}
141
+ >
142
+ {zones.map((z) => (
143
+ <g key={z.label}>
144
+ <rect
145
+ x={z.x}
146
+ y={z.y}
147
+ width={z.w}
148
+ height={z.h}
149
+ rx={6}
150
+ className="fill-bg-raised stroke-border-strong"
151
+ />
152
+ <text
153
+ x={z.x + 8}
154
+ y={z.y + 18}
155
+ className="fill-text-secondary text-text-11 font-medium"
156
+ >
157
+ {z.label}
158
+ </text>
159
+ </g>
160
+ ))}
161
+ {geofence ? (
162
+ <circle
163
+ cx={geofence.x}
164
+ cy={geofence.y}
165
+ r={geofence.r}
166
+ className="fill-none stroke-success"
167
+ strokeWidth={1.5}
168
+ strokeDasharray="6 4"
169
+ />
170
+ ) : null}
171
+ {pins.map((p, i) => (
172
+ <g key={`${p.label}-${i}`} transform={`translate(${p.x} ${p.y})`}>
173
+ <circle r={9} className={cx(pinFill(p.tone), "stroke-bg-canvas")} strokeWidth={2} />
174
+ <text
175
+ y={3.5}
176
+ textAnchor="middle"
177
+ className="fill-accent-text-on text-text-11 font-bold"
178
+ >
179
+ {i + 1}
180
+ </text>
181
+ </g>
182
+ ))}
183
+ </svg>
184
+ </div>
185
+ <figcaption>
186
+ <ol
187
+ aria-label={t("view.map")}
188
+ className="flex flex-col gap-6 m-0 p-0 list-none text-text-12"
189
+ >
190
+ {pins.map((p, i) => (
191
+ <li key={`${p.label}-${i}`} className="flex gap-8">
192
+ <span className="w-(--space-12) text-text-tertiary tabular-nums">{i + 1}</span>
193
+ {p.label}
194
+ </li>
195
+ ))}
196
+ </ol>
197
+ </figcaption>
198
+ </figure>
199
+ );
200
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ type RichTextEditorProps,
3
+ type FilePreviewerProps,
4
+ type MapGeo,
5
+ type MapViewProps,
6
+ } from "./Media.types.ts";
7
+ export { RichTextEditor } from "./RichTextEditor.tsx";
8
+ export { FilePreviewer } from "./FilePreviewer.tsx";
9
+ export { toLngLat } from "./toLngLat.tsx";
10
+ export { MapView } from "./MapView.tsx";
11
+ export type { SuggestionItem, SuggestionSource } from "../RichTextEditor/extensions.ts";
@@ -0,0 +1,47 @@
1
+ import type { Doc } from "yjs";
2
+ import type {
3
+ FilePreviewerProps as ReferenceFilePreviewerProps,
4
+ MapViewProps as ReferenceMapViewProps,
5
+ RichTextEditorProps as ReferenceRichTextEditorProps,
6
+ } from "../types/reference";
7
+ import { type SuggestionSource } from "../RichTextEditor/extensions.ts";
8
+
9
+ export type RichTextEditorProps = ReferenceRichTextEditorProps & {
10
+ /** A Yjs document for live co-editing; with it the document is the source of truth and local undo is off. */
11
+ ydoc?: Doc;
12
+ /** A Yjs provider (Hocuspocus, y-websocket) bound to `ydoc`. */
13
+ provider?: unknown;
14
+ onChange?: (html: string) => void;
15
+ /** Returns the URL to link the selection to, or null to cancel; the Link button shows when given. */
16
+ onRequestLink?: () => string | null | Promise<string | null>;
17
+ editable?: boolean;
18
+ /** People for `@` mentions; the trigger is off without it. */
19
+ mentions?: SuggestionSource;
20
+ /** Records for `#` links, keyed by URID; the trigger is off without it. */
21
+ records?: SuggestionSource;
22
+ /** The URL a record link opens; `/r/<urid>` by default. */
23
+ recordHref?: (urid: string) => string;
24
+ };
25
+
26
+ export type FilePreviewerProps = ReferenceFilePreviewerProps & {
27
+ onPage?: (page: number) => void;
28
+ onZoomIn?: () => void;
29
+ onZoomOut?: () => void;
30
+ onDownload?: () => void;
31
+ /** False for a file with no zoom, such as a video or a file shown only by name. */
32
+ zoomable?: boolean;
33
+ /** False while there is nothing to download yet, or when the file was refused. */
34
+ downloadable?: boolean;
35
+ };
36
+
37
+ export interface MapGeo {
38
+ /** MapLibre style URL for the basemap tiles. */
39
+ styleUrl: string;
40
+ /** [west, south, east, north] that the drawing's width and height map onto. */
41
+ bounds: [number, number, number, number];
42
+ }
43
+
44
+ export type MapViewProps = ReferenceMapViewProps & {
45
+ /** Renders the same zones, pins and geofence on MapLibre tiles; the drawing remains the fallback. */
46
+ geo?: MapGeo;
47
+ };
@@ -0,0 +1,198 @@
1
+ import { useId, useMemo, useRef, useState, type ReactElement } from "react";
2
+ import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
3
+ import StarterKit from "@tiptap/starter-kit";
4
+ import Collaboration from "@tiptap/extension-collaboration";
5
+ import { cx } from "../lib/cx.ts";
6
+ import { useT } from "../lib/context.tsx";
7
+ import { Icon } from "../Icon/Icon.tsx";
8
+ import { iconButtonClass } from "../Button/Button.tsx";
9
+ import {
10
+ blockExtensions,
11
+ mentionExtension,
12
+ recordLinkExtension,
13
+ slashCommands,
14
+ slashExtension,
15
+ type MenuListener,
16
+ type SuggestionMenu,
17
+ } from "../RichTextEditor/extensions.ts";
18
+ import { type RichTextEditorProps } from "./Media.types.ts";
19
+
20
+ /**
21
+ * A document editor on Tiptap with an accessible formatting toolbar (Cmd or Ctrl+B and +I), `/`
22
+ * blocks, `@` mentions, `#` record links, tables and checklists and, with `ydoc`, live co-editing
23
+ * through Yjs.
24
+ */
25
+ export function RichTextEditor({
26
+ label,
27
+ html,
28
+ presence,
29
+ ydoc,
30
+ provider,
31
+ onChange,
32
+ onRequestLink,
33
+ editable = true,
34
+ mentions,
35
+ records,
36
+ recordHref = (urid) => `/r/${encodeURIComponent(urid)}`,
37
+ }: RichTextEditorProps): ReactElement {
38
+ const t = useT();
39
+ const [menu, setMenu] = useState<SuggestionMenu | null>(null);
40
+ const listener = useRef<MenuListener>(setMenu);
41
+ listener.current = setMenu;
42
+ const listen = () => listener.current;
43
+ const menuId = useId();
44
+ const extensions = useMemo(
45
+ () => [
46
+ ...(ydoc
47
+ ? [
48
+ StarterKit.configure({ undoRedo: false, link: { openOnClick: false } }),
49
+ Collaboration.configure({ document: ydoc, ...(provider ? { provider } : {}) }),
50
+ ]
51
+ : [StarterKit.configure({ link: { openOnClick: false } })]),
52
+ ...blockExtensions(),
53
+ slashExtension(slashCommands(t), listen),
54
+ ...(mentions ? [mentionExtension(mentions, listen)] : []),
55
+ ...(records ? [recordLinkExtension(records, recordHref, listen)] : []),
56
+ ],
57
+ // The editor is created once; later prop changes do not rebuild its schema.
58
+ [],
59
+ );
60
+ const editor = useEditor({
61
+ extensions,
62
+ ...(ydoc ? {} : { content: html }),
63
+ editable,
64
+ immediatelyRender: true,
65
+ editorProps: {
66
+ attributes: {
67
+ role: "textbox",
68
+ "aria-multiline": "true",
69
+ "aria-label": label,
70
+ class: cx(
71
+ "min-h-(--space-64) px-16 py-12 outline-none text-text-14 leading-relaxed [&_ul]:ps-20 [&_ol]:ps-20 [&_a]:text-accent-text [&_a]:underline",
72
+ "[&_.xos-mention]:text-accent-text [&_.xos-mention]:font-medium [&_ul[data-type=taskList]]:ps-0 [&_ul[data-type=taskList]]:list-none [&_li[data-type=taskItem]]:flex [&_li[data-type=taskItem]]:gap-8",
73
+ "[&_table]:border-collapse [&_td]:border [&_td]:border-border-subtle [&_td]:p-6 [&_th]:border [&_th]:border-border-subtle [&_th]:p-6 [&_th]:bg-bg-hover [&_th]:text-start [&_blockquote]:ps-12 [&_blockquote]:border-s-stroke-accent [&_blockquote]:border-border-strong",
74
+ ),
75
+ },
76
+ },
77
+ onUpdate: ({ editor: e }) => onChange?.(e.getHTML()),
78
+ });
79
+ const state = useEditorState({
80
+ editor,
81
+ selector: ({ editor: e }) => ({
82
+ bold: e?.isActive("bold") ?? false,
83
+ italic: e?.isActive("italic") ?? false,
84
+ bullets: e?.isActive("bulletList") ?? false,
85
+ tasks: e?.isActive("taskList") ?? false,
86
+ table: e?.isActive("table") ?? false,
87
+ link: e?.isActive("link") ?? false,
88
+ }),
89
+ });
90
+ const tool = (
91
+ icon: string,
92
+ name: string,
93
+ active: boolean,
94
+ run: () => void,
95
+ shortcut?: string,
96
+ ) => (
97
+ <button
98
+ key={name}
99
+ type="button"
100
+ aria-label={name}
101
+ aria-pressed={active}
102
+ title={shortcut ? t("tooltip.withShortcut", { label: name, shortcut }) : name}
103
+ onMouseDown={(e) => e.preventDefault()}
104
+ onClick={run}
105
+ className={cx(iconButtonClass(), "aria-pressed:bg-bg-hover aria-pressed:text-text-primary")}
106
+ >
107
+ <Icon name={icon} />
108
+ </button>
109
+ );
110
+ return (
111
+ <div className="max-w-(--layout-peek) rounded-card border border-border-control bg-bg-surface focus-within:outline-solid focus-within:outline-stroke-focus focus-within:outline-focus-ring">
112
+ <div
113
+ role="toolbar"
114
+ aria-label={t("rte.toolbar")}
115
+ className="flex flex-wrap items-center gap-2 p-4 border-b border-border-subtle"
116
+ >
117
+ {tool(
118
+ "Bold",
119
+ t("rte.bold"),
120
+ state?.bold ?? false,
121
+ () => editor?.chain().focus().toggleBold().run(),
122
+ "⌘B",
123
+ )}
124
+ {tool(
125
+ "Italic",
126
+ t("rte.italic"),
127
+ state?.italic ?? false,
128
+ () => editor?.chain().focus().toggleItalic().run(),
129
+ "⌘I",
130
+ )}
131
+ {tool("List", t("rte.bullets"), state?.bullets ?? false, () =>
132
+ editor?.chain().focus().toggleBulletList().run(),
133
+ )}
134
+ {tool("ListChecks", t("rte.checklist"), state?.tasks ?? false, () =>
135
+ editor?.chain().focus().toggleTaskList().run(),
136
+ )}
137
+ {tool("Table", t("rte.table"), state?.table ?? false, () =>
138
+ editor?.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
139
+ )}
140
+ {onRequestLink
141
+ ? tool("Link2", t("rte.link"), state?.link ?? false, () => {
142
+ void Promise.resolve(onRequestLink()).then((href) => {
143
+ if (href) editor?.chain().focus().extendMarkRange("link").setLink({ href }).run();
144
+ else if (href === "") editor?.chain().focus().unsetLink().run();
145
+ });
146
+ })
147
+ : null}
148
+ {presence ? <span className="ms-auto">{presence}</span> : null}
149
+ </div>
150
+ <EditorContent editor={editor} />
151
+ {menu ? (
152
+ <ul
153
+ id={menuId}
154
+ role="listbox"
155
+ aria-label={t(`rte.menu.${menu.kind}`)}
156
+ className="fixed z-popover flex flex-col min-w-(--ui-menu-min) max-h-(--ui-listbox-max) overflow-auto m-0 p-4 list-none rounded-card border border-border-subtle bg-bg-raised shadow-menu xos-anim-pop"
157
+ style={
158
+ menu.rect
159
+ ? { insetBlockStart: menu.rect.bottom + 4, insetInlineStart: menu.rect.left }
160
+ : undefined
161
+ }
162
+ >
163
+ {menu.items.length === 0 ? (
164
+ <li
165
+ role="option"
166
+ aria-selected={false}
167
+ aria-disabled
168
+ className="px-8 py-6 text-text-secondary"
169
+ >
170
+ {t("rte.noMatches")}
171
+ </li>
172
+ ) : (
173
+ menu.items.map((item, i) => (
174
+ <li
175
+ key={item.id}
176
+ role="option"
177
+ aria-selected={i === menu.active}
178
+ onMouseDown={(e) => {
179
+ e.preventDefault();
180
+ menu.select(i);
181
+ }}
182
+ className="flex items-center justify-between gap-12 min-h-target-fine px-8 py-6 rounded-chip cursor-pointer aria-selected:bg-bg-hover"
183
+ >
184
+ <span>{item.label}</span>
185
+ {item.meta ? (
186
+ <span className="font-mono text-text-12 text-text-secondary">{item.meta}</span>
187
+ ) : null}
188
+ </li>
189
+ ))
190
+ )}
191
+ </ul>
192
+ ) : null}
193
+ <div className="px-12 py-6 border-t border-border-subtle text-text-11 text-text-secondary">
194
+ {t("rte.hint")}
195
+ </div>
196
+ </div>
197
+ );
198
+ }
@@ -0,0 +1,13 @@
1
+ import { type MapGeo } from "./Media.types.ts";
2
+
3
+ /** Converts a drawing point to longitude and latitude across the geo bounds. */
4
+ export function toLngLat(
5
+ x: number,
6
+ y: number,
7
+ width: number,
8
+ height: number,
9
+ bounds: MapGeo["bounds"],
10
+ ): [number, number] {
11
+ const [west, south, east, north] = bounds;
12
+ return [west + (x / width) * (east - west), north - (y / height) * (north - south)];
13
+ }