@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
|
@@ -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,86 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
const commandButtonClass =
|
|
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
|
+
|
|
8
|
+
/** Style the dashboard command, primary, danger, icon, and menu row button surfaces. Links use it with `className`. */
|
|
9
|
+
const buttonVariants = cva(
|
|
10
|
+
"cursor-pointer font-sans no-underline transition-colors data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: "",
|
|
15
|
+
primary: "",
|
|
16
|
+
danger: "",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "",
|
|
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",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
compoundVariants: [
|
|
25
|
+
{
|
|
26
|
+
size: "default",
|
|
27
|
+
variant: "default",
|
|
28
|
+
className: cn(
|
|
29
|
+
commandButtonClass,
|
|
30
|
+
"border-dashboard-border-emphasis bg-dashboard-surface-raised text-dashboard-text hover:border-dashboard-border-heavy hover:bg-dashboard-surface-hover hover:text-dashboard-text focus-visible:outline-dashboard-focus data-disabled:hover:border-dashboard-border-emphasis data-disabled:hover:bg-dashboard-surface-raised data-disabled:hover:text-dashboard-text",
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
size: "default",
|
|
35
|
+
variant: "primary",
|
|
36
|
+
className: cn(
|
|
37
|
+
commandButtonClass,
|
|
38
|
+
"border-dashboard-text-solid bg-dashboard-text-solid text-dashboard-text-inverse hover:border-dashboard-text-subtle hover:bg-dashboard-text-subtle focus-visible:outline-dashboard-focus data-disabled:hover:border-dashboard-text-solid data-disabled:hover:bg-dashboard-text-solid",
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
size: "default",
|
|
43
|
+
variant: "danger",
|
|
44
|
+
className: cn(
|
|
45
|
+
commandButtonClass,
|
|
46
|
+
"border-dashboard-border-strong bg-transparent text-dashboard-text-muted hover:border-rose-300/40 hover:text-rose-300 focus-visible:outline-rose-300/55 data-disabled:hover:border-dashboard-border-strong data-disabled:hover:text-dashboard-text-muted",
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
size: "icon",
|
|
51
|
+
variant: ["default", "primary"],
|
|
52
|
+
className:
|
|
53
|
+
"hover:!bg-dashboard-fill-strong hover:!text-dashboard-text focus-visible:outline-dashboard-focus",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
size: "icon",
|
|
57
|
+
variant: "danger",
|
|
58
|
+
className:
|
|
59
|
+
"hover:!bg-rose-300/10 hover:!text-rose-300 focus-visible:outline-rose-300/55",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
defaultVariants: {
|
|
63
|
+
variant: "default",
|
|
64
|
+
size: "default",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/** Render a Base UI button with a dashboard button surface. */
|
|
70
|
+
function Button({
|
|
71
|
+
className,
|
|
72
|
+
variant = "default",
|
|
73
|
+
size = "default",
|
|
74
|
+
...props
|
|
75
|
+
}: Omit<ButtonPrimitive.Props, "className"> &
|
|
76
|
+
VariantProps<typeof buttonVariants> & { className?: string }) {
|
|
77
|
+
return (
|
|
78
|
+
<ButtonPrimitive
|
|
79
|
+
data-slot="button"
|
|
80
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as RechartsPrimitive from "recharts";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
const INITIAL_DIMENSION = { width: 320, height: 200 } as const;
|
|
6
|
+
|
|
7
|
+
type ChartConfig = Record<
|
|
8
|
+
string,
|
|
9
|
+
{
|
|
10
|
+
label?: React.ReactNode;
|
|
11
|
+
color?: string;
|
|
12
|
+
}
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Size a Recharts chart to its box and expose each config color as
|
|
17
|
+
* `var(--color-<key>)` inside it.
|
|
18
|
+
*/
|
|
19
|
+
function ChartContainer({
|
|
20
|
+
id,
|
|
21
|
+
className,
|
|
22
|
+
children,
|
|
23
|
+
config,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"div"> & {
|
|
26
|
+
config: ChartConfig;
|
|
27
|
+
children: React.ComponentProps<
|
|
28
|
+
typeof RechartsPrimitive.ResponsiveContainer
|
|
29
|
+
>["children"];
|
|
30
|
+
}) {
|
|
31
|
+
const uniqueId = React.useId();
|
|
32
|
+
const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
data-slot="chart"
|
|
37
|
+
data-chart={chartId}
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex aspect-video justify-center font-sans text-2xs tabular-nums [&_.recharts-cartesian-axis-tick-value]:fill-dashboard-text-muted [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-dashboard-border-subtle [&_.recharts-curve.recharts-tooltip-cursor]:stroke-dashboard-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-dashboard-fill-soft [&_.recharts-reference-line_[stroke='#ccc']]:stroke-dashboard-border [&_.recharts-surface]:outline-hidden [&_.recharts-surface]:focus-visible:outline-2 [&_.recharts-surface]:focus-visible:outline-dashboard-focus",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<ChartStyle id={chartId} config={config} />
|
|
45
|
+
<RechartsPrimitive.ResponsiveContainer
|
|
46
|
+
initialDimension={INITIAL_DIMENSION}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
55
|
+
const colors = Object.entries(config).flatMap(([key, { color }]) =>
|
|
56
|
+
color ? [` --color-${key}: ${color};`] : [],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!colors.length) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<style
|
|
65
|
+
dangerouslySetInnerHTML={{
|
|
66
|
+
__html: `[data-chart=${id}] {\n${colors.join("\n")}\n}`,
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const ChartTooltip = RechartsPrimitive.Tooltip;
|
|
73
|
+
|
|
74
|
+
export { ChartContainer, ChartTooltip };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
import { CheckIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/** Render a Base UI checkbox with the dashboard control surface. */
|
|
6
|
+
function Checkbox({
|
|
7
|
+
className,
|
|
8
|
+
...props
|
|
9
|
+
}: Omit<CheckboxPrimitive.Root.Props, "className"> & { className?: string }) {
|
|
10
|
+
return (
|
|
11
|
+
<CheckboxPrimitive.Root
|
|
12
|
+
data-slot="checkbox"
|
|
13
|
+
className={cn(
|
|
14
|
+
"grid size-4 shrink-0 cursor-pointer place-items-center rounded-sm border border-dashboard-border-interactive bg-dashboard-control text-dashboard-text-inverse transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus data-checked:border-dashboard-focus data-checked:bg-dashboard-focus data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<CheckboxPrimitive.Indicator data-slot="checkbox-indicator">
|
|
20
|
+
<CheckIcon aria-hidden="true" size={12} strokeWidth={3} />
|
|
21
|
+
</CheckboxPrimitive.Indicator>
|
|
22
|
+
</CheckboxPrimitive.Root>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Checkbox };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
|
|
2
|
+
|
|
3
|
+
/** Own open state for one trigger and its panel. */
|
|
4
|
+
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
|
5
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Toggle the panel and expose its state with `aria-expanded`. */
|
|
9
|
+
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Render the panel only while open, unless `keepMounted` is set. */
|
|
16
|
+
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
|
|
17
|
+
return (
|
|
18
|
+
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Own the open state and modal focus rules for a dialog. */
|
|
5
|
+
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
|
6
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
|
10
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Close the dialog through Base UI dismissal and focus handling. */
|
|
14
|
+
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
15
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function DialogOverlay({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: DialogPrimitive.Backdrop.Props) {
|
|
22
|
+
return (
|
|
23
|
+
<DialogPrimitive.Backdrop
|
|
24
|
+
data-slot="dialog-overlay"
|
|
25
|
+
className={cn(
|
|
26
|
+
"fixed inset-0 isolate z-50 bg-dashboard-overlay transition-opacity duration-180 motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Show the dialog above a backdrop, centered in the viewport. */
|
|
35
|
+
function DialogContent({ className, ...props }: DialogPrimitive.Popup.Props) {
|
|
36
|
+
return (
|
|
37
|
+
<DialogPortal>
|
|
38
|
+
<DialogOverlay />
|
|
39
|
+
<DialogPrimitive.Popup
|
|
40
|
+
data-slot="dialog-content"
|
|
41
|
+
data-dashboard-modal=""
|
|
42
|
+
className={cn(
|
|
43
|
+
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl border border-dashboard-border-emphasis bg-dashboard-surface-raised p-4 text-sm text-dashboard-text shadow-lg outline-none transition-[opacity,scale] duration-180 motion-reduce:transition-none sm:max-w-sm data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:scale-95 data-starting-style:scale-95",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</DialogPortal>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Give the dialog its accessible name. */
|
|
53
|
+
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
54
|
+
return (
|
|
55
|
+
<DialogPrimitive.Title
|
|
56
|
+
data-slot="dialog-title"
|
|
57
|
+
className={cn(
|
|
58
|
+
"m-0 text-base leading-none font-medium text-dashboard-text",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Dialog, DialogClose, DialogContent, DialogTitle };
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/** Explain why the region is empty and what the user can do next. */
|
|
19
|
+
function EmptyDescription({ className, ...props }: ComponentProps<"div">) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="empty-description"
|
|
23
|
+
className={cn(
|
|
24
|
+
"text-base leading-relaxed text-dashboard-text-muted",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { Empty, EmptyDescription };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Stack a label, control, and messages for one form field. */
|
|
5
|
+
function Field({ className, ...props }: ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div data-slot="field" className={cn("grid gap-2", className)} {...props} />
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Label the field control. */
|
|
12
|
+
function FieldLabel({ className, ...props }: ComponentProps<"label">) {
|
|
13
|
+
return (
|
|
14
|
+
<label
|
|
15
|
+
data-slot="field-label"
|
|
16
|
+
className={cn("text-sm font-semibold text-dashboard-text", className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Give help text below the field control. */
|
|
23
|
+
function FieldDescription({ className, ...props }: ComponentProps<"p">) {
|
|
24
|
+
return (
|
|
25
|
+
<p
|
|
26
|
+
data-slot="field-description"
|
|
27
|
+
className={cn(
|
|
28
|
+
"m-0 text-xs leading-relaxed text-dashboard-text-muted sm:text-sm",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Announce a field validation error. Render nothing when there is no error. */
|
|
37
|
+
function FieldError({ className, ...props }: ComponentProps<"p">) {
|
|
38
|
+
if (!props.children) return null;
|
|
39
|
+
return (
|
|
40
|
+
<p
|
|
41
|
+
role="alert"
|
|
42
|
+
data-slot="field-error"
|
|
43
|
+
className={cn("m-0 text-xs leading-relaxed text-rose-300", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { Field, FieldDescription, FieldError, FieldLabel };
|
|
@@ -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,35 @@
|
|
|
1
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
/** Style the dashboard text control surface shared by inputs and text areas. */
|
|
6
|
+
export const textControlClass =
|
|
7
|
+
"block w-full rounded-md border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 font-sans text-sm text-dashboard-text placeholder:text-dashboard-text-muted/70 focus:border-dashboard-focus focus:outline-none focus:ring-1 focus:ring-dashboard-focus/25 aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-50";
|
|
8
|
+
|
|
9
|
+
const inputVariants = cva(textControlClass, {
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
default: "",
|
|
13
|
+
comfortable: "min-h-11 text-base sm:min-h-10 sm:text-sm",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: { size: "default" },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** Render a Base UI text input. Use `size="comfortable"` for larger mobile controls. */
|
|
20
|
+
function Input({
|
|
21
|
+
className,
|
|
22
|
+
size,
|
|
23
|
+
...props
|
|
24
|
+
}: Omit<InputPrimitive.Props, "className" | "size"> &
|
|
25
|
+
VariantProps<typeof inputVariants> & { className?: string }) {
|
|
26
|
+
return (
|
|
27
|
+
<InputPrimitive
|
|
28
|
+
data-slot="input"
|
|
29
|
+
className={cn(inputVariants({ size }), className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Input };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
4
|
+
import { cn } from "@dashboard/lib/utils";
|
|
5
|
+
|
|
6
|
+
const nativeSelectVariants = cva(
|
|
7
|
+
"block w-full min-w-0 appearance-none pr-9 disabled:cursor-not-allowed disabled:opacity-60 forced-colors:appearance-auto [color-scheme:var(--dashboard-color-scheme,dark)]",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
/** Form field. Touch screens get the taller target. */
|
|
12
|
+
form: "min-h-11 rounded-md border border-dashboard-border-emphasis bg-dashboard-ink py-2 pl-3 text-base text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 sm:min-h-10 sm:text-sm",
|
|
13
|
+
/** Compact control in a filter bar. */
|
|
14
|
+
filter:
|
|
15
|
+
"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",
|
|
16
|
+
/** Borderless sort control that fills its frame. */
|
|
17
|
+
sort: "h-full bg-dashboard-control pl-2 font-sans text-xs text-dashboard-text-muted outline-none",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { variant: "form" },
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
/** Keep native choices and draw the dashboard arrow. */
|
|
25
|
+
function NativeSelect({
|
|
26
|
+
className,
|
|
27
|
+
variant,
|
|
28
|
+
...props
|
|
29
|
+
}: Omit<ComponentProps<"select">, "size"> &
|
|
30
|
+
VariantProps<typeof nativeSelectVariants>) {
|
|
31
|
+
return (
|
|
32
|
+
<span
|
|
33
|
+
data-slot="native-select-wrapper"
|
|
34
|
+
className={cn(
|
|
35
|
+
"relative block w-full min-w-0",
|
|
36
|
+
variant === "sort" && "h-full",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
<select
|
|
41
|
+
data-slot="native-select"
|
|
42
|
+
className={nativeSelectVariants({ variant })}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
<ChevronDownIcon
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
data-slot="native-select-icon"
|
|
48
|
+
className={cn(
|
|
49
|
+
"pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-dashboard-text-muted forced-colors:hidden",
|
|
50
|
+
props.disabled && "opacity-60",
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
</span>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { NativeSelect };
|
|
@@ -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
|
+
};
|