@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.
- package/LICENSE +21 -0
- package/README.md +32 -2
- package/package.json +76 -4
- package/scripts/generate-stories.mjs +85 -0
- package/src/AIDisclosureBadge/README.md +7 -0
- package/src/AccessGridMatrix/README.md +3 -0
- package/src/Accordion/README.md +7 -0
- package/src/ActivityFeedItem/README.md +3 -0
- package/src/AgencySlate/README.md +3 -0
- package/src/AppShell/README.md +5 -0
- package/src/ApplicationForm/README.md +3 -0
- package/src/AssertionRankBadge/README.md +5 -0
- package/src/AvailabilityCalendar/README.md +3 -0
- package/src/Avatar/Avatar.tsx +56 -0
- package/src/Avatar/README.md +3 -0
- package/src/Avatar/avatar-class.ts +19 -0
- package/src/AvatarStack/README.md +3 -0
- package/src/Badge/README.md +3 -0
- package/src/Banner/README.md +3 -0
- package/src/BidSheet/README.md +3 -0
- package/src/Board/README.md +6 -0
- package/src/BrandMark/BrandMark.tsx +95 -0
- package/src/BrandMark/README.md +7 -0
- package/src/BrandSlot/README.md +6 -0
- package/src/Breadcrumbs/README.md +3 -0
- package/src/BudgetGrid/README.md +10 -0
- package/src/BulkActionBar/README.md +3 -0
- package/src/Button/Button.tsx +100 -0
- package/src/Button/README.md +10 -0
- package/src/Button/button-class.ts +54 -0
- package/src/ButtonLink/README.md +7 -0
- package/src/Calendar/README.md +3 -0
- package/src/Card/README.md +3 -0
- package/src/Chart/README.md +9 -0
- package/src/Charts/Calendar.tsx +228 -0
- package/src/Charts/Chart.tsx +547 -0
- package/src/Charts/Charts.tsx +20 -0
- package/src/Charts/Charts.types.ts +40 -0
- package/src/Charts/Gallery.tsx +25 -0
- package/src/Charts/Heatmap.tsx +116 -0
- package/src/Charts/OrgChart.tsx +53 -0
- package/src/Charts/ResourceSchedule.tsx +91 -0
- package/src/Charts/Sparkline.tsx +59 -0
- package/src/Charts/Timeline.tsx +777 -0
- package/src/Charts/categories.ts +21 -0
- package/src/Charts/timeline-plan.ts +191 -0
- package/src/Checkbox/README.md +6 -0
- package/src/ChecklistRunner/README.md +3 -0
- package/src/Chips/Badge.tsx +27 -0
- package/src/Chips/Chips.shared.tsx +34 -0
- package/src/Chips/Chips.tsx +21 -0
- package/src/Chips/Chips.types.ts +35 -0
- package/src/Chips/DepartmentChip.tsx +16 -0
- package/src/Chips/PhaseChip.tsx +16 -0
- package/src/Chips/PropertyChip.tsx +15 -0
- package/src/Chips/RecordKindChip.tsx +14 -0
- package/src/Chips/StateChip.tsx +19 -0
- package/src/Chips/Tag.tsx +47 -0
- package/src/Chips/URIDChip.tsx +14 -0
- package/src/Chips/chipClass.tsx +8 -0
- package/src/Chips/codeClass.tsx +6 -0
- package/src/ClockButton/README.md +6 -0
- package/src/CodeSample/README.md +7 -0
- package/src/ColorPicker/README.md +5 -0
- package/src/Combobox/README.md +7 -0
- package/src/CommandMenu/CommandMenu.tsx +179 -0
- package/src/CommandMenu/README.md +5 -0
- package/src/ComparisonTable/README.md +9 -0
- package/src/Compass/ChecklistRunner.tsx +128 -0
- package/src/Compass/ClockButton.tsx +57 -0
- package/src/Compass/Compass.shared.tsx +58 -0
- package/src/Compass/Compass.tsx +26 -0
- package/src/Compass/Compass.types.ts +79 -0
- package/src/Compass/DaySheetView.tsx +104 -0
- package/src/Compass/ForceUpdateScreen.tsx +29 -0
- package/src/Compass/LiveActivity.tsx +60 -0
- package/src/Compass/OfflineBanner.tsx +31 -0
- package/src/Compass/PhotoCapture.tsx +69 -0
- package/src/Compass/QuickIncidentSheet.tsx +46 -0
- package/src/Compass/ScanSheet.tsx +83 -0
- package/src/Compass/ShiftCard.tsx +55 -0
- package/src/Compass/SignaturePad.tsx +71 -0
- package/src/Compass/SyncConflictSheet.tsx +60 -0
- package/src/ConsentBanner/ConsentBanner.tsx +210 -0
- package/src/ConsentBanner/ConsentBannerStatic.tsx +163 -0
- package/src/ConsentBanner/ConsentSettings.tsx +213 -0
- package/src/ConsentBanner/README.md +101 -0
- package/src/ConsentBanner/consent.ts +238 -0
- package/src/ConsentBanner/index.ts +40 -0
- package/src/ConsentBanner/static.ts +11 -0
- package/src/ConsentBanner/useConsent.ts +160 -0
- package/src/ContextMenu/README.md +3 -0
- package/src/CoordinateMatrix/README.md +6 -0
- package/src/CredentialBadge/README.md +7 -0
- package/src/CurrencyInput/README.md +7 -0
- package/src/DataTable/DataTable.tsx +1034 -0
- package/src/DataTable/DataTableResizer.tsx +72 -0
- package/src/DataTable/DataTableVirtualizer.tsx +58 -0
- package/src/DataTable/README.md +9 -0
- package/src/DatePicker/README.md +5 -0
- package/src/DateRangePicker/README.md +3 -0
- package/src/DaySheetView/README.md +3 -0
- package/src/DepartmentChip/README.md +3 -0
- package/src/DepartmentGlyph/README.md +18 -0
- package/src/Dialog/README.md +3 -0
- package/src/DiffViewer/README.md +3 -0
- package/src/Divider/README.md +3 -0
- package/src/Drawer/README.md +3 -0
- package/src/DropdownMenu/README.md +3 -0
- package/src/EPKViewer/README.md +3 -0
- package/src/Editing/ColorPicker.tsx +125 -0
- package/src/Editing/DiffViewer.tsx +49 -0
- package/src/Editing/Editing.tsx +29 -0
- package/src/Editing/Editing.types.ts +71 -0
- package/src/Editing/FilterBuilder.tsx +72 -0
- package/src/Editing/QuickAdd.tsx +102 -0
- package/src/Editing/QuickAddChips.tsx +180 -0
- package/src/Editing/contrastRatio.tsx +11 -0
- package/src/Editing/parseQuickAdd.tsx +187 -0
- package/src/Editing/quickAddCore.ts +204 -0
- package/src/Editing/timeShorthand.ts +157 -0
- package/src/Editing/useQuickAdd.ts +201 -0
- package/src/EmergencyCodeCard/README.md +10 -0
- package/src/EmptyState/README.md +5 -0
- package/src/EngagementTimeline/README.md +3 -0
- package/src/ErrorState/README.md +3 -0
- package/src/FAQ/README.md +7 -0
- package/src/FeatureGrid/README.md +7 -0
- package/src/Feedback/Banner.tsx +40 -0
- package/src/Feedback/EmptyState.tsx +32 -0
- package/src/Feedback/ErrorState.tsx +32 -0
- package/src/Feedback/Feedback.shared.tsx +38 -0
- package/src/Feedback/Feedback.tsx +18 -0
- package/src/Feedback/Feedback.types.ts +40 -0
- package/src/Feedback/InlineAlert.tsx +19 -0
- package/src/Feedback/RefusalNotice.tsx +46 -0
- package/src/Feedback/RegionBoundary.tsx +102 -0
- package/src/Feedback/Toast.tsx +31 -0
- package/src/Feedback/ToastRegion.tsx +26 -0
- package/src/Feedback/UndoToast.tsx +97 -0
- package/src/FeedbackWidget/README.md +3 -0
- package/src/Field/Field.tsx +81 -0
- package/src/FileDrop/README.md +3 -0
- package/src/FilePreviewer/README.md +3 -0
- package/src/FilterBuilder/README.md +3 -0
- package/src/ForceUpdateScreen/README.md +3 -0
- package/src/Forms/Checkbox.tsx +14 -0
- package/src/Forms/CurrencyInput.tsx +40 -0
- package/src/Forms/Forms.shared.tsx +5 -0
- package/src/Forms/Forms.tsx +20 -0
- package/src/Forms/Forms.types.ts +33 -0
- package/src/Forms/Input.tsx +28 -0
- package/src/Forms/Money.tsx +15 -0
- package/src/Forms/NumberInput.tsx +85 -0
- package/src/Forms/Radio.tsx +31 -0
- package/src/Forms/Select.tsx +54 -0
- package/src/Forms/Switch.tsx +14 -0
- package/src/Forms/Textarea.tsx +30 -0
- package/src/Gallery/README.md +5 -0
- package/src/GateLifecycleRail/README.md +7 -0
- package/src/GateReadinessPanel/README.md +8 -0
- package/src/Gateway/AgencySlate.tsx +76 -0
- package/src/Gateway/ApplicationForm.tsx +48 -0
- package/src/Gateway/AvailabilityCalendar.tsx +82 -0
- package/src/Gateway/EPKViewer.tsx +77 -0
- package/src/Gateway/EngagementTimeline.tsx +43 -0
- package/src/Gateway/Gateway.shared.tsx +5 -0
- package/src/Gateway/Gateway.tsx +30 -0
- package/src/Gateway/Gateway.types.ts +69 -0
- package/src/Gateway/OnboardingPacket.tsx +61 -0
- package/src/Gateway/OpportunityCard.tsx +73 -0
- package/src/Gateway/OpportunityFilters.tsx +64 -0
- package/src/Gateway/PaymentTracker.tsx +71 -0
- package/src/Gateway/PayoutDetailsForm.tsx +62 -0
- package/src/Gateway/ProfileEditor.tsx +45 -0
- package/src/Gateway/ProgressBar.tsx +30 -0
- package/src/Gateway/RatingDialog.tsx +83 -0
- package/src/Gateway/UpNextCard.tsx +76 -0
- package/src/Glyphs/Glyphs.tsx +66 -0
- package/src/Grids/BidSheet.tsx +63 -0
- package/src/Grids/BudgetGrid.tsx +164 -0
- package/src/Grids/Grids.shared.tsx +5 -0
- package/src/Grids/Grids.tsx +11 -0
- package/src/Grids/Grids.types.ts +38 -0
- package/src/Grids/SpreadsheetGrid.tsx +445 -0
- package/src/Grids/columnTotal.tsx +14 -0
- package/src/Grids/glForCode.tsx +11 -0
- package/src/Grids/parseCell.tsx +16 -0
- package/src/Help/FeedbackWidget.tsx +89 -0
- package/src/Help/Help.tsx +14 -0
- package/src/Help/Help.types.ts +51 -0
- package/src/Help/HelpPanel.tsx +116 -0
- package/src/Help/NotificationCenter.tsx +100 -0
- package/src/Help/OnboardingChecklist.tsx +57 -0
- package/src/Help/ShortcutEditor.tsx +81 -0
- package/src/Help/WhatsNew.tsx +33 -0
- package/src/HelpPanel/README.md +5 -0
- package/src/HoverCard/README.md +3 -0
- package/src/Icon/Icon.tsx +47 -0
- package/src/Icon/README.md +9 -0
- package/src/IconButton/README.md +6 -0
- package/src/Identity/BrandSlot.tsx +20 -0
- package/src/Identity/BrandSlotFrame.tsx +38 -0
- package/src/Identity/CredentialBadge.tsx +92 -0
- package/src/Identity/Identity.tsx +14 -0
- package/src/Identity/Identity.types.ts +25 -0
- package/src/Identity/PresenceIndicator.tsx +37 -0
- package/src/Identity/QRCode.tsx +22 -0
- package/src/Identity/WhiteLabelPreview.tsx +33 -0
- package/src/Identity/qrPath.tsx +16 -0
- package/src/Identity/tokenVariable.tsx +9 -0
- package/src/InlineAlert/README.md +3 -0
- package/src/Input/README.md +8 -0
- package/src/Kbd/Kbd.tsx +30 -0
- package/src/Kbd/README.md +6 -0
- package/src/Layout/AppShell.tsx +151 -0
- package/src/Layout/Layout.tsx +14 -0
- package/src/Layout/Layout.types.ts +43 -0
- package/src/Layout/PageHeader.tsx +22 -0
- package/src/Layout/ResizablePanel.tsx +81 -0
- package/src/Layout/ScrollRegion.tsx +107 -0
- package/src/Layout/SplitView.tsx +78 -0
- package/src/Layout/Stepper.tsx +39 -0
- package/src/LegalText/LegalText.tsx +289 -0
- package/src/LegalText/README.md +10 -0
- package/src/LegalText/index.ts +29 -0
- package/src/LegalText/legal-markdown.ts +581 -0
- package/src/Link/Link.tsx +25 -0
- package/src/Link/README.md +5 -0
- package/src/Link/link-class.ts +10 -0
- package/src/LiveActivity/README.md +3 -0
- package/src/LogoSlotWall/README.md +7 -0
- package/src/MapView/README.md +5 -0
- package/src/Marketing/AIDisclosureBadge.tsx +31 -0
- package/src/Marketing/ButtonLink.tsx +32 -0
- package/src/Marketing/CodeSample.tsx +47 -0
- package/src/Marketing/ComparisonScroll.tsx +7 -0
- package/src/Marketing/ComparisonTable.tsx +104 -0
- package/src/Marketing/FAQ.tsx +26 -0
- package/src/Marketing/FeatureGrid.tsx +65 -0
- package/src/Marketing/GateLifecycleRail.tsx +52 -0
- package/src/Marketing/LogoSlotWall.tsx +53 -0
- package/src/Marketing/Marketing.tsx +49 -0
- package/src/Marketing/Marketing.types.ts +64 -0
- package/src/Marketing/MarketingFooter.tsx +88 -0
- package/src/Marketing/MarketingHero.tsx +45 -0
- package/src/Marketing/MarketingNav.tsx +76 -0
- package/src/Marketing/MarketingPoints.tsx +16 -0
- package/src/Marketing/MarketingSection.tsx +52 -0
- package/src/Marketing/PersonaSwitcher.tsx +137 -0
- package/src/Marketing/PriceDisplay.tsx +56 -0
- package/src/Marketing/PricingTable.tsx +122 -0
- package/src/Marketing/ProofStatStrip.tsx +35 -0
- package/src/Marketing/ScreenshotFrame.tsx +56 -0
- package/src/Marketing/SurfaceTriptych.tsx +43 -0
- package/src/Marketing/Testimonial.tsx +45 -0
- package/src/Marketing/marketing-copy.ts +25 -0
- package/src/Marketing/provided.tsx +36 -0
- package/src/MarketingFooter/README.md +7 -0
- package/src/MarketingHero/README.md +7 -0
- package/src/MarketingNav/README.md +7 -0
- package/src/MarketingSection/README.md +7 -0
- package/src/Media/FilePreviewer.tsx +86 -0
- package/src/Media/MapView.tsx +200 -0
- package/src/Media/Media.tsx +11 -0
- package/src/Media/Media.types.ts +47 -0
- package/src/Media/RichTextEditor.tsx +198 -0
- package/src/Media/toLngLat.tsx +13 -0
- package/src/Menus/ContextMenu.tsx +106 -0
- package/src/Menus/DropdownMenu.tsx +51 -0
- package/src/Menus/Menus.shared.tsx +67 -0
- package/src/Menus/Menus.tsx +13 -0
- package/src/Menus/Menus.types.ts +49 -0
- package/src/Menus/OrgSwitcher.tsx +69 -0
- package/src/Menus/compactTriggerClass.tsx +7 -0
- package/src/Menus/menuClass.tsx +4 -0
- package/src/Menus/menuItemClass.tsx +7 -0
- package/src/Menus/orgSwitcherTriggerClass.tsx +7 -0
- package/src/Money/README.md +6 -0
- package/src/MultiSelect/README.md +3 -0
- package/src/Navigation/Accordion.tsx +59 -0
- package/src/Navigation/Breadcrumbs.tsx +59 -0
- package/src/Navigation/MoreFields.tsx +42 -0
- package/src/Navigation/Navigation.shared.tsx +32 -0
- package/src/Navigation/Navigation.tsx +21 -0
- package/src/Navigation/Navigation.types.ts +63 -0
- package/src/Navigation/Pagination.tsx +30 -0
- package/src/Navigation/SegmentedControl.tsx +87 -0
- package/src/Navigation/Sidebar.tsx +106 -0
- package/src/Navigation/TabBar.tsx +61 -0
- package/src/Navigation/Tabs.tsx +79 -0
- package/src/Navigation/TopNav.tsx +86 -0
- package/src/Navigation/sidebar-classes.ts +18 -0
- package/src/NotificationCenter/README.md +3 -0
- package/src/NumberInput/README.md +3 -0
- package/src/OfflineBanner/README.md +3 -0
- package/src/OnboardingChecklist/README.md +3 -0
- package/src/OnboardingPacket/README.md +3 -0
- package/src/OpportunityCard/README.md +5 -0
- package/src/OpportunityFilters/README.md +3 -0
- package/src/OrgChart/README.md +5 -0
- package/src/OrgSwitcher/README.md +3 -0
- package/src/Overlays/Dialog.tsx +54 -0
- package/src/Overlays/Drawer.tsx +62 -0
- package/src/Overlays/HoverCard.tsx +59 -0
- package/src/Overlays/Overlays.shared.tsx +12 -0
- package/src/Overlays/Overlays.tsx +20 -0
- package/src/Overlays/Overlays.types.ts +66 -0
- package/src/Overlays/Popover.tsx +70 -0
- package/src/Overlays/PropertyList.tsx +19 -0
- package/src/Overlays/ShortcutSheet.tsx +62 -0
- package/src/Overlays/SidePeek.tsx +117 -0
- package/src/Overlays/Tooltip.tsx +73 -0
- package/src/Overlays/panelClass.tsx +6 -0
- package/src/Overlays/popoverPanelClass.tsx +6 -0
- package/src/PageHeader/README.md +3 -0
- package/src/Pagination/README.md +3 -0
- package/src/PaymentTracker/README.md +3 -0
- package/src/PayoutDetailsForm/README.md +3 -0
- package/src/PersonaSwitcher/README.md +7 -0
- package/src/PhaseChip/README.md +5 -0
- package/src/PhaseGlyph/README.md +17 -0
- package/src/PhotoCapture/README.md +3 -0
- package/src/Pickers/Combobox.tsx +91 -0
- package/src/Pickers/DatePicker.tsx +60 -0
- package/src/Pickers/DateRangePicker.tsx +78 -0
- package/src/Pickers/FileDrop.tsx +84 -0
- package/src/Pickers/MultiSelect.tsx +118 -0
- package/src/Pickers/Pickers.shared.tsx +337 -0
- package/src/Pickers/Pickers.tsx +17 -0
- package/src/Pickers/Pickers.types.ts +47 -0
- package/src/Pickers/Slider.tsx +59 -0
- package/src/Pickers/TimePicker.tsx +68 -0
- package/src/Pickers/rankOptions.tsx +12 -0
- package/src/Popover/README.md +3 -0
- package/src/PresenceIndicator/README.md +3 -0
- package/src/PriceDisplay/README.md +7 -0
- package/src/PricingTable/README.md +9 -0
- package/src/ProfileEditor/README.md +3 -0
- package/src/ProgressBar/README.md +3 -0
- package/src/ProofStatStrip/README.md +7 -0
- package/src/PropertyChip/README.md +3 -0
- package/src/ProvenanceBadge/README.md +3 -0
- package/src/QRCode/README.md +6 -0
- package/src/QuickAdd/README.md +5 -0
- package/src/QuickIncidentSheet/README.md +3 -0
- package/src/Radio/README.md +3 -0
- package/src/RadioChannelTable/README.md +7 -0
- package/src/RatingDialog/README.md +3 -0
- package/src/ReconciliationTable/README.md +3 -0
- package/src/RecordKindChip/README.md +3 -0
- package/src/RecordKindGlyph/README.md +34 -0
- package/src/RecordRow/README.md +7 -0
- package/src/RefusalNotice/README.md +5 -0
- package/src/ResizablePanel/README.md +3 -0
- package/src/ResourceSchedule/README.md +6 -0
- package/src/RichTextEditor/README.md +3 -0
- package/src/RichTextEditor/extensions.ts +217 -0
- package/src/RunOfShowLive/README.md +5 -0
- package/src/ScanSheet/README.md +3 -0
- package/src/ScreenshotFrame/README.md +7 -0
- package/src/ScrollRegion/README.md +9 -0
- package/src/SegmentedControl/README.md +5 -0
- package/src/Select/README.md +5 -0
- package/src/ShiftCard/README.md +3 -0
- package/src/ShortcutEditor/README.md +3 -0
- package/src/ShortcutSheet/README.md +3 -0
- package/src/SidePeek/README.md +6 -0
- package/src/Sidebar/README.md +5 -0
- package/src/SignaturePad/README.md +3 -0
- package/src/Skeleton/README.md +3 -0
- package/src/Slider/README.md +3 -0
- package/src/SplitView/README.md +3 -0
- package/src/SpreadsheetGrid/README.md +9 -0
- package/src/StalenessIndicator/README.md +3 -0
- package/src/StatTile/README.md +6 -0
- package/src/StateChip/README.md +6 -0
- package/src/StateIcon/README.md +6 -0
- package/src/StateIcon/StateIcon.tsx +127 -0
- package/src/StatusHeatmap/README.md +15 -0
- package/src/Stepper/README.md +3 -0
- package/src/Surface/Skeleton.tsx +21 -0
- package/src/Surface/Surface.tsx +63 -0
- package/src/SurfaceTriptych/README.md +7 -0
- package/src/Switch/README.md +5 -0
- package/src/SyncConflictSheet/README.md +3 -0
- package/src/TabBar/README.md +7 -0
- package/src/Tabs/README.md +6 -0
- package/src/Tag/README.md +3 -0
- package/src/TemplateAuth/README.md +6 -0
- package/src/TemplateCollection/README.md +6 -0
- package/src/TemplateDashboard/README.md +8 -0
- package/src/TemplateDocument/README.md +5 -0
- package/src/TemplateGatewayShell/README.md +5 -0
- package/src/TemplateGridEditor/README.md +3 -0
- package/src/TemplateRecord/README.md +3 -0
- package/src/TemplateSettings/README.md +5 -0
- package/src/TemplateSplitView/README.md +3 -0
- package/src/TemplateSystem/README.md +5 -0
- package/src/TemplateWizard/README.md +3 -0
- package/src/Templates/TemplateAuth.tsx +16 -0
- package/src/Templates/TemplateCollection.tsx +35 -0
- package/src/Templates/TemplateDashboard.tsx +200 -0
- package/src/Templates/TemplateDocument.tsx +24 -0
- package/src/Templates/TemplateGatewayShell.tsx +36 -0
- package/src/Templates/TemplateGridEditor.tsx +17 -0
- package/src/Templates/TemplateRecord.tsx +31 -0
- package/src/Templates/TemplateSettings.tsx +26 -0
- package/src/Templates/TemplateSplitView.tsx +27 -0
- package/src/Templates/TemplateSystem.tsx +19 -0
- package/src/Templates/TemplateSystemLink.tsx +55 -0
- package/src/Templates/TemplateWizard.tsx +43 -0
- package/src/Templates/Templates.tsx +29 -0
- package/src/Templates/Templates.types.ts +153 -0
- package/src/Testimonial/README.md +7 -0
- package/src/Textarea/README.md +3 -0
- package/src/TimePicker/README.md +5 -0
- package/src/Timeline/README.md +6 -0
- package/src/Toast/README.md +3 -0
- package/src/Tooltip/README.md +3 -0
- package/src/TopNav/README.md +5 -0
- package/src/TreeView/README.md +3 -0
- package/src/URIDChip/README.md +5 -0
- package/src/UndoToast/README.md +3 -0
- package/src/UpNextCard/README.md +6 -0
- package/src/Views/ActivityFeedItem.tsx +52 -0
- package/src/Views/Board.tsx +229 -0
- package/src/Views/BulkActionBar.tsx +76 -0
- package/src/Views/RecordList.tsx +146 -0
- package/src/Views/RecordRow.tsx +132 -0
- package/src/Views/StatTile.tsx +88 -0
- package/src/Views/TreeView.tsx +161 -0
- package/src/Views/Views.tsx +18 -0
- package/src/Views/Views.types.ts +99 -0
- package/src/WhatsNew/README.md +3 -0
- package/src/WhiteLabelPreview/README.md +8 -0
- package/src/XOS/AccessGridMatrix.tsx +111 -0
- package/src/XOS/AssertionRankBadge.tsx +27 -0
- package/src/XOS/CoordinateMatrix.tsx +123 -0
- package/src/XOS/EmergencyCodeCard.tsx +109 -0
- package/src/XOS/GateReadinessPanel.tsx +100 -0
- package/src/XOS/ProvenanceBadge.tsx +19 -0
- package/src/XOS/RadioChannelTable.tsx +50 -0
- package/src/XOS/ReconciliationTable.tsx +64 -0
- package/src/XOS/RunOfShowLive.tsx +97 -0
- package/src/XOS/StalenessIndicator.tsx +31 -0
- package/src/XOS/StatusHeatmap.tsx +200 -0
- package/src/XOS/XOS.shared.tsx +90 -0
- package/src/XOS/XOS.tsx +29 -0
- package/src/XOS/XOS.types.ts +99 -0
- package/src/XOS/resolveStep.tsx +16 -0
- package/src/brand/defaults/README.md +13 -0
- package/src/brand/defaults/app-icon.svg +6 -0
- package/src/brand/defaults/favicon.svg +6 -0
- package/src/brand/defaults/logo-dark.svg +6 -0
- package/src/brand/defaults/logo-light.svg +6 -0
- package/src/brand/defaults/splash.svg +6 -0
- package/src/index.ts +606 -0
- package/src/lib/context.tsx +156 -0
- package/src/lib/cx.ts +4 -0
- package/src/lib/format.ts +200 -0
- package/src/lib/hooks.ts +187 -0
- package/src/lib/i18n.ts +97 -0
- package/src/lib/icons.ts +45 -0
- package/src/lib/live.ts +92 -0
- package/src/lib/preferences.ts +242 -0
- package/src/lib/route-transition.tsx +22 -0
- package/src/lib/selection.ts +92 -0
- package/src/lib/shortcut-targets.ts +125 -0
- package/src/lib/theme.ts +50 -0
- package/src/lib/undo-stack.ts +125 -0
- package/src/marketing.ts +33 -0
- package/src/pwa/action-outcome.tsx +81 -0
- package/src/pwa/analytics.ts +236 -0
- package/src/pwa/browser-push.tsx +132 -0
- package/src/pwa/client.ts +126 -0
- package/src/pwa/index.ts +40 -0
- package/src/pwa/offline-key.ts +18 -0
- package/src/pwa/service-worker.ts +229 -0
- package/src/static.ts +10 -0
- package/src/styles/base.css +208 -0
- package/src/styles/components.css +453 -0
- package/src/styles/index.css +16 -0
- package/src/styles/marketing.css +1260 -0
- package/src/styles/reference.css +5792 -0
- package/src/types/reference.d.ts +1608 -0
- package/tailwind.config.cjs +3 -0
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useId,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type DragEvent,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type PointerEvent,
|
|
10
|
+
type ReactElement,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { Direction } from "radix-ui";
|
|
13
|
+
import { cx } from "../lib/cx.ts";
|
|
14
|
+
import { useFormat, useT } from "../lib/context.tsx";
|
|
15
|
+
import { StateIcon } from "../StateIcon/StateIcon.tsx";
|
|
16
|
+
import { ScrollRegion } from "../Layout/Layout.tsx";
|
|
17
|
+
import { Button } from "../Button/Button.tsx";
|
|
18
|
+
import { Icon } from "../Icon/Icon.tsx";
|
|
19
|
+
import { Tag } from "../Chips/Tag.tsx";
|
|
20
|
+
import { type TimelineChange, type TimelineProps } from "./Charts.types.ts";
|
|
21
|
+
import { categoryText, TL_CATEGORY } from "./categories.ts";
|
|
22
|
+
import { planChange, sameMoves, sameRows, withMoves, type TimelinePlan } from "./timeline-plan.ts";
|
|
23
|
+
|
|
24
|
+
const TL_BAR: Record<string, string> = {
|
|
25
|
+
active: cx("bg-accent"),
|
|
26
|
+
complete: cx("bg-success opacity-85"),
|
|
27
|
+
blocked: cx("bg-bg-hover ring-(length:--ui-hairline-half) ring-inset ring-danger"),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** What a pointer drag changes: the whole bar, or one of its ends. */
|
|
31
|
+
type DragMode = "move" | "start" | "end";
|
|
32
|
+
|
|
33
|
+
interface Drag {
|
|
34
|
+
readonly row: number;
|
|
35
|
+
readonly mode: DragMode;
|
|
36
|
+
readonly x: number;
|
|
37
|
+
readonly from: TimelineChange;
|
|
38
|
+
readonly pointer: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Pointer travel, in pixels, before a press on a bar becomes a drag. */
|
|
42
|
+
const DRAG_SLOP = 4;
|
|
43
|
+
|
|
44
|
+
/** The drag payload type of a record dropped on the day grid (`onDayDrop`). */
|
|
45
|
+
export const RECORD_MIME = "application/x-xos-record";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The day index under a pointer on a day grid `count` days wide, counted from the inline start: from
|
|
49
|
+
* the left edge in left-to-right layouts and from the right edge in right-to-left ones.
|
|
50
|
+
*/
|
|
51
|
+
export function dayAtPointer(
|
|
52
|
+
clientX: number,
|
|
53
|
+
rect: { readonly left: number; readonly right: number },
|
|
54
|
+
dayWidth: number,
|
|
55
|
+
count: number,
|
|
56
|
+
rtl: boolean,
|
|
57
|
+
): number {
|
|
58
|
+
const offset = rtl ? rect.right - clientX : clientX - rect.left;
|
|
59
|
+
return Math.max(0, Math.min(count - 1, Math.floor(offset / dayWidth)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The Gantt view: bars by state, baselines, dependencies, gate markers, today and the critical path.
|
|
64
|
+
* The drawing is hidden from assistive technology; a table beside it carries every row.
|
|
65
|
+
*
|
|
66
|
+
* With `onReschedule`, bars move and resize (Section 11.11). Dragging a bar moves it and dragging an
|
|
67
|
+
* end resizes it; with a bar focused, arrow keys move it a day, Shift with an arrow moves its end,
|
|
68
|
+
* Enter applies and Escape cancels. While a change is pending, the successors it pushes along
|
|
69
|
+
* `links` show where they would land, a preview line says what moves and whether the critical path
|
|
70
|
+
* changes, and a live region reads the same line. Apply takes the second interaction. With
|
|
71
|
+
* `awaitPreview`, Apply and Enter wait for the page's own preview of the exact change.
|
|
72
|
+
*
|
|
73
|
+
* With `onDayDrop`, the day grid accepts a dragged record (MIME type application/x-xos-record) and
|
|
74
|
+
* reports the day under the pointer, mirrored in right-to-left layouts (P8, the schedule's
|
|
75
|
+
* Unscheduled list). The page offers a keyboard path of its own beside it.
|
|
76
|
+
*
|
|
77
|
+
* Day-type tags (`days[].tags`, B4) draw as a band under the dates and are named with every day the table and the
|
|
78
|
+
* announcements read; row `segments` draw inside their row and are named in its table cell. Each carries text and an
|
|
79
|
+
* icon besides its colour. With `anchorDay`, the table gains an Offset column read against the anchor.
|
|
80
|
+
*/
|
|
81
|
+
export function Timeline({
|
|
82
|
+
label,
|
|
83
|
+
days,
|
|
84
|
+
rows,
|
|
85
|
+
gates = [],
|
|
86
|
+
today,
|
|
87
|
+
dayWidth = 32,
|
|
88
|
+
links = [],
|
|
89
|
+
onReschedule,
|
|
90
|
+
onPendingChange,
|
|
91
|
+
preview = null,
|
|
92
|
+
awaitPreview = false,
|
|
93
|
+
onDayDrop,
|
|
94
|
+
anchorDay,
|
|
95
|
+
}: TimelineProps): ReactElement {
|
|
96
|
+
const t = useT();
|
|
97
|
+
const format = useFormat();
|
|
98
|
+
const dir = Direction.useDirection();
|
|
99
|
+
const uid = useId().replace(/:/g, "");
|
|
100
|
+
const markerId = `${uid}-arrow`;
|
|
101
|
+
const rowH = 36;
|
|
102
|
+
const width = days.length * dayWidth;
|
|
103
|
+
// A grid with no rows still takes a drop: two rows high.
|
|
104
|
+
const height = Math.max(rows.length, onDayDrop ? 2 : 0) * rowH;
|
|
105
|
+
const editable = onReschedule !== undefined;
|
|
106
|
+
const [draft, setDraft] = useState<TimelineChange | null>(null);
|
|
107
|
+
const [applying, setApplying] = useState(false);
|
|
108
|
+
// What the live region last said about a change that is no longer pending (applied, canceled).
|
|
109
|
+
const [notice, setNotice] = useState("");
|
|
110
|
+
const drag = useRef<Drag | null>(null);
|
|
111
|
+
const dragged = useRef(false);
|
|
112
|
+
const pending = useRef(onPendingChange);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
pending.current = onPendingChange;
|
|
115
|
+
});
|
|
116
|
+
const controls = useRef<HTMLDivElement>(null);
|
|
117
|
+
const previewBar = useRef<HTMLDivElement>(null);
|
|
118
|
+
// The bar that takes focus back once the preview's Apply or Cancel, which held it, unmounts.
|
|
119
|
+
const refocus = useRef<string | null>(null);
|
|
120
|
+
|
|
121
|
+
const dayLabel = (i: number) => {
|
|
122
|
+
const d = days[i];
|
|
123
|
+
if (!d) return "";
|
|
124
|
+
const name = d.label ?? `${d.dow} ${d.date}`;
|
|
125
|
+
const tags = categoryText(d.tags);
|
|
126
|
+
return tags ? t("timeline.dayWithTypes", { day: name, types: tags }) : name;
|
|
127
|
+
};
|
|
128
|
+
const tagged = days.some((d) => (d.tags?.length ?? 0) > 0);
|
|
129
|
+
const offsetText = (start: number) => {
|
|
130
|
+
if (anchorDay === undefined) return "";
|
|
131
|
+
const n = start - anchorDay;
|
|
132
|
+
if (n === 0) return t("timeline.offsetOn");
|
|
133
|
+
const key = n < 0 ? "timeline.offsetBefore" : "timeline.offsetAfter";
|
|
134
|
+
return Math.abs(n) === 1 ? t(`${key}One`) : t(key, { n: Math.abs(n) });
|
|
135
|
+
};
|
|
136
|
+
const offsetShort = (start: number) => {
|
|
137
|
+
if (anchorDay === undefined) return "";
|
|
138
|
+
const n = start - anchorDay;
|
|
139
|
+
return n === 0 ? "D0" : n < 0 ? `D\u2212${-n}` : `D+${n}`;
|
|
140
|
+
};
|
|
141
|
+
const spans = useMemo(
|
|
142
|
+
() =>
|
|
143
|
+
rows.map((r) => ({ start: r.start, end: r.end, ...(r.milestone ? { point: true } : {}) })),
|
|
144
|
+
[rows],
|
|
145
|
+
);
|
|
146
|
+
const page =
|
|
147
|
+
draft &&
|
|
148
|
+
preview &&
|
|
149
|
+
preview.change.row === draft.row &&
|
|
150
|
+
preview.change.start === draft.start &&
|
|
151
|
+
preview.change.end === draft.end
|
|
152
|
+
? preview
|
|
153
|
+
: null;
|
|
154
|
+
const isLocked = useMemo(() => (i: number) => rows[i]?.locked === true, [rows]);
|
|
155
|
+
// The estimate from `links`; the page's own preview replaces it once it matches the change.
|
|
156
|
+
const estimate: TimelinePlan | null = useMemo(
|
|
157
|
+
() => (draft ? planChange(spans, links, draft, isLocked) : null),
|
|
158
|
+
[draft, spans, links, isLocked],
|
|
159
|
+
);
|
|
160
|
+
const plan: TimelinePlan | null = useMemo(
|
|
161
|
+
() => (draft && page ? withMoves(spans, links, draft, page.moves, isLocked) : estimate),
|
|
162
|
+
[draft, page, spans, links, isLocked, estimate],
|
|
163
|
+
);
|
|
164
|
+
const cycle = draft !== null && plan === null;
|
|
165
|
+
const refusal = page?.refusal ?? null;
|
|
166
|
+
// The page's answer for this exact change has not arrived yet, so the estimate stands in for it.
|
|
167
|
+
const waiting = awaitPreview && draft !== null && page === null && !cycle;
|
|
168
|
+
const blocked = cycle || waiting || refusal !== null || (plan?.locked.length ?? 0) > 0;
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
pending.current?.(draft);
|
|
172
|
+
const key = refocus.current;
|
|
173
|
+
if (draft || key === null) return;
|
|
174
|
+
refocus.current = null;
|
|
175
|
+
const bars = controls.current?.querySelectorAll<HTMLButtonElement>("button[data-row]") ?? [];
|
|
176
|
+
for (const bar of bars) if (bar.dataset["row"] === key) bar.focus();
|
|
177
|
+
}, [draft]);
|
|
178
|
+
|
|
179
|
+
/** Where each row sits now: the pending change and what it pushes, else where it is. */
|
|
180
|
+
const at = (i: number): { start: number; end: number } => {
|
|
181
|
+
if (draft && draft.row === i) return draft;
|
|
182
|
+
const move = plan?.moves.find((m) => m.row === i);
|
|
183
|
+
return move ?? rows[i] ?? { start: 0, end: 0 };
|
|
184
|
+
};
|
|
185
|
+
const moved = (i: number) => {
|
|
186
|
+
const r = rows[i];
|
|
187
|
+
const now = at(i);
|
|
188
|
+
return !!r && (now.start !== r.start || now.end !== r.end);
|
|
189
|
+
};
|
|
190
|
+
const criticalNow = (i: number) =>
|
|
191
|
+
plan ? plan.criticalAfter.includes(i) : rows[i]?.critical === true;
|
|
192
|
+
|
|
193
|
+
const summary = (() => {
|
|
194
|
+
if (!draft) return "";
|
|
195
|
+
const row = rows[draft.row];
|
|
196
|
+
if (!row) return "";
|
|
197
|
+
const keys = (list: readonly number[]) =>
|
|
198
|
+
format.list(list.flatMap((i) => (rows[i] ? [rows[i].key] : [])));
|
|
199
|
+
const parts = [
|
|
200
|
+
t("timeline.preview.change", {
|
|
201
|
+
key: row.key,
|
|
202
|
+
start: dayLabel(draft.start),
|
|
203
|
+
end: dayLabel(draft.end),
|
|
204
|
+
}),
|
|
205
|
+
];
|
|
206
|
+
if (cycle) return [...parts, t("timeline.preview.cycle")].join(" ");
|
|
207
|
+
if (plan) {
|
|
208
|
+
const count = plan.moves.length;
|
|
209
|
+
parts.push(
|
|
210
|
+
count === 0
|
|
211
|
+
? t("timeline.preview.movesNone")
|
|
212
|
+
: t(count === 1 ? "timeline.preview.movesOne" : "timeline.preview.movesMany", {
|
|
213
|
+
count,
|
|
214
|
+
keys: keys(plan.moves.map((m) => m.row)),
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
parts.push(
|
|
218
|
+
t(
|
|
219
|
+
sameRows(plan.criticalBefore, plan.criticalAfter)
|
|
220
|
+
? "timeline.preview.criticalSame"
|
|
221
|
+
: "timeline.preview.criticalChanges",
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
const shift = plan.finishShift;
|
|
225
|
+
if (shift !== 0) {
|
|
226
|
+
const n = Math.abs(shift);
|
|
227
|
+
const later = shift > 0 ? "Later" : "Earlier";
|
|
228
|
+
parts.push(t(`timeline.preview.finish${later}${n === 1 ? "One" : "Many"}`, { count: n }));
|
|
229
|
+
}
|
|
230
|
+
if (plan.locked.length > 0) {
|
|
231
|
+
parts.push(t("timeline.preview.locked", { keys: keys(plan.locked) }));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (page?.note) parts.push(page.note);
|
|
235
|
+
if (refusal) parts.push(refusal);
|
|
236
|
+
if (waiting) parts.push(t("timeline.preview.checking"));
|
|
237
|
+
return parts.join(" ");
|
|
238
|
+
})();
|
|
239
|
+
|
|
240
|
+
const lastDay = Math.max(0, days.length - 1);
|
|
241
|
+
const shifted = (from: TimelineChange, mode: DragMode, by: number): TimelineChange => {
|
|
242
|
+
if (mode === "move") {
|
|
243
|
+
const d = Math.max(-from.start, Math.min(lastDay - from.end, by));
|
|
244
|
+
return { row: from.row, start: from.start + d, end: from.end + d };
|
|
245
|
+
}
|
|
246
|
+
if (mode === "end") {
|
|
247
|
+
return { ...from, end: Math.max(from.start, Math.min(lastDay, from.end + by)) };
|
|
248
|
+
}
|
|
249
|
+
return { ...from, start: Math.min(from.end, Math.max(0, from.start + by)) };
|
|
250
|
+
};
|
|
251
|
+
const base = (i: number): TimelineChange => {
|
|
252
|
+
if (draft && draft.row === i) return draft;
|
|
253
|
+
const r = rows[i];
|
|
254
|
+
return { row: i, start: r?.start ?? 0, end: r?.end ?? 0 };
|
|
255
|
+
};
|
|
256
|
+
const propose = (next: TimelineChange) => {
|
|
257
|
+
const r = rows[next.row];
|
|
258
|
+
setDraft(r && r.start === next.start && r.end === next.end ? null : next);
|
|
259
|
+
};
|
|
260
|
+
/** Before the preview closes: if focus is on its buttons, hand it back to the row's bar. */
|
|
261
|
+
const keepFocus = (row: number) => {
|
|
262
|
+
if (previewBar.current?.contains(document.activeElement))
|
|
263
|
+
refocus.current = rows[row]?.key ?? null;
|
|
264
|
+
};
|
|
265
|
+
const cancel = () => {
|
|
266
|
+
if (!draft) return;
|
|
267
|
+
keepFocus(draft.row);
|
|
268
|
+
setDraft(null);
|
|
269
|
+
setNotice(t("timeline.preview.canceled"));
|
|
270
|
+
};
|
|
271
|
+
// Apply or Enter pressed while the page's preview is on its way: applied when it arrives, if it
|
|
272
|
+
// moves the same rows the estimate showed; otherwise its answer stays up to be applied again.
|
|
273
|
+
const [queued, setQueued] = useState<TimelineChange | null>(null);
|
|
274
|
+
const apply = async () => {
|
|
275
|
+
if (!draft || !onReschedule || applying) return;
|
|
276
|
+
if (waiting) {
|
|
277
|
+
setQueued(draft);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (blocked) return;
|
|
281
|
+
const change = draft;
|
|
282
|
+
const key = rows[change.row]?.key ?? "";
|
|
283
|
+
setApplying(true);
|
|
284
|
+
try {
|
|
285
|
+
const result = await onReschedule(change);
|
|
286
|
+
if (result !== false) {
|
|
287
|
+
keepFocus(change.row);
|
|
288
|
+
setDraft((d) => (d === change ? null : d));
|
|
289
|
+
setNotice(t("timeline.preview.applied", { key }));
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
setApplying(false);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (!queued || waiting) return;
|
|
298
|
+
setQueued(null);
|
|
299
|
+
if (queued !== draft || blocked || !page || page.note || !estimate) return;
|
|
300
|
+
if (sameMoves(estimate.moves, page.moves)) void apply();
|
|
301
|
+
// apply reads this render's draft and preview, which are the ones checked here.
|
|
302
|
+
}, [queued, waiting, draft, blocked, page, estimate]);
|
|
303
|
+
|
|
304
|
+
const sign = dir === "rtl" ? -1 : 1;
|
|
305
|
+
const onKey = (event: KeyboardEvent<HTMLButtonElement>, i: number) => {
|
|
306
|
+
const step = event.key === "ArrowRight" ? sign : event.key === "ArrowLeft" ? -sign : 0;
|
|
307
|
+
if (step !== 0) {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
propose(shifted(base(i), event.shiftKey && !rows[i]?.milestone ? "end" : "move", step));
|
|
310
|
+
event.currentTarget.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
311
|
+
} else if (event.key === "Enter" && draft?.row === i) {
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
void apply();
|
|
314
|
+
} else if (event.key === "Escape" && draft) {
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
event.stopPropagation();
|
|
317
|
+
cancel();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const onPointerDown = (event: PointerEvent<HTMLElement>, i: number, mode: DragMode) => {
|
|
321
|
+
if (event.button > 0) return;
|
|
322
|
+
event.stopPropagation();
|
|
323
|
+
if (draft && draft.row !== i) setDraft(null);
|
|
324
|
+
drag.current = {
|
|
325
|
+
row: i,
|
|
326
|
+
mode,
|
|
327
|
+
x: event.clientX,
|
|
328
|
+
from: draft && draft.row === i ? draft : base(i),
|
|
329
|
+
pointer: event.pointerId,
|
|
330
|
+
};
|
|
331
|
+
dragged.current = false;
|
|
332
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
333
|
+
};
|
|
334
|
+
const onPointerMove = (event: PointerEvent<HTMLElement>) => {
|
|
335
|
+
const d = drag.current;
|
|
336
|
+
if (!d || d.pointer !== event.pointerId) return;
|
|
337
|
+
const dx = event.clientX - d.x;
|
|
338
|
+
if (!dragged.current && Math.abs(dx) < DRAG_SLOP) return;
|
|
339
|
+
dragged.current = true;
|
|
340
|
+
propose(shifted(d.from, d.mode, Math.round((dx * sign) / dayWidth)));
|
|
341
|
+
};
|
|
342
|
+
const onPointerEnd = (event: PointerEvent<HTMLElement>) => {
|
|
343
|
+
if (drag.current?.pointer !== event.pointerId) return;
|
|
344
|
+
drag.current = null;
|
|
345
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const barBox = (i: number) => {
|
|
349
|
+
const p = at(i);
|
|
350
|
+
return {
|
|
351
|
+
insetInlineStart: p.start * dayWidth + 2,
|
|
352
|
+
inlineSize: (p.end - p.start + 1) * dayWidth - 4,
|
|
353
|
+
insetBlockStart: i * rowH + 8,
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<figure aria-label={label} className="relative m-0">
|
|
359
|
+
<table className="sr-only">
|
|
360
|
+
<caption>{label}</caption>
|
|
361
|
+
<thead>
|
|
362
|
+
<tr>
|
|
363
|
+
{[
|
|
364
|
+
"timeline.key",
|
|
365
|
+
"timeline.record",
|
|
366
|
+
"timeline.state",
|
|
367
|
+
"timeline.start",
|
|
368
|
+
"timeline.end",
|
|
369
|
+
"timeline.dependsOn",
|
|
370
|
+
...(anchorDay !== undefined ? ["timeline.offset"] : []),
|
|
371
|
+
].map((k) => (
|
|
372
|
+
<th key={k} scope="col">
|
|
373
|
+
{t(k)}
|
|
374
|
+
</th>
|
|
375
|
+
))}
|
|
376
|
+
</tr>
|
|
377
|
+
</thead>
|
|
378
|
+
<tbody>
|
|
379
|
+
{rows.map((r) => (
|
|
380
|
+
<tr key={r.key}>
|
|
381
|
+
<td>{r.key}</td>
|
|
382
|
+
<td>
|
|
383
|
+
{r.title}
|
|
384
|
+
{r.milestone ? ` (${t("timeline.milestone")})` : ""}
|
|
385
|
+
{r.critical ? ` (${t("timeline.critical")})` : ""}
|
|
386
|
+
{editable && r.locked ? ` (${t("timeline.locked")})` : ""}
|
|
387
|
+
{r.segments?.length
|
|
388
|
+
? ` (${r.segments
|
|
389
|
+
.map((s) =>
|
|
390
|
+
t("timeline.segment", {
|
|
391
|
+
label: s.label,
|
|
392
|
+
start: dayLabel(s.start),
|
|
393
|
+
end: dayLabel(s.end),
|
|
394
|
+
}),
|
|
395
|
+
)
|
|
396
|
+
.join("; ")})`
|
|
397
|
+
: ""}
|
|
398
|
+
{r.badge ? ` (${r.badge.label})` : ""}
|
|
399
|
+
</td>
|
|
400
|
+
<td>{t(`recordStates.${r.state === "in-review" ? "inReview" : r.state}`)}</td>
|
|
401
|
+
<td>{dayLabel(r.start)}</td>
|
|
402
|
+
<td>{dayLabel(r.end)}</td>
|
|
403
|
+
<td>{r.dependsOn !== undefined ? (rows[r.dependsOn]?.key ?? "") : ""}</td>
|
|
404
|
+
{anchorDay !== undefined ? (
|
|
405
|
+
<td>
|
|
406
|
+
<abbr title={offsetText(r.start)}>{offsetShort(r.start)}</abbr>
|
|
407
|
+
<span className="sr-only"> {offsetText(r.start)}</span>
|
|
408
|
+
</td>
|
|
409
|
+
) : null}
|
|
410
|
+
</tr>
|
|
411
|
+
))}
|
|
412
|
+
</tbody>
|
|
413
|
+
</table>
|
|
414
|
+
<div className="flex overflow-hidden rounded-card border border-border-subtle bg-bg-canvas">
|
|
415
|
+
<div
|
|
416
|
+
aria-hidden="true"
|
|
417
|
+
className="flex-none w-(--ui-tree) max-sm:w-(--ui-skeleton-inline) bg-bg-surface border-e border-border-subtle"
|
|
418
|
+
>
|
|
419
|
+
<div className="flex items-center h-(--ui-shell-head) px-12 font-semibold border-b border-border-subtle">
|
|
420
|
+
{label}
|
|
421
|
+
</div>
|
|
422
|
+
{tagged ? (
|
|
423
|
+
<div className="flex items-center h-(--space-20) px-12 text-text-11 text-text-secondary border-b border-border-subtle">
|
|
424
|
+
{t("timeline.dayTypes")}
|
|
425
|
+
</div>
|
|
426
|
+
) : null}
|
|
427
|
+
{rows.map((r) => (
|
|
428
|
+
<div
|
|
429
|
+
key={r.key}
|
|
430
|
+
className="flex items-center gap-6 h-row-default px-12 whitespace-nowrap overflow-hidden"
|
|
431
|
+
>
|
|
432
|
+
<StateIcon state={r.state} />
|
|
433
|
+
<span className="font-mono text-text-12 text-text-secondary">{r.key}</span>
|
|
434
|
+
<span className="truncate" title={r.title}>
|
|
435
|
+
{r.title}
|
|
436
|
+
</span>
|
|
437
|
+
{r.badge ? (
|
|
438
|
+
<span className="ms-auto flex-none">
|
|
439
|
+
<Tag tone={r.badge.tone ?? "neutral"}>{r.badge.label}</Tag>
|
|
440
|
+
</span>
|
|
441
|
+
) : null}
|
|
442
|
+
</div>
|
|
443
|
+
))}
|
|
444
|
+
</div>
|
|
445
|
+
{/* The day grid scrolls sideways; the region takes keyboard focus when it overflows, so
|
|
446
|
+
arrow keys reach the later dates (2.1.1). Its drawing stays hidden: the table above
|
|
447
|
+
carries the data, and the bar controls laid over it carry rescheduling. */}
|
|
448
|
+
<ScrollRegion label={t("timeline.scroll", { label })} frameClassName="flex-1">
|
|
449
|
+
{/* Without rescheduling the whole content is the hidden drawing; with it, the drawing is
|
|
450
|
+
hidden and the bar controls beside it are not. */}
|
|
451
|
+
<div
|
|
452
|
+
className="relative"
|
|
453
|
+
style={{ inlineSize: width }}
|
|
454
|
+
{...(editable ? {} : { "aria-hidden": true })}
|
|
455
|
+
{...(onDayDrop
|
|
456
|
+
? {
|
|
457
|
+
"data-day-drop": "",
|
|
458
|
+
onDragOver: (e: DragEvent<HTMLDivElement>) => {
|
|
459
|
+
if (e.dataTransfer.types.includes(RECORD_MIME)) {
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
e.dataTransfer.dropEffect = "move";
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
onDrop: (e: DragEvent<HTMLDivElement>) => {
|
|
465
|
+
const payload = e.dataTransfer.getData(RECORD_MIME);
|
|
466
|
+
if (!payload) return;
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
469
|
+
onDayDrop(
|
|
470
|
+
dayAtPointer(e.clientX, rect, dayWidth, days.length, dir === "rtl"),
|
|
471
|
+
payload,
|
|
472
|
+
);
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
: {})}
|
|
476
|
+
>
|
|
477
|
+
<div {...(editable ? { "aria-hidden": true } : {})}>
|
|
478
|
+
<div
|
|
479
|
+
className="flex h-(--ui-shell-head) border-b border-border-subtle"
|
|
480
|
+
style={{ inlineSize: width }}
|
|
481
|
+
>
|
|
482
|
+
{days.map((d, i) => (
|
|
483
|
+
<div
|
|
484
|
+
key={i}
|
|
485
|
+
style={{ inlineSize: dayWidth }}
|
|
486
|
+
className={cx(
|
|
487
|
+
"flex flex-none flex-col items-center justify-center text-text-11 text-text-tertiary",
|
|
488
|
+
d.weekend && "bg-bg-surface",
|
|
489
|
+
)}
|
|
490
|
+
>
|
|
491
|
+
<span>{d.dow}</span>
|
|
492
|
+
<strong className="text-text-12 font-medium text-text-secondary tabular-nums">
|
|
493
|
+
{d.date}
|
|
494
|
+
</strong>
|
|
495
|
+
</div>
|
|
496
|
+
))}
|
|
497
|
+
</div>
|
|
498
|
+
{tagged ? (
|
|
499
|
+
<div
|
|
500
|
+
className="flex h-(--space-20) border-b border-border-subtle"
|
|
501
|
+
style={{ inlineSize: width }}
|
|
502
|
+
>
|
|
503
|
+
{days.map((d, i) => (
|
|
504
|
+
<div
|
|
505
|
+
key={i}
|
|
506
|
+
style={{ inlineSize: dayWidth }}
|
|
507
|
+
title={categoryText(d.tags)}
|
|
508
|
+
className="flex flex-none items-center justify-center gap-1 overflow-hidden"
|
|
509
|
+
>
|
|
510
|
+
{(d.tags ?? []).slice(0, 2).map((tag) => (
|
|
511
|
+
<span
|
|
512
|
+
key={tag.label}
|
|
513
|
+
className={cx(
|
|
514
|
+
"inline-flex size-(--space-16) items-center justify-center rounded-mark text-viz-ink forced-colors:border",
|
|
515
|
+
TL_CATEGORY[tag.category],
|
|
516
|
+
)}
|
|
517
|
+
>
|
|
518
|
+
{tag.icon ? <Icon name={tag.icon} size={12} /> : tag.label.slice(0, 1)}
|
|
519
|
+
</span>
|
|
520
|
+
))}
|
|
521
|
+
</div>
|
|
522
|
+
))}
|
|
523
|
+
</div>
|
|
524
|
+
) : null}
|
|
525
|
+
<div className="relative" style={{ inlineSize: width, blockSize: height }}>
|
|
526
|
+
{days.map((d, i) =>
|
|
527
|
+
d.weekend ? (
|
|
528
|
+
<div
|
|
529
|
+
key={i}
|
|
530
|
+
className="absolute inset-y-0 bg-bg-surface"
|
|
531
|
+
style={{ insetInlineStart: i * dayWidth, inlineSize: dayWidth }}
|
|
532
|
+
/>
|
|
533
|
+
) : null,
|
|
534
|
+
)}
|
|
535
|
+
{rows.map((r, i) => {
|
|
536
|
+
const top = i * rowH;
|
|
537
|
+
const p = at(i);
|
|
538
|
+
const isMoved = moved(i);
|
|
539
|
+
return (
|
|
540
|
+
<div key={r.key}>
|
|
541
|
+
{(r.segments ?? []).map((seg) => (
|
|
542
|
+
<div
|
|
543
|
+
key={`${seg.label}-${seg.start}`}
|
|
544
|
+
title={seg.label}
|
|
545
|
+
className={cx(
|
|
546
|
+
"absolute flex h-(--space-6) items-center rounded-pill opacity-70 forced-colors:border",
|
|
547
|
+
TL_CATEGORY[seg.category],
|
|
548
|
+
)}
|
|
549
|
+
style={{
|
|
550
|
+
insetInlineStart: seg.start * dayWidth,
|
|
551
|
+
inlineSize: (seg.end - seg.start + 1) * dayWidth,
|
|
552
|
+
insetBlockStart: top + 30,
|
|
553
|
+
}}
|
|
554
|
+
/>
|
|
555
|
+
))}
|
|
556
|
+
{r.baselineStart !== undefined && r.baselineEnd !== undefined ? (
|
|
557
|
+
<div
|
|
558
|
+
className="absolute h-(--space-4) rounded-pill bg-text-tertiary opacity-60"
|
|
559
|
+
style={{
|
|
560
|
+
insetInlineStart: r.baselineStart * dayWidth,
|
|
561
|
+
inlineSize: (r.baselineEnd - r.baselineStart + 1) * dayWidth,
|
|
562
|
+
insetBlockStart: top + 26,
|
|
563
|
+
}}
|
|
564
|
+
/>
|
|
565
|
+
) : null}
|
|
566
|
+
{isMoved && !r.milestone ? (
|
|
567
|
+
// Where the bar was before the pending change.
|
|
568
|
+
<div
|
|
569
|
+
className="absolute h-(--space-20) rounded-chip border border-dashed border-border-strong"
|
|
570
|
+
style={{
|
|
571
|
+
insetInlineStart: r.start * dayWidth + 2,
|
|
572
|
+
inlineSize: (r.end - r.start + 1) * dayWidth - 4,
|
|
573
|
+
insetBlockStart: top + 8,
|
|
574
|
+
}}
|
|
575
|
+
/>
|
|
576
|
+
) : null}
|
|
577
|
+
{r.milestone ? (
|
|
578
|
+
<div
|
|
579
|
+
className={cx(
|
|
580
|
+
"absolute size-(--space-12) rotate-45 rounded-mark bg-warning",
|
|
581
|
+
isMoved && "ring-(length:--stroke-width-focus) ring-accent",
|
|
582
|
+
)}
|
|
583
|
+
style={{
|
|
584
|
+
insetInlineStart: p.start * dayWidth + dayWidth / 2 - 6,
|
|
585
|
+
insetBlockStart: top + 12,
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
) : (
|
|
589
|
+
<div
|
|
590
|
+
className={cx(
|
|
591
|
+
"absolute h-(--space-20) overflow-hidden rounded-chip",
|
|
592
|
+
TL_BAR[r.state] ??
|
|
593
|
+
"bg-bg-hover ring-(length:--stroke-width-hairline) ring-inset ring-border-strong",
|
|
594
|
+
criticalNow(i) &&
|
|
595
|
+
"ring-(length:--stroke-width-focus) ring-inset ring-danger",
|
|
596
|
+
isMoved &&
|
|
597
|
+
(r.locked
|
|
598
|
+
? "outline-(length:--stroke-width-focus) outline-dashed outline-danger"
|
|
599
|
+
: "outline-(length:--stroke-width-focus) outline-dashed outline-accent"),
|
|
600
|
+
"forced-colors:bg-[Highlight]",
|
|
601
|
+
)}
|
|
602
|
+
style={barBox(i)}
|
|
603
|
+
>
|
|
604
|
+
{r.progress !== undefined ? (
|
|
605
|
+
<span
|
|
606
|
+
className="block h-full bg-viz-ink opacity-20"
|
|
607
|
+
style={{ inlineSize: `${r.progress}%` }}
|
|
608
|
+
/>
|
|
609
|
+
) : null}
|
|
610
|
+
</div>
|
|
611
|
+
)}
|
|
612
|
+
</div>
|
|
613
|
+
);
|
|
614
|
+
})}
|
|
615
|
+
{gates.map((g) => (
|
|
616
|
+
<div
|
|
617
|
+
key={g.label}
|
|
618
|
+
className="absolute inset-y-0 border-s border-dashed border-state-in-review"
|
|
619
|
+
style={{ insetInlineStart: g.day * dayWidth }}
|
|
620
|
+
>
|
|
621
|
+
<span className="absolute top-2 start-4 px-2 whitespace-nowrap text-text-11 text-text-secondary bg-bg-canvas">
|
|
622
|
+
{g.label}
|
|
623
|
+
</span>
|
|
624
|
+
</div>
|
|
625
|
+
))}
|
|
626
|
+
{today !== undefined ? (
|
|
627
|
+
<div
|
|
628
|
+
className="absolute inset-y-0 w-(--stroke-width-focus) bg-danger"
|
|
629
|
+
style={{ insetInlineStart: today * dayWidth + dayWidth / 2 }}
|
|
630
|
+
/>
|
|
631
|
+
) : null}
|
|
632
|
+
<svg
|
|
633
|
+
className="absolute start-0 top-0 overflow-visible pointer-events-none rtl:-scale-x-100"
|
|
634
|
+
width={width}
|
|
635
|
+
height={height}
|
|
636
|
+
>
|
|
637
|
+
<defs>
|
|
638
|
+
<marker
|
|
639
|
+
id={markerId}
|
|
640
|
+
viewBox="0 0 8 8"
|
|
641
|
+
refX={7}
|
|
642
|
+
refY={4}
|
|
643
|
+
markerWidth={6}
|
|
644
|
+
markerHeight={6}
|
|
645
|
+
orient="auto"
|
|
646
|
+
>
|
|
647
|
+
<path d="M0 0 L8 4 L0 8 z" className="fill-text-secondary" />
|
|
648
|
+
</marker>
|
|
649
|
+
</defs>
|
|
650
|
+
{rows.map((r, i) => {
|
|
651
|
+
if (r.dependsOn === undefined) return null;
|
|
652
|
+
if (!rows[r.dependsOn]) return null;
|
|
653
|
+
const x1 = (at(r.dependsOn).end + 1) * dayWidth;
|
|
654
|
+
const y1 = r.dependsOn * rowH + rowH / 2;
|
|
655
|
+
const x2 = at(i).start * dayWidth;
|
|
656
|
+
const y2 = i * rowH + rowH / 2;
|
|
657
|
+
const sx = Math.min(x1 - 8, x2 - 4);
|
|
658
|
+
return (
|
|
659
|
+
<path
|
|
660
|
+
key={r.key}
|
|
661
|
+
d={`M${sx} ${y1 + 10} V${y2} H${x2 - 2}`}
|
|
662
|
+
className="fill-none stroke-text-secondary"
|
|
663
|
+
strokeWidth={1.25}
|
|
664
|
+
markerEnd={`url(#${markerId})`}
|
|
665
|
+
/>
|
|
666
|
+
);
|
|
667
|
+
})}
|
|
668
|
+
</svg>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
{editable ? (
|
|
672
|
+
<div
|
|
673
|
+
ref={controls}
|
|
674
|
+
role="group"
|
|
675
|
+
aria-label={t("timeline.controls", { label })}
|
|
676
|
+
className="absolute inset-x-0 bottom-0"
|
|
677
|
+
style={{ blockSize: height }}
|
|
678
|
+
>
|
|
679
|
+
<span id={`${uid}-hint`} className="sr-only">
|
|
680
|
+
{t("timeline.moveHint")}
|
|
681
|
+
</span>
|
|
682
|
+
{rows.map((r, i) =>
|
|
683
|
+
r.locked ? null : (
|
|
684
|
+
<button
|
|
685
|
+
key={r.key}
|
|
686
|
+
type="button"
|
|
687
|
+
data-row={r.key}
|
|
688
|
+
aria-label={t("timeline.bar", {
|
|
689
|
+
key: r.key,
|
|
690
|
+
title: r.title,
|
|
691
|
+
start: dayLabel(at(i).start),
|
|
692
|
+
end: dayLabel(at(i).end),
|
|
693
|
+
})}
|
|
694
|
+
aria-describedby={`${uid}-hint`}
|
|
695
|
+
className={cx(
|
|
696
|
+
"absolute h-(--space-20) p-0 rounded-chip border-0 bg-transparent cursor-grab touch-none",
|
|
697
|
+
"active:cursor-grabbing",
|
|
698
|
+
)}
|
|
699
|
+
style={
|
|
700
|
+
r.milestone
|
|
701
|
+
? {
|
|
702
|
+
insetInlineStart: at(i).start * dayWidth + dayWidth / 2 - 10,
|
|
703
|
+
inlineSize: 20,
|
|
704
|
+
insetBlockStart: i * rowH + 8,
|
|
705
|
+
}
|
|
706
|
+
: barBox(i)
|
|
707
|
+
}
|
|
708
|
+
onKeyDown={(e) => onKey(e, i)}
|
|
709
|
+
onPointerDown={(e) => onPointerDown(e, i, "move")}
|
|
710
|
+
onPointerMove={onPointerMove}
|
|
711
|
+
onPointerUp={onPointerEnd}
|
|
712
|
+
onPointerCancel={onPointerEnd}
|
|
713
|
+
>
|
|
714
|
+
{r.milestone ? null : (
|
|
715
|
+
<>
|
|
716
|
+
<span
|
|
717
|
+
aria-hidden="true"
|
|
718
|
+
className="absolute inset-y-0 start-0 w-(--space-6) cursor-ew-resize"
|
|
719
|
+
onPointerDown={(e) => onPointerDown(e, i, "start")}
|
|
720
|
+
onPointerMove={onPointerMove}
|
|
721
|
+
onPointerUp={onPointerEnd}
|
|
722
|
+
onPointerCancel={onPointerEnd}
|
|
723
|
+
/>
|
|
724
|
+
<span
|
|
725
|
+
aria-hidden="true"
|
|
726
|
+
className="absolute inset-y-0 end-0 w-(--space-6) cursor-ew-resize"
|
|
727
|
+
onPointerDown={(e) => onPointerDown(e, i, "end")}
|
|
728
|
+
onPointerMove={onPointerMove}
|
|
729
|
+
onPointerUp={onPointerEnd}
|
|
730
|
+
onPointerCancel={onPointerEnd}
|
|
731
|
+
/>
|
|
732
|
+
</>
|
|
733
|
+
)}
|
|
734
|
+
</button>
|
|
735
|
+
),
|
|
736
|
+
)}
|
|
737
|
+
</div>
|
|
738
|
+
) : null}
|
|
739
|
+
</div>
|
|
740
|
+
</ScrollRegion>
|
|
741
|
+
</div>
|
|
742
|
+
{editable && draft ? (
|
|
743
|
+
<div
|
|
744
|
+
ref={previewBar}
|
|
745
|
+
role="group"
|
|
746
|
+
aria-label={t("timeline.preview.label")}
|
|
747
|
+
onKeyDown={(e) => {
|
|
748
|
+
if (e.key === "Escape") {
|
|
749
|
+
e.stopPropagation();
|
|
750
|
+
cancel();
|
|
751
|
+
}
|
|
752
|
+
}}
|
|
753
|
+
className="flex flex-wrap items-center gap-8 mt-8 px-12 py-8 rounded-card border border-border-subtle bg-bg-surface"
|
|
754
|
+
>
|
|
755
|
+
<p className="m-0 flex-1 min-w-0 text-text-13">{summary}</p>
|
|
756
|
+
<Button
|
|
757
|
+
size="sm"
|
|
758
|
+
variant="primary"
|
|
759
|
+
disabled={blocked && !waiting}
|
|
760
|
+
loading={applying || (queued !== null && queued === draft)}
|
|
761
|
+
onClick={() => void apply()}
|
|
762
|
+
>
|
|
763
|
+
{t("timeline.preview.apply")}
|
|
764
|
+
</Button>
|
|
765
|
+
<Button size="sm" variant="ghost" onClick={cancel}>
|
|
766
|
+
{t("timeline.preview.cancel")}
|
|
767
|
+
</Button>
|
|
768
|
+
</div>
|
|
769
|
+
) : null}
|
|
770
|
+
{editable ? (
|
|
771
|
+
<p aria-live="polite" className="sr-only">
|
|
772
|
+
{draft ? summary : notice}
|
|
773
|
+
</p>
|
|
774
|
+
) : null}
|
|
775
|
+
</figure>
|
|
776
|
+
);
|
|
777
|
+
}
|