@sentry/junior-dashboard 0.263.0 → 0.265.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 +148 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +9 -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/BottomSheet.tsx +26 -18
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +34 -33
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +101 -64
- 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/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- 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/button.tsx +85 -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/field.tsx +49 -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/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +151 -0
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -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/components/ui/useSheet.ts +74 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +87 -58
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +198 -95
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +111 -92
- package/src/client/conversations/ConversationPage.tsx +241 -110
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +93 -19
- package/src/client/conversations/ShareConversationButton.tsx +28 -3
- 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/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 +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -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/format.ts +5 -0
- package/src/client/lib/utils.ts +1 -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 +77 -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/Sheet.LICENSE +0 -21
- package/src/client/components/Sheet.tsx +0 -141
- 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/conversations/ConversationDetailsFrame.tsx +0 -91
- 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,77 @@
|
|
|
1
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/** Group sections with Base UI disclosure and keyboard behavior. */
|
|
6
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
7
|
+
return (
|
|
8
|
+
<AccordionPrimitive.Root
|
|
9
|
+
data-slot="accordion"
|
|
10
|
+
className={cn("flex w-full flex-col", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Keep one section’s trigger and panel state together. */
|
|
17
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
18
|
+
return (
|
|
19
|
+
<AccordionPrimitive.Item
|
|
20
|
+
data-slot="accordion-item"
|
|
21
|
+
className={cn(
|
|
22
|
+
"not-last:border-b not-last:border-dashboard-border-subtle",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Render the section heading and its expand or collapse control. */
|
|
31
|
+
function AccordionTrigger({
|
|
32
|
+
className,
|
|
33
|
+
children,
|
|
34
|
+
...props
|
|
35
|
+
}: AccordionPrimitive.Trigger.Props) {
|
|
36
|
+
return (
|
|
37
|
+
<AccordionPrimitive.Header className="flex">
|
|
38
|
+
<AccordionPrimitive.Trigger
|
|
39
|
+
data-slot="accordion-trigger"
|
|
40
|
+
className={cn(
|
|
41
|
+
"group/accordion-trigger relative flex min-w-0 flex-1 cursor-pointer items-center justify-between gap-3 rounded-sm py-2.5 text-left font-sans text-sm font-medium text-dashboard-text transition-colors hover:text-dashboard-text-solid outline-none focus-visible:outline-2 focus-visible:outline-dashboard-focus aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-dashboard-text-muted",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
<ChevronDownIcon
|
|
48
|
+
data-slot="accordion-trigger-icon"
|
|
49
|
+
className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
|
|
50
|
+
/>
|
|
51
|
+
<ChevronUpIcon
|
|
52
|
+
data-slot="accordion-trigger-icon"
|
|
53
|
+
className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
|
|
54
|
+
/>
|
|
55
|
+
</AccordionPrimitive.Trigger>
|
|
56
|
+
</AccordionPrimitive.Header>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Reveal section content with measured height and reduced-motion support. */
|
|
61
|
+
function AccordionContent({
|
|
62
|
+
className,
|
|
63
|
+
children,
|
|
64
|
+
...props
|
|
65
|
+
}: AccordionPrimitive.Panel.Props) {
|
|
66
|
+
return (
|
|
67
|
+
<AccordionPrimitive.Panel
|
|
68
|
+
data-slot="accordion-content"
|
|
69
|
+
className="h-(--accordion-panel-height) overflow-hidden text-sm transition-[height] duration-180 ease-out data-ending-style:h-0 data-starting-style:h-0 motion-reduce:transition-none"
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
<div className={cn("pt-0 pb-2.5", className)}>{children}</div>
|
|
73
|
+
</AccordionPrimitive.Panel>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
|
|
6
|
+
/** Require an explicit response before a destructive action. */
|
|
7
|
+
export function AlertDialog(props: AlertDialogPrimitive.Root.Props) {
|
|
8
|
+
return <AlertDialogPrimitive.Root {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Show a named confirmation above the page with modal focus handling. */
|
|
12
|
+
export function AlertDialogContent({
|
|
13
|
+
className,
|
|
14
|
+
...props
|
|
15
|
+
}: AlertDialogPrimitive.Popup.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<AlertDialogPrimitive.Portal>
|
|
18
|
+
<AlertDialogPrimitive.Backdrop 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" />
|
|
19
|
+
<AlertDialogPrimitive.Popup
|
|
20
|
+
data-dashboard-modal=""
|
|
21
|
+
data-slot="alert-dialog-content"
|
|
22
|
+
className={cn(
|
|
23
|
+
"fixed top-1/2 left-1/2 z-50 grid grid-cols-1 max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto rounded-xl border border-dashboard-border-emphasis bg-dashboard-surface-raised p-5 text-dashboard-text shadow-lg outline-none transition-[opacity,scale] duration-180 motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:scale-95 data-starting-style:scale-95",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</AlertDialogPrimitive.Portal>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Name the action that needs confirmation. */
|
|
33
|
+
export function AlertDialogTitle({
|
|
34
|
+
className,
|
|
35
|
+
...props
|
|
36
|
+
}: AlertDialogPrimitive.Title.Props) {
|
|
37
|
+
return (
|
|
38
|
+
<AlertDialogPrimitive.Title
|
|
39
|
+
className={cn("m-0 font-display text-lg font-medium", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Describe what the action changes before the user confirms it. */
|
|
46
|
+
export function AlertDialogDescription({
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
}: AlertDialogPrimitive.Description.Props) {
|
|
50
|
+
return (
|
|
51
|
+
<AlertDialogPrimitive.Description
|
|
52
|
+
className={cn(
|
|
53
|
+
"m-0 break-words text-sm leading-relaxed text-dashboard-text-muted",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Keep confirmation controls together and reachable on small screens. */
|
|
62
|
+
export function AlertDialogFooter({
|
|
63
|
+
className,
|
|
64
|
+
...props
|
|
65
|
+
}: ComponentProps<"div">) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn("flex flex-wrap justify-end gap-2", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Run the confirmed action; its owner decides when the dialog can close. */
|
|
75
|
+
export function AlertDialogAction(props: ComponentProps<typeof Button>) {
|
|
76
|
+
return (
|
|
77
|
+
<Button data-slot="alert-dialog-action" className="min-h-11" {...props} />
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Cancel through Base UI so focus returns to the source control. */
|
|
82
|
+
export function AlertDialogCancel(props: AlertDialogPrimitive.Close.Props) {
|
|
83
|
+
return (
|
|
84
|
+
<AlertDialogPrimitive.Close
|
|
85
|
+
render={<Button className="min-h-11" />}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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, and icon 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
|
+
},
|
|
22
|
+
},
|
|
23
|
+
compoundVariants: [
|
|
24
|
+
{
|
|
25
|
+
size: "default",
|
|
26
|
+
variant: "default",
|
|
27
|
+
className: cn(
|
|
28
|
+
commandButtonClass,
|
|
29
|
+
"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",
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
size: "default",
|
|
34
|
+
variant: "primary",
|
|
35
|
+
className: cn(
|
|
36
|
+
commandButtonClass,
|
|
37
|
+
"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",
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
size: "default",
|
|
42
|
+
variant: "danger",
|
|
43
|
+
className: cn(
|
|
44
|
+
commandButtonClass,
|
|
45
|
+
"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",
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
size: "icon",
|
|
50
|
+
variant: ["default", "primary"],
|
|
51
|
+
className:
|
|
52
|
+
"hover:!bg-dashboard-fill-strong hover:!text-dashboard-text focus-visible:outline-dashboard-focus",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
size: "icon",
|
|
56
|
+
variant: "danger",
|
|
57
|
+
className:
|
|
58
|
+
"hover:!bg-rose-300/10 hover:!text-rose-300 focus-visible:outline-rose-300/55",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
defaultVariants: {
|
|
62
|
+
variant: "default",
|
|
63
|
+
size: "default",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
/** Render a Base UI button with a dashboard button surface. */
|
|
69
|
+
function Button({
|
|
70
|
+
className,
|
|
71
|
+
variant = "default",
|
|
72
|
+
size = "default",
|
|
73
|
+
...props
|
|
74
|
+
}: Omit<ButtonPrimitive.Props, "className"> &
|
|
75
|
+
VariantProps<typeof buttonVariants> & { className?: string }) {
|
|
76
|
+
return (
|
|
77
|
+
<ButtonPrimitive
|
|
78
|
+
data-slot="button"
|
|
79
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
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,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,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,67 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Own an anchored popover's state and dismissal. */
|
|
5
|
+
export function Popover(props: PopoverPrimitive.Root.Props) {
|
|
6
|
+
return <PopoverPrimitive.Root {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Open a popover from the owning control. */
|
|
10
|
+
export function PopoverTrigger(props: PopoverPrimitive.Trigger.Props) {
|
|
11
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Position popover content within the viewport and share its themed surface. */
|
|
15
|
+
export function PopoverContent({
|
|
16
|
+
className,
|
|
17
|
+
align = "center",
|
|
18
|
+
side = "bottom",
|
|
19
|
+
...props
|
|
20
|
+
}: PopoverPrimitive.Popup.Props &
|
|
21
|
+
Pick<PopoverPrimitive.Positioner.Props, "align" | "side">) {
|
|
22
|
+
return (
|
|
23
|
+
<PopoverPrimitive.Portal>
|
|
24
|
+
<PopoverPrimitive.Positioner
|
|
25
|
+
align={align}
|
|
26
|
+
side={side}
|
|
27
|
+
sideOffset={8}
|
|
28
|
+
className="isolate z-50"
|
|
29
|
+
>
|
|
30
|
+
<PopoverPrimitive.Popup
|
|
31
|
+
data-slot="popover-content"
|
|
32
|
+
className={cn(
|
|
33
|
+
"w-80 max-w-[calc(100vw-2rem)] max-h-(--available-height) origin-(--transform-origin) overflow-y-auto rounded-lg border border-dashboard-border-emphasis bg-dashboard-surface-raised p-4 text-sm text-dashboard-text shadow-lg outline-none focus-visible:outline-2 focus-visible:outline-dashboard-focus transition-[opacity,scale] duration-180 motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:scale-95 data-starting-style:scale-95",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</PopoverPrimitive.Positioner>
|
|
39
|
+
</PopoverPrimitive.Portal>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Name a popover through its visible heading. */
|
|
44
|
+
export function PopoverTitle({
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: PopoverPrimitive.Title.Props) {
|
|
48
|
+
return (
|
|
49
|
+
<PopoverPrimitive.Title
|
|
50
|
+
className={cn("m-0 text-sm font-medium", className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Explain a popover without duplicating its heading. */
|
|
57
|
+
export function PopoverDescription({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: PopoverPrimitive.Description.Props) {
|
|
61
|
+
return (
|
|
62
|
+
<PopoverPrimitive.Description
|
|
63
|
+
className={cn("m-0 leading-relaxed text-dashboard-text-muted", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
2
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
3
|
+
import { cn } from "@dashboard/lib/utils";
|
|
4
|
+
|
|
5
|
+
/** Own the selected value and arrow-key focus for a set of radios. */
|
|
6
|
+
function RadioGroup<Value>(props: RadioGroupPrimitive.Props<Value>) {
|
|
7
|
+
return <RadioGroupPrimitive data-slot="radio-group" {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Render one Base UI radio with the dashboard control surface. */
|
|
11
|
+
function RadioGroupItem({
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}: Omit<RadioPrimitive.Root.Props, "className"> & { className?: string }) {
|
|
15
|
+
return (
|
|
16
|
+
<RadioPrimitive.Root
|
|
17
|
+
data-slot="radio-group-item"
|
|
18
|
+
className={cn(
|
|
19
|
+
"grid size-4 shrink-0 cursor-pointer place-items-center rounded-full border border-dashboard-border-interactive bg-dashboard-control 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",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<RadioPrimitive.Indicator
|
|
25
|
+
data-slot="radio-group-indicator"
|
|
26
|
+
className="size-1.5 rounded-full bg-dashboard-text-inverse"
|
|
27
|
+
/>
|
|
28
|
+
</RadioPrimitive.Root>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { RadioGroup, RadioGroupItem };
|