koishi-plugin-chatluna-affinity 0.3.0 → 0.3.2
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/client/AffinityDashboardPage.vue +30 -0
- package/client/components/ui/alert.tsx +50 -0
- package/client/components/ui/avatar.tsx +47 -0
- package/client/components/ui/badge.tsx +35 -0
- package/client/components/ui/button.tsx +53 -0
- package/client/components/ui/card.tsx +57 -0
- package/client/components/ui/chart.tsx +136 -0
- package/client/components/ui/sonner.tsx +23 -0
- package/client/components/ui/table.tsx +77 -0
- package/client/components/ui/tabs.tsx +82 -0
- package/client/dashboard/AffinityDashboard.tsx +1123 -0
- package/client/dashboard/mount.tsx +9 -0
- package/client/dashboard/types.ts +85 -0
- package/client/index.ts +13 -0
- package/client/lib/utils.ts +6 -0
- package/client/style.css +125 -0
- package/dist/index.js +38400 -159
- package/dist/style.css +1 -1
- package/lib/index.d.ts +68 -15
- package/lib/index.js +673 -8
- package/package.json +57 -16
- package/readme.md +4 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
好感度仪表盘页面
|
|
3
|
+
作为 Koishi ctx.page 的 Vue 外壳挂载 React 仪表盘
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<k-layout container="page-affinity-dashboard-shell" main="page-affinity-dashboard">
|
|
7
|
+
<k-content>
|
|
8
|
+
<div ref="dashboardRoot" />
|
|
9
|
+
</k-content>
|
|
10
|
+
</k-layout>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
15
|
+
import { mountAffinityDashboard } from './dashboard/mount'
|
|
16
|
+
|
|
17
|
+
const dashboardRoot = ref<HTMLElement | null>(null)
|
|
18
|
+
let disposeDashboard: (() => void) | null = null
|
|
19
|
+
|
|
20
|
+
onMounted(async () => {
|
|
21
|
+
await nextTick()
|
|
22
|
+
if (!dashboardRoot.value) return
|
|
23
|
+
disposeDashboard = mountAffinityDashboard(dashboardRoot.value)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
onBeforeUnmount(() => {
|
|
27
|
+
disposeDashboard?.()
|
|
28
|
+
disposeDashboard = null
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
const alertVariants = cva(
|
|
6
|
+
"relative w-full rounded-2xl border p-4 text-sm [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-background text-foreground",
|
|
11
|
+
destructive:
|
|
12
|
+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function Alert({
|
|
22
|
+
className,
|
|
23
|
+
variant,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
role="alert"
|
|
29
|
+
className={cn(alertVariants({ variant, className }))}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"h5">) {
|
|
36
|
+
return (
|
|
37
|
+
<h5 className={cn("mb-1 font-medium leading-none", className)} {...props} />
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Avatar({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
|
9
|
+
return (
|
|
10
|
+
<AvatarPrimitive.Root
|
|
11
|
+
className={cn(
|
|
12
|
+
"relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full",
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function AvatarImage({
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
24
|
+
return (
|
|
25
|
+
<AvatarPrimitive.Image
|
|
26
|
+
className={cn("aspect-square h-full w-full", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function AvatarFallback({
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
36
|
+
return (
|
|
37
|
+
<AvatarPrimitive.Fallback
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex h-full w-full items-center justify-center rounded-full bg-muted text-xs",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium transition-colors",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
12
|
+
secondary:
|
|
13
|
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
destructive:
|
|
15
|
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
16
|
+
outline: "text-foreground",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
function Badge({
|
|
26
|
+
className,
|
|
27
|
+
variant,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
30
|
+
return (
|
|
31
|
+
<span className={cn(badgeVariants({ variant, className }))} {...props} />
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
destructive:
|
|
13
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
+
outline:
|
|
15
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: "h-10 px-4 py-2",
|
|
22
|
+
sm: "h-8 rounded-xl px-3 text-xs",
|
|
23
|
+
icon: "h-8 w-8 rounded-xl",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
variant: "default",
|
|
28
|
+
size: "default",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function Button({
|
|
34
|
+
className,
|
|
35
|
+
variant,
|
|
36
|
+
size,
|
|
37
|
+
asChild = false,
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<"button"> &
|
|
40
|
+
VariantProps<typeof buttonVariants> & {
|
|
41
|
+
asChild?: boolean;
|
|
42
|
+
}) {
|
|
43
|
+
const Comp = asChild ? Slot : "button";
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Comp
|
|
47
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className={cn(
|
|
8
|
+
"rounded-2xl border bg-card text-card-foreground shadow-sm",
|
|
9
|
+
className,
|
|
10
|
+
)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
17
|
+
return (
|
|
18
|
+
<div className={cn("flex flex-col space-y-1.5 p-4", className)} {...props} />
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={cn("text-base font-semibold leading-none", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
41
|
+
return <div className={cn("p-4 pt-0", className)} {...props} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
45
|
+
return (
|
|
46
|
+
<div className={cn("flex items-center p-4 pt-0", className)} {...props} />
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
Card,
|
|
52
|
+
CardHeader,
|
|
53
|
+
CardFooter,
|
|
54
|
+
CardTitle,
|
|
55
|
+
CardDescription,
|
|
56
|
+
CardContent,
|
|
57
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as RechartsPrimitive from "recharts";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
export type ChartConfig = Record<
|
|
6
|
+
string,
|
|
7
|
+
{
|
|
8
|
+
label?: React.ReactNode;
|
|
9
|
+
color?: string;
|
|
10
|
+
}
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
const ChartContext = React.createContext<{ config: ChartConfig } | null>(null);
|
|
14
|
+
|
|
15
|
+
function useChart() {
|
|
16
|
+
const context = React.useContext(ChartContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error("useChart must be used within a <ChartContainer />");
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ChartContainer({
|
|
24
|
+
id,
|
|
25
|
+
className,
|
|
26
|
+
children,
|
|
27
|
+
config,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<"div"> & {
|
|
30
|
+
config: ChartConfig;
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
}) {
|
|
33
|
+
const uniqueId = React.useId();
|
|
34
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ChartContext.Provider value={{ config }}>
|
|
38
|
+
<div
|
|
39
|
+
data-chart={chartId}
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line]:stroke-border/70 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<ChartStyle id={chartId} config={config} />
|
|
47
|
+
<RechartsPrimitive.ResponsiveContainer>
|
|
48
|
+
{children as React.ReactElement}
|
|
49
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
50
|
+
</div>
|
|
51
|
+
</ChartContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ChartStyle({ id, config }: { id: string; config: ChartConfig }) {
|
|
56
|
+
const colorConfig = Object.entries(config).filter(([, item]) => item.color);
|
|
57
|
+
|
|
58
|
+
if (!colorConfig.length) return null;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<style
|
|
62
|
+
dangerouslySetInnerHTML={{
|
|
63
|
+
__html: `
|
|
64
|
+
[data-chart=${id}] {
|
|
65
|
+
${colorConfig
|
|
66
|
+
.map(([key, item]) => ` --color-${key}: ${item.color};`)
|
|
67
|
+
.join("\n")}
|
|
68
|
+
}
|
|
69
|
+
`,
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ChartTooltip = RechartsPrimitive.Tooltip;
|
|
76
|
+
|
|
77
|
+
function ChartTooltipContent({
|
|
78
|
+
active,
|
|
79
|
+
payload,
|
|
80
|
+
label,
|
|
81
|
+
className,
|
|
82
|
+
hideLabel = false,
|
|
83
|
+
}: {
|
|
84
|
+
active?: boolean;
|
|
85
|
+
payload?: Array<{
|
|
86
|
+
color?: string;
|
|
87
|
+
dataKey?: string | number;
|
|
88
|
+
name?: string | number;
|
|
89
|
+
value?: unknown;
|
|
90
|
+
}>;
|
|
91
|
+
label?: React.ReactNode;
|
|
92
|
+
className?: string;
|
|
93
|
+
hideLabel?: boolean;
|
|
94
|
+
}) {
|
|
95
|
+
const { config } = useChart();
|
|
96
|
+
|
|
97
|
+
if (!active || !payload?.length) return null;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
className={cn(
|
|
102
|
+
"grid min-w-32 gap-1.5 rounded-2xl border bg-background px-3 py-2 text-xs shadow-xl",
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{!hideLabel && label ? (
|
|
107
|
+
<div className="font-medium text-foreground">{label}</div>
|
|
108
|
+
) : null}
|
|
109
|
+
<div className="grid gap-1.5">
|
|
110
|
+
{payload.map((item) => {
|
|
111
|
+
const key = String(item.dataKey || item.name || "");
|
|
112
|
+
const itemConfig = config[key];
|
|
113
|
+
const color = item.color || itemConfig?.color;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
className="flex min-w-0 items-center gap-2 text-muted-foreground"
|
|
118
|
+
key={key}
|
|
119
|
+
>
|
|
120
|
+
<span
|
|
121
|
+
className="h-2.5 w-2.5 shrink-0 rounded-[2px]"
|
|
122
|
+
style={{ backgroundColor: color }}
|
|
123
|
+
/>
|
|
124
|
+
<span className="truncate">{itemConfig?.label || key}</span>
|
|
125
|
+
<span className="ml-auto font-mono font-medium text-foreground">
|
|
126
|
+
{String(item.value ?? "")}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { ChartContainer, ChartTooltip, ChartTooltipContent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
2
|
+
|
|
3
|
+
function Toaster(props: ToasterProps) {
|
|
4
|
+
return (
|
|
5
|
+
<Sonner
|
|
6
|
+
className="toaster group"
|
|
7
|
+
toastOptions={{
|
|
8
|
+
classNames: {
|
|
9
|
+
toast:
|
|
10
|
+
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
|
11
|
+
description: "group-[.toast]:text-muted-foreground",
|
|
12
|
+
actionButton:
|
|
13
|
+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
|
14
|
+
cancelButton:
|
|
15
|
+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
16
|
+
},
|
|
17
|
+
}}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Toaster };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="relative w-full overflow-auto rounded-2xl border bg-background">
|
|
7
|
+
<table className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
13
|
+
return <thead className={cn("[&_tr]:border-b", className)} {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
17
|
+
return (
|
|
18
|
+
<tbody className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
23
|
+
return (
|
|
24
|
+
<tfoot
|
|
25
|
+
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
32
|
+
return (
|
|
33
|
+
<tr
|
|
34
|
+
className={cn(
|
|
35
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
44
|
+
return (
|
|
45
|
+
<th
|
|
46
|
+
className={cn(
|
|
47
|
+
"h-10 px-3 text-left align-middle font-medium text-muted-foreground",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
56
|
+
return <td className={cn("p-3 align-middle", className)} {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
|
|
60
|
+
return (
|
|
61
|
+
<caption
|
|
62
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
Table,
|
|
70
|
+
TableHeader,
|
|
71
|
+
TableBody,
|
|
72
|
+
TableFooter,
|
|
73
|
+
TableHead,
|
|
74
|
+
TableRow,
|
|
75
|
+
TableCell,
|
|
76
|
+
TableCaption,
|
|
77
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Tabs as HeroTabs } from "@heroui/react/tabs";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
type TabsProps = Omit<
|
|
6
|
+
React.ComponentProps<typeof HeroTabs>,
|
|
7
|
+
"defaultSelectedKey" | "onSelectionChange" | "selectedKey"
|
|
8
|
+
> & {
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
onValueChange?: (value: string) => void;
|
|
11
|
+
value?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function Tabs({
|
|
15
|
+
className,
|
|
16
|
+
defaultValue,
|
|
17
|
+
onValueChange,
|
|
18
|
+
value,
|
|
19
|
+
...props
|
|
20
|
+
}: TabsProps) {
|
|
21
|
+
return (
|
|
22
|
+
<HeroTabs
|
|
23
|
+
className={cn("flex flex-col gap-4", className)}
|
|
24
|
+
defaultSelectedKey={defaultValue}
|
|
25
|
+
selectedKey={value}
|
|
26
|
+
onSelectionChange={(key) => onValueChange?.(String(key))}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TabsList({
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof HeroTabs.List>) {
|
|
36
|
+
return (
|
|
37
|
+
<HeroTabs.ListContainer className="w-fit">
|
|
38
|
+
<HeroTabs.List
|
|
39
|
+
className={cn("w-fit shadow-none", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
</HeroTabs.ListContainer>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function TabsTrigger({
|
|
47
|
+
className,
|
|
48
|
+
value,
|
|
49
|
+
children,
|
|
50
|
+
...props
|
|
51
|
+
}: Omit<React.ComponentProps<typeof HeroTabs.Tab>, "id"> & {
|
|
52
|
+
value: string;
|
|
53
|
+
}) {
|
|
54
|
+
return (
|
|
55
|
+
<HeroTabs.Tab
|
|
56
|
+
className={cn("min-w-fit whitespace-nowrap px-4 text-sm", className)}
|
|
57
|
+
id={value}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
<HeroTabs.Indicator />
|
|
62
|
+
</HeroTabs.Tab>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function TabsContent({
|
|
67
|
+
className,
|
|
68
|
+
value,
|
|
69
|
+
...props
|
|
70
|
+
}: Omit<React.ComponentProps<typeof HeroTabs.Panel>, "id"> & {
|
|
71
|
+
value: string;
|
|
72
|
+
}) {
|
|
73
|
+
return (
|
|
74
|
+
<HeroTabs.Panel
|
|
75
|
+
className={cn("mt-0 p-0 outline-none", className)}
|
|
76
|
+
id={value}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|