@sentry/junior-dashboard 0.260.0 → 0.261.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 +40 -40
- package/dist/index.js +2 -2
- package/dist/tailwind.css +1 -1
- package/package.json +6 -6
- package/src/client/components/README.md +2 -1
- package/src/client/components/Select.tsx +41 -7
- package/src/client/components/controls/DirectorySortSelect.tsx +6 -4
- package/src/client/conversations/ConversationWorkspace.tsx +9 -12
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -4
- package/src/client/debouncedValue.ts +14 -0
- package/src/client/pages/automations/AutomationFilters.tsx +4 -3
- package/src/client/pages/code/RepositoryTabs.tsx +3 -2
- package/src/client/pages/dev/ComponentsPage.tsx +4 -0
- package/src/client/pages/dev/SelectFixtures.tsx +62 -0
|
@@ -1,18 +1,52 @@
|
|
|
1
1
|
import type { SelectHTMLAttributes } from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
2
3
|
import { cn } from "../styles";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const selectClassName = {
|
|
6
|
+
form: "min-h-11 rounded-md border border-dashboard-border-emphasis bg-dashboard-ink pl-3 py-2 text-base text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 sm:min-h-10 sm:text-sm",
|
|
7
|
+
filter:
|
|
8
|
+
"h-9 rounded-lg border border-dashboard-border-emphasis bg-dashboard-control pl-2 text-sm text-dashboard-text transition-colors hover:border-dashboard-border-interactive focus:border-dashboard-focus focus:outline-none focus:ring-1 focus:ring-dashboard-focus/25",
|
|
9
|
+
sort: "h-full bg-dashboard-control pl-2 font-sans text-xs text-dashboard-text-muted outline-none",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Keep native choices and position their arrow consistently across the dashboard. */
|
|
5
13
|
export function Select({
|
|
6
14
|
className,
|
|
15
|
+
variant = "form",
|
|
7
16
|
...props
|
|
8
|
-
}: SelectHTMLAttributes<HTMLSelectElement>
|
|
17
|
+
}: SelectHTMLAttributes<HTMLSelectElement> & {
|
|
18
|
+
variant?: keyof typeof selectClassName;
|
|
19
|
+
}) {
|
|
20
|
+
const showArrow = !props.multiple && (props.size ?? 1) <= 1;
|
|
21
|
+
|
|
9
22
|
return (
|
|
10
|
-
<
|
|
11
|
-
{...props}
|
|
23
|
+
<span
|
|
12
24
|
className={cn(
|
|
13
|
-
"block
|
|
14
|
-
|
|
25
|
+
"relative block w-full min-w-0",
|
|
26
|
+
variant === "sort" && "h-full",
|
|
15
27
|
)}
|
|
16
|
-
|
|
28
|
+
>
|
|
29
|
+
<select
|
|
30
|
+
{...props}
|
|
31
|
+
className={cn(
|
|
32
|
+
"block w-full min-w-0 disabled:cursor-not-allowed disabled:opacity-60 [color-scheme:var(--dashboard-color-scheme,dark)]",
|
|
33
|
+
selectClassName[variant],
|
|
34
|
+
showArrow
|
|
35
|
+
? "appearance-none pr-9 forced-colors:appearance-auto"
|
|
36
|
+
: "pr-3",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
/>
|
|
40
|
+
{showArrow ? (
|
|
41
|
+
<ChevronDown
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
className={cn(
|
|
44
|
+
"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-dashboard-text-muted forced-colors:hidden",
|
|
45
|
+
props.disabled && "opacity-60",
|
|
46
|
+
)}
|
|
47
|
+
size={16}
|
|
48
|
+
/>
|
|
49
|
+
) : null}
|
|
50
|
+
</span>
|
|
17
51
|
);
|
|
18
52
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Select } from "../Select";
|
|
2
|
+
|
|
1
3
|
export type DirectorySortOption = {
|
|
2
4
|
label: string;
|
|
3
5
|
value: string;
|
|
4
6
|
};
|
|
5
7
|
|
|
6
|
-
/**
|
|
8
|
+
/** Keep directory sorting consistent with the dashboard's shared select. */
|
|
7
9
|
export function DirectorySortSelect(props: {
|
|
8
10
|
ariaLabel: string;
|
|
9
11
|
onChange(value: string): void;
|
|
@@ -15,9 +17,9 @@ export function DirectorySortSelect(props: {
|
|
|
15
17
|
<span className="flex h-full items-center border-r border-dashboard-border-subtle px-2 font-sans font-medium text-xs text-dashboard-text-muted">
|
|
16
18
|
Sort
|
|
17
19
|
</span>
|
|
18
|
-
<
|
|
20
|
+
<Select
|
|
19
21
|
aria-label={props.ariaLabel}
|
|
20
|
-
|
|
22
|
+
variant="sort"
|
|
21
23
|
value={props.value}
|
|
22
24
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
23
25
|
>
|
|
@@ -30,7 +32,7 @@ export function DirectorySortSelect(props: {
|
|
|
30
32
|
{option.label}
|
|
31
33
|
</option>
|
|
32
34
|
))}
|
|
33
|
-
</
|
|
35
|
+
</Select>
|
|
34
36
|
</label>
|
|
35
37
|
);
|
|
36
38
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputImage } from "@sentry/junior/api/schema";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Globe2, LockKeyhole } from "lucide-react";
|
|
4
4
|
import { useNavigate, useParams } from "react-router";
|
|
5
5
|
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
} from "./queries";
|
|
18
18
|
import { conversationPath, NEW_CONVERSATION_PATH } from "./conversationRoutes";
|
|
19
19
|
import { buildConversations, getDashboardTimeZone } from "../format";
|
|
20
|
+
import { useDebouncedValue } from "../debouncedValue";
|
|
20
21
|
import type { Conversation } from "../types";
|
|
21
|
-
import { cn, dashboardContainerClass } from "../styles";
|
|
22
22
|
import { ConversationPage } from "./ConversationPage";
|
|
23
23
|
import { useConversationFinishedIndicators } from "./useConversationFinishedIndicators";
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ const CONVERSATION_PAGE_SIZE = 20;
|
|
|
28
28
|
export function ConversationWorkspace() {
|
|
29
29
|
const [page, setPage] = useState(1);
|
|
30
30
|
const [query, setQuery] = useState("");
|
|
31
|
-
const search =
|
|
31
|
+
const search = useDebouncedValue(query.trim());
|
|
32
32
|
const params = useParams();
|
|
33
33
|
const navigate = useNavigate();
|
|
34
34
|
const selectedId = params.conversationId;
|
|
@@ -90,12 +90,7 @@ export function ConversationWorkspace() {
|
|
|
90
90
|
|
|
91
91
|
if (home) {
|
|
92
92
|
return (
|
|
93
|
-
<main
|
|
94
|
-
className={cn(
|
|
95
|
-
dashboardContainerClass,
|
|
96
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
97
|
-
)}
|
|
98
|
-
>
|
|
93
|
+
<main className="h-full min-h-0 w-full overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6">
|
|
99
94
|
<div className="mx-auto grid w-full max-w-6xl gap-8">
|
|
100
95
|
{createView}
|
|
101
96
|
<section aria-label="Conversations" className="grid gap-3">
|
|
@@ -192,25 +187,27 @@ function NewConversationView(props: {
|
|
|
192
187
|
footerStart={
|
|
193
188
|
<div
|
|
194
189
|
aria-label="Conversation visibility"
|
|
195
|
-
className="inline-flex items-center gap-
|
|
190
|
+
className="inline-flex shrink-0 items-center gap-0.5 rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-0.5"
|
|
196
191
|
role="group"
|
|
197
192
|
>
|
|
198
193
|
<ToggleButton
|
|
194
|
+
className="min-w-20 gap-1.5"
|
|
199
195
|
onClick={() => setVisibility("public")}
|
|
200
196
|
pressed={isPublic}
|
|
201
197
|
type="button"
|
|
202
198
|
variant="segment"
|
|
203
199
|
>
|
|
204
|
-
<Globe2 aria-hidden="true" className="
|
|
200
|
+
<Globe2 aria-hidden="true" className="size-3" />
|
|
205
201
|
Public
|
|
206
202
|
</ToggleButton>
|
|
207
203
|
<ToggleButton
|
|
204
|
+
className="min-w-20 gap-1.5"
|
|
208
205
|
onClick={() => setVisibility("private")}
|
|
209
206
|
pressed={!isPublic}
|
|
210
207
|
type="button"
|
|
211
208
|
variant="segment"
|
|
212
209
|
>
|
|
213
|
-
<LockKeyhole aria-hidden="true" className="
|
|
210
|
+
<LockKeyhole aria-hidden="true" className="size-3" />
|
|
214
211
|
Private
|
|
215
212
|
</ToggleButton>
|
|
216
213
|
</div>
|
|
@@ -58,10 +58,7 @@ function ConversationHomeLoading() {
|
|
|
58
58
|
<main
|
|
59
59
|
aria-busy="true"
|
|
60
60
|
aria-live="polite"
|
|
61
|
-
className=
|
|
62
|
-
dashboardContainerClass,
|
|
63
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
64
|
-
)}
|
|
61
|
+
className="h-full min-h-0 w-full overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6"
|
|
65
62
|
role="status"
|
|
66
63
|
>
|
|
67
64
|
<span className="sr-only">Loading conversations</span>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return `value` once it stops changing for `delayMs`. Use it for search text
|
|
5
|
+
* that is local state, so each keystroke does not send a request.
|
|
6
|
+
*/
|
|
7
|
+
export function useDebouncedValue<T>(value: T, delayMs = 250): T {
|
|
8
|
+
const [debounced, setDebounced] = useState(value);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const timeout = window.setTimeout(() => setDebounced(value), delayMs);
|
|
11
|
+
return () => window.clearTimeout(timeout);
|
|
12
|
+
}, [delayMs, value]);
|
|
13
|
+
return debounced;
|
|
14
|
+
}
|
|
@@ -9,6 +9,7 @@ import { Button, ToggleButton } from "../../components/Button";
|
|
|
9
9
|
import { DirectorySortSelect } from "../../components/controls/DirectorySortSelect";
|
|
10
10
|
import { FilterGroup } from "../../components/FilterBar";
|
|
11
11
|
import { SearchInput } from "../../components/SearchInput";
|
|
12
|
+
import { Select } from "../../components/Select";
|
|
12
13
|
|
|
13
14
|
/** Keep collection controls visible while a filtered page loads. */
|
|
14
15
|
export function AutomationFilters(props: {
|
|
@@ -157,9 +158,9 @@ function AutomationFilterSelect(props: {
|
|
|
157
158
|
}) {
|
|
158
159
|
return (
|
|
159
160
|
<FilterGroup label={props.label}>
|
|
160
|
-
<
|
|
161
|
+
<Select
|
|
161
162
|
aria-label={props.ariaLabel ?? `Filter by ${props.label.toLowerCase()}`}
|
|
162
|
-
|
|
163
|
+
variant="filter"
|
|
163
164
|
value={props.value}
|
|
164
165
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
165
166
|
>
|
|
@@ -173,7 +174,7 @@ function AutomationFilterSelect(props: {
|
|
|
173
174
|
{option.label}
|
|
174
175
|
</option>
|
|
175
176
|
))}
|
|
176
|
-
</
|
|
177
|
+
</Select>
|
|
177
178
|
</FilterGroup>
|
|
178
179
|
);
|
|
179
180
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import type { CodeRepositoryReport } from "@sentry/junior/api/schema";
|
|
3
3
|
|
|
4
4
|
import { useCodeRepositoryConversationsData } from "../../api";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { Card } from "../../components/layout/Card";
|
|
18
18
|
import { ConversationHomeList } from "../../conversations/ConversationHomeList";
|
|
19
19
|
import { buildConversations, getDashboardTimeZone } from "../../format";
|
|
20
|
+
import { useDebouncedValue } from "../../debouncedValue";
|
|
20
21
|
import { CodeActivityChart } from "./CodeActivityChart";
|
|
21
22
|
import { CodeChangeRow } from "./CodeChangeRow";
|
|
22
23
|
import { repositoryActivity } from "./RepositorySections";
|
|
@@ -28,7 +29,7 @@ const noFinishedConversations = new Set<string>();
|
|
|
28
29
|
export function RepositoryConversationsTab(props: { repositoryId: string }) {
|
|
29
30
|
const [query, setQuery] = useState("");
|
|
30
31
|
const [page, setPage] = useState(1);
|
|
31
|
-
const search =
|
|
32
|
+
const search = useDebouncedValue(query.trim());
|
|
32
33
|
const feed = useCodeRepositoryConversationsData(props.repositoryId, search);
|
|
33
34
|
const conversations = buildConversations(feed.data?.conversations ?? []);
|
|
34
35
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FormNotice } from "../../components/FormNotice";
|
|
2
2
|
import { SearchInput } from "../../components/SearchInput";
|
|
3
3
|
import { Select } from "../../components/Select";
|
|
4
|
+
import { SelectFixtures } from "./SelectFixtures";
|
|
4
5
|
import { ObjectFixtures } from "./ObjectFixtures";
|
|
5
6
|
import { ChartFixtures } from "./ChartFixtures";
|
|
6
7
|
import { SegmentedNavFixture, SegmentedTabsFixture } from "./SegmentedFixtures";
|
|
@@ -229,6 +230,9 @@ function FoundationsGalleryPage() {
|
|
|
229
230
|
Your edits are still here. Review the latest version before saving.
|
|
230
231
|
</FormNotice>
|
|
231
232
|
</Fixture>
|
|
233
|
+
<Fixture title="Select controls">
|
|
234
|
+
<SelectFixtures />
|
|
235
|
+
</Fixture>
|
|
232
236
|
<Fixture title="Conversation navigation">
|
|
233
237
|
<DashboardChromeProvider>
|
|
234
238
|
<DashboardHeader
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { DirectorySortSelect } from "../../components/controls/DirectorySortSelect";
|
|
3
|
+
import { Field } from "../../components/Field";
|
|
4
|
+
import { FilterGroup } from "../../components/FilterBar";
|
|
5
|
+
import { Select } from "../../components/Select";
|
|
6
|
+
|
|
7
|
+
/** Show shared select arrows at form, filter, and directory sizes. */
|
|
8
|
+
export function SelectFixtures() {
|
|
9
|
+
const [sort, setSort] = useState("recent");
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="grid min-w-0 gap-4">
|
|
13
|
+
<Field label="Schedule" htmlFor="gallery-select-form">
|
|
14
|
+
<Select id="gallery-select-form" defaultValue="weekly">
|
|
15
|
+
<option value="daily">Every day</option>
|
|
16
|
+
<option value="weekly">Every week</option>
|
|
17
|
+
</Select>
|
|
18
|
+
</Field>
|
|
19
|
+
<FilterGroup label="State">
|
|
20
|
+
<Select aria-label="State" variant="filter">
|
|
21
|
+
<option>All states</option>
|
|
22
|
+
<option>Active</option>
|
|
23
|
+
<option>Paused</option>
|
|
24
|
+
</Select>
|
|
25
|
+
</FilterGroup>
|
|
26
|
+
<DirectorySortSelect
|
|
27
|
+
ariaLabel="Gallery sort"
|
|
28
|
+
onChange={setSort}
|
|
29
|
+
options={[
|
|
30
|
+
{ label: "Recently created", value: "recent" },
|
|
31
|
+
{ label: "Oldest created", value: "oldest" },
|
|
32
|
+
]}
|
|
33
|
+
value={sort}
|
|
34
|
+
/>
|
|
35
|
+
<FilterGroup className="w-36" label="Opened">
|
|
36
|
+
<Select aria-label="Opened">
|
|
37
|
+
<option>Last 90 days</option>
|
|
38
|
+
<option>Any time</option>
|
|
39
|
+
</Select>
|
|
40
|
+
</FilterGroup>
|
|
41
|
+
<Field label="Disabled" htmlFor="gallery-select-disabled">
|
|
42
|
+
<Select id="gallery-select-disabled" disabled>
|
|
43
|
+
<option>Every week</option>
|
|
44
|
+
</Select>
|
|
45
|
+
</Field>
|
|
46
|
+
<Field
|
|
47
|
+
label="Destination"
|
|
48
|
+
htmlFor="gallery-select-invalid"
|
|
49
|
+
error="Choose a destination."
|
|
50
|
+
>
|
|
51
|
+
<Select
|
|
52
|
+
id="gallery-select-invalid"
|
|
53
|
+
aria-invalid
|
|
54
|
+
aria-describedby="gallery-select-invalid-error"
|
|
55
|
+
>
|
|
56
|
+
<option value="">Choose a destination</option>
|
|
57
|
+
<option value="conversation">This Conversation</option>
|
|
58
|
+
</Select>
|
|
59
|
+
</Field>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|