@sentry/junior-dashboard 0.265.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/dist/app.js +2 -2
- package/dist/client.js +48 -48
- package/dist/index.js +2 -2
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
- package/src/client/components/ActorAvatar.tsx +10 -22
- package/src/client/components/ConnectionBanner.tsx +4 -3
- package/src/client/components/CopyButton.tsx +3 -4
- package/src/client/components/EmptyTelemetry.tsx +5 -3
- package/src/client/components/ImageAttachment.tsx +49 -63
- package/src/client/components/InlineError.tsx +8 -4
- package/src/client/components/Notice.tsx +19 -26
- package/src/client/components/Pagination.tsx +26 -26
- package/src/client/components/ParticipantAvatarStack.tsx +10 -10
- package/src/client/components/README.md +17 -1
- package/src/client/components/SearchInput.tsx +51 -41
- package/src/client/components/VersionDriftBanner.tsx +9 -7
- package/src/client/components/charts/CacheWrites.tsx +52 -43
- 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 +2 -1
- package/src/client/components/ui/dialog.tsx +66 -0
- package/src/client/components/ui/empty.tsx +32 -0
- package/src/client/components/ui/input-group.tsx +96 -0
- package/src/client/components/ui/pagination.tsx +60 -0
- package/src/client/components/ui/spinner.tsx +21 -0
- package/src/client/components/ui/table.tsx +22 -1
- package/src/client/components/ui/tooltip.tsx +1 -1
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationHeader.tsx +5 -4
- package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
- package/src/client/conversations/ConversationSidebar.tsx +5 -4
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +67 -59
- package/src/client/conversations/DetailsList.tsx +44 -29
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/PendingMailboxStack.tsx +5 -10
- package/src/client/conversations/README.md +3 -0
- package/src/client/conversations/ShareConversationButton.tsx +3 -4
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +34 -2
- package/src/client/conversations/TranscriptTurnContextView.tsx +45 -38
- package/src/client/conversations/eventLog.ts +1 -0
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/mock-reporting/watches.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.266.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"recharts": "3.8.0",
|
|
38
38
|
"shiki": "4.1.0",
|
|
39
39
|
"zod": "^4.5.4",
|
|
40
|
-
"@sentry/junior": "0.
|
|
41
|
-
"@sentry/junior-plugin-api": "0.
|
|
40
|
+
"@sentry/junior": "0.266.0",
|
|
41
|
+
"@sentry/junior-plugin-api": "0.266.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@oxlint/plugins": "1.79.0",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cn } from "../styles";
|
|
1
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
3
2
|
|
|
4
3
|
/** Show an actor's image, with initials when the image is missing or fails. */
|
|
5
4
|
export function ActorAvatar(props: {
|
|
@@ -7,7 +6,6 @@ export function ActorAvatar(props: {
|
|
|
7
6
|
imageUrl?: string;
|
|
8
7
|
size: "detail" | "list";
|
|
9
8
|
}) {
|
|
10
|
-
const [failedUrl, setFailedUrl] = useState<string>();
|
|
11
9
|
const words = props.name.trim().split(/\s+/).filter(Boolean);
|
|
12
10
|
// The compact size fits one initial at the 12px type floor.
|
|
13
11
|
const initials =
|
|
@@ -17,24 +15,14 @@ export function ActorAvatar(props: {
|
|
|
17
15
|
? `${words[0]![0]}${words.at(-1)![0]}`
|
|
18
16
|
: props.name.slice(0, 2);
|
|
19
17
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{initials.toUpperCase()}
|
|
28
|
-
|
|
29
|
-
<img
|
|
30
|
-
alt=""
|
|
31
|
-
className="absolute inset-0 size-full object-cover"
|
|
32
|
-
loading="lazy"
|
|
33
|
-
onError={() => setFailedUrl(props.imageUrl)}
|
|
34
|
-
referrerPolicy="no-referrer"
|
|
35
|
-
src={props.imageUrl}
|
|
36
|
-
/>
|
|
37
|
-
) : null}
|
|
38
|
-
</span>
|
|
18
|
+
<Avatar aria-hidden="true" size={props.size === "list" ? "sm" : "default"}>
|
|
19
|
+
<AvatarImage
|
|
20
|
+
alt=""
|
|
21
|
+
loading="lazy"
|
|
22
|
+
referrerPolicy="no-referrer"
|
|
23
|
+
src={props.imageUrl}
|
|
24
|
+
/>
|
|
25
|
+
<AvatarFallback>{initials.toUpperCase()}</AvatarFallback>
|
|
26
|
+
</Avatar>
|
|
39
27
|
);
|
|
40
28
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useDashboardOnline } from "../connection";
|
|
2
|
+
import { Alert } from "./ui/alert";
|
|
2
3
|
|
|
3
4
|
/** Explain that cached data remains visible while network reads are paused. */
|
|
4
5
|
export function ConnectionBanner() {
|
|
@@ -6,12 +7,12 @@ export function ConnectionBanner() {
|
|
|
6
7
|
if (online) return null;
|
|
7
8
|
|
|
8
9
|
return (
|
|
9
|
-
<
|
|
10
|
+
<Alert
|
|
10
11
|
aria-live="polite"
|
|
11
|
-
className="border-t border-amber-300/15 bg-amber-300/[0.07] px-3 py-1.5 text-center font-sans text-xs text-amber-100/80"
|
|
12
|
+
className="block rounded-none border-0 border-t border-amber-300/15 bg-amber-300/[0.07] px-3 py-1.5 text-center font-sans text-xs text-amber-100/80"
|
|
12
13
|
role="status"
|
|
13
14
|
>
|
|
14
15
|
You’re offline. Drafts stay on this device.
|
|
15
|
-
</
|
|
16
|
+
</Alert>
|
|
16
17
|
);
|
|
17
18
|
}
|
|
@@ -44,16 +44,15 @@ export function CopyButton(props: {
|
|
|
44
44
|
|
|
45
45
|
if (props.layout === "menu") {
|
|
46
46
|
return (
|
|
47
|
-
<
|
|
47
|
+
<Button
|
|
48
48
|
aria-label={label}
|
|
49
|
-
className="flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
50
49
|
disabled={!props.getText || status === "copying"}
|
|
51
50
|
onClick={() => void copyText()}
|
|
52
|
-
|
|
51
|
+
size="menu"
|
|
53
52
|
>
|
|
54
53
|
<Icon aria-hidden="true" size={16} strokeWidth={2} />
|
|
55
54
|
<span>{label}</span>
|
|
56
|
-
</
|
|
55
|
+
</Button>
|
|
57
56
|
);
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
import { Empty, EmptyDescription } from "./ui/empty";
|
|
4
|
+
|
|
3
5
|
/** Render the dashboard empty-state block with quiet warning context. */
|
|
4
6
|
export function EmptyTelemetry(props: { children: ReactNode }) {
|
|
5
7
|
return (
|
|
6
|
-
<
|
|
7
|
-
{props.children}
|
|
8
|
-
</
|
|
8
|
+
<Empty>
|
|
9
|
+
<EmptyDescription>{props.children}</EmptyDescription>
|
|
10
|
+
</Empty>
|
|
9
11
|
);
|
|
10
12
|
}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { ExternalLink, X } from "lucide-react";
|
|
2
2
|
import {
|
|
3
3
|
type MouseEvent,
|
|
4
|
-
useEffect,
|
|
5
|
-
useId,
|
|
6
|
-
useRef,
|
|
7
4
|
useState,
|
|
8
5
|
type ComponentPropsWithoutRef,
|
|
9
6
|
} from "react";
|
|
10
7
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "../bodyScrollLock";
|
|
8
|
+
import { Button } from "./ui/button";
|
|
9
|
+
import { Dialog, DialogClose, DialogContent, DialogTitle } from "./ui/dialog";
|
|
10
|
+
import { useSheet } from "./ui/useSheet";
|
|
15
11
|
|
|
16
12
|
/** Return true when an image link click should open the inline preview. */
|
|
17
13
|
export function shouldPreviewImageAttachment(
|
|
@@ -45,9 +41,6 @@ export function ImageAttachment(
|
|
|
45
41
|
triggerClassName?: string;
|
|
46
42
|
} & Omit<ComponentPropsWithoutRef<"img">, "alt" | "className" | "src">,
|
|
47
43
|
) {
|
|
48
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
49
|
-
const bodyScrollLockedRef = useRef(false);
|
|
50
|
-
const titleId = useId();
|
|
51
44
|
const [open, setOpen] = useState(false);
|
|
52
45
|
const {
|
|
53
46
|
context,
|
|
@@ -58,20 +51,6 @@ export function ImageAttachment(
|
|
|
58
51
|
...imageProps
|
|
59
52
|
} = props;
|
|
60
53
|
|
|
61
|
-
const unlockBodyScroll = () => {
|
|
62
|
-
if (!bodyScrollLockedRef.current) return;
|
|
63
|
-
releaseBodyScrollLock();
|
|
64
|
-
bodyScrollLockedRef.current = false;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const close = () => {
|
|
68
|
-
dialogRef.current?.close();
|
|
69
|
-
setOpen(false);
|
|
70
|
-
unlockBodyScroll();
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
useEffect(() => () => unlockBodyScroll(), []);
|
|
74
|
-
|
|
75
54
|
return (
|
|
76
55
|
<>
|
|
77
56
|
<a
|
|
@@ -80,12 +59,7 @@ export function ImageAttachment(
|
|
|
80
59
|
onClick={(event) => {
|
|
81
60
|
if (!shouldPreviewImageAttachment(event)) return;
|
|
82
61
|
event.preventDefault();
|
|
83
|
-
const dialog = dialogRef.current;
|
|
84
|
-
if (!dialog || dialog.open) return;
|
|
85
|
-
acquireBodyScrollLock();
|
|
86
|
-
bodyScrollLockedRef.current = true;
|
|
87
62
|
setOpen(true);
|
|
88
|
-
dialog.showModal();
|
|
89
63
|
}}
|
|
90
64
|
rel="noreferrer"
|
|
91
65
|
target="_blank"
|
|
@@ -97,62 +71,74 @@ export function ImageAttachment(
|
|
|
97
71
|
src={src}
|
|
98
72
|
/>
|
|
99
73
|
</a>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
74
|
+
{open ? (
|
|
75
|
+
<ImagePreview
|
|
76
|
+
context={context}
|
|
77
|
+
filename={filename}
|
|
78
|
+
onClose={() => setOpen(false)}
|
|
79
|
+
src={src}
|
|
80
|
+
/>
|
|
81
|
+
) : null}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Show one image above the page. `useSheet` shares the document scroll lock
|
|
88
|
+
* with sheets and keeps Escape and backdrop dismissal on the front modal.
|
|
89
|
+
*/
|
|
90
|
+
function ImagePreview(props: {
|
|
91
|
+
context?: string;
|
|
92
|
+
filename: string;
|
|
93
|
+
onClose(): void;
|
|
94
|
+
src: string;
|
|
95
|
+
}) {
|
|
96
|
+
const sheet = useSheet(props.onClose);
|
|
97
|
+
return (
|
|
98
|
+
<Dialog {...sheet.rootProps}>
|
|
99
|
+
<DialogContent
|
|
100
|
+
{...sheet.contentProps}
|
|
101
|
+
className="top-0 left-0 h-dvh w-screen max-w-none translate-x-0 translate-y-0 gap-0 overflow-hidden rounded-none border-0 bg-dashboard-ink p-0 shadow-none sm:max-w-none md:top-1/2 md:left-1/2 md:h-[min(90dvh,960px)] md:w-[min(92vw,1440px)] md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:border"
|
|
111
102
|
>
|
|
112
103
|
<div className="grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] bg-dashboard-bg-elevated">
|
|
113
104
|
<header className="grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-1.5 border-b border-dashboard-border-strong bg-dashboard-surface-raised px-3 py-2 pt-[max(0.5rem,env(safe-area-inset-top))] md:px-4">
|
|
114
105
|
<div className="min-w-0">
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{filename}
|
|
120
|
-
</div>
|
|
121
|
-
{context ? (
|
|
106
|
+
<DialogTitle className="truncate font-mono text-xs leading-normal font-normal">
|
|
107
|
+
{props.filename}
|
|
108
|
+
</DialogTitle>
|
|
109
|
+
{props.context ? (
|
|
122
110
|
<div className="truncate font-mono text-2xs text-dashboard-text-muted">
|
|
123
|
-
{context}
|
|
111
|
+
{props.context}
|
|
124
112
|
</div>
|
|
125
113
|
) : null}
|
|
126
114
|
</div>
|
|
127
115
|
<a
|
|
128
116
|
className="inline-flex min-h-10 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-dashboard-text-muted no-underline transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text"
|
|
129
|
-
href={src}
|
|
117
|
+
href={props.src}
|
|
130
118
|
rel="noreferrer"
|
|
131
119
|
target="_blank"
|
|
132
120
|
>
|
|
133
121
|
<ExternalLink aria-hidden="true" size={14} />
|
|
134
122
|
<span className="max-sm:sr-only">Open raw file</span>
|
|
135
123
|
</a>
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
<DialogClose
|
|
125
|
+
render={
|
|
126
|
+
<Button className="h-10 gap-1.5 border-0 bg-transparent px-2.5 text-xs text-dashboard-text-muted hover:bg-dashboard-fill-hover hover:text-dashboard-text" />
|
|
127
|
+
}
|
|
140
128
|
>
|
|
141
129
|
<X aria-hidden="true" size={16} />
|
|
142
130
|
<span>Close</span>
|
|
143
|
-
</
|
|
131
|
+
</DialogClose>
|
|
144
132
|
</header>
|
|
145
133
|
<div className="grid min-h-0 place-items-center overflow-auto p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] md:p-6">
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
/>
|
|
152
|
-
) : null}
|
|
134
|
+
<img
|
|
135
|
+
alt={props.filename}
|
|
136
|
+
className="max-h-full max-w-full object-contain"
|
|
137
|
+
src={props.src}
|
|
138
|
+
/>
|
|
153
139
|
</div>
|
|
154
140
|
</div>
|
|
155
|
-
</
|
|
156
|
-
|
|
141
|
+
</DialogContent>
|
|
142
|
+
</Dialog>
|
|
157
143
|
);
|
|
158
144
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
|
+
import { Alert } from "./ui/alert";
|
|
4
5
|
|
|
5
6
|
/** Render a compact error message with consistent alert semantics. */
|
|
6
7
|
export function InlineError(props: {
|
|
@@ -8,11 +9,14 @@ export function InlineError(props: {
|
|
|
8
9
|
className?: string;
|
|
9
10
|
}) {
|
|
10
11
|
return (
|
|
11
|
-
<
|
|
12
|
-
className={cn(
|
|
13
|
-
|
|
12
|
+
<Alert
|
|
13
|
+
className={cn(
|
|
14
|
+
"block rounded-none border-0 bg-transparent p-0 text-rose-300",
|
|
15
|
+
props.className,
|
|
16
|
+
)}
|
|
17
|
+
variant="destructive"
|
|
14
18
|
>
|
|
15
19
|
{props.children}
|
|
16
|
-
</
|
|
20
|
+
</Alert>
|
|
17
21
|
);
|
|
18
22
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
2
|
import type { LucideIcon } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../styles";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
|
6
|
+
import { Button } from "./ui/button";
|
|
5
7
|
|
|
6
8
|
type NoticeTone = "default" | "error";
|
|
7
9
|
|
|
@@ -14,7 +16,7 @@ type NoticeProps = {
|
|
|
14
16
|
tone?: NoticeTone;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
type NoticeActionProps =
|
|
19
|
+
type NoticeActionProps = ComponentProps<typeof Button> & {
|
|
18
20
|
tone?: NoticeTone;
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -29,11 +31,10 @@ export function Notice({
|
|
|
29
31
|
}: NoticeProps) {
|
|
30
32
|
const isError = tone === "error";
|
|
31
33
|
return (
|
|
32
|
-
<
|
|
33
|
-
className=
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)}
|
|
34
|
+
<Alert
|
|
35
|
+
className="block gap-0 overflow-hidden rounded-xl p-0 shadow-[0_16px_40px_rgba(0,0,0,0.5)]"
|
|
36
|
+
role={isError ? "alert" : "status"}
|
|
37
|
+
variant={isError ? "destructive" : "default"}
|
|
37
38
|
>
|
|
38
39
|
<div className="flex min-w-0 items-center gap-3 px-3 py-3">
|
|
39
40
|
{Icon ? (
|
|
@@ -48,31 +49,26 @@ export function Notice({
|
|
|
48
49
|
<Icon aria-hidden="true" size={16} />
|
|
49
50
|
</div>
|
|
50
51
|
) : null}
|
|
51
|
-
<div className="min-w-0 flex-1"
|
|
52
|
-
<
|
|
53
|
-
className={cn(
|
|
54
|
-
"font-display text-sm font-medium leading-snug",
|
|
55
|
-
isError ? "text-rose-50" : "text-dashboard-text",
|
|
56
|
-
)}
|
|
57
|
-
>
|
|
52
|
+
<div className="min-w-0 flex-1">
|
|
53
|
+
<AlertTitle className="font-display text-sm leading-snug">
|
|
58
54
|
{title}
|
|
59
55
|
{detail ? " " : null}
|
|
60
|
-
</
|
|
56
|
+
</AlertTitle>
|
|
61
57
|
{detail ? (
|
|
62
|
-
<
|
|
58
|
+
<AlertDescription
|
|
63
59
|
className={cn(
|
|
64
60
|
"mt-0.5 truncate font-sans text-xs",
|
|
65
|
-
isError
|
|
61
|
+
isError && "text-rose-100/70",
|
|
66
62
|
)}
|
|
67
63
|
>
|
|
68
64
|
{detail}
|
|
69
|
-
</
|
|
65
|
+
</AlertDescription>
|
|
70
66
|
) : null}
|
|
71
67
|
</div>
|
|
72
68
|
{action}
|
|
73
69
|
</div>
|
|
74
70
|
{children}
|
|
75
|
-
</
|
|
71
|
+
</Alert>
|
|
76
72
|
);
|
|
77
73
|
}
|
|
78
74
|
|
|
@@ -80,22 +76,19 @@ export function Notice({
|
|
|
80
76
|
export function NoticeAction({
|
|
81
77
|
className,
|
|
82
78
|
tone = "default",
|
|
83
|
-
type = "button",
|
|
84
79
|
...props
|
|
85
80
|
}: NoticeActionProps) {
|
|
86
81
|
const isError = tone === "error";
|
|
87
82
|
return (
|
|
88
|
-
<
|
|
83
|
+
<Button
|
|
89
84
|
{...props}
|
|
90
85
|
className={cn(
|
|
91
|
-
"shrink-0
|
|
92
|
-
isError ? "focus:ring-rose-300/40" : "focus:ring-dashboard-focus/35",
|
|
86
|
+
"h-auto shrink-0 rounded-lg border-0 px-3 py-1.5 text-xs leading-normal",
|
|
93
87
|
isError
|
|
94
|
-
? "
|
|
95
|
-
: "
|
|
88
|
+
? "bg-rose-200/15 text-rose-50 hover:bg-rose-200/25 hover:text-rose-50 focus-visible:outline-rose-300/55 data-disabled:hover:bg-rose-200/15 data-disabled:hover:text-rose-50"
|
|
89
|
+
: "bg-dashboard-accent/10 text-dashboard-accent-text hover:bg-dashboard-accent/20 hover:text-dashboard-accent-text data-disabled:hover:bg-dashboard-accent/10 data-disabled:hover:text-dashboard-accent-text",
|
|
96
90
|
className,
|
|
97
91
|
)}
|
|
98
|
-
type={type}
|
|
99
92
|
/>
|
|
100
93
|
);
|
|
101
94
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
2
|
-
|
|
3
1
|
import { cn } from "../styles";
|
|
4
2
|
import { Button } from "./ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Pagination,
|
|
5
|
+
PaginationContent,
|
|
6
|
+
PaginationItem,
|
|
7
|
+
PaginationNext,
|
|
8
|
+
PaginationPrevious,
|
|
9
|
+
} from "./ui/pagination";
|
|
5
10
|
|
|
6
11
|
/** Render a centered load-more control for cursor or progressive lists. */
|
|
7
12
|
export function LoadMorePagination(props: {
|
|
@@ -54,10 +59,9 @@ export function PagePagination(props: {
|
|
|
54
59
|
const end = Math.min(props.total, current * props.pageSize);
|
|
55
60
|
|
|
56
61
|
return (
|
|
57
|
-
<
|
|
58
|
-
aria-label="Pagination"
|
|
62
|
+
<Pagination
|
|
59
63
|
className={cn(
|
|
60
|
-
"flex
|
|
64
|
+
"flex-wrap items-center justify-between gap-3",
|
|
61
65
|
props.className,
|
|
62
66
|
)}
|
|
63
67
|
>
|
|
@@ -65,27 +69,23 @@ export function PagePagination(props: {
|
|
|
65
69
|
Showing {start.toLocaleString("en-US")}-{end.toLocaleString("en-US")} of{" "}
|
|
66
70
|
{props.total.toLocaleString("en-US")}
|
|
67
71
|
</p>
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
>
|
|
75
|
-
|
|
76
|
-
</Button>
|
|
77
|
-
<span className="min-w-16 text-center font-sans tabular-nums text-xs text-dashboard-text-muted">
|
|
72
|
+
<PaginationContent>
|
|
73
|
+
<PaginationItem>
|
|
74
|
+
<PaginationPrevious
|
|
75
|
+
disabled={current <= 1}
|
|
76
|
+
onClick={() => props.onPageChange(current - 1)}
|
|
77
|
+
/>
|
|
78
|
+
</PaginationItem>
|
|
79
|
+
<PaginationItem className="min-w-16 text-center font-sans tabular-nums text-xs text-dashboard-text-muted">
|
|
78
80
|
{current} / {props.pageCount}
|
|
79
|
-
</
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
81
|
+
</PaginationItem>
|
|
82
|
+
<PaginationItem>
|
|
83
|
+
<PaginationNext
|
|
84
|
+
disabled={current >= props.pageCount}
|
|
85
|
+
onClick={() => props.onPageChange(current + 1)}
|
|
86
|
+
/>
|
|
87
|
+
</PaginationItem>
|
|
88
|
+
</PaginationContent>
|
|
89
|
+
</Pagination>
|
|
90
90
|
);
|
|
91
91
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ActorIdentity } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
3
|
import { actorLabel } from "../format";
|
|
4
|
-
import { cn } from "../styles";
|
|
5
4
|
import type { Conversation } from "../types";
|
|
6
5
|
import { ActorAvatar } from "./ActorAvatar";
|
|
6
|
+
import { Avatar, AvatarFallback } from "./ui/avatar";
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
8
8
|
|
|
9
9
|
const MAX_VISIBLE_PARTICIPANTS = 3;
|
|
@@ -51,7 +51,7 @@ function ParticipantTooltipContent(props: { participant: ActorIdentity }) {
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function
|
|
54
|
+
function ParticipantAvatar(props: {
|
|
55
55
|
participant: ActorIdentity;
|
|
56
56
|
size: ParticipantAvatarStackProps["size"];
|
|
57
57
|
}) {
|
|
@@ -101,7 +101,7 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
101
101
|
role="group"
|
|
102
102
|
>
|
|
103
103
|
{visible.map((participant, index) => (
|
|
104
|
-
<
|
|
104
|
+
<ParticipantAvatar
|
|
105
105
|
key={participantKey(participant, index)}
|
|
106
106
|
participant={participant}
|
|
107
107
|
size={props.size}
|
|
@@ -111,19 +111,19 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
111
111
|
<Tooltip>
|
|
112
112
|
<TooltipTrigger
|
|
113
113
|
render={
|
|
114
|
-
<
|
|
114
|
+
<Avatar
|
|
115
115
|
aria-label={`${hidden.length} more participants: ${hidden
|
|
116
116
|
.map(participantDescription)
|
|
117
117
|
.join("; ")}`}
|
|
118
|
-
className=
|
|
119
|
-
"relative -ml-1 inline-grid shrink-0 pointer-events-none [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto place-items-center rounded-full border-2 border-dashboard-bg bg-dashboard-surface-active font-sans font-semibold tabular-nums leading-none text-dashboard-text-muted focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus",
|
|
120
|
-
props.size === "list" ? "size-6 text-2xs" : "size-7 text-xs",
|
|
121
|
-
)}
|
|
118
|
+
className="-ml-1 pointer-events-none bg-dashboard-surface-active shadow-none focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto"
|
|
122
119
|
onKeyDown={(event) => event.stopPropagation()}
|
|
120
|
+
size={props.size === "list" ? "sm" : "default"}
|
|
123
121
|
tabIndex={0}
|
|
124
122
|
>
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
<AvatarFallback className="tabular-nums tracking-normal text-dashboard-text-muted">
|
|
124
|
+
+{hidden.length}
|
|
125
|
+
</AvatarFallback>
|
|
126
|
+
</Avatar>
|
|
127
127
|
}
|
|
128
128
|
/>
|
|
129
129
|
<TooltipContent>
|
|
@@ -35,6 +35,11 @@ file when every caller needs the same style or variant. App components compose
|
|
|
35
35
|
| `Drawer`, `BottomSheet` | `ui/sheet` |
|
|
36
36
|
| `layout/DashboardHeader` (nav) | `ui/sheet` |
|
|
37
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` |
|
|
38
43
|
| `TimeRangeSelector` | `ui/toggle-group` |
|
|
39
44
|
| `ThemeSwitcher` | `ui/toggle-group` |
|
|
40
45
|
| `TranscriptViewToggle` | `ui/toggle-group` |
|
|
@@ -42,6 +47,9 @@ file when every caller needs the same style or variant. App components compose
|
|
|
42
47
|
| `charts/ActivityChartTooltip` | `ui/tooltip` |
|
|
43
48
|
| `conversations/ToolFrame` | `ui/trace` |
|
|
44
49
|
| `TranscriptReasoningView` | `ui/trace` |
|
|
50
|
+
| `ImageAttachment` (preview) | `ui/dialog` |
|
|
51
|
+
| `Notice`, `InlineError` | `ui/alert` |
|
|
52
|
+
| App banners | `ui/alert` |
|
|
45
53
|
| Recharts activity charts | `ui/chart` |
|
|
46
54
|
|
|
47
55
|
Rules for adopted components:
|
|
@@ -54,6 +62,11 @@ Rules for adopted components:
|
|
|
54
62
|
- Use `ui/alert-dialog` for every confirmation. Do not use `window.confirm`.
|
|
55
63
|
Keep the confirmed item in state after close, so the closing dialog keeps its
|
|
56
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.
|
|
57
70
|
- `ui/toast` has no global notification manager. Features call
|
|
58
71
|
`useToastManager` inside the mounted `Toaster` provider.
|
|
59
72
|
- Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
|
|
@@ -68,6 +81,8 @@ Rules for adopted components:
|
|
|
68
81
|
- Build a form field from `ui/field`. Give `FieldError` the id
|
|
69
82
|
`<control id>-error` and `FieldDescription` the id `<control id>-help`. The
|
|
70
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`.
|
|
71
86
|
- For a link that looks like a button, put `buttonVariants()` on the router
|
|
72
87
|
`Link`. A form page has one `Button variant="primary"` for its main save
|
|
73
88
|
action.
|
|
@@ -141,7 +156,8 @@ Shared class strings live in `src/client/styles.ts`.
|
|
|
141
156
|
- **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
|
|
142
157
|
variants for page bodies. Use `PageRouteLoading` when a page keeps its real
|
|
143
158
|
header above a body skeleton. Use a feature-owned loading component for
|
|
144
|
-
distinct geometry, such as conversations.
|
|
159
|
+
distinct geometry, such as conversations. Use `ui/spinner` for an inline
|
|
160
|
+
pending icon.
|
|
145
161
|
|
|
146
162
|
## Navigation
|
|
147
163
|
|