@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,180 @@
1
+ import { useEffect, useState, type ReactElement } from "react";
2
+ import { useLocale, useT } from "../lib/context.tsx";
3
+ import { formatList } from "../lib/format.ts";
4
+ import { Icon } from "../Icon/Icon.tsx";
5
+ import { chipClass } from "../Chips/Chips.tsx";
6
+ import { RecordKindGlyph } from "../Glyphs/Glyphs.tsx";
7
+ import { isRecordKind } from "@xos/icons/glyphs";
8
+ import {
9
+ finalizeQuickAdd,
10
+ quickAddKey,
11
+ quickAddSigil,
12
+ type QuickAddParse,
13
+ type QuickAddResolution,
14
+ type QuickAddToken,
15
+ type QuickAddTokenType,
16
+ } from "./quickAddCore.ts";
17
+
18
+ /**
19
+ * Sentences of a quick add line, from the app's own catalog. The shared `ui` catalog ships in every
20
+ * page's first JavaScript, so quick add adds none to it (ADR 0073); without these, chips are named as
21
+ * typed and the announcement lists the title and the chips.
22
+ */
23
+ export interface QuickAddCopy {
24
+ /** Names a chip by what it sets, such as "Owner: Dana Kim". */
25
+ field?: (name: string, value: string) => string;
26
+ /** What is announced once typing pauses, such as "Hang truss, with Owner: Dana Kim". */
27
+ announce?: (title: string, fields: string) => string;
28
+ /** Names the list of an ambiguous token's candidates, such as "Choose a match for ~open". */
29
+ choose?: (token: string) => string;
30
+ /** The list's entry before a candidate is chosen, such as "Matches: 2". */
31
+ matches?: (count: number) => string;
32
+ }
33
+
34
+ export interface QuickAddChipsProps {
35
+ parse: QuickAddParse;
36
+ /** Resolutions by token key; without them every token shows as typed. */
37
+ resolutions?: Readonly<Record<string, QuickAddResolution>>;
38
+ /** Candidates chosen for ambiguous tokens, by token key. */
39
+ choices?: Readonly<Record<string, string>>;
40
+ /** Gives each chip a remove button that turns its token back into text. */
41
+ onDismiss?: (token: QuickAddToken) => void;
42
+ /** Called when the person picks one candidate of an ambiguous token. */
43
+ onChoose?: (token: QuickAddToken, id: string) => void;
44
+ /** What each kind of token sets, for chip names and the announcement, such as Owner or Due. */
45
+ labels?: Partial<Record<QuickAddTokenType, string>>;
46
+ copy?: QuickAddCopy;
47
+ }
48
+
49
+ /** A parsed day and time spoken in the person's language; the wall clock is formatted as is. */
50
+ function spokenDate(locale: string, token: QuickAddToken): string {
51
+ const at = (iso: string, time?: string) => {
52
+ const [y = 1970, m = 1, d = 1] = iso.split("-").map(Number);
53
+ const [h = 0, mi = 0] = (time ?? "00:00").split(":").map(Number);
54
+ return new Date(Date.UTC(y, m - 1, d, h, mi));
55
+ };
56
+ const format = new Intl.DateTimeFormat(locale, {
57
+ weekday: "short",
58
+ month: "short",
59
+ day: "numeric",
60
+ timeZone: "UTC",
61
+ ...(token.time ? { hour: "numeric", minute: "2-digit" } : {}),
62
+ });
63
+ if (!token.iso) return token.value;
64
+ const start = at(token.iso, token.time);
65
+ return token.endIso
66
+ ? format.formatRange(start, at(token.endIso, token.endTime ?? token.time))
67
+ : format.format(start);
68
+ }
69
+
70
+ /**
71
+ * The chips of a quick add line (Section 11.7): the title as it will save, then one chip per token,
72
+ * named by what it sets. A token the app could not resolve stays in the title; one with several
73
+ * matches offers them in a list. Remove buttons and lists are reachable with Tab. A status region
74
+ * announces the result once typing pauses.
75
+ */
76
+ export function QuickAddChips({
77
+ parse,
78
+ resolutions,
79
+ choices = {},
80
+ onDismiss,
81
+ onChoose,
82
+ labels = {},
83
+ copy = {},
84
+ }: QuickAddChipsProps): ReactElement {
85
+ const t = useT();
86
+ const locale = useLocale();
87
+ // Tokens still waiting for an answer show as typed, so the title does not jump while it comes.
88
+ const shown: Record<string, QuickAddResolution> = {};
89
+ const chips = parse.tokens.filter((token) => {
90
+ if (token.type === "date" || resolutions === undefined) return true;
91
+ const key = quickAddKey(token);
92
+ const resolution = resolutions[key] ?? { status: "resolved", id: key, label: token.value };
93
+ shown[key] = resolution;
94
+ return resolution.status !== "unresolved";
95
+ });
96
+ const preview = finalizeQuickAdd(parse, resolutions === undefined ? undefined : shown, choices);
97
+ const title = preview.title || t("record.untitled");
98
+ const typed = (token: QuickAddToken) => `${quickAddSigil(token.type)}${token.value}`;
99
+ const valueOf = (token: QuickAddToken) =>
100
+ token.type === "date"
101
+ ? spokenDate(locale, token)
102
+ : (preview.tokens.find((p) => p.type === token.type)?.label ?? token.value);
103
+ /** A chip's name: what it sets and its value when the app names them, else its value as typed. */
104
+ const fieldOf = (token: QuickAddToken, value: string) => {
105
+ const name = labels[token.type];
106
+ if (name && copy.field) return copy.field(name, value);
107
+ return token.type === "date" || value !== token.value ? value : typed(token);
108
+ };
109
+
110
+ const summary = preview.tokens.map((token) => fieldOf(token, valueOf(token)));
111
+ const message =
112
+ summary.length === 0
113
+ ? title
114
+ : (copy.announce?.(title, formatList(locale, summary)) ??
115
+ formatList(locale, [title, ...summary]));
116
+ const [spoken, setSpoken] = useState("");
117
+ useEffect(() => {
118
+ const timer = setTimeout(() => setSpoken(message), 600);
119
+ return () => clearTimeout(timer);
120
+ }, [message]);
121
+
122
+ return (
123
+ <div className="flex flex-wrap items-center gap-4">
124
+ <span className="me-4 font-medium">{title}</span>
125
+ {chips.map((token) => {
126
+ const key = quickAddKey(token);
127
+ const resolution = shown[key];
128
+ const candidates = resolution?.status === "ambiguous" ? resolution.candidates : null;
129
+ const chosen = candidates?.find((c) => c.id === choices[key]);
130
+ const resolved = resolution?.status === "resolved" ? resolution : null;
131
+ const text =
132
+ chosen?.label ?? (resolved && resolved.id !== key ? resolved.label : token.value);
133
+ const kind = resolved && resolved.id !== key ? resolved.id : token.value;
134
+ const detail = token.type === "date" ? spokenDate(locale, token) : resolved?.detail;
135
+ return (
136
+ <span key={key} className={chipClass} {...(detail ? { title: detail } : {})}>
137
+ <Icon name={token.icon} size={12} color="var(--color-text-secondary)" />
138
+ {token.type === "kind" && isRecordKind(kind) ? (
139
+ <RecordKindGlyph kind={kind} size={12} />
140
+ ) : null}
141
+ {candidates && !chosen ? (
142
+ <select
143
+ aria-label={copy.choose?.(typed(token)) ?? typed(token)}
144
+ value=""
145
+ onChange={(e) => onChoose?.(token, e.target.value)}
146
+ className="xos-hit max-w-full border-0 bg-transparent text-inherit cursor-pointer"
147
+ >
148
+ <option value="">
149
+ {copy.matches?.(candidates.length) ?? `${token.value} (${candidates.length})`}
150
+ </option>
151
+ {candidates.map((c) => (
152
+ <option key={c.id} value={c.id}>
153
+ {c.detail ? `${c.label} · ${c.detail}` : c.label}
154
+ </option>
155
+ ))}
156
+ </select>
157
+ ) : (
158
+ text
159
+ )}
160
+ {onDismiss ? (
161
+ <button
162
+ type="button"
163
+ aria-label={t("chip.remove", {
164
+ label: fieldOf(token, chosen?.label ?? valueOf(token)),
165
+ })}
166
+ onClick={() => onDismiss(token)}
167
+ className="xos-hit inline-grid place-items-center ms-2 p-0 border-0 bg-transparent text-text-tertiary hover:text-text-primary rounded-mark cursor-pointer"
168
+ >
169
+ <Icon name="X" size={12} />
170
+ </button>
171
+ ) : null}
172
+ </span>
173
+ );
174
+ })}
175
+ <span role="status" className="sr-only">
176
+ {spoken}
177
+ </span>
178
+ </div>
179
+ );
180
+ }
@@ -0,0 +1,11 @@
1
+ import { composite, ratio } from "@xos/tokens/ratio";
2
+
3
+ /** WCAG 2.2 contrast ratio between two hex colors: the @xos/tokens contrast gate arithmetic itself. */
4
+ export function contrastRatio(a: string, b: string): number {
5
+ return ratio(a, b);
6
+ }
7
+
8
+ /** A fill with an overlay over it (a hover or pressed state), composited as the contrast gate does. */
9
+ export function overlaid(fill: string, overlay: string, opacity: number): string {
10
+ return composite(fill, overlay, opacity);
11
+ }
@@ -0,0 +1,187 @@
1
+ import * as en from "chrono-node/en";
2
+ import * as es from "chrono-node/es";
3
+ import {
4
+ addDays,
5
+ quickAddKey,
6
+ tidyTitle,
7
+ wallClockAt,
8
+ zoneOffsetMinutes,
9
+ type QuickAddParse,
10
+ type QuickAddSpan,
11
+ type QuickAddTokenType,
12
+ } from "./quickAddCore.ts";
13
+
14
+ export interface QuickAddOptions {
15
+ /** The moment relative phrases count from ("tomorrow", "next Friday"); now by default. */
16
+ referenceDate?: Date;
17
+ /** The language of the date phrases: Spanish for `es-*` (chrono's Spanish parser), English otherwise. */
18
+ locale?: string;
19
+ /** The IANA zone whose calendar and clock the phrases are read on; the device's by default. */
20
+ timeZone?: string;
21
+ /** Keys (`quickAddKey`) of tokens the person turned back into text. */
22
+ dismissed?: readonly string[];
23
+ }
24
+
25
+ const ICONS: Readonly<Record<QuickAddTokenType, string>> = {
26
+ person: "User",
27
+ tag: "Hash",
28
+ kind: "Shapes",
29
+ scope: "MapPin",
30
+ date: "Calendar",
31
+ };
32
+
33
+ const SIGIL_TYPES: Readonly<Record<string, QuickAddTokenType>> = {
34
+ "@": "person",
35
+ "#": "tag",
36
+ "/": "kind",
37
+ "~": "scope",
38
+ };
39
+
40
+ // A token starts the line or follows white space, so dana@example.com, and/or and w/crew stay text.
41
+ const TOKEN = /(^|\s)([@#/~])([\p{L}\p{N}][\p{L}\p{N}_.-]*)/gu;
42
+ const URID = /^\d{4}\.\d{2}\.\d{2}$/;
43
+
44
+ /** Whether a sigil's text names something of its type: people, tags and kinds start with a letter. */
45
+ function accepts(type: QuickAddTokenType, body: string): boolean {
46
+ if (body === "") return false;
47
+ if (type === "tag") return /^\p{L}/u.test(body) || URID.test(body);
48
+ if (type === "kind") return /^\p{L}[\p{L}-]*$/u.test(body);
49
+ if (type === "person") return /^\p{L}/u.test(body);
50
+ return true;
51
+ }
52
+
53
+ // Short words chrono reads as days or months that are ordinary words too ("Sun stage wash",
54
+ // "sat down", "limpiar mar"): alone they stay text; "Sun 8am" or "mar 3" still parse.
55
+ const AMBIGUOUS: Readonly<Record<"en" | "es", ReadonlySet<string>>> = {
56
+ en: new Set([
57
+ ...["sun", "sat", "wed", "mar", "jan", "april", "may", "june", "august"],
58
+ ...["morning", "afternoon", "evening", "night"],
59
+ ]),
60
+ es: new Set(["mar", "dom", "sol", "mayo"]),
61
+ };
62
+
63
+ // The word just before a date that only joins it to the title ("Hang truss by Friday", "el viernes").
64
+ const CONNECTORS: Readonly<Record<"en" | "es", ReadonlySet<string>>> = {
65
+ en: new Set(["at", "on", "by", "due", "for", "from"]),
66
+ es: new Set(["el", "la", "a", "al", "para", "de", "del", "en", "desde"]),
67
+ };
68
+
69
+ // Phrases chrono stops short of: it reads only the last word, a day early.
70
+ const DAY_AFTER: Readonly<Record<"en" | "es", RegExp>> = {
71
+ en: /\bday after\s+$/i,
72
+ es: /(^|\s)pasado\s+$/i,
73
+ };
74
+
75
+ interface DatePart {
76
+ iso: string;
77
+ time?: string;
78
+ }
79
+
80
+ interface ChronoComponents {
81
+ get(component: "year" | "month" | "day" | "hour" | "minute"): number | null;
82
+ isCertain(component: "hour" | "timezoneOffset"): boolean;
83
+ date(): Date;
84
+ }
85
+
86
+ function pad(n: number): string {
87
+ return String(n).padStart(2, "0");
88
+ }
89
+
90
+ /** The day, and the time when one was named, on the zone's calendar. */
91
+ function datePart(c: ChronoComponents, zone: string): DatePart {
92
+ if (c.isCertain("timezoneOffset")) {
93
+ // "9pm EST" names its own zone: read the instant on the line's zone instead.
94
+ const w = wallClockAt(c.date(), zone);
95
+ return { iso: `${w.y}-${pad(w.m)}-${pad(w.d)}`, time: `${pad(w.h)}:${pad(w.mi)}` };
96
+ }
97
+ const iso = `${c.get("year")}-${pad(c.get("month") ?? 1)}-${pad(c.get("day") ?? 1)}`;
98
+ return c.isCertain("hour")
99
+ ? { iso, time: `${pad(c.get("hour") ?? 0)}:${pad(c.get("minute") ?? 0)}` }
100
+ : { iso };
101
+ }
102
+
103
+ /** A numeric date written in the other locale's order (24/10 in English, 10/24 in Spanish), or 24/7. */
104
+ function wrongOrder(text: string, lang: "en" | "es"): boolean {
105
+ if (/^24\s*\/\s*7$/.test(text)) return true;
106
+ const numeric = /^(\d{1,2})[/.-](\d{1,2})(?:[/.-]\d{2,4})?$/.exec(text);
107
+ if (!numeric) return false;
108
+ const [first, second] = [Number(numeric[1]), Number(numeric[2])];
109
+ return lang === "en" ? first > 12 : second > 12;
110
+ }
111
+
112
+ function blank(text: string, index: number, length: number): string {
113
+ return text.slice(0, index) + " ".repeat(length) + text.slice(index + length);
114
+ }
115
+
116
+ /**
117
+ * Parses @person, #tag, /kind and ~scope tokens, then a date phrase with chrono-node ("tomorrow at
118
+ * 3pm", "next Friday", "viernes 8am"), read in the given language on the given zone's calendar. What
119
+ * remains is the title. A line sets one of each: a second person, tag, kind, scope or date stays in
120
+ * the title as typed, as does any token the person dismissed.
121
+ */
122
+ export function parseQuickAdd(text: string, options: Date | QuickAddOptions = {}): QuickAddParse {
123
+ const opts: QuickAddOptions = options instanceof Date ? { referenceDate: options } : options;
124
+ const reference = opts.referenceDate ?? new Date();
125
+ const zone = opts.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
126
+ const lang: "en" | "es" = opts.locale?.toLowerCase().startsWith("es") ? "es" : "en";
127
+ const dismissed = new Set(opts.dismissed ?? []);
128
+ const tokens: QuickAddSpan[] = [];
129
+ const taken = new Set<QuickAddTokenType>();
130
+ // Every token's text is hidden from chrono, so ~friday is a scope and never a date.
131
+ let masked = text;
132
+ for (const match of text.matchAll(TOKEN)) {
133
+ const type = SIGIL_TYPES[match[2] ?? ""];
134
+ const body = (match[3] ?? "").replace(/[._-]+$/u, "");
135
+ if (type === undefined || !accepts(type, body)) continue;
136
+ const index = (match.index ?? 0) + (match[1] ?? "").length;
137
+ masked = blank(masked, index, body.length + 1);
138
+ const token = { type, value: body, icon: ICONS[type] };
139
+ if (taken.has(type) || dismissed.has(quickAddKey(token))) continue;
140
+ taken.add(type);
141
+ tokens.push({ ...token, index, length: body.length + 1 });
142
+ }
143
+
144
+ const chrono = lang === "es" ? es.casual : en.casual;
145
+ const at = { instant: reference, timezone: zoneOffsetMinutes(reference, zone) };
146
+ for (const result of chrono.parse(masked, at, { forwardDate: true })) {
147
+ let index = result.index;
148
+ let phrase = result.text;
149
+ let start = datePart(result.start, zone);
150
+ const before = DAY_AFTER[lang].exec(masked.slice(0, index));
151
+ if (before && /^(tomorrow|mañana)\b/i.test(phrase)) {
152
+ const extra = before[0].trimStart();
153
+ index -= extra.length;
154
+ phrase = text.slice(index, result.index + result.text.length);
155
+ start = { ...start, iso: addDays(start.iso, 1) };
156
+ }
157
+ if (wrongOrder(phrase.trim(), lang)) continue;
158
+ if (AMBIGUOUS[lang].has(phrase.trim().toLocaleLowerCase("en-US"))) continue;
159
+ const token = { type: "date" as const, value: phrase, icon: ICONS.date };
160
+ if (dismissed.has(quickAddKey(token))) continue;
161
+ const end = result.end ? datePart(result.end, zone) : null;
162
+ if (end && start.time && end.time && `${end.iso}T${end.time}` <= `${start.iso}T${start.time}`) {
163
+ // "10pm-6am" ends the next morning.
164
+ end.iso = addDays(start.iso, 1);
165
+ }
166
+ const lead = /(\S+)\s+$/u.exec(text.slice(0, index));
167
+ const connector = lead?.[1] && CONNECTORS[lang].has(lead[1].toLocaleLowerCase("en-US"));
168
+ const cut = connector && lead ? index - lead[0].length : index;
169
+ tokens.push({
170
+ ...token,
171
+ iso: start.iso,
172
+ ...(start.time ? { time: start.time } : {}),
173
+ ...(end ? { endIso: end.iso } : {}),
174
+ ...(end?.time ? { endTime: end.time } : {}),
175
+ index: cut,
176
+ length: index + phrase.length - cut,
177
+ });
178
+ break;
179
+ }
180
+
181
+ tokens.sort((a, b) => a.index - b.index);
182
+ let title = text;
183
+ for (const t of [...tokens].reverse()) {
184
+ title = `${title.slice(0, t.index)} ${title.slice(t.index + t.length)}`;
185
+ }
186
+ return { text, title: tidyTitle(title), tokens };
187
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * The parts of quick add (Section 11.7) that run anywhere, servers included: the token and result
3
+ * shapes, the key a resolution is filed under, folding the tokens an app could not resolve back into
4
+ * the title, and reading a wall-clock time in an IANA zone. `parseQuickAdd` (chrono-node) is the
5
+ * browser half; `@xos/ui/quick-add` exports only this file, so a server action never loads chrono.
6
+ */
7
+
8
+ export type QuickAddTokenType = "person" | "tag" | "kind" | "scope" | "date";
9
+
10
+ export interface QuickAddToken {
11
+ type: QuickAddTokenType;
12
+ /** The value without its sigil (`dana` for `@dana`); for a date, the phrase as typed. */
13
+ value: string;
14
+ icon: string;
15
+ /** For dates: the day as an ISO date, on the calendar of the zone the line was parsed in. */
16
+ iso?: string;
17
+ /** For dates that name a time of day: the time as HH:MM, 24-hour. */
18
+ time?: string;
19
+ /** For a range such as "Friday 8am-4pm": the day it ends. */
20
+ endIso?: string;
21
+ /** For a range that names its end time: the time as HH:MM, 24-hour. */
22
+ endTime?: string;
23
+ /** For a token the app resolved: the id of the person, tag, kind or scope it names. */
24
+ id?: string;
25
+ /** For a token the app resolved: the name to show. */
26
+ label?: string;
27
+ }
28
+
29
+ /** A token with where it sits in the typed line, so it can be cut out or left in place. */
30
+ export interface QuickAddSpan extends QuickAddToken {
31
+ /** Offset of the text the token removes from the title, its leading "by" or "el" included. */
32
+ index: number;
33
+ length: number;
34
+ }
35
+
36
+ export interface QuickAddResult {
37
+ /** The line without the tokens it set; tokens nothing resolved stay in it as typed. */
38
+ title: string;
39
+ tokens: QuickAddToken[];
40
+ }
41
+
42
+ /** What `parseQuickAdd` reads from a line, before any token is resolved. */
43
+ export interface QuickAddParse extends QuickAddResult {
44
+ text: string;
45
+ tokens: QuickAddSpan[];
46
+ }
47
+
48
+ export interface QuickAddCandidate {
49
+ id: string;
50
+ label: string;
51
+ /** Tells candidates with the same name apart, for example the project a scope belongs to. */
52
+ detail?: string;
53
+ }
54
+
55
+ /** How an app resolved one token: to one thing, to several it cannot choose between, or to nothing. */
56
+ export type QuickAddResolution =
57
+ | { status: "resolved"; id: string; label: string; detail?: string }
58
+ | { status: "ambiguous"; candidates: QuickAddCandidate[] }
59
+ | { status: "unresolved" };
60
+
61
+ /**
62
+ * Resolves people, tags, kinds and scopes against what the person may see, keyed by `quickAddKey`.
63
+ * A key the answer leaves out counts as unresolved.
64
+ */
65
+ export type QuickAddResolver = (
66
+ tokens: readonly QuickAddToken[],
67
+ ) => Promise<Readonly<Record<string, QuickAddResolution>>>;
68
+
69
+ const SIGILS: Readonly<Record<Exclude<QuickAddTokenType, "date">, string>> = {
70
+ person: "@",
71
+ tag: "#",
72
+ kind: "/",
73
+ scope: "~",
74
+ };
75
+
76
+ /** The sigil a token is typed with; dates have none. */
77
+ export function quickAddSigil(type: QuickAddTokenType): string {
78
+ return type === "date" ? "" : SIGILS[type];
79
+ }
80
+
81
+ /** The key a token's resolution is filed under: its type and value, case and accents as typed folded. */
82
+ export function quickAddKey(token: Pick<QuickAddToken, "type" | "value">): string {
83
+ return `${token.type}:${token.value.normalize("NFC").toLocaleLowerCase("en-US")}`;
84
+ }
85
+
86
+ /** A token without its place in the line. */
87
+ function tokenOf(span: QuickAddSpan): QuickAddToken {
88
+ return Object.fromEntries(
89
+ Object.entries(span).filter(([key]) => key !== "index" && key !== "length"),
90
+ ) as unknown as QuickAddToken;
91
+ }
92
+
93
+ /** One space between words, none before punctuation a removed token left behind. */
94
+ export function tidyTitle(text: string): string {
95
+ return text
96
+ .replace(/\s+/g, " ")
97
+ .replace(/\s+([,.;:!?])/g, "$1")
98
+ .replace(/^[,.;:!?]\s*/, "")
99
+ .trim();
100
+ }
101
+
102
+ /**
103
+ * The line a person typed as the app should save it. Dates always apply. Without resolutions every
104
+ * token applies; with them, a token applies when it resolved or the person chose one of its
105
+ * candidates, and any other token stays in the title exactly where it was typed.
106
+ */
107
+ export function finalizeQuickAdd(
108
+ parse: QuickAddParse,
109
+ resolutions?: Readonly<Record<string, QuickAddResolution>>,
110
+ choices: Readonly<Record<string, string>> = {},
111
+ ): QuickAddResult {
112
+ const applied: { span: QuickAddSpan; token: QuickAddToken }[] = [];
113
+ for (const span of parse.tokens) {
114
+ const token = tokenOf(span);
115
+ if (token.type === "date" || resolutions === undefined) {
116
+ applied.push({ span, token });
117
+ continue;
118
+ }
119
+ const key = quickAddKey(token);
120
+ const resolution = resolutions[key];
121
+ if (resolution?.status === "resolved") {
122
+ applied.push({ span, token: { ...token, id: resolution.id, label: resolution.label } });
123
+ } else if (resolution?.status === "ambiguous") {
124
+ const chosen = resolution.candidates.find((c) => c.id === choices[key]);
125
+ if (chosen) applied.push({ span, token: { ...token, id: chosen.id, label: chosen.label } });
126
+ }
127
+ }
128
+ let title = parse.text;
129
+ for (const { span } of [...applied].sort((a, b) => b.span.index - a.span.index)) {
130
+ title = `${title.slice(0, span.index)} ${title.slice(span.index + span.length)}`;
131
+ }
132
+ return {
133
+ title: tidyTitle(title),
134
+ tokens: applied.sort((a, b) => a.span.index - b.span.index).map((a) => a.token),
135
+ };
136
+ }
137
+
138
+ interface WallClock {
139
+ y: number;
140
+ m: number;
141
+ d: number;
142
+ h: number;
143
+ mi: number;
144
+ }
145
+
146
+ const wallFormats = new Map<string, Intl.DateTimeFormat>();
147
+
148
+ /** The wall clock in a zone at an instant. */
149
+ export function wallClockAt(at: Date | number, zone: string): WallClock {
150
+ let format = wallFormats.get(zone);
151
+ if (format === undefined) {
152
+ format = new Intl.DateTimeFormat("en-US", {
153
+ timeZone: zone,
154
+ hourCycle: "h23",
155
+ year: "numeric",
156
+ month: "2-digit",
157
+ day: "2-digit",
158
+ hour: "2-digit",
159
+ minute: "2-digit",
160
+ });
161
+ wallFormats.set(zone, format);
162
+ }
163
+ const parts = format.formatToParts(at);
164
+ const get = (type: string) => Number(parts.find((p) => p.type === type)?.value ?? 0);
165
+ return { y: get("year"), m: get("month"), d: get("day"), h: get("hour") % 24, mi: get("minute") };
166
+ }
167
+
168
+ /** Minutes the zone runs ahead of UTC at an instant (-240 for New York in summer). */
169
+ export function zoneOffsetMinutes(at: Date | number, zone: string): number {
170
+ const ms = typeof at === "number" ? at : at.getTime();
171
+ const w = wallClockAt(ms, zone);
172
+ return Math.round(
173
+ (Date.UTC(w.y, w.m - 1, w.d, w.h, w.mi) - Math.floor(ms / 60_000) * 60_000) / 60_000,
174
+ );
175
+ }
176
+
177
+ /**
178
+ * The instant a wall-clock day and time name in a zone, as ISO. A time skipped by a daylight saving
179
+ * change reads as the same time after the change (2:30 on the spring-forward night is 3:30), and a
180
+ * time the fall-back night repeats reads as its first occurrence.
181
+ */
182
+ export function zonedInstant(day: string, time: string, zone: string): string {
183
+ const [y = 1970, m = 1, d = 1] = day.split("-").map(Number);
184
+ const [h = 0, mi = 0] = time.split(":").map(Number);
185
+ const guess = Date.UTC(y, m - 1, d, h, mi);
186
+ const first = guess - zoneOffsetMinutes(guess, zone) * 60_000;
187
+ const second = guess - zoneOffsetMinutes(first, zone) * 60_000;
188
+ const wall = wallClockAt(second, zone);
189
+ return new Date(wall.h === h && wall.mi === mi ? second : first).toISOString();
190
+ }
191
+
192
+ /** A calendar day moved by whole days, as an ISO date. */
193
+ export function addDays(day: string, days: number): string {
194
+ const [y = 1970, m = 1, d = 1] = day.split("-").map(Number);
195
+ return new Date(Date.UTC(y, m - 1, d + days)).toISOString().slice(0, 10);
196
+ }
197
+
198
+ // The schedule grid's time shorthand (B4) travels with quick add, so `@xos/ui/quick-add` carries both halves.
199
+ export {
200
+ parseDuration,
201
+ parseTimeShorthand,
202
+ type TimeShorthand,
203
+ type TimeShorthandOptions,
204
+ } from "./timeShorthand.ts";