@sentry/junior-dashboard 0.265.0 → 0.267.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 +49 -12
- package/dist/client.js +94 -63
- package/dist/index.js +49 -12
- package/dist/tailwind.css +1 -1
- package/package.json +11 -5
- package/src/app.ts +0 -2
- package/src/auth.ts +4 -7
- package/src/client/App.tsx +35 -53
- 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/DetailsPanel.tsx +12 -13
- package/src/client/components/Drawer.tsx +46 -23
- 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 +13 -10
- package/src/client/components/README.md +28 -3
- 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-group.tsx +59 -0
- package/src/client/components/ui/button.tsx +2 -1
- package/src/client/components/ui/dialog.tsx +60 -0
- package/src/client/components/ui/drawer.tsx +140 -0
- package/src/client/components/ui/empty.tsx +61 -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/separator.tsx +22 -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/components/ui/useSheet.ts +5 -1
- package/src/client/conversations/ComposerImages.tsx +66 -14
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationComposer.tsx +84 -12
- package/src/client/conversations/ConversationDetails.tsx +3 -27
- package/src/client/conversations/ConversationEventDetails.tsx +26 -10
- package/src/client/conversations/ConversationHeader.tsx +5 -4
- package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
- package/src/client/conversations/ConversationPage.tsx +156 -2
- package/src/client/conversations/ConversationSidebar.tsx +5 -4
- package/src/client/conversations/ConversationTranscript.tsx +48 -16
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +67 -59
- package/src/client/conversations/DetailsList.tsx +77 -29
- package/src/client/conversations/EventDetails.tsx +46 -19
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/ForkConversationButton.tsx +79 -73
- package/src/client/conversations/PendingMailboxStack.tsx +312 -119
- package/src/client/conversations/README.md +30 -10
- package/src/client/conversations/ShareConversationButton.tsx +3 -4
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
- package/src/client/conversations/TranscriptActivityGroup.tsx +22 -19
- package/src/client/conversations/TranscriptAttachment.tsx +39 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptFailureView.tsx +10 -13
- package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
- package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
- package/src/client/conversations/TranscriptMessageView.tsx +7 -1
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +13 -7
- package/src/client/conversations/TranscriptTurnContextView.tsx +78 -56
- package/src/client/conversations/TranscriptView.tsx +7 -3
- package/src/client/conversations/UserInputRequestCard.tsx +244 -0
- package/src/client/conversations/conversationOutbox.ts +46 -0
- package/src/client/conversations/eventLog.ts +1 -0
- package/src/client/conversations/eventTime.ts +13 -1
- package/src/client/conversations/eventTranscript.ts +12 -5
- package/src/client/conversations/queries.ts +127 -26
- package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/client/conversations/userInputRequest.ts +56 -0
- package/src/client/format.ts +10 -3
- package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
- package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
- package/src/client/pages/automations/AutomationEditor.tsx +182 -156
- package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
- package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
- package/src/client/pages/automations/AutomationOverview.tsx +238 -0
- package/src/client/pages/automations/AutomationPage.tsx +343 -0
- package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
- package/src/client/pages/automations/AutomationRow.tsx +11 -13
- package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
- package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
- package/src/client/pages/automations/AutomationStatus.tsx +164 -108
- package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
- package/src/client/pages/automations/AutomationsPage.tsx +10 -98
- package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
- package/src/client/pages/automations/README.md +56 -46
- package/src/client/types.ts +3 -0
- package/src/mock-reporting/fixtures.ts +44 -2
- package/src/mock-reporting/watches.ts +1 -1
- package/src/client/conversations/transcriptStyles.ts +0 -10
- package/src/client/pages/automations/AutomationDetails.tsx +0 -193
- package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
- package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
const alertVariants = cva(
|
|
6
|
+
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"border-dashboard-border-strong bg-dashboard-surface-raised text-dashboard-text",
|
|
12
|
+
destructive:
|
|
13
|
+
"border-rose-300/20 bg-rose-300/[0.08] text-rose-100 *:data-[slot=alert-description]:text-rose-100/70 *:[svg]:text-rose-300",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "default",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/** Show a message in a live region. Pass `role="status"` for polite updates. */
|
|
23
|
+
function Alert({
|
|
24
|
+
className,
|
|
25
|
+
variant,
|
|
26
|
+
...props
|
|
27
|
+
}: ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
data-slot="alert"
|
|
31
|
+
role="alert"
|
|
32
|
+
className={cn(alertVariants({ variant }), className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function AlertTitle({ className, ...props }: ComponentProps<"div">) {
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
data-slot="alert-title"
|
|
42
|
+
className={cn(
|
|
43
|
+
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-dashboard-text",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function AlertDescription({ className, ...props }: ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="alert-description"
|
|
55
|
+
className={cn(
|
|
56
|
+
"text-sm text-balance text-dashboard-text-muted md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-dashboard-text [&_p:not(:last-child)]:mb-4",
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Render a round avatar frame. Use `size="sm"` in dense lists. */
|
|
5
|
+
function Avatar({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: AvatarPrimitive.Root.Props & {
|
|
10
|
+
size?: "default" | "sm";
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<AvatarPrimitive.Root
|
|
14
|
+
data-slot="avatar"
|
|
15
|
+
data-size={size}
|
|
16
|
+
className={cn(
|
|
17
|
+
"group/avatar relative inline-grid size-7 shrink-0 place-items-center overflow-hidden rounded-full border-2 border-dashboard-bg bg-dashboard-focus shadow-sm select-none data-[size=sm]:size-6",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Show the avatar image after it loads. */
|
|
26
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
27
|
+
return (
|
|
28
|
+
<AvatarPrimitive.Image
|
|
29
|
+
data-slot="avatar-image"
|
|
30
|
+
className={cn("size-full object-cover", className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Show initials or a count while the image is missing, loading, or broken. */
|
|
37
|
+
function AvatarFallback({
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: AvatarPrimitive.Fallback.Props) {
|
|
41
|
+
return (
|
|
42
|
+
<AvatarPrimitive.Fallback
|
|
43
|
+
data-slot="avatar-fallback"
|
|
44
|
+
className={cn(
|
|
45
|
+
"grid size-full place-items-center font-sans text-xs leading-none font-semibold tracking-tight text-dashboard-text-inverse group-data-[size=sm]/avatar:text-2xs",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
import { Separator } from "@dashboard/components/ui/separator";
|
|
5
|
+
|
|
6
|
+
const buttonGroupVariants = cva(
|
|
7
|
+
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
orientation: {
|
|
11
|
+
horizontal:
|
|
12
|
+
"*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
|
|
13
|
+
vertical:
|
|
14
|
+
"flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
orientation: "horizontal",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Join related buttons into one control and name it for assistive technology. */
|
|
24
|
+
function ButtonGroup({
|
|
25
|
+
className,
|
|
26
|
+
orientation,
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
role="group"
|
|
32
|
+
data-slot="button-group"
|
|
33
|
+
data-orientation={orientation}
|
|
34
|
+
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Draw a divider between buttons in a group. */
|
|
41
|
+
function ButtonGroupSeparator({
|
|
42
|
+
className,
|
|
43
|
+
orientation = "vertical",
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<typeof Separator>) {
|
|
46
|
+
return (
|
|
47
|
+
<Separator
|
|
48
|
+
data-slot="button-group-separator"
|
|
49
|
+
orientation={orientation}
|
|
50
|
+
className={cn(
|
|
51
|
+
"relative self-stretch bg-dashboard-border data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { ButtonGroup, ButtonGroupSeparator };
|
|
@@ -5,7 +5,7 @@ import { cn } from "@dashboard/lib/utils";
|
|
|
5
5
|
const commandButtonClass =
|
|
6
6
|
"inline-flex h-9 max-w-full items-center justify-center gap-2 rounded-md border px-3 text-sm font-medium leading-none focus-visible:outline-2 focus-visible:outline-offset-2";
|
|
7
7
|
|
|
8
|
-
/** Style the dashboard command, primary, danger, and
|
|
8
|
+
/** Style the dashboard command, primary, danger, icon, and menu row button surfaces. Links use it with `className`. */
|
|
9
9
|
const buttonVariants = cva(
|
|
10
10
|
"cursor-pointer font-sans no-underline transition-colors data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
11
11
|
{
|
|
@@ -18,6 +18,7 @@ const buttonVariants = cva(
|
|
|
18
18
|
size: {
|
|
19
19
|
default: "",
|
|
20
20
|
icon: "grid size-9 place-items-center rounded-md border-0 bg-transparent p-0 text-dashboard-text-muted focus-visible:outline-2 focus-visible:outline-offset-1 data-disabled:hover:!bg-transparent data-disabled:hover:!text-dashboard-text-muted",
|
|
21
|
+
menu: "flex w-full 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 hover:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none data-disabled:hover:bg-transparent",
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
compoundVariants: [
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
/** Own the open state and modal focus rules for a centered dialog. */
|
|
6
|
+
function Dialog(props: DialogPrimitive.Root.Props) {
|
|
7
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Close the dialog through Base UI dismissal and focus handling. */
|
|
11
|
+
function DialogClose(props: DialogPrimitive.Close.Props) {
|
|
12
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Show a short task above the page. Small screens keep it near the top, clear of the keyboard. */
|
|
16
|
+
function DialogContent({ className, ...props }: DialogPrimitive.Popup.Props) {
|
|
17
|
+
return (
|
|
18
|
+
<DialogPrimitive.Portal data-slot="dialog-portal">
|
|
19
|
+
{/* Dim an open sheet too, so a dialog opened from it stands out. */}
|
|
20
|
+
<DialogPrimitive.Backdrop
|
|
21
|
+
forceRender
|
|
22
|
+
data-slot="dialog-overlay"
|
|
23
|
+
className="fixed inset-0 z-50 bg-dashboard-overlay transition-opacity duration-180 motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0"
|
|
24
|
+
/>
|
|
25
|
+
<DialogPrimitive.Popup
|
|
26
|
+
data-dashboard-modal=""
|
|
27
|
+
data-slot="dialog-content"
|
|
28
|
+
className={cn(
|
|
29
|
+
"fixed top-[max(1rem,env(safe-area-inset-top))] left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-md -translate-x-1/2 gap-4 overflow-y-auto rounded-xl border border-dashboard-border-emphasis bg-dashboard-surface-raised p-5 text-sm text-dashboard-text shadow-lg outline-none transition-[opacity,scale] duration-180 motion-reduce:transition-none data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 sm:top-1/2 sm:-translate-y-1/2",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
</DialogPrimitive.Portal>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Name the dialog task. */
|
|
39
|
+
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
40
|
+
return (
|
|
41
|
+
<DialogPrimitive.Title
|
|
42
|
+
data-slot="dialog-title"
|
|
43
|
+
className={cn("m-0 font-display text-lg font-medium", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Keep dialog actions together at the end of the dialog. */
|
|
50
|
+
function DialogFooter({ className, ...props }: ComponentProps<"div">) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="dialog-footer"
|
|
54
|
+
className={cn("flex flex-wrap justify-end gap-2", className)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Dialog, DialogClose, DialogContent, DialogFooter, DialogTitle };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
type DrawerContextProps = {
|
|
6
|
+
modal: DrawerPrimitive.Root.Props["modal"];
|
|
7
|
+
swipeDirection: NonNullable<DrawerPrimitive.Root.Props["swipeDirection"]>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const DrawerContext = React.createContext<DrawerContextProps | null>(null);
|
|
11
|
+
|
|
12
|
+
function useDrawer() {
|
|
13
|
+
const context = React.useContext(DrawerContext);
|
|
14
|
+
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error("useDrawer must be used within a Drawer.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return context;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Own the open state, swipe dismissal, and nesting for a shadcn drawer.
|
|
24
|
+
* A drawer rendered inside another drawer stacks in front of it.
|
|
25
|
+
*/
|
|
26
|
+
function Drawer({
|
|
27
|
+
modal = true,
|
|
28
|
+
swipeDirection = "down",
|
|
29
|
+
...props
|
|
30
|
+
}: DrawerPrimitive.Root.Props) {
|
|
31
|
+
const contextValue = React.useMemo(
|
|
32
|
+
() => ({ modal, swipeDirection }),
|
|
33
|
+
[modal, swipeDirection],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<DrawerContext.Provider value={contextValue}>
|
|
38
|
+
<DrawerPrimitive.Root
|
|
39
|
+
data-slot="drawer"
|
|
40
|
+
modal={modal}
|
|
41
|
+
swipeDirection={swipeDirection}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
</DrawerContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function DrawerPortal({ ...props }: DrawerPrimitive.Portal.Props) {
|
|
49
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Close the drawer through Base UI dismissal and focus handling. */
|
|
53
|
+
function DrawerClose({ ...props }: DrawerPrimitive.Close.Props) {
|
|
54
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function DrawerOverlay({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: DrawerPrimitive.Backdrop.Props) {
|
|
61
|
+
return (
|
|
62
|
+
<DrawerPrimitive.Backdrop
|
|
63
|
+
data-slot="drawer-overlay"
|
|
64
|
+
className={cn(
|
|
65
|
+
"fixed inset-0 z-50 min-h-dvh bg-dashboard-overlay opacity-[calc(1-var(--drawer-swipe-progress))] transition-opacity duration-450 ease-[cubic-bezier(0.32,0.72,0,1)] select-none motion-reduce:transition-none data-ending-style:pointer-events-none data-ending-style:opacity-0 data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-starting-style:opacity-0 data-swiping:duration-0 supports-[-webkit-touch-callout:none]:absolute",
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Place the drawer on its swipe edge. While a nested drawer is open, this
|
|
75
|
+
* drawer scales back, peeks out behind it, and hides its content.
|
|
76
|
+
*/
|
|
77
|
+
function DrawerContent({
|
|
78
|
+
className,
|
|
79
|
+
children,
|
|
80
|
+
...props
|
|
81
|
+
}: DrawerPrimitive.Popup.Props) {
|
|
82
|
+
const { modal, swipeDirection } = useDrawer();
|
|
83
|
+
const swipeAxis =
|
|
84
|
+
swipeDirection === "down" || swipeDirection === "up" ? "y" : "x";
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<DrawerPortal>
|
|
88
|
+
{/* App drawers trap focus without a modal root, so keep the overlay.
|
|
89
|
+
Base UI skips it for a nested drawer. */}
|
|
90
|
+
<DrawerOverlay />
|
|
91
|
+
<DrawerPrimitive.Viewport
|
|
92
|
+
data-slot="drawer-viewport"
|
|
93
|
+
data-modal={modal}
|
|
94
|
+
className="pointer-events-none fixed inset-0 z-50 select-none data-[modal=true]:pointer-events-auto"
|
|
95
|
+
>
|
|
96
|
+
<DrawerPrimitive.Popup
|
|
97
|
+
data-slot="drawer-popup"
|
|
98
|
+
data-swipe-axis={swipeAxis}
|
|
99
|
+
className={cn(
|
|
100
|
+
// Base.
|
|
101
|
+
"group/drawer-popup pointer-events-auto fixed z-50 m-(--drawer-inset,0px) flex h-(--drawer-content-height) max-h-(--drawer-content-max-height,none) min-h-0 w-(--drawer-content-width,auto) transform-[translate3d(var(--translate-x,0px),var(--translate-y,0px),0)_scale(var(--stack-scale))] flex-col border-dashboard-border-strong bg-popover text-sm text-popover-foreground transition-[transform,height,opacity,filter] duration-450 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform outline-none motion-reduce:transition-none select-none [interpolate-size:allow-keywords] data-[swipe-direction=down]:rounded-t-xl data-[swipe-direction=down]:border-t data-[swipe-direction=left]:rounded-r-xl data-[swipe-direction=left]:border-r data-[swipe-direction=right]:rounded-l-xl data-[swipe-direction=right]:border-l data-[swipe-direction=up]:rounded-b-xl data-[swipe-direction=up]:border-b",
|
|
102
|
+
// Nested.
|
|
103
|
+
"data-nested-drawer-open:overflow-hidden data-nested-drawer-open:brightness-95",
|
|
104
|
+
// Bleed.
|
|
105
|
+
"after:pointer-events-none after:absolute after:bg-(--drawer-bleed-background,var(--color-popover)) data-[swipe-axis=x]:after:inset-y-0 data-[swipe-axis=x]:after:w-(--bleed) data-[swipe-axis=y]:after:inset-x-0 data-[swipe-axis=y]:after:h-(--bleed) data-[swipe-direction=down]:after:top-full data-[swipe-direction=left]:after:right-full data-[swipe-direction=right]:after:left-full data-[swipe-direction=up]:after:bottom-full",
|
|
106
|
+
// Sizing.
|
|
107
|
+
"[--drawer-content-height:var(--drawer-height,auto)] data-[swipe-axis=x]:[--drawer-content-width:75%] data-[swipe-axis=y]:[--drawer-content-max-height:calc(100dvh-6rem)] data-[swipe-axis=y]:data-snap-points:[--drawer-content-height:100dvh] data-[swipe-axis=x]:sm:[--drawer-content-width:24rem]",
|
|
108
|
+
// Stack.
|
|
109
|
+
"[--bleed:3rem] [--peek:1rem] [--stack-height:var(--drawer-frontmost-height,var(--drawer-height,0px))] [--stack-peek-offset:max(0px,calc((var(--nested-drawers)-var(--stack-progress))*var(--peek)))] [--stack-progress:clamp(0,var(--drawer-swipe-progress),1)] [--stack-scale-base:max(0,calc(1-(var(--nested-drawers)*var(--stack-step))))] [--stack-scale:clamp(0,calc(var(--stack-scale-base)+(var(--stack-step)*var(--stack-progress))),1)] [--stack-shrink:calc(1-var(--stack-scale))] [--stack-step:0.05]",
|
|
110
|
+
// Transitions.
|
|
111
|
+
"data-ending-style:transform-(--closed-transform) data-ending-style:opacity-[0.9999] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-nested-drawer-swiping:duration-0 data-ending-style:data-nested-drawer-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-starting-style:transform-(--closed-transform) data-swiping:duration-0 data-ending-style:data-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)]",
|
|
112
|
+
// Axis: y.
|
|
113
|
+
"data-[swipe-axis=y]:inset-x-0 data-[swipe-axis=y]:data-nested-drawer-open:h-(--stack-height)",
|
|
114
|
+
// Axis: x.
|
|
115
|
+
"data-[swipe-axis=x]:inset-y-0 data-[swipe-axis=x]:flex-row",
|
|
116
|
+
// Direction: down.
|
|
117
|
+
"data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:origin-bottom data-[swipe-direction=down]:[--closed-transform:translate3d(0,calc(100%+var(--drawer-inset,0px)+2px),0)] data-[swipe-direction=down]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--stack-shrink)*var(--stack-height)))]",
|
|
118
|
+
// Direction: up.
|
|
119
|
+
"data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:origin-top data-[swipe-direction=up]:[--closed-transform:translate3d(0,calc(-100%-var(--drawer-inset,0px)-2px),0)] data-[swipe-direction=up]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)+var(--stack-peek-offset)+(var(--stack-shrink)*var(--stack-height)))]",
|
|
120
|
+
// Direction: left.
|
|
121
|
+
"data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:origin-left data-[swipe-direction=left]:[--closed-transform:translate3d(calc(-100%-var(--drawer-inset,0px)-2px),0,0)] data-[swipe-direction=left]:[--translate-x:calc(var(--drawer-swipe-movement-x)+var(--stack-peek-offset)+(var(--stack-shrink)*100%))]",
|
|
122
|
+
// Direction: right.
|
|
123
|
+
"data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:origin-right data-[swipe-direction=right]:[--closed-transform:translate3d(calc(100%+var(--drawer-inset,0px)+2px),0,0)] data-[swipe-direction=right]:[--translate-x:calc(var(--drawer-swipe-movement-x)-var(--stack-peek-offset)-(var(--stack-shrink)*100%))]",
|
|
124
|
+
className,
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
<DrawerPrimitive.Content
|
|
129
|
+
data-slot="drawer-content"
|
|
130
|
+
className="flex min-h-0 flex-1 flex-col overflow-hidden overscroll-contain rounded-[inherit] transition-opacity duration-300 ease-[cubic-bezier(0.45,1.005,0,1.005)] select-text motion-reduce:transition-none group-data-nested-drawer-open/drawer-popup:opacity-0 group-data-nested-drawer-swiping/drawer-popup:opacity-100 group-data-swiping/drawer-popup:select-none"
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</DrawerPrimitive.Content>
|
|
134
|
+
</DrawerPrimitive.Popup>
|
|
135
|
+
</DrawerPrimitive.Viewport>
|
|
136
|
+
</DrawerPortal>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { Drawer, DrawerClose, DrawerContent };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Frame an empty state inside a page or list region. */
|
|
5
|
+
function Empty({ className, ...props }: ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="empty"
|
|
9
|
+
className={cn(
|
|
10
|
+
"min-w-0 rounded-lg border border-dashboard-border-strong bg-dashboard-bg px-4 py-3",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Hold the icon that names the kind of empty state. */
|
|
19
|
+
function EmptyMedia({ className, ...props }: ComponentProps<"div">) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
data-slot="empty-media"
|
|
24
|
+
className={cn(
|
|
25
|
+
"grid size-8 shrink-0 place-items-center rounded-md bg-dashboard-fill-strong text-dashboard-text-subtle [&_svg:not([class*='size-'])]:size-4",
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Name the empty state in a few words. */
|
|
34
|
+
function EmptyTitle({ className, ...props }: ComponentProps<"div">) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
data-slot="empty-title"
|
|
38
|
+
className={cn(
|
|
39
|
+
"font-display text-base font-medium leading-tight text-dashboard-text",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Explain why the region is empty and what the user can do next. */
|
|
48
|
+
function EmptyDescription({ className, ...props }: ComponentProps<"div">) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="empty-description"
|
|
52
|
+
className={cn(
|
|
53
|
+
"text-base leading-relaxed text-dashboard-text-muted",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Empty, EmptyDescription, EmptyMedia, EmptyTitle };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@dashboard/components/ui/button";
|
|
6
|
+
import { Input } from "@dashboard/components/ui/input";
|
|
7
|
+
|
|
8
|
+
/** Frame one text input with its icons and inline buttons. */
|
|
9
|
+
function InputGroup({ className, ...props }: ComponentProps<"div">) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="input-group"
|
|
13
|
+
role="group"
|
|
14
|
+
className={cn(
|
|
15
|
+
"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-dashboard-border bg-dashboard-overlay-soft transition-colors outline-none hover:border-dashboard-border-emphasis has-[[data-slot=input-group-control]:focus-visible]:border-dashboard-focus has-[[data-slot=input-group-control]:focus-visible]:ring-1 has-[[data-slot=input-group-control]:focus-visible]:ring-dashboard-focus/25",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const inputGroupAddonVariants = cva(
|
|
24
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-dashboard-text-muted select-none",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
align: {
|
|
28
|
+
"inline-start": "order-first pl-2.5",
|
|
29
|
+
"inline-end": "order-last pr-1.5",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
align: "inline-start",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/** Place an icon or button before or after the input. A click on the addon focuses the input. */
|
|
39
|
+
function InputGroupAddon({
|
|
40
|
+
className,
|
|
41
|
+
align = "inline-start",
|
|
42
|
+
...props
|
|
43
|
+
}: ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
role="group"
|
|
47
|
+
data-slot="input-group-addon"
|
|
48
|
+
data-align={align}
|
|
49
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
50
|
+
onClick={(e) => {
|
|
51
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
|
55
|
+
}}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Render a compact icon button inside an input group. */
|
|
62
|
+
function InputGroupButton({
|
|
63
|
+
className,
|
|
64
|
+
type = "button",
|
|
65
|
+
...props
|
|
66
|
+
}: Omit<ComponentProps<typeof Button>, "size" | "type"> & {
|
|
67
|
+
type?: "button" | "submit" | "reset";
|
|
68
|
+
}) {
|
|
69
|
+
return (
|
|
70
|
+
<Button
|
|
71
|
+
type={type}
|
|
72
|
+
size="icon"
|
|
73
|
+
className={cn("size-6", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Render the text input inside an input group. */
|
|
80
|
+
function InputGroupInput({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: ComponentProps<typeof Input>) {
|
|
84
|
+
return (
|
|
85
|
+
<Input
|
|
86
|
+
data-slot="input-group-control"
|
|
87
|
+
className={cn(
|
|
88
|
+
"h-full min-w-0 flex-1 rounded-none border-0 bg-transparent px-2 focus:ring-0",
|
|
89
|
+
className,
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@dashboard/components/ui/button";
|
|
6
|
+
|
|
7
|
+
/** Mark a page control group as navigation. */
|
|
8
|
+
function Pagination({ className, ...props }: ComponentProps<"nav">) {
|
|
9
|
+
return (
|
|
10
|
+
<nav
|
|
11
|
+
role="navigation"
|
|
12
|
+
aria-label="Pagination"
|
|
13
|
+
data-slot="pagination"
|
|
14
|
+
className={cn("flex w-full justify-center", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Hold the page controls as a list. */
|
|
21
|
+
function PaginationContent({ className, ...props }: ComponentProps<"ul">) {
|
|
22
|
+
return (
|
|
23
|
+
<ul
|
|
24
|
+
data-slot="pagination-content"
|
|
25
|
+
className={cn("flex items-center gap-2", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Hold one page control. */
|
|
32
|
+
function PaginationItem({ ...props }: ComponentProps<"li">) {
|
|
33
|
+
return <li data-slot="pagination-item" {...props} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Go to the previous page. */
|
|
37
|
+
function PaginationPrevious(props: ComponentProps<typeof Button>) {
|
|
38
|
+
return (
|
|
39
|
+
<Button aria-label="Previous page" size="icon" {...props}>
|
|
40
|
+
<ChevronLeftIcon aria-hidden="true" size={16} />
|
|
41
|
+
</Button>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Go to the next page. */
|
|
46
|
+
function PaginationNext(props: ComponentProps<typeof Button>) {
|
|
47
|
+
return (
|
|
48
|
+
<Button aria-label="Next page" size="icon" {...props}>
|
|
49
|
+
<ChevronRightIcon aria-hidden="true" size={16} />
|
|
50
|
+
</Button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
Pagination,
|
|
56
|
+
PaginationContent,
|
|
57
|
+
PaginationItem,
|
|
58
|
+
PaginationNext,
|
|
59
|
+
PaginationPrevious,
|
|
60
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
function Separator({
|
|
5
|
+
className,
|
|
6
|
+
orientation = "horizontal",
|
|
7
|
+
...props
|
|
8
|
+
}: SeparatorPrimitive.Props) {
|
|
9
|
+
return (
|
|
10
|
+
<SeparatorPrimitive
|
|
11
|
+
data-slot="separator"
|
|
12
|
+
orientation={orientation}
|
|
13
|
+
className={cn(
|
|
14
|
+
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Separator };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { Loader2Icon } from "lucide-react";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
/** Render a loading spinner. It stops spinning when the user prefers reduced motion. */
|
|
6
|
+
function Spinner({ className, ...props }: ComponentProps<"svg">) {
|
|
7
|
+
return (
|
|
8
|
+
<Loader2Icon
|
|
9
|
+
data-slot="spinner"
|
|
10
|
+
role="status"
|
|
11
|
+
aria-label="Loading"
|
|
12
|
+
className={cn(
|
|
13
|
+
"size-4 animate-spin motion-reduce:animate-none",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Spinner };
|