@cosmicdrift/kumiko-renderer-web 0.133.0 → 0.135.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/package.json +4 -4
- package/src/__tests__/dashboard-screen.test.tsx +207 -1
- package/src/__tests__/workspace-shell.test.tsx +5 -37
- package/src/app/dashboard-body.tsx +273 -30
- package/src/index.ts +12 -0
- package/src/widgets/__tests__/form-kit.test.tsx +103 -0
- package/src/widgets/detail-list.tsx +19 -4
- package/src/widgets/feed-list.tsx +35 -0
- package/src/widgets/form-fields.tsx +66 -0
- package/src/widgets/index.ts +5 -0
- package/src/widgets/progress-list.tsx +38 -0
- package/src/widgets/result-panel.tsx +101 -0
- package/src/widgets/use-draft.ts +35 -0
- package/src/ui/card.tsx +0 -93
- package/src/ui/collapsible.tsx +0 -34
- package/src/ui/dropdown-menu.tsx +0 -258
- package/src/ui/select.tsx +0 -191
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { ProgressBar } from "./progress-bar";
|
|
3
|
+
|
|
4
|
+
export type ProgressListRow = {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly value: string;
|
|
8
|
+
readonly fraction: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Liste aus Label/Wert-Kopfzeile + Fortschrittsbalken pro Eintrag (z.B.
|
|
12
|
+
* Tilgungsfortschritt pro Kredit). */
|
|
13
|
+
export function ProgressList({
|
|
14
|
+
rows,
|
|
15
|
+
emptyContent,
|
|
16
|
+
testId,
|
|
17
|
+
}: {
|
|
18
|
+
readonly rows: readonly ProgressListRow[];
|
|
19
|
+
readonly emptyContent?: ReactNode;
|
|
20
|
+
readonly testId?: string;
|
|
21
|
+
}): ReactNode {
|
|
22
|
+
if (rows.length === 0) {
|
|
23
|
+
return <div data-testid={testId}>{emptyContent}</div>;
|
|
24
|
+
}
|
|
25
|
+
return (
|
|
26
|
+
<ul data-testid={testId} className="flex max-h-64 flex-col gap-3 overflow-y-auto pr-1">
|
|
27
|
+
{rows.map((row) => (
|
|
28
|
+
<li key={row.id} className="flex flex-col gap-1">
|
|
29
|
+
<div className="flex items-baseline justify-between gap-2 text-sm">
|
|
30
|
+
<span className="font-medium">{row.label}</span>
|
|
31
|
+
<span className="tabular-nums text-muted-foreground">{row.value}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<ProgressBar value={row.fraction} />
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { DetailList } from "./detail-list";
|
|
5
|
+
import { SectionCard } from "./section-card";
|
|
6
|
+
|
|
7
|
+
/** Ergebnis-Sektion eines Rechners: SectionCard mit Empty-Zustand (Banner)
|
|
8
|
+
* oder Kennzahl-Liste (DetailList) + optionalen Extras (Tabelle, Hinweise).
|
|
9
|
+
* Ersetzt die handgebauten `<dl>`/Banner-Blöcke in Custom-Screens. */
|
|
10
|
+
export function ResultPanel({
|
|
11
|
+
title,
|
|
12
|
+
subtitle,
|
|
13
|
+
empty,
|
|
14
|
+
emptyText,
|
|
15
|
+
rows,
|
|
16
|
+
children,
|
|
17
|
+
testId,
|
|
18
|
+
}: {
|
|
19
|
+
readonly title: string;
|
|
20
|
+
readonly subtitle?: string;
|
|
21
|
+
readonly empty?: boolean;
|
|
22
|
+
readonly emptyText?: ReactNode;
|
|
23
|
+
readonly rows?: readonly {
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly value: ReactNode;
|
|
26
|
+
readonly emphasize?: boolean;
|
|
27
|
+
}[];
|
|
28
|
+
readonly children?: ReactNode;
|
|
29
|
+
readonly testId?: string;
|
|
30
|
+
}): ReactNode {
|
|
31
|
+
const { Banner } = usePrimitives();
|
|
32
|
+
return (
|
|
33
|
+
<SectionCard title={title} subtitle={subtitle} testId={testId}>
|
|
34
|
+
{empty === true ? (
|
|
35
|
+
<Banner variant="info" padded>
|
|
36
|
+
{emptyText}
|
|
37
|
+
</Banner>
|
|
38
|
+
) : (
|
|
39
|
+
<div className="flex flex-col gap-4">
|
|
40
|
+
{rows !== undefined && rows.length > 0 && <DetailList rows={rows} />}
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</SectionCard>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ResultColumn<Row> {
|
|
49
|
+
readonly header: string;
|
|
50
|
+
readonly align?: "left" | "right";
|
|
51
|
+
readonly cell: (row: Row) => ReactNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Statische, prop-getriebene Ergebnistabelle für berechnete Zeilen (Tranchen,
|
|
55
|
+
* Szenarien). Nimmt dem Screen die `<table>`+tabular-nums-Ketten ab.
|
|
56
|
+
* ponytail: bewusst die minimale Read-only-Tabelle — für Sort/Pager/Facets
|
|
57
|
+
* stattdessen usePrimitives().DataTable bzw. QueryTable nutzen. */
|
|
58
|
+
export function ResultTable<Row>({
|
|
59
|
+
columns,
|
|
60
|
+
rows,
|
|
61
|
+
rowKey,
|
|
62
|
+
testId,
|
|
63
|
+
}: {
|
|
64
|
+
readonly columns: readonly ResultColumn<Row>[];
|
|
65
|
+
readonly rows: readonly Row[];
|
|
66
|
+
readonly rowKey: (row: Row, index: number) => string;
|
|
67
|
+
readonly testId?: string;
|
|
68
|
+
}): ReactNode {
|
|
69
|
+
return (
|
|
70
|
+
<div className="overflow-x-auto">
|
|
71
|
+
<table data-testid={testId} className="w-full text-sm">
|
|
72
|
+
<thead>
|
|
73
|
+
<tr className="border-b text-left text-muted-foreground">
|
|
74
|
+
{columns.map((col) => (
|
|
75
|
+
<th
|
|
76
|
+
key={col.header}
|
|
77
|
+
className={cn("py-1.5 font-medium", col.align === "right" && "text-right")}
|
|
78
|
+
>
|
|
79
|
+
{col.header}
|
|
80
|
+
</th>
|
|
81
|
+
))}
|
|
82
|
+
</tr>
|
|
83
|
+
</thead>
|
|
84
|
+
<tbody>
|
|
85
|
+
{rows.map((row, i) => (
|
|
86
|
+
<tr key={rowKey(row, i)} className="border-b last:border-0">
|
|
87
|
+
{columns.map((col) => (
|
|
88
|
+
<td
|
|
89
|
+
key={col.header}
|
|
90
|
+
className={cn("py-1.5", col.align === "right" && "text-right tabular-nums")}
|
|
91
|
+
>
|
|
92
|
+
{col.cell(row)}
|
|
93
|
+
</td>
|
|
94
|
+
))}
|
|
95
|
+
</tr>
|
|
96
|
+
))}
|
|
97
|
+
</tbody>
|
|
98
|
+
</table>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/** Formular-State für Rechner-Screens: ein Draft-Objekt + `patch` für
|
|
4
|
+
* Teil-Updates + `field(name)` das die `{ id, name, value, onChange }`-Props
|
|
5
|
+
* fertig für die Feld-Widgets (NumberField/MoneyField/PercentField) liefert.
|
|
6
|
+
* Ersetzt das pro Screen wiederholte Draft-Interface + patch-Helper. */
|
|
7
|
+
export function useDraft<T extends object>(
|
|
8
|
+
defaults: T,
|
|
9
|
+
): {
|
|
10
|
+
readonly draft: T;
|
|
11
|
+
readonly patch: (changes: Partial<T>) => void;
|
|
12
|
+
readonly reset: () => void;
|
|
13
|
+
readonly field: <K extends keyof T>(
|
|
14
|
+
name: K,
|
|
15
|
+
) => {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly value: T[K];
|
|
19
|
+
readonly onChange: (v: T[K]) => void;
|
|
20
|
+
};
|
|
21
|
+
} {
|
|
22
|
+
const [draft, setDraft] = useState<T>(defaults);
|
|
23
|
+
const patch = useCallback((changes: Partial<T>) => setDraft((d) => ({ ...d, ...changes })), []);
|
|
24
|
+
const reset = useCallback(() => setDraft(defaults), [defaults]);
|
|
25
|
+
const field = useCallback(
|
|
26
|
+
<K extends keyof T>(name: K) => ({
|
|
27
|
+
id: String(name),
|
|
28
|
+
name: String(name),
|
|
29
|
+
value: draft[name],
|
|
30
|
+
onChange: (v: T[K]) => setDraft((d) => ({ ...d, [name]: v })),
|
|
31
|
+
}),
|
|
32
|
+
[draft],
|
|
33
|
+
);
|
|
34
|
+
return { draft, patch, reset, field };
|
|
35
|
+
}
|
package/src/ui/card.tsx
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
|
|
2
|
-
import * as React from "react"
|
|
3
|
-
|
|
4
|
-
import { cn } from "../lib/cn"
|
|
5
|
-
|
|
6
|
-
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
7
|
-
return (
|
|
8
|
-
<div
|
|
9
|
-
data-slot="card"
|
|
10
|
-
className={cn(
|
|
11
|
-
"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
|
|
12
|
-
className
|
|
13
|
-
)}
|
|
14
|
-
{...props}
|
|
15
|
-
/>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
20
|
-
return (
|
|
21
|
-
<div
|
|
22
|
-
data-slot="card-header"
|
|
23
|
-
className={cn(
|
|
24
|
-
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
25
|
-
className
|
|
26
|
-
)}
|
|
27
|
-
{...props}
|
|
28
|
-
/>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
33
|
-
return (
|
|
34
|
-
<div
|
|
35
|
-
data-slot="card-title"
|
|
36
|
-
className={cn("leading-none font-semibold", className)}
|
|
37
|
-
{...props}
|
|
38
|
-
/>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
43
|
-
return (
|
|
44
|
-
<div
|
|
45
|
-
data-slot="card-description"
|
|
46
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
47
|
-
{...props}
|
|
48
|
-
/>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
53
|
-
return (
|
|
54
|
-
<div
|
|
55
|
-
data-slot="card-action"
|
|
56
|
-
className={cn(
|
|
57
|
-
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
58
|
-
className
|
|
59
|
-
)}
|
|
60
|
-
{...props}
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
66
|
-
return (
|
|
67
|
-
<div
|
|
68
|
-
data-slot="card-content"
|
|
69
|
-
className={cn("px-6", className)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
76
|
-
return (
|
|
77
|
-
<div
|
|
78
|
-
data-slot="card-footer"
|
|
79
|
-
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
80
|
-
{...props}
|
|
81
|
-
/>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export {
|
|
86
|
-
Card,
|
|
87
|
-
CardHeader,
|
|
88
|
-
CardFooter,
|
|
89
|
-
CardTitle,
|
|
90
|
-
CardAction,
|
|
91
|
-
CardDescription,
|
|
92
|
-
CardContent,
|
|
93
|
-
}
|
package/src/ui/collapsible.tsx
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
|
|
2
|
-
"use client"
|
|
3
|
-
|
|
4
|
-
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
|
5
|
-
|
|
6
|
-
function Collapsible({
|
|
7
|
-
...props
|
|
8
|
-
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
|
9
|
-
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function CollapsibleTrigger({
|
|
13
|
-
...props
|
|
14
|
-
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
|
15
|
-
return (
|
|
16
|
-
<CollapsiblePrimitive.CollapsibleTrigger
|
|
17
|
-
data-slot="collapsible-trigger"
|
|
18
|
-
{...props}
|
|
19
|
-
/>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function CollapsibleContent({
|
|
24
|
-
...props
|
|
25
|
-
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
|
26
|
-
return (
|
|
27
|
-
<CollapsiblePrimitive.CollapsibleContent
|
|
28
|
-
data-slot="collapsible-content"
|
|
29
|
-
{...props}
|
|
30
|
-
/>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
package/src/ui/dropdown-menu.tsx
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck — vendored shadcn, regenerate via scripts/sync-shadcn.ts
|
|
2
|
-
"use client"
|
|
3
|
-
|
|
4
|
-
import * as React from "react"
|
|
5
|
-
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
|
6
|
-
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
|
7
|
-
|
|
8
|
-
import { cn } from "../lib/cn"
|
|
9
|
-
|
|
10
|
-
function DropdownMenu({
|
|
11
|
-
...props
|
|
12
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
13
|
-
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function DropdownMenuPortal({
|
|
17
|
-
...props
|
|
18
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
19
|
-
return (
|
|
20
|
-
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function DropdownMenuTrigger({
|
|
25
|
-
...props
|
|
26
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
27
|
-
return (
|
|
28
|
-
<DropdownMenuPrimitive.Trigger
|
|
29
|
-
data-slot="dropdown-menu-trigger"
|
|
30
|
-
{...props}
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function DropdownMenuContent({
|
|
36
|
-
className,
|
|
37
|
-
sideOffset = 4,
|
|
38
|
-
...props
|
|
39
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
40
|
-
return (
|
|
41
|
-
<DropdownMenuPrimitive.Portal>
|
|
42
|
-
<DropdownMenuPrimitive.Content
|
|
43
|
-
data-slot="dropdown-menu-content"
|
|
44
|
-
sideOffset={sideOffset}
|
|
45
|
-
className={cn(
|
|
46
|
-
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
47
|
-
className
|
|
48
|
-
)}
|
|
49
|
-
{...props}
|
|
50
|
-
/>
|
|
51
|
-
</DropdownMenuPrimitive.Portal>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function DropdownMenuGroup({
|
|
56
|
-
...props
|
|
57
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
58
|
-
return (
|
|
59
|
-
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function DropdownMenuItem({
|
|
64
|
-
className,
|
|
65
|
-
inset,
|
|
66
|
-
variant = "default",
|
|
67
|
-
...props
|
|
68
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
69
|
-
inset?: boolean
|
|
70
|
-
variant?: "default" | "destructive"
|
|
71
|
-
}) {
|
|
72
|
-
return (
|
|
73
|
-
<DropdownMenuPrimitive.Item
|
|
74
|
-
data-slot="dropdown-menu-item"
|
|
75
|
-
data-inset={inset}
|
|
76
|
-
data-variant={variant}
|
|
77
|
-
className={cn(
|
|
78
|
-
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
|
79
|
-
className
|
|
80
|
-
)}
|
|
81
|
-
{...props}
|
|
82
|
-
/>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function DropdownMenuCheckboxItem({
|
|
87
|
-
className,
|
|
88
|
-
children,
|
|
89
|
-
checked,
|
|
90
|
-
...props
|
|
91
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
92
|
-
return (
|
|
93
|
-
<DropdownMenuPrimitive.CheckboxItem
|
|
94
|
-
data-slot="dropdown-menu-checkbox-item"
|
|
95
|
-
className={cn(
|
|
96
|
-
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
97
|
-
className
|
|
98
|
-
)}
|
|
99
|
-
checked={checked}
|
|
100
|
-
{...props}
|
|
101
|
-
>
|
|
102
|
-
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
103
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
104
|
-
<CheckIcon className="size-4" />
|
|
105
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
106
|
-
</span>
|
|
107
|
-
{children}
|
|
108
|
-
</DropdownMenuPrimitive.CheckboxItem>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function DropdownMenuRadioGroup({
|
|
113
|
-
...props
|
|
114
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
115
|
-
return (
|
|
116
|
-
<DropdownMenuPrimitive.RadioGroup
|
|
117
|
-
data-slot="dropdown-menu-radio-group"
|
|
118
|
-
{...props}
|
|
119
|
-
/>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function DropdownMenuRadioItem({
|
|
124
|
-
className,
|
|
125
|
-
children,
|
|
126
|
-
...props
|
|
127
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
128
|
-
return (
|
|
129
|
-
<DropdownMenuPrimitive.RadioItem
|
|
130
|
-
data-slot="dropdown-menu-radio-item"
|
|
131
|
-
className={cn(
|
|
132
|
-
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
133
|
-
className
|
|
134
|
-
)}
|
|
135
|
-
{...props}
|
|
136
|
-
>
|
|
137
|
-
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
138
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
139
|
-
<CircleIcon className="size-2 fill-current" />
|
|
140
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
141
|
-
</span>
|
|
142
|
-
{children}
|
|
143
|
-
</DropdownMenuPrimitive.RadioItem>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function DropdownMenuLabel({
|
|
148
|
-
className,
|
|
149
|
-
inset,
|
|
150
|
-
...props
|
|
151
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
152
|
-
inset?: boolean
|
|
153
|
-
}) {
|
|
154
|
-
return (
|
|
155
|
-
<DropdownMenuPrimitive.Label
|
|
156
|
-
data-slot="dropdown-menu-label"
|
|
157
|
-
data-inset={inset}
|
|
158
|
-
className={cn(
|
|
159
|
-
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
|
160
|
-
className
|
|
161
|
-
)}
|
|
162
|
-
{...props}
|
|
163
|
-
/>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function DropdownMenuSeparator({
|
|
168
|
-
className,
|
|
169
|
-
...props
|
|
170
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
171
|
-
return (
|
|
172
|
-
<DropdownMenuPrimitive.Separator
|
|
173
|
-
data-slot="dropdown-menu-separator"
|
|
174
|
-
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
175
|
-
{...props}
|
|
176
|
-
/>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function DropdownMenuShortcut({
|
|
181
|
-
className,
|
|
182
|
-
...props
|
|
183
|
-
}: React.ComponentProps<"span">) {
|
|
184
|
-
return (
|
|
185
|
-
<span
|
|
186
|
-
data-slot="dropdown-menu-shortcut"
|
|
187
|
-
className={cn(
|
|
188
|
-
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
189
|
-
className
|
|
190
|
-
)}
|
|
191
|
-
{...props}
|
|
192
|
-
/>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function DropdownMenuSub({
|
|
197
|
-
...props
|
|
198
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
199
|
-
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function DropdownMenuSubTrigger({
|
|
203
|
-
className,
|
|
204
|
-
inset,
|
|
205
|
-
children,
|
|
206
|
-
...props
|
|
207
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
208
|
-
inset?: boolean
|
|
209
|
-
}) {
|
|
210
|
-
return (
|
|
211
|
-
<DropdownMenuPrimitive.SubTrigger
|
|
212
|
-
data-slot="dropdown-menu-sub-trigger"
|
|
213
|
-
data-inset={inset}
|
|
214
|
-
className={cn(
|
|
215
|
-
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
216
|
-
className
|
|
217
|
-
)}
|
|
218
|
-
{...props}
|
|
219
|
-
>
|
|
220
|
-
{children}
|
|
221
|
-
<ChevronRightIcon className="ml-auto size-4" />
|
|
222
|
-
</DropdownMenuPrimitive.SubTrigger>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function DropdownMenuSubContent({
|
|
227
|
-
className,
|
|
228
|
-
...props
|
|
229
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
230
|
-
return (
|
|
231
|
-
<DropdownMenuPrimitive.SubContent
|
|
232
|
-
data-slot="dropdown-menu-sub-content"
|
|
233
|
-
className={cn(
|
|
234
|
-
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
235
|
-
className
|
|
236
|
-
)}
|
|
237
|
-
{...props}
|
|
238
|
-
/>
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export {
|
|
243
|
-
DropdownMenu,
|
|
244
|
-
DropdownMenuPortal,
|
|
245
|
-
DropdownMenuTrigger,
|
|
246
|
-
DropdownMenuContent,
|
|
247
|
-
DropdownMenuGroup,
|
|
248
|
-
DropdownMenuLabel,
|
|
249
|
-
DropdownMenuItem,
|
|
250
|
-
DropdownMenuCheckboxItem,
|
|
251
|
-
DropdownMenuRadioGroup,
|
|
252
|
-
DropdownMenuRadioItem,
|
|
253
|
-
DropdownMenuSeparator,
|
|
254
|
-
DropdownMenuShortcut,
|
|
255
|
-
DropdownMenuSub,
|
|
256
|
-
DropdownMenuSubTrigger,
|
|
257
|
-
DropdownMenuSubContent,
|
|
258
|
-
}
|