portal-mojo 0.1.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 +201 -0
- package/README.md +55 -0
- package/docs/DetailView.md +173 -0
- package/docs/ModelTable.md +128 -0
- package/docs/README.md +98 -0
- package/docs/admin-assistant.md +72 -0
- package/docs/admin-bouncer.md +120 -0
- package/docs/admin-cloudwatch.md +38 -0
- package/docs/admin-credentials.md +116 -0
- package/docs/admin-dashboard.md +15 -0
- package/docs/admin-devices-geoip.md +284 -0
- package/docs/admin-dns-certificates.md +122 -0
- package/docs/admin-dns-records.md +65 -0
- package/docs/admin-dns-registrar.md +33 -0
- package/docs/admin-dns.md +80 -0
- package/docs/admin-identity-users.md +103 -0
- package/docs/admin-incidents.md +71 -0
- package/docs/admin-jobs.md +426 -0
- package/docs/admin-members.md +112 -0
- package/docs/admin-messaging.md +56 -0
- package/docs/admin-metrics-explorer.md +63 -0
- package/docs/admin-monitoring.md +99 -0
- package/docs/admin-network-security.md +393 -0
- package/docs/admin-phonehub.md +27 -0
- package/docs/admin-push.md +36 -0
- package/docs/admin-rules.md +87 -0
- package/docs/admin-security-tickets.md +40 -0
- package/docs/admin-settings.md +78 -0
- package/docs/admin-shortlinks.md +32 -0
- package/docs/admin-storage.md +141 -0
- package/docs/auth-pages.md +212 -0
- package/docs/auth.md +58 -0
- package/docs/calendar.md +122 -0
- package/docs/charts.md +303 -0
- package/docs/client.md +206 -0
- package/docs/collection-multiselect.md +117 -0
- package/docs/collection-select.md +132 -0
- package/docs/combobox.md +105 -0
- package/docs/dataview.md +226 -0
- package/docs/datepicker.md +151 -0
- package/docs/daterange.md +170 -0
- package/docs/datetimepicker.md +249 -0
- package/docs/defineModel.md +72 -0
- package/docs/detail-primitives.md +174 -0
- package/docs/feedback.md +85 -0
- package/docs/file-upload-ux.md +146 -0
- package/docs/forms.md +435 -0
- package/docs/grouping-and-fmt.md +98 -0
- package/docs/idioms.md +112 -0
- package/docs/image-editor.md +101 -0
- package/docs/loading.md +242 -0
- package/docs/location-address.md +76 -0
- package/docs/markdown.md +203 -0
- package/docs/menus-and-access.md +89 -0
- package/docs/multiselect-dropdown.md +153 -0
- package/docs/params.md +73 -0
- package/docs/popover.md +130 -0
- package/docs/realtime.md +61 -0
- package/docs/record-feed.md +238 -0
- package/docs/right-panel.md +42 -0
- package/docs/tabs-and-form-wizard.md +60 -0
- package/docs/taginput.md +120 -0
- package/docs/timepicker.md +173 -0
- package/docs/timezone-select.md +147 -0
- package/docs/worldmap.md +302 -0
- package/package.json +55 -0
- package/src/admin/assistant/AssistantFeed.tsx +29 -0
- package/src/admin/assistant/AssistantPanel.tsx +65 -0
- package/src/admin/assistant/api.ts +17 -0
- package/src/admin/assistant/data.ts +63 -0
- package/src/admin/assistant/index.ts +21 -0
- package/src/admin/assistant/launchers.tsx +33 -0
- package/src/admin/assistant/pages.tsx +78 -0
- package/src/admin/assistant/streaming.ts +238 -0
- package/src/admin/assistant/types.ts +30 -0
- package/src/admin/bouncer/devices.tsx +230 -0
- package/src/admin/bouncer/index.ts +34 -0
- package/src/admin/bouncer/models.ts +199 -0
- package/src/admin/bouncer/signals.tsx +182 -0
- package/src/admin/bouncer/signatures.tsx +220 -0
- package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
- package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
- package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
- package/src/admin/cloudwatch/data.ts +163 -0
- package/src/admin/cloudwatch/index.ts +16 -0
- package/src/admin/credentials/group-api-keys.tsx +433 -0
- package/src/admin/credentials/index.ts +4 -0
- package/src/admin/credentials/models.ts +244 -0
- package/src/admin/credentials/secret-dialog.tsx +77 -0
- package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
- package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
- package/src/admin/dashboard/data.ts +60 -0
- package/src/admin/dashboard/index.ts +2 -0
- package/src/admin/dns/CertificateDetail.tsx +115 -0
- package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
- package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
- package/src/admin/dns/CertificatesPage.tsx +85 -0
- package/src/admin/dns/DnsRecordEditor.tsx +110 -0
- package/src/admin/dns/DnsRecordsPage.tsx +36 -0
- package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
- package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
- package/src/admin/dns/DomainDetail.tsx +41 -0
- package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
- package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
- package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
- package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
- package/src/admin/dns/DomainsPage.tsx +27 -0
- package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
- package/src/admin/dns/RegistrantContactPage.tsx +42 -0
- package/src/admin/dns/api.ts +515 -0
- package/src/admin/dns/certificate-data.ts +221 -0
- package/src/admin/dns/data.ts +32 -0
- package/src/admin/dns/dns-data.ts +266 -0
- package/src/admin/dns/dns-integration.ts +24 -0
- package/src/admin/dns/domain-links.ts +37 -0
- package/src/admin/dns/index.ts +86 -0
- package/src/admin/dns/models.ts +418 -0
- package/src/admin/dns/purchase-data.ts +67 -0
- package/src/admin/dns/registrant-data.ts +52 -0
- package/src/admin/identity/index.ts +5 -0
- package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
- package/src/admin/identity/members/MemberDetail.tsx +226 -0
- package/src/admin/identity/members/MembersPage.tsx +144 -0
- package/src/admin/identity/members/index.ts +24 -0
- package/src/admin/identity/members/member-flows.tsx +132 -0
- package/src/admin/identity/members/models.ts +204 -0
- package/src/admin/identity/users/UserDetail.tsx +257 -0
- package/src/admin/identity/users/UsersPage.tsx +163 -0
- package/src/admin/identity/users/index.ts +27 -0
- package/src/admin/identity/users/models.ts +232 -0
- package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
- package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
- package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
- package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
- package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
- package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
- package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
- package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
- package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
- package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
- package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
- package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
- package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
- package/src/admin/identity/users/sections/actions.tsx +752 -0
- package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
- package/src/admin/identity/users/sections/queries.ts +74 -0
- package/src/admin/identity/users/sections/shared.tsx +299 -0
- package/src/admin/incidents/EventDetail.tsx +43 -0
- package/src/admin/incidents/EventsPage.tsx +52 -0
- package/src/admin/incidents/IncidentDetail.tsx +68 -0
- package/src/admin/incidents/IncidentsPage.tsx +102 -0
- package/src/admin/incidents/forensics.tsx +73 -0
- package/src/admin/incidents/index.ts +16 -0
- package/src/admin/incidents/models.ts +113 -0
- package/src/admin/incidents/sanitize.ts +71 -0
- package/src/admin/index.ts +383 -0
- package/src/admin/jobs/JobDashboardPage.tsx +111 -0
- package/src/admin/jobs/JobDetail.tsx +721 -0
- package/src/admin/jobs/JobRunnersPage.tsx +216 -0
- package/src/admin/jobs/JobsTablePage.tsx +122 -0
- package/src/admin/jobs/RunnerDetail.tsx +576 -0
- package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
- package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
- package/src/admin/jobs/columns.tsx +297 -0
- package/src/admin/jobs/control.ts +488 -0
- package/src/admin/jobs/index.ts +85 -0
- package/src/admin/jobs/models.ts +599 -0
- package/src/admin/jobs/queries.ts +272 -0
- package/src/admin/jobs/scheduled-task-form.tsx +493 -0
- package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
- package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
- package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
- package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
- package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
- package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
- package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
- package/src/admin/messaging/MailboxesPage.tsx +15 -0
- package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
- package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
- package/src/admin/messaging/SentMessagesPage.tsx +11 -0
- package/src/admin/messaging/api.ts +17 -0
- package/src/admin/messaging/data.ts +10 -0
- package/src/admin/messaging/index.ts +23 -0
- package/src/admin/messaging/models.ts +76 -0
- package/src/admin/messaging/push/PushPage.tsx +44 -0
- package/src/admin/messaging/push/api.ts +17 -0
- package/src/admin/messaging/push/index.ts +9 -0
- package/src/admin/messaging/push/models.ts +43 -0
- package/src/admin/messaging/sanitize.ts +44 -0
- package/src/admin/monitoring/LogInspector.tsx +160 -0
- package/src/admin/monitoring/LogsPage.tsx +135 -0
- package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
- package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
- package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
- package/src/admin/monitoring/index.ts +55 -0
- package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
- package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
- package/src/admin/monitoring/models.ts +155 -0
- package/src/admin/network/BlockedIPsPage.tsx +194 -0
- package/src/admin/network/BlockedIpDetail.tsx +262 -0
- package/src/admin/network/FirewallLogPage.tsx +115 -0
- package/src/admin/network/IPSetDetail.tsx +280 -0
- package/src/admin/network/IPSetEditor.tsx +191 -0
- package/src/admin/network/IPSetsPage.tsx +177 -0
- package/src/admin/network/geofence/BlocksTab.tsx +289 -0
- package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
- package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
- package/src/admin/network/geofence/PostureHeader.tsx +184 -0
- package/src/admin/network/geofence/RuleEditor.tsx +176 -0
- package/src/admin/network/geofence/RulesTab.tsx +254 -0
- package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
- package/src/admin/network/geofence/geofence-data.ts +674 -0
- package/src/admin/network/index.ts +68 -0
- package/src/admin/network/models.ts +607 -0
- package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
- package/src/admin/phonehub/api.ts +47 -0
- package/src/admin/phonehub/data.ts +6 -0
- package/src/admin/phonehub/index.ts +10 -0
- package/src/admin/phonehub/models.ts +68 -0
- package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
- package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
- package/src/admin/rules/RuleSetsPage.tsx +33 -0
- package/src/admin/rules/editors.tsx +54 -0
- package/src/admin/rules/handler-dsl.ts +213 -0
- package/src/admin/rules/index.ts +13 -0
- package/src/admin/rules/models.ts +112 -0
- package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
- package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
- package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
- package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
- package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
- package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
- package/src/admin/security/devices/index.ts +40 -0
- package/src/admin/security/devices/models.ts +470 -0
- package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
- package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
- package/src/admin/security/geoip/geoip-forms.ts +102 -0
- package/src/admin/security/geoip/index.ts +27 -0
- package/src/admin/security/geoip/models.ts +397 -0
- package/src/admin/security/index.ts +31 -0
- package/src/admin/security/models.ts +137 -0
- package/src/admin/security/tickets.tsx +562 -0
- package/src/admin/security-permissions.ts +5 -0
- package/src/admin/settings/SettingDetail.tsx +57 -0
- package/src/admin/settings/SettingEditor.tsx +157 -0
- package/src/admin/settings/SettingsPage.tsx +72 -0
- package/src/admin/settings/index.ts +19 -0
- package/src/admin/settings/model.ts +143 -0
- package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
- package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
- package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
- package/src/admin/shortlinks/index.ts +18 -0
- package/src/admin/shortlinks/models.ts +246 -0
- package/src/admin/storage/BackendsPage.tsx +43 -0
- package/src/admin/storage/BucketDetail.tsx +77 -0
- package/src/admin/storage/BucketsPage.tsx +64 -0
- package/src/admin/storage/FileManagerDetail.tsx +36 -0
- package/src/admin/storage/FilePreview.tsx +54 -0
- package/src/admin/storage/FileUploadSurface.tsx +250 -0
- package/src/admin/storage/FileView.tsx +109 -0
- package/src/admin/storage/FilesPage.tsx +53 -0
- package/src/admin/storage/file-renditions.ts +99 -0
- package/src/admin/storage/index.ts +31 -0
- package/src/admin/storage/models.ts +585 -0
- package/src/admin/storage/storage-dialogs.tsx +80 -0
- package/src/charts/CircularProgress.tsx +281 -0
- package/src/charts/KPIStrip.tsx +162 -0
- package/src/charts/KPITile.tsx +125 -0
- package/src/charts/MetricsChart.tsx +459 -0
- package/src/charts/MetricsMiniWidget.tsx +588 -0
- package/src/charts/MiniChart.tsx +283 -0
- package/src/charts/PieChart.tsx +354 -0
- package/src/charts/SeriesChart.tsx +378 -0
- package/src/charts/chart-dialogs.tsx +149 -0
- package/src/charts/exportChart.ts +144 -0
- package/src/charts/index.ts +28 -0
- package/src/charts/pie-math.ts +127 -0
- package/src/charts/stats.ts +265 -0
- package/src/charts/worldmap/WorldMap.tsx +649 -0
- package/src/charts/worldmap/countryCentroids.ts +304 -0
- package/src/charts/worldmap/geo.ts +293 -0
- package/src/charts/worldmap/use-world-land.ts +47 -0
- package/src/charts/worldmap/world-land.ts +18 -0
- package/src/charts/worldmap/worldmap-data.ts +285 -0
- package/src/client/auth.ts +737 -0
- package/src/client/client.ts +268 -0
- package/src/client/duid.ts +31 -0
- package/src/client/errors.ts +33 -0
- package/src/client/group-context.ts +28 -0
- package/src/client/group.tsx +126 -0
- package/src/client/hooks.ts +50 -0
- package/src/client/index.ts +37 -0
- package/src/client/jwt.ts +75 -0
- package/src/client/location.ts +285 -0
- package/src/client/lookups.ts +41 -0
- package/src/client/markdown-fallback.tsx +115 -0
- package/src/client/markdown-parse.ts +386 -0
- package/src/client/markdown-sanitize.ts +217 -0
- package/src/client/markdown.ts +122 -0
- package/src/client/me.ts +188 -0
- package/src/client/mock.ts +9984 -0
- package/src/client/model.ts +227 -0
- package/src/client/params.ts +292 -0
- package/src/client/realtime-mock.ts +221 -0
- package/src/client/realtime.ts +561 -0
- package/src/client/record-feed.ts +329 -0
- package/src/client/safe-export.ts +71 -0
- package/src/client/types.ts +278 -0
- package/src/client/upload.ts +421 -0
- package/src/ui/AddressField.tsx +273 -0
- package/src/ui/AttachmentQueue.tsx +92 -0
- package/src/ui/CollectionMultiSelect.tsx +622 -0
- package/src/ui/CollectionSelect.tsx +517 -0
- package/src/ui/ComboBox.tsx +336 -0
- package/src/ui/DetailView.tsx +438 -0
- package/src/ui/FileDrop.tsx +194 -0
- package/src/ui/FileField.tsx +369 -0
- package/src/ui/FilterBar.tsx +269 -0
- package/src/ui/FormFields.tsx +77 -0
- package/src/ui/FormView.tsx +360 -0
- package/src/ui/FormWizard.tsx +336 -0
- package/src/ui/GroupSwitcher.tsx +202 -0
- package/src/ui/Guarded.tsx +16 -0
- package/src/ui/MarkdownView.tsx +89 -0
- package/src/ui/ModelTable.tsx +848 -0
- package/src/ui/MultiSelectDropdown.tsx +390 -0
- package/src/ui/Popover.tsx +204 -0
- package/src/ui/RecordFeed.tsx +627 -0
- package/src/ui/RequiresGroup.tsx +32 -0
- package/src/ui/RightPanel.tsx +107 -0
- package/src/ui/SidebarNav.tsx +330 -0
- package/src/ui/Tabs.tsx +161 -0
- package/src/ui/TagInput.tsx +381 -0
- package/src/ui/ThemeProvider.tsx +52 -0
- package/src/ui/UploadQueue.tsx +603 -0
- package/src/ui/armed-button.tsx +91 -0
- package/src/ui/date/Calendar.tsx +498 -0
- package/src/ui/date/DatePicker.tsx +289 -0
- package/src/ui/date/DateRangePicker.tsx +280 -0
- package/src/ui/date/DateTimePicker.tsx +587 -0
- package/src/ui/date/PresetRail.tsx +165 -0
- package/src/ui/date/TimePicker.tsx +624 -0
- package/src/ui/date/TimezoneSelect.tsx +267 -0
- package/src/ui/date/fns.ts +679 -0
- package/src/ui/detail/DataView.tsx +692 -0
- package/src/ui/detail/FlowStrip.tsx +80 -0
- package/src/ui/detail/KnownFieldsCard.tsx +121 -0
- package/src/ui/detail/MetadataSection.tsx +219 -0
- package/src/ui/detail/StackTraceView.tsx +150 -0
- package/src/ui/detail/StatusPanel.tsx +73 -0
- package/src/ui/detail/Timeline.tsx +52 -0
- package/src/ui/detail/index.ts +12 -0
- package/src/ui/field-registry.tsx +428 -0
- package/src/ui/field-wire.ts +322 -0
- package/src/ui/form-autosave.ts +583 -0
- package/src/ui/format.ts +444 -0
- package/src/ui/grouping.ts +150 -0
- package/src/ui/image-editor/ImageEditor.tsx +484 -0
- package/src/ui/image-editor/canvas.ts +254 -0
- package/src/ui/image-editor/index.ts +3 -0
- package/src/ui/image-editor/math.ts +416 -0
- package/src/ui/index.ts +58 -0
- package/src/ui/loading.tsx +604 -0
- package/src/ui/menu-registry.ts +213 -0
- package/src/ui/modal.tsx +297 -0
- package/src/ui/password.tsx +302 -0
- package/src/ui/schema-form-core.tsx +315 -0
- package/src/ui/toast.tsx +219 -0
- package/src/ui/ui.tsx +45 -0
package/src/ui/toast.tsx
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Toast manager — the ToastService API shape (success/error/info/warning) on
|
|
2
|
+
// a small external store, plus the two maestro idioms ported from
|
|
3
|
+
// workspaces/js/dom.js:
|
|
4
|
+
// · toast.undo / undoToast (dom.js:84-103) — act IMMEDIATELY, offer Undo
|
|
5
|
+
// for a grace window (replaces confirm() for reversible actions). Undo
|
|
6
|
+
// dismisses at once and runs the handler; otherwise auto-dismisses.
|
|
7
|
+
// · toast.progress / progressToast (dom.js:41-79) — persistent card with a
|
|
8
|
+
// bar. Handle: update(0..100) / finalizing(msg?) / done(msg?) /
|
|
9
|
+
// fail(msg?) / remove();
|
|
10
|
+
// optional onCancel renders a ✕ that ABORTS the caller's operation (the
|
|
11
|
+
// caller then calls fail/remove — cancel itself never dismisses).
|
|
12
|
+
//
|
|
13
|
+
// Basic toasts keep the original behavior byte-for-byte: 3.5s life, newest
|
|
14
|
+
// last, capped at 5. The cap deliberately does NOT count undo/progress cards
|
|
15
|
+
// — evicting a live progress bar because five saves toasted would orphan an
|
|
16
|
+
// in-flight operation's only indicator.
|
|
17
|
+
//
|
|
18
|
+
// Visual spec from web-mojo's toast.css (left accent bar, compact card,
|
|
19
|
+
// bottom-right); idiom styles live in the app's theme/idioms.css — tokens
|
|
20
|
+
// only, both themes.
|
|
21
|
+
import { useSyncExternalStore } from 'react';
|
|
22
|
+
|
|
23
|
+
type Level = 'success' | 'error' | 'info' | 'warning';
|
|
24
|
+
|
|
25
|
+
interface BasicToast { id: number; kind: 'basic'; level: Level; message: string }
|
|
26
|
+
interface UndoToastItem { id: number; kind: 'undo'; message: string; onUndo?: () => void }
|
|
27
|
+
interface ProgressToastItem {
|
|
28
|
+
id: number;
|
|
29
|
+
kind: 'progress';
|
|
30
|
+
label: string;
|
|
31
|
+
pct: number;
|
|
32
|
+
state: 'active' | 'finalizing' | 'done' | 'error';
|
|
33
|
+
onCancel?: () => void;
|
|
34
|
+
}
|
|
35
|
+
type ToastItem = BasicToast | UndoToastItem | ProgressToastItem;
|
|
36
|
+
|
|
37
|
+
let nextId = 1;
|
|
38
|
+
let toasts: ToastItem[] = [];
|
|
39
|
+
const listeners = new Set<() => void>();
|
|
40
|
+
const emit = () => listeners.forEach((fn) => fn());
|
|
41
|
+
|
|
42
|
+
function remove(id: number) {
|
|
43
|
+
if (!toasts.some((t) => t.id === id)) return;
|
|
44
|
+
toasts = toasts.filter((t) => t.id !== id);
|
|
45
|
+
emit();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Immutable in-place update — the store array is replaced so React sees it. */
|
|
49
|
+
function patch(id: number, changes: Partial<ProgressToastItem>) {
|
|
50
|
+
toasts = toasts.map((t) => (t.id === id ? ({ ...t, ...changes } as ToastItem) : t));
|
|
51
|
+
emit();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function push(level: Level, message: string) {
|
|
55
|
+
const id = nextId++;
|
|
56
|
+
const basics = toasts.filter((t) => t.kind === 'basic');
|
|
57
|
+
// Cap BASIC toasts at 5 (original behavior); persistent cards are exempt.
|
|
58
|
+
const evict = basics.length >= 5 ? basics[0]!.id : null;
|
|
59
|
+
toasts = [...toasts.filter((t) => t.id !== evict), { id, kind: 'basic', level, message }];
|
|
60
|
+
emit();
|
|
61
|
+
setTimeout(() => remove(id), 3500);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface UndoToastHandle { dismiss: () => void }
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Act-then-offer-Undo (dom.js undoToast port). The ACTION HAS ALREADY
|
|
68
|
+
* HAPPENED when this shows — clicking Undo dismisses and calls `onUndo`;
|
|
69
|
+
* otherwise the card auto-dismisses after `timeout` ms and the action
|
|
70
|
+
* stands. Returns { dismiss } for callers that resolve the ambiguity early.
|
|
71
|
+
*/
|
|
72
|
+
export function undoToast(message: string, onUndo?: () => void, opts: { timeout?: number } = {}): UndoToastHandle {
|
|
73
|
+
const id = nextId++;
|
|
74
|
+
toasts = [...toasts, { id, kind: 'undo', message, onUndo }];
|
|
75
|
+
emit();
|
|
76
|
+
const timer = setTimeout(() => remove(id), opts.timeout ?? 6000);
|
|
77
|
+
return {
|
|
78
|
+
dismiss: () => { clearTimeout(timer); remove(id); },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ProgressToastHandle {
|
|
83
|
+
/** Set the bar, clamped+rounded to 0–100. */
|
|
84
|
+
update: (pct: number) => void;
|
|
85
|
+
/** 100% + non-cancellable finalization state; the operation is not settled yet. */
|
|
86
|
+
finalizing: (message?: string) => void;
|
|
87
|
+
/** 100% + ✓, optional label swap, auto-removes after 1.4s. */
|
|
88
|
+
done: (message?: string) => void;
|
|
89
|
+
/** Error styling + ✕, optional label swap, auto-removes after 5s. */
|
|
90
|
+
fail: (message?: string) => void;
|
|
91
|
+
/** Immediate removal (e.g. after the caller aborted on cancel). */
|
|
92
|
+
remove: () => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Persistent progress card (dom.js progressToast port). Never auto-dismisses
|
|
97
|
+
* while active — the caller drives it to done()/fail()/remove(). Pass
|
|
98
|
+
* `onCancel` to render a ✕ that calls it; cancel ABORTS the operation on the
|
|
99
|
+
* caller's side and the caller then settles the toast.
|
|
100
|
+
*/
|
|
101
|
+
export function progressToast(label: string, opts: { onCancel?: () => void } = {}): ProgressToastHandle {
|
|
102
|
+
const id = nextId++;
|
|
103
|
+
toasts = [...toasts, { id, kind: 'progress', label, pct: 0, state: 'active', onCancel: opts.onCancel }];
|
|
104
|
+
emit();
|
|
105
|
+
let settled = false;
|
|
106
|
+
return {
|
|
107
|
+
update: (pct: number) => {
|
|
108
|
+
if (settled) return;
|
|
109
|
+
patch(id, { pct: Math.max(0, Math.min(100, Math.round(pct || 0))) });
|
|
110
|
+
},
|
|
111
|
+
finalizing: (message?: string) => {
|
|
112
|
+
if (settled) return;
|
|
113
|
+
patch(id, { pct: 100, state: 'finalizing', ...(message ? { label: message } : {}), onCancel: undefined });
|
|
114
|
+
},
|
|
115
|
+
done: (message?: string) => {
|
|
116
|
+
if (settled) return;
|
|
117
|
+
settled = true;
|
|
118
|
+
patch(id, { pct: 100, state: 'done', ...(message ? { label: message } : {}), onCancel: undefined });
|
|
119
|
+
setTimeout(() => remove(id), 1400);
|
|
120
|
+
},
|
|
121
|
+
fail: (message?: string) => {
|
|
122
|
+
if (settled) return;
|
|
123
|
+
settled = true;
|
|
124
|
+
patch(id, { state: 'error', ...(message ? { label: message } : {}), onCancel: undefined });
|
|
125
|
+
setTimeout(() => remove(id), 5000);
|
|
126
|
+
},
|
|
127
|
+
remove: () => { settled = true; remove(id); },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const toast = {
|
|
132
|
+
success: (m: string) => push('success', m),
|
|
133
|
+
error: (m: string) => push('error', m),
|
|
134
|
+
info: (m: string) => push('info', m),
|
|
135
|
+
/** Partial outcomes (batch "N succeeded, M failed"). */
|
|
136
|
+
warning: (m: string) => push('warning', m),
|
|
137
|
+
/** Act immediately, offer a grace-window Undo. See undoToast(). */
|
|
138
|
+
undo: undoToast,
|
|
139
|
+
/** Persistent progress card with a bar. See progressToast(). */
|
|
140
|
+
progress: progressToast,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const ICONS: Record<Level, string> = {
|
|
144
|
+
success: 'bi-check-circle-fill',
|
|
145
|
+
error: 'bi-x-circle-fill',
|
|
146
|
+
info: 'bi-info-circle-fill',
|
|
147
|
+
warning: 'bi-exclamation-triangle-fill',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
function ProgressCard({ t }: { t: ProgressToastItem }) {
|
|
151
|
+
return (
|
|
152
|
+
<div className={`toast-card toast-progress${t.state === 'finalizing' ? ' is-finalizing' : ''}${t.state === 'done' ? ' is-done' : ''}${t.state === 'error' ? ' toast-error is-failed' : ''}`}>
|
|
153
|
+
<div className="progress-head">
|
|
154
|
+
<div className="progress-name" title={t.label}>{t.label}</div>
|
|
155
|
+
{t.onCancel && t.state === 'active' && (
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
className="progress-cancel"
|
|
159
|
+
title="Cancel"
|
|
160
|
+
aria-label="Cancel"
|
|
161
|
+
onClick={() => t.onCancel?.()}
|
|
162
|
+
>
|
|
163
|
+
<i className="bi bi-x-lg" />
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
<div className="progress-row">
|
|
168
|
+
<div
|
|
169
|
+
className="progress-bar"
|
|
170
|
+
role="progressbar"
|
|
171
|
+
aria-valuenow={t.pct}
|
|
172
|
+
aria-valuemin={0}
|
|
173
|
+
aria-valuemax={100}
|
|
174
|
+
>
|
|
175
|
+
<div className="progress-fill" style={{ width: `${t.pct}%` }} />
|
|
176
|
+
</div>
|
|
177
|
+
<span className="progress-pct">
|
|
178
|
+
{t.state === 'done' ? <i className="bi bi-check-lg" /> : t.state === 'error' ? <i className="bi bi-x-lg" /> : t.state === 'finalizing' ? <i className="bi bi-arrow-repeat spin" /> : `${t.pct}%`}
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function ToastHost() {
|
|
186
|
+
const items = useSyncExternalStore(
|
|
187
|
+
(cb) => { listeners.add(cb); return () => listeners.delete(cb); },
|
|
188
|
+
() => toasts,
|
|
189
|
+
);
|
|
190
|
+
return (
|
|
191
|
+
<div className="toast-host" role="status" aria-live="polite">
|
|
192
|
+
{items.map((t) => {
|
|
193
|
+
if (t.kind === 'basic') {
|
|
194
|
+
return (
|
|
195
|
+
<div key={t.id} className={`toast-card toast-${t.level}`}>
|
|
196
|
+
<i className={`bi ${ICONS[t.level]}`} />
|
|
197
|
+
<span>{t.message}</span>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (t.kind === 'undo') {
|
|
202
|
+
return (
|
|
203
|
+
<div key={t.id} className="toast-card toast-undo">
|
|
204
|
+
<span className="undo-msg">{t.message}</span>
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
className="undo-btn"
|
|
208
|
+
onClick={() => { remove(t.id); t.onUndo?.(); }}
|
|
209
|
+
>
|
|
210
|
+
Undo
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return <ProgressCard key={t.id} t={t} />;
|
|
216
|
+
})}
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
package/src/ui/ui.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Small display primitives: Badge (the HTML-returning formatters become
|
|
2
|
+
// components), MetricCard (flat monochrome KPI), Spark (dependency-free SVG
|
|
3
|
+
// mini chart — the chartlib porting pattern in miniature).
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { inferTone, type Tone } from './format';
|
|
6
|
+
|
|
7
|
+
export function Badge({ tone, children }: { tone?: Tone; children: ReactNode }) {
|
|
8
|
+
const t = tone ?? (typeof children === 'string' || typeof children === 'boolean' ? inferTone(children) : 'muted');
|
|
9
|
+
return <span className={`chip chip-${t}`}>{children === true ? 'Yes' : children === false ? 'No' : children}</span>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function MetricCard({ label, value, hint, icon }: { label: string; value: ReactNode; hint?: string; icon?: string }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="metric">
|
|
15
|
+
<div className="metric-top">
|
|
16
|
+
<span className="metric-label">{label}</span>
|
|
17
|
+
{icon && <i className={`bi ${icon} metric-icon`} />}
|
|
18
|
+
</div>
|
|
19
|
+
<div className="metric-value">{value}</div>
|
|
20
|
+
{hint && <div className="metric-hint">{hint}</div>}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Inline SVG sparkline with area fill and emphasized endpoint. */
|
|
26
|
+
export function Spark({ data, height = 48 }: { data: number[]; height?: number }) {
|
|
27
|
+
if (data.length < 2) return null;
|
|
28
|
+
const w = 100;
|
|
29
|
+
const max = Math.max(...data, 1);
|
|
30
|
+
const min = Math.min(...data, 0);
|
|
31
|
+
const span = max - min || 1;
|
|
32
|
+
const pts = data.map((v, i) => [
|
|
33
|
+
(i / (data.length - 1)) * w,
|
|
34
|
+
height - 4 - ((v - min) / span) * (height - 10),
|
|
35
|
+
]);
|
|
36
|
+
const line = pts.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(' ');
|
|
37
|
+
// No endpoint circle: preserveAspectRatio="none" would stretch it into a
|
|
38
|
+
// blob. The stroke stays crisp via vector-effect: non-scaling-stroke.
|
|
39
|
+
return (
|
|
40
|
+
<svg viewBox={`0 0 ${w} ${height}`} className="spark" preserveAspectRatio="none" role="img" aria-label="Trend">
|
|
41
|
+
<polyline points={`0,${height} ${line} ${w},${height}`} className="spark-fill" />
|
|
42
|
+
<polyline points={line} className="spark-line" />
|
|
43
|
+
</svg>
|
|
44
|
+
);
|
|
45
|
+
}
|