@sentry/junior-dashboard 0.264.0 → 0.266.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 +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +145 -136
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/ActorAvatar.tsx +10 -22
- package/src/client/components/BottomSheet.tsx +12 -2
- package/src/client/components/ConnectionBanner.tsx +4 -3
- package/src/client/components/CopyButton.tsx +82 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/EmptyTelemetry.tsx +5 -3
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/ImageAttachment.tsx +49 -63
- package/src/client/components/InlineError.tsx +8 -4
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/Notice.tsx +19 -26
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +27 -27
- package/src/client/components/ParticipantAvatarStack.tsx +43 -39
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +103 -78
- package/src/client/components/SearchInput.tsx +51 -41
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/VersionDriftBanner.tsx +9 -7
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +59 -37
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/alert.tsx +64 -0
- package/src/client/components/ui/avatar.tsx +53 -0
- package/src/client/components/ui/button.tsx +86 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/dialog.tsx +66 -0
- package/src/client/components/ui/empty.tsx +32 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input-group.tsx +96 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/pagination.tsx +60 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +13 -3
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/spinner.tsx +21 -0
- package/src/client/components/ui/table.tsx +101 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +147 -62
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +13 -7
- package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +55 -88
- package/src/client/conversations/ConversationPage.tsx +220 -101
- package/src/client/conversations/ConversationSidebar.tsx +49 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +173 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +139 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -38
- package/src/client/conversations/README.md +89 -13
- package/src/client/conversations/ShareConversationButton.tsx +28 -17
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +34 -2
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +58 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/client/format.ts +5 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +0 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/components/ui/sheet.LICENSE +0 -21
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -8,12 +8,7 @@ import {
|
|
|
8
8
|
ShieldCheck,
|
|
9
9
|
UserRound,
|
|
10
10
|
} from "lucide-react";
|
|
11
|
-
import {
|
|
12
|
-
useEffect,
|
|
13
|
-
useRef,
|
|
14
|
-
useState,
|
|
15
|
-
type PointerEvent as ReactPointerEvent,
|
|
16
|
-
} from "react";
|
|
11
|
+
import { useState } from "react";
|
|
17
12
|
import { Link } from "react-router";
|
|
18
13
|
import type { PersonalSpendReport } from "@sentry/junior/api/schema";
|
|
19
14
|
import type { PluginUserPageLink } from "@sentry/junior-plugin-api";
|
|
@@ -23,6 +18,7 @@ import { pluginUserPagePath } from "../pages/user/PluginUserPage";
|
|
|
23
18
|
import { cn, dashboardInteractiveTextClass } from "../styles";
|
|
24
19
|
import type { Identity } from "../types";
|
|
25
20
|
import { ThemeSwitcher } from "./ThemeSwitcher";
|
|
21
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
26
22
|
|
|
27
23
|
type ProfileMenuProps = {
|
|
28
24
|
identity: Identity;
|
|
@@ -65,14 +61,6 @@ export function ProfileMenu({
|
|
|
65
61
|
variant = "popover",
|
|
66
62
|
}: ProfileMenuProps) {
|
|
67
63
|
const [open, setOpen] = useState(false);
|
|
68
|
-
const rootRef = useRef<HTMLDivElement>(null);
|
|
69
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
70
|
-
const openTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
71
|
-
undefined,
|
|
72
|
-
);
|
|
73
|
-
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
74
|
-
undefined,
|
|
75
|
-
);
|
|
76
64
|
const email = identity.user.email!;
|
|
77
65
|
const name = identity.user.name?.trim() || email;
|
|
78
66
|
const sevenDaySpend = spend
|
|
@@ -85,54 +73,6 @@ export function ProfileMenu({
|
|
|
85
73
|
(page) => page.navigation === "profile",
|
|
86
74
|
);
|
|
87
75
|
|
|
88
|
-
function clearHoverTimers() {
|
|
89
|
-
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
90
|
-
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
91
|
-
openTimerRef.current = undefined;
|
|
92
|
-
closeTimerRef.current = undefined;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function openOnHover(event: ReactPointerEvent<HTMLDivElement>) {
|
|
96
|
-
if (event.pointerType !== "mouse") return;
|
|
97
|
-
clearHoverTimers();
|
|
98
|
-
openTimerRef.current = setTimeout(() => setOpen(true), HOVER_OPEN_DELAY_MS);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function closeOnHover(event: ReactPointerEvent<HTMLDivElement>) {
|
|
102
|
-
if (event.pointerType !== "mouse") return;
|
|
103
|
-
clearHoverTimers();
|
|
104
|
-
closeTimerRef.current = setTimeout(
|
|
105
|
-
() => setOpen(false),
|
|
106
|
-
HOVER_CLOSE_DELAY_MS,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
useEffect(() => clearHoverTimers, []);
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
if (variant !== "popover" || !open) return;
|
|
114
|
-
|
|
115
|
-
function closeOnOutsideClick(event: PointerEvent) {
|
|
116
|
-
if (!rootRef.current?.contains(event.target as Node)) {
|
|
117
|
-
setOpen(false);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function closeOnEscape(event: KeyboardEvent) {
|
|
122
|
-
if (event.key === "Escape") {
|
|
123
|
-
setOpen(false);
|
|
124
|
-
triggerRef.current?.focus();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
document.addEventListener("pointerdown", closeOnOutsideClick);
|
|
129
|
-
document.addEventListener("keydown", closeOnEscape);
|
|
130
|
-
return () => {
|
|
131
|
-
document.removeEventListener("pointerdown", closeOnOutsideClick);
|
|
132
|
-
document.removeEventListener("keydown", closeOnEscape);
|
|
133
|
-
};
|
|
134
|
-
}, [open, variant]);
|
|
135
|
-
|
|
136
76
|
const avatar = (
|
|
137
77
|
<span
|
|
138
78
|
aria-hidden="true"
|
|
@@ -335,32 +275,28 @@ export function ProfileMenu({
|
|
|
335
275
|
}
|
|
336
276
|
|
|
337
277
|
return (
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
278
|
+
<Popover
|
|
279
|
+
onOpenChange={(nextOpen, details) => {
|
|
280
|
+
// A mouse click always keeps the menu open, also after hover opened it.
|
|
281
|
+
// A keyboard press (detail 0) still toggles it.
|
|
282
|
+
const mouseClick =
|
|
283
|
+
details.event instanceof MouseEvent && details.event.detail > 0;
|
|
284
|
+
if (!nextOpen && details.reason === "trigger-press" && mouseClick) {
|
|
285
|
+
return;
|
|
343
286
|
}
|
|
287
|
+
setOpen(nextOpen);
|
|
344
288
|
}}
|
|
345
|
-
|
|
346
|
-
onPointerLeave={closeOnHover}
|
|
347
|
-
ref={rootRef}
|
|
289
|
+
open={open}
|
|
348
290
|
>
|
|
349
|
-
<
|
|
350
|
-
aria-controls="profile-popover"
|
|
351
|
-
aria-expanded={open}
|
|
352
|
-
aria-haspopup="true"
|
|
291
|
+
<PopoverTrigger
|
|
353
292
|
aria-label={`${open ? "Close" : "Open"} profile menu for ${name}. Personal model spend: 7 days ${sevenDaySpend}, 30 days ${thirtyDaySpend}.`}
|
|
354
293
|
className={cn(
|
|
355
294
|
"group flex h-10 cursor-pointer items-center gap-2 rounded-lg border-0 bg-transparent px-1.5 text-dashboard-text transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus/70",
|
|
356
295
|
open && "bg-dashboard-fill-hover text-dashboard-text",
|
|
357
296
|
)}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}}
|
|
362
|
-
ref={triggerRef}
|
|
363
|
-
type="button"
|
|
297
|
+
closeDelay={HOVER_CLOSE_DELAY_MS}
|
|
298
|
+
delay={HOVER_OPEN_DELAY_MS}
|
|
299
|
+
openOnHover
|
|
364
300
|
>
|
|
365
301
|
<span className="hidden items-center gap-2 md:flex">
|
|
366
302
|
<span className="flex items-baseline gap-1 whitespace-nowrap tabular-nums">
|
|
@@ -399,18 +335,16 @@ export function ProfileMenu({
|
|
|
399
335
|
size={14}
|
|
400
336
|
strokeWidth={2}
|
|
401
337
|
/>
|
|
402
|
-
</
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
) : null}
|
|
414
|
-
</div>
|
|
338
|
+
</PopoverTrigger>
|
|
339
|
+
<PopoverContent
|
|
340
|
+
align="end"
|
|
341
|
+
className="w-64 rounded-xl border-0 bg-dashboard-surface-raised/95 p-1.5 shadow-2xl shadow-black/75 backdrop-blur-xl"
|
|
342
|
+
id="profile-popover"
|
|
343
|
+
>
|
|
344
|
+
{identityBlock}
|
|
345
|
+
<ThemeSwitcher className="border-b border-dashboard-border-strong px-2.5 py-2" />
|
|
346
|
+
{menuLinks(() => setOpen(false))}
|
|
347
|
+
</PopoverContent>
|
|
348
|
+
</Popover>
|
|
415
349
|
);
|
|
416
350
|
}
|
|
@@ -16,23 +16,76 @@ pnpm --filter @sentry/junior-dashboard exec shadcn add <component>
|
|
|
16
16
|
|
|
17
17
|
The CLI writes components to `src/client/components/ui/`. Its `@dashboard/*`
|
|
18
18
|
alias points to `src/client/*`. The existing `@/*` alias still points to core.
|
|
19
|
-
`
|
|
20
|
-
|
|
19
|
+
The `ui/` files join classes with the merging `cn` from
|
|
20
|
+
`src/client/lib/utils.ts`, so a caller's `className` wins over a base class.
|
|
21
21
|
|
|
22
22
|
The shadcn color tokens in `src/tailwind.css` use the dashboard tokens in both
|
|
23
|
-
themes.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
themes. The `dark:` variant follows `data-theme`. Keep the existing Tailwind
|
|
24
|
+
radius scale. Do not apply the CLI's global border or body resets. Check each
|
|
25
|
+
added component against the rules below before use.
|
|
26
|
+
|
|
27
|
+
Each `ui/` file is shadcn source that uses dashboard tokens. Remove exports
|
|
28
|
+
that no app code uses. Use `ui/` components directly. Do not add an app
|
|
29
|
+
component that only renames a `ui/` component or its props. Change the `ui/`
|
|
30
|
+
file when every caller needs the same style or variant. App components compose
|
|
31
|
+
`ui/` source only when they add structure or behavior:
|
|
32
|
+
|
|
33
|
+
| App component | shadcn source |
|
|
34
|
+
| ------------------------------ | ----------------- |
|
|
35
|
+
| `Drawer`, `BottomSheet` | `ui/sheet` |
|
|
36
|
+
| `layout/DashboardHeader` (nav) | `ui/sheet` |
|
|
37
|
+
| `ProfileMenu` (desktop) | `ui/popover` |
|
|
38
|
+
| `ActorAvatar` | `ui/avatar` |
|
|
39
|
+
| `ParticipantAvatarStack` | `ui/avatar` |
|
|
40
|
+
| `SearchInput` | `ui/input-group` |
|
|
41
|
+
| `PagePagination` | `ui/pagination` |
|
|
42
|
+
| `EmptyTelemetry` | `ui/empty` |
|
|
43
|
+
| `TimeRangeSelector` | `ui/toggle-group` |
|
|
44
|
+
| `ThemeSwitcher` | `ui/toggle-group` |
|
|
45
|
+
| `TranscriptViewToggle` | `ui/toggle-group` |
|
|
46
|
+
| `FilterTabList` | `ui/tabs` |
|
|
47
|
+
| `charts/ActivityChartTooltip` | `ui/tooltip` |
|
|
48
|
+
| `conversations/ToolFrame` | `ui/trace` |
|
|
49
|
+
| `TranscriptReasoningView` | `ui/trace` |
|
|
50
|
+
| `ImageAttachment` (preview) | `ui/dialog` |
|
|
51
|
+
| `Notice`, `InlineError` | `ui/alert` |
|
|
52
|
+
| App banners | `ui/alert` |
|
|
53
|
+
| Recharts activity charts | `ui/chart` |
|
|
54
|
+
|
|
55
|
+
Rules for adopted components:
|
|
56
|
+
|
|
57
|
+
- Use `ui/trace` for transcript activity rows. It composes `ui/collapsible`.
|
|
58
|
+
A closed tool row unmounts its panel, so it does not run Shiki. Reasoning
|
|
59
|
+
sets `hiddenUntilFound`, so browser find reaches closed text. Transcript
|
|
60
|
+
search holds rows open and turns off the row button. Put only inline
|
|
61
|
+
elements, such as `span`, inside the row button.
|
|
62
|
+
- Use `ui/alert-dialog` for every confirmation. Do not use `window.confirm`.
|
|
63
|
+
Keep the confirmed item in state after close, so the closing dialog keeps its
|
|
64
|
+
text.
|
|
65
|
+
- Use `useSheet` for each app `ui/dialog`, as for sheets. It shares the
|
|
66
|
+
document scroll lock. Only the front modal closes on Escape or a backdrop
|
|
67
|
+
tap. Mount the dialog only while it is open.
|
|
68
|
+
- `ui/alert` sets `role="alert"`. Pass `role="status"` for a message that is
|
|
69
|
+
not an error.
|
|
70
|
+
- `ui/toast` has no global notification manager. Features call
|
|
71
|
+
`useToastManager` inside the mounted `Toaster` provider.
|
|
72
|
+
- Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
|
|
73
|
+
`aria-label`.
|
|
74
|
+
- Give an icon-only control its label with `TooltipContent size="compact"`.
|
|
75
|
+
Do not use the native `title` attribute. Tooltips open on mouse hover and
|
|
76
|
+
keyboard focus only, so a tap still runs the control.
|
|
77
|
+
- When a `ui/button` tooltip trigger can be disabled, set
|
|
78
|
+
`focusableWhenDisabled`. A native disabled button can drop hover and focus,
|
|
79
|
+
so Firefox closes the tooltip and hides pending or result labels.
|
|
80
|
+
`ui/button` styles `data-disabled`, so both disabled modes look the same.
|
|
81
|
+
- Build a form field from `ui/field`. Give `FieldError` the id
|
|
82
|
+
`<control id>-error` and `FieldDescription` the id `<control id>-help`. The
|
|
83
|
+
control sets `aria-invalid` and `aria-describedby`.
|
|
84
|
+
- Use `Button size="menu"` for a full-width icon and label row in a mobile
|
|
85
|
+
overflow menu. A pressed row adds `bg-dashboard-fill-strong`.
|
|
86
|
+
- For a link that looks like a button, put `buttonVariants()` on the router
|
|
87
|
+
`Link`. A form page has one `Button variant="primary"` for its main save
|
|
88
|
+
action.
|
|
36
89
|
|
|
37
90
|
## Foundations
|
|
38
91
|
|
|
@@ -46,9 +99,9 @@ token so each theme can redefine it.
|
|
|
46
99
|
paint from the saved choice or the system color scheme. `ThemeSwitcher` saves
|
|
47
100
|
the choice for this browser. Use the `light:` variant only for one-off values
|
|
48
101
|
that no token covers, such as provider brand colors.
|
|
49
|
-
- **Charts.** Use `var(--color-*)` for series colors
|
|
50
|
-
|
|
51
|
-
|
|
102
|
+
- **Charts.** Use `var(--color-*)` for series colors, not hex values, so they
|
|
103
|
+
follow the theme. Name series colors in the `ChartContainer` config and fill
|
|
104
|
+
each series with `var(--color-<key>)`.
|
|
52
105
|
- **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
|
|
53
106
|
`bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
|
|
54
107
|
`surface-raised` (buttons, tooltips), `control`, `surface-hover`,
|
|
@@ -81,80 +134,54 @@ token so each theme can redefine it.
|
|
|
81
134
|
use `rounded-lg`. The composer, menus, and transcript object cards use
|
|
82
135
|
`rounded-xl`. Avatars and count badges use `rounded-full`.
|
|
83
136
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`src/client/styles.ts` holds the few class strings that many components share.
|
|
87
|
-
|
|
88
|
-
- `cn` joins class names.
|
|
89
|
-
- `dashboardContainerClass` sets the page width.
|
|
90
|
-
- `dashboardInlineLinkClass` styles a neutral underlined link inside prose.
|
|
91
|
-
- `dashboardInteractiveTextClass` styles muted text that brightens on hover.
|
|
92
|
-
- `dashboardComposerSurfaceClass` styles the composer surface.
|
|
137
|
+
Shared class strings live in `src/client/styles.ts`.
|
|
93
138
|
|
|
94
|
-
##
|
|
139
|
+
## App components
|
|
95
140
|
|
|
96
|
-
### Primitives
|
|
97
|
-
|
|
98
|
-
- **Actions.** `Button` (`default`, `primary`, `danger`, and `size="icon"`) and
|
|
99
|
-
`ButtonLink`. Form pages use `Button tone="primary"` for their one main save
|
|
100
|
-
action. Use `ToggleButton` variants (`pill`, `segment`, `text`) for pressed
|
|
101
|
-
states, not one-off styles.
|
|
102
|
-
- **Forms.** `Field` (`default`, `compact`), `TextInput`, `TextArea`, `Select`,
|
|
103
|
-
and `SearchInput`. Use `TextInput size="comfortable"` for larger mobile
|
|
104
|
-
controls. Use `TextArea prose` for instructions, not code. `Field error`
|
|
105
|
-
renders an error at `<htmlFor>-error`. The owning control must set
|
|
106
|
-
`aria-invalid` and `aria-describedby`. `Select` owns the native control and its
|
|
107
|
-
inset arrow. Use its `filter` and `sort` variants for compact controls.
|
|
108
141
|
- **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
|
|
109
|
-
when the label must stay visible. `StatusChip` is a
|
|
110
|
-
sentence-case text, with no border or fill. It is not a control.
|
|
142
|
+
when the label must stay visible. `StatusChip` is not a control.
|
|
111
143
|
- **Messages.** Use `Notice` for compact transient messages and `FormNotice`
|
|
112
144
|
for wrapping form recovery text.
|
|
113
|
-
- **
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
Item icons.
|
|
123
|
-
|
|
124
|
-
- **Loading.** Use `
|
|
145
|
+
- **Disclosure.** Use `ui/collapsible` for one show-or-hide section. Put a
|
|
146
|
+
`ChevronRight` with `dashboardDisclosureChevronClass` in a `group` trigger.
|
|
147
|
+
Set `hiddenUntilFound` on the content when browser find must reach closed
|
|
148
|
+
text. Closed content then keeps its box, so put margins and padding on a
|
|
149
|
+
child. Use `ui/accordion` for a set of sections with headings.
|
|
150
|
+
- **Details.** `DetailsPanel` docks an aside at 1280px and above, and uses a
|
|
151
|
+
`BottomSheet` below that width. It owns Escape and the scroll frame for
|
|
152
|
+
docked panels.
|
|
153
|
+
- **Identity.** `ObjectIcon` owns the native Ticket, Code change, Automation,
|
|
154
|
+
Deployment, and Item icons. Do not replace it with a provider logo or a
|
|
155
|
+
status-only icon.
|
|
156
|
+
- **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
|
|
125
157
|
variants for page bodies. Use `PageRouteLoading` when a page keeps its real
|
|
126
158
|
header above a body skeleton. Use a feature-owned loading component for
|
|
127
|
-
distinct geometry, such as conversations.
|
|
159
|
+
distinct geometry, such as conversations. Use `ui/spinner` for an inline
|
|
160
|
+
pending icon.
|
|
128
161
|
|
|
129
|
-
|
|
162
|
+
## Navigation
|
|
130
163
|
|
|
131
164
|
The dashboard has three navigation levels. Each has its own look, so the
|
|
132
165
|
levels stay distinct.
|
|
133
166
|
|
|
134
167
|
- **App navigation** in `layout/DashboardHeader` uses pills.
|
|
135
168
|
- **Page links** use `SegmentedNav`, through `layout/SecondaryNavigation`. They
|
|
136
|
-
are underline tabs that sit on the header border.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
selected value, items, change handler, and panel content. It owns tab and
|
|
140
|
-
panel labels and keyboard focus. Pass `scroll` to keep the tabs fixed while
|
|
141
|
-
the panel content scrolls. That mode fills its frame and owns the content
|
|
142
|
-
padding, so its scrollbar stays at the frame edge. `BottomSheet` content owns
|
|
143
|
-
its own padding.
|
|
169
|
+
are underline tabs that sit on the header border.
|
|
170
|
+
- **Panel switches** use `ui/tabs`. Give `TabsList` an `aria-label`. Set
|
|
171
|
+
`activateOnFocus` so arrow keys select the tab.
|
|
144
172
|
|
|
145
|
-
Keep collection filters in `FilterTabList`.
|
|
173
|
+
Keep collection filters in `FilterTabList`. It uses `TabsList variant="line"`
|
|
174
|
+
and adds the count badges.
|
|
146
175
|
|
|
147
|
-
|
|
176
|
+
## Patterns
|
|
148
177
|
|
|
149
178
|
- **Layout.** `layout/` holds the shell chrome, page headers, cards, and
|
|
150
179
|
section titles. `DashboardChrome` owns the sticky header block and its one
|
|
151
180
|
bottom border.
|
|
152
|
-
- **
|
|
153
|
-
|
|
154
|
-
`charts/ActivityChart`
|
|
155
|
-
|
|
156
|
-
day or hour charts compose those helpers. They do not format UTC bucket keys
|
|
157
|
-
inline.
|
|
181
|
+
- **Charts.** Put `role="img"` and the chart `aria-label` on the Recharts
|
|
182
|
+
chart, so the tooltip live region stays outside the image. Day and hour
|
|
183
|
+
charts compose the `charts/ActivityChart` axis, average line, and tooltip
|
|
184
|
+
helpers. They do not format UTC bucket keys inline.
|
|
158
185
|
- **Conversation mobile geometry** is a hard ownership edge:
|
|
159
186
|
- `layout/VisualViewportShell` owns the fixed shell, visualViewport CSS
|
|
160
187
|
variables, and body lock.
|
|
@@ -166,16 +193,14 @@ Keep collection filters in `FilterTabList`.
|
|
|
166
193
|
dock, and must not use `ComposerDock`. Do not add viewport height, offset, or
|
|
167
194
|
bottom safe-area math in page modules.
|
|
168
195
|
|
|
169
|
-
### Features
|
|
170
|
-
|
|
171
196
|
Feature surfaces stay under `conversations/` or `pages/` until two real callers
|
|
172
|
-
share them. Transcript
|
|
173
|
-
|
|
197
|
+
share them. See [Transcript components](../conversations/README.md) for
|
|
198
|
+
transcript ownership.
|
|
174
199
|
|
|
175
200
|
## Rules
|
|
176
201
|
|
|
177
202
|
- Prefer a named component on the second real use.
|
|
178
203
|
- Keep Tailwind classes on the owning component.
|
|
179
|
-
-
|
|
180
|
-
|
|
204
|
+
- Check new or changed shared components on a real page with mock data and in
|
|
205
|
+
the dashboard E2E screenshots.
|
|
181
206
|
- One-off page glue may stay inline when extraction only adds indirection.
|
|
@@ -2,6 +2,12 @@ import type { Ref } from "react";
|
|
|
2
2
|
import { Search, X } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../styles";
|
|
5
|
+
import {
|
|
6
|
+
InputGroup,
|
|
7
|
+
InputGroupAddon,
|
|
8
|
+
InputGroupButton,
|
|
9
|
+
InputGroupInput,
|
|
10
|
+
} from "./ui/input-group";
|
|
5
11
|
|
|
6
12
|
type SearchInputSize = "compact" | "default";
|
|
7
13
|
|
|
@@ -15,23 +21,14 @@ export function SearchInput(props: {
|
|
|
15
21
|
size?: SearchInputSize;
|
|
16
22
|
value: string;
|
|
17
23
|
}) {
|
|
18
|
-
const size = props.size ?? "compact";
|
|
24
|
+
const size = sizeClass[props.size ?? "compact"];
|
|
19
25
|
return (
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
aria-hidden="true"
|
|
23
|
-
className={cn(
|
|
24
|
-
"pointer-events-none absolute top-1/2 -translate-y-1/2 text-dashboard-text-muted",
|
|
25
|
-
searchIconClass[size],
|
|
26
|
-
)}
|
|
27
|
-
size={size === "compact" ? 13 : 14}
|
|
28
|
-
strokeWidth={2.5}
|
|
29
|
-
/>
|
|
30
|
-
<input
|
|
26
|
+
<InputGroup className={cn(size.group, props.className)}>
|
|
27
|
+
<InputGroupInput
|
|
31
28
|
aria-label={props.label}
|
|
32
29
|
className={cn(
|
|
33
|
-
"
|
|
34
|
-
|
|
30
|
+
"[&::-webkit-search-cancel-button]:appearance-none",
|
|
31
|
+
size.input,
|
|
35
32
|
)}
|
|
36
33
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
37
34
|
placeholder={props.placeholder}
|
|
@@ -39,40 +36,53 @@ export function SearchInput(props: {
|
|
|
39
36
|
type="search"
|
|
40
37
|
value={props.value}
|
|
41
38
|
/>
|
|
42
|
-
{
|
|
43
|
-
<
|
|
39
|
+
<InputGroupAddon className={size.icon}>
|
|
40
|
+
<Search aria-hidden="true" size={size.iconSize} strokeWidth={2.5} />
|
|
41
|
+
</InputGroupAddon>
|
|
42
|
+
{/* The clear button keeps its space while hidden, so the field width does not change. */}
|
|
43
|
+
<InputGroupAddon align="inline-end" className={size.clear}>
|
|
44
|
+
<InputGroupButton
|
|
44
45
|
aria-label="Clear search"
|
|
45
|
-
className={
|
|
46
|
-
"absolute top-1/2 grid size-6 -translate-y-1/2 cursor-pointer place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus",
|
|
47
|
-
clearButtonClass[size],
|
|
48
|
-
)}
|
|
46
|
+
className={props.value ? undefined : "invisible"}
|
|
49
47
|
onClick={(event) => {
|
|
50
48
|
props.onChange("");
|
|
51
|
-
// The button
|
|
52
|
-
event.currentTarget
|
|
49
|
+
// The button hides once the value is empty, so keep focus in the search box.
|
|
50
|
+
event.currentTarget
|
|
51
|
+
.closest("[data-slot=input-group]")
|
|
52
|
+
?.querySelector("input")
|
|
53
|
+
?.focus();
|
|
53
54
|
}}
|
|
54
|
-
type="button"
|
|
55
55
|
>
|
|
56
56
|
<X aria-hidden="true" size={14} strokeWidth={2.5} />
|
|
57
|
-
</
|
|
58
|
-
|
|
59
|
-
</
|
|
57
|
+
</InputGroupButton>
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
</InputGroup>
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
compact:
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
"
|
|
63
|
+
// Match the icon, text, and clear button offsets of the bordered input.
|
|
64
|
+
const sizeClass: Record<
|
|
65
|
+
SearchInputSize,
|
|
66
|
+
{
|
|
67
|
+
clear: string;
|
|
68
|
+
group: string;
|
|
69
|
+
icon: string;
|
|
70
|
+
iconSize: number;
|
|
71
|
+
input: string;
|
|
72
|
+
}
|
|
73
|
+
> = {
|
|
74
|
+
compact: {
|
|
75
|
+
clear: "pr-1.25",
|
|
76
|
+
group: "h-9",
|
|
77
|
+
icon: "pl-2.25",
|
|
78
|
+
iconSize: 13,
|
|
79
|
+
input: "pr-1 pl-2.5",
|
|
80
|
+
},
|
|
81
|
+
default: {
|
|
82
|
+
clear: "pr-1.75",
|
|
83
|
+
group: "h-10",
|
|
84
|
+
icon: "pl-2.75",
|
|
85
|
+
iconSize: 14,
|
|
86
|
+
input: "pr-1 pl-2.75",
|
|
87
|
+
},
|
|
78
88
|
};
|
|
@@ -81,7 +81,7 @@ function edgeFade(edges: HiddenEdges): CSSProperties | undefined {
|
|
|
81
81
|
/**
|
|
82
82
|
* Show links between sibling pages as underline tabs. The current page has
|
|
83
83
|
* a solid underline that sits on the border below the row. App navigation
|
|
84
|
-
* uses pills and in-place panel switches use `
|
|
84
|
+
* uses pills and in-place panel switches use `ui/tabs`, so the three
|
|
85
85
|
* levels stay distinct.
|
|
86
86
|
*
|
|
87
87
|
* With `fill`, links share the row width equally while their labels fit.
|
|
@@ -2,11 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
export const shimmerTextClassName =
|
|
7
|
-
"animate-[junior-text-shimmer_1.6s_linear_infinite] bg-[linear-gradient(90deg,#777_0%,#d6d6d6_40%,#fff_50%,#d6d6d6_60%,#777_100%)] bg-[length:200%_100%] bg-clip-text text-transparent motion-reduce:animate-none";
|
|
8
|
-
|
|
9
|
-
/** Render text with the dashboard running shimmer when `active` is true. */
|
|
5
|
+
/** Render text with the shadcn `shimmer` utility when `active` is true. */
|
|
10
6
|
export function ShimmerText(props: {
|
|
11
7
|
active?: boolean;
|
|
12
8
|
as?: "span" | "strong";
|
|
@@ -19,7 +15,7 @@ export function ShimmerText(props: {
|
|
|
19
15
|
if (!props.active && !props.className && !props["aria-label"]) {
|
|
20
16
|
return props.children;
|
|
21
17
|
}
|
|
22
|
-
const className = cn(props.active &&
|
|
18
|
+
const className = cn(props.active && "shimmer", props.className);
|
|
23
19
|
if (props.as === "strong") {
|
|
24
20
|
return (
|
|
25
21
|
<strong aria-label={props["aria-label"]} className={className}>
|
|
@@ -3,7 +3,8 @@ import { Monitor, Moon, Sun, type LucideIcon } from "lucide-react";
|
|
|
3
3
|
import type { DashboardThemeChoice } from "../../theme";
|
|
4
4
|
import { cn } from "../styles";
|
|
5
5
|
import { useDashboardThemeChoice } from "../theme";
|
|
6
|
-
import {
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
7
|
+
import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group";
|
|
7
8
|
|
|
8
9
|
const themeChoices = [
|
|
9
10
|
{ Icon: Monitor, label: "System", value: "system" },
|
|
@@ -25,24 +26,27 @@ export function ThemeSwitcher(props: { className?: string }) {
|
|
|
25
26
|
<span className="font-sans text-sm font-semibold text-dashboard-text">
|
|
26
27
|
Theme
|
|
27
28
|
</span>
|
|
28
|
-
<
|
|
29
|
+
<ToggleGroup
|
|
29
30
|
aria-label="Theme"
|
|
30
|
-
className="
|
|
31
|
-
|
|
31
|
+
className="gap-0.5 rounded-lg border border-dashboard-border p-0.5"
|
|
32
|
+
onValueChange={([next]) => {
|
|
33
|
+
if (next) setChoice(next);
|
|
34
|
+
}}
|
|
35
|
+
value={[choice]}
|
|
32
36
|
>
|
|
33
37
|
{themeChoices.map(({ Icon, label, value }) => (
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
</
|
|
38
|
+
<Tooltip key={value}>
|
|
39
|
+
<TooltipTrigger
|
|
40
|
+
render={
|
|
41
|
+
<ToggleGroupItem aria-label={`${label} theme`} value={value}>
|
|
42
|
+
<Icon aria-hidden="true" size={14} strokeWidth={2} />
|
|
43
|
+
</ToggleGroupItem>
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
<TooltipContent size="compact">{label}</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
44
48
|
))}
|
|
45
|
-
</
|
|
49
|
+
</ToggleGroup>
|
|
46
50
|
</div>
|
|
47
51
|
);
|
|
48
52
|
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { JUNIOR_VERSION } from "@sentry/junior/version";
|
|
2
2
|
|
|
3
|
+
import { Alert } from "./ui/alert";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
|
|
3
6
|
/** Prompt the viewer to load the dashboard bundle that matches the server. */
|
|
4
7
|
export function VersionDriftBanner(props: { serverVersion?: string }) {
|
|
5
8
|
if (!dashboardVersionDrift(props.serverVersion)) return null;
|
|
6
9
|
|
|
7
10
|
return (
|
|
8
|
-
<
|
|
11
|
+
<Alert
|
|
9
12
|
aria-live="polite"
|
|
10
|
-
className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-t border-dashboard-accent/15 bg-dashboard-accent/[0.07] px-3 py-1.5 text-center font-sans text-xs text-dashboard-accent-text/80"
|
|
13
|
+
className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 rounded-none border-0 border-t border-dashboard-accent/15 bg-dashboard-accent/[0.07] px-3 py-1.5 text-center font-sans text-xs text-dashboard-accent-text/80"
|
|
11
14
|
role="status"
|
|
12
15
|
>
|
|
13
16
|
<span>The dashboard changed. Refresh to load the latest version.</span>
|
|
14
|
-
<
|
|
15
|
-
className="
|
|
17
|
+
<Button
|
|
18
|
+
className="h-auto border-0 bg-transparent p-0 text-xs font-semibold leading-normal text-dashboard-accent-text underline decoration-dashboard-accent/40 underline-offset-2 hover:bg-transparent hover:text-dashboard-text-solid"
|
|
16
19
|
onClick={() => window.location.reload()}
|
|
17
|
-
type="button"
|
|
18
20
|
>
|
|
19
21
|
Refresh
|
|
20
|
-
</
|
|
21
|
-
</
|
|
22
|
+
</Button>
|
|
23
|
+
</Alert>
|
|
22
24
|
);
|
|
23
25
|
}
|
|
24
26
|
|