refactor(hub): migrate dashboard to shadcn components
Replace manual CSS classes with shadcn Card, CardHeader, CardTitle, CardContent, and Badge components. Remove inline <style> block — use Tailwind utility classes via shadcn theme tokens. Add Biome formatting across all components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
18bf58be1a
commit
d94524b70d
@@ -1,9 +1,9 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from "next/server";
|
||||||
import http from 'node:http';
|
import http from "node:http";
|
||||||
|
|
||||||
const JAEGER = 'http://jaeger:16686';
|
const JAEGER = "http://jaeger:16686";
|
||||||
const PROMETHEUS = 'http://prometheus:9090';
|
const PROMETHEUS = "http://prometheus:9090";
|
||||||
const DOCKER_SOCK = '/var/run/docker.sock';
|
const DOCKER_SOCK = "/var/run/docker.sock";
|
||||||
|
|
||||||
interface Container {
|
interface Container {
|
||||||
Names: string[];
|
Names: string[];
|
||||||
@@ -28,17 +28,19 @@ interface Service {
|
|||||||
|
|
||||||
function dockerFetch(path: string): Promise<unknown> {
|
function dockerFetch(path: string): Promise<unknown> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http.get({ socketPath: DOCKER_SOCK, path }, (res) => {
|
http
|
||||||
let data = '';
|
.get({ socketPath: DOCKER_SOCK, path }, (res) => {
|
||||||
res.on('data', (c: string) => (data += c));
|
let data = "";
|
||||||
res.on('end', () => {
|
res.on("data", (c: string) => (data += c));
|
||||||
try {
|
res.on("end", () => {
|
||||||
resolve(JSON.parse(data));
|
try {
|
||||||
} catch {
|
resolve(JSON.parse(data));
|
||||||
resolve(null);
|
} catch {
|
||||||
}
|
resolve(null);
|
||||||
});
|
}
|
||||||
}).on('error', reject);
|
});
|
||||||
|
})
|
||||||
|
.on("error", reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,27 +54,29 @@ async function fetchJSON(url: string): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseServices(containers: Container[]): Service[] {
|
function parseServices(containers: Container[]): Service[] {
|
||||||
const project = containers.find((c) => c.Labels['com.docker.compose.project'])
|
const project = containers.find((c) => c.Labels["com.docker.compose.project"])
|
||||||
?.Labels['com.docker.compose.project'];
|
?.Labels["com.docker.compose.project"];
|
||||||
|
|
||||||
return containers
|
return containers
|
||||||
.filter((c) => {
|
.filter((c) => {
|
||||||
if (!c.NetworkSettings?.Networks?.['app-shared-net']) return false;
|
if (!c.NetworkSettings?.Networks?.["app-shared-net"]) return false;
|
||||||
if (project && c.Labels['com.docker.compose.project'] !== project) return false;
|
if (project && c.Labels["com.docker.compose.project"] !== project)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((c) => ({
|
.map((c) => ({
|
||||||
name: c.Names[0].replace(/^\//, ''),
|
name: c.Names[0].replace(/^\//, ""),
|
||||||
state: c.State,
|
state: c.State,
|
||||||
hasWeb: Object.keys(c.Labels).some(
|
hasWeb: Object.keys(c.Labels).some(
|
||||||
(k) => k.startsWith('traefik.http.routers.') && k.endsWith('.rule'),
|
(k) => k.startsWith("traefik.http.routers.") && k.endsWith(".rule"),
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTraces(): Promise<Trace[]> {
|
async function fetchTraces(): Promise<Trace[]> {
|
||||||
const svcRes = await fetchJSON(`${JAEGER}/api/services`);
|
const svcRes = await fetchJSON(`${JAEGER}/api/services`);
|
||||||
if (!svcRes || !Array.isArray((svcRes as { data?: string[] }).data)) return [];
|
if (!svcRes || !Array.isArray((svcRes as { data?: string[] }).data))
|
||||||
|
return [];
|
||||||
|
|
||||||
const now = Date.now() * 1000;
|
const now = Date.now() * 1000;
|
||||||
const start = now - 5 * 60 * 1_000_000;
|
const start = now - 5 * 60 * 1_000_000;
|
||||||
@@ -84,12 +88,24 @@ async function fetchTraces(): Promise<Trace[]> {
|
|||||||
);
|
);
|
||||||
if (!d || !Array.isArray((d as { data?: unknown[] }).data)) continue;
|
if (!d || !Array.isArray((d as { data?: unknown[] }).data)) continue;
|
||||||
|
|
||||||
for (const t of (d as { data: { duration: number; spans: { operationName: string; processID: string; tags: { key: string; value: unknown }[] }[]; processes: Record<string, { serviceName: string }> }[] }).data) {
|
for (const t of (
|
||||||
|
d as {
|
||||||
|
data: {
|
||||||
|
duration: number;
|
||||||
|
spans: {
|
||||||
|
operationName: string;
|
||||||
|
processID: string;
|
||||||
|
tags: { key: string; value: unknown }[];
|
||||||
|
}[];
|
||||||
|
processes: Record<string, { serviceName: string }>;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
).data) {
|
||||||
if (!t.spans?.length) continue;
|
if (!t.spans?.length) continue;
|
||||||
const span = t.spans[0];
|
const span = t.spans[0];
|
||||||
const svc = t.processes[span.processID]?.serviceName || 'unknown';
|
const svc = t.processes[span.processID]?.serviceName || "unknown";
|
||||||
const hasError = t.spans.some((s) =>
|
const hasError = t.spans.some((s) =>
|
||||||
s.tags?.some((tag) => tag.key === 'error' && tag.value === true),
|
s.tags?.some((tag) => tag.key === "error" && tag.value === true),
|
||||||
);
|
);
|
||||||
all.push({
|
all.push({
|
||||||
service: svc,
|
service: svc,
|
||||||
@@ -108,16 +124,21 @@ async function promRange(query: string, steps = 20): Promise<number[]> {
|
|||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const u = `${PROMETHEUS}/api/v1/query_range?query=${encodeURIComponent(query)}&start=${now - 300}&end=${now}&step=${(300 / steps).toFixed(0)}`;
|
const u = `${PROMETHEUS}/api/v1/query_range?query=${encodeURIComponent(query)}&start=${now - 300}&end=${now}&step=${(300 / steps).toFixed(0)}`;
|
||||||
const d = await fetchJSON(u);
|
const d = await fetchJSON(u);
|
||||||
const result = (d as { data?: { result?: { values?: unknown[][] }[] } })?.data?.result;
|
const result = (d as { data?: { result?: { values?: unknown[][] }[] } })?.data
|
||||||
|
?.result;
|
||||||
if (!result?.[0]?.values) return [];
|
if (!result?.[0]?.values) return [];
|
||||||
return result[0].values.map((v) => { const f = parseFloat(v[1] as string); return isNaN(f) ? 0 : f; });
|
return result[0].values.map((v) => {
|
||||||
|
const f = parseFloat(v[1] as string);
|
||||||
|
return isNaN(f) ? 0 : f;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promQuery(query: string): Promise<number | null> {
|
async function promQuery(query: string): Promise<number | null> {
|
||||||
const d = await fetchJSON(
|
const d = await fetchJSON(
|
||||||
`${PROMETHEUS}/api/v1/query?query=${encodeURIComponent(query)}`,
|
`${PROMETHEUS}/api/v1/query?query=${encodeURIComponent(query)}`,
|
||||||
);
|
);
|
||||||
const result = (d as { data?: { result?: { value?: unknown[] }[] } })?.data?.result;
|
const result = (d as { data?: { result?: { value?: unknown[] }[] } })?.data
|
||||||
|
?.result;
|
||||||
if (!result?.length) return null;
|
if (!result?.length) return null;
|
||||||
const val = result[0].value?.[1];
|
const val = result[0].value?.[1];
|
||||||
return val ? parseFloat(val as string) : null;
|
return val ? parseFloat(val as string) : null;
|
||||||
@@ -145,39 +166,62 @@ interface DashboardData {
|
|||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const [containersRaw] = await Promise.all([
|
const [containersRaw] = await Promise.all([
|
||||||
dockerFetch('/containers/json?all=true') as Promise<Container[]>,
|
dockerFetch("/containers/json?all=true") as Promise<Container[]>,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const containers = Array.isArray(containersRaw) ? containersRaw : [];
|
const containers = Array.isArray(containersRaw) ? containersRaw : [];
|
||||||
const services = parseServices(containers);
|
const services = parseServices(containers);
|
||||||
|
|
||||||
const [traces, cpu, ram, disk, load1, load5, load15, netIn, netOut, rps, latency, errors] =
|
const [
|
||||||
await Promise.all([
|
traces,
|
||||||
fetchTraces(),
|
cpu,
|
||||||
promQuery(`100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)`),
|
ram,
|
||||||
promQuery(`(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100`),
|
disk,
|
||||||
promQuery(`(1 - node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"}) * 100`),
|
load1,
|
||||||
promQuery('node_load1'),
|
load5,
|
||||||
promQuery('node_load5'),
|
load15,
|
||||||
promQuery('node_load15'),
|
netIn,
|
||||||
promQuery(`rate(node_network_receive_bytes_total{device!="lo"}[1m])`),
|
netOut,
|
||||||
promQuery(`rate(node_network_transmit_bytes_total{device!="lo"}[1m])`),
|
rps,
|
||||||
promRange('sum(rate(traefik_service_requests_total[1m]))'),
|
latency,
|
||||||
promRange('avg(traefik_service_request_duration_seconds_sum / traefik_service_request_duration_seconds_count) * 1000'),
|
errors,
|
||||||
promRange('sum(rate(traefik_service_requests_total{code=~"5.."}[1m]))'),
|
] = await Promise.all([
|
||||||
]);
|
fetchTraces(),
|
||||||
|
promQuery(
|
||||||
|
`100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)`,
|
||||||
|
),
|
||||||
|
promQuery(
|
||||||
|
`(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100`,
|
||||||
|
),
|
||||||
|
promQuery(
|
||||||
|
`(1 - node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"}) * 100`,
|
||||||
|
),
|
||||||
|
promQuery("node_load1"),
|
||||||
|
promQuery("node_load5"),
|
||||||
|
promQuery("node_load15"),
|
||||||
|
promQuery(`rate(node_network_receive_bytes_total{device!="lo"}[1m])`),
|
||||||
|
promQuery(`rate(node_network_transmit_bytes_total{device!="lo"}[1m])`),
|
||||||
|
promRange("sum(rate(traefik_service_requests_total[1m]))"),
|
||||||
|
promRange(
|
||||||
|
"avg(traefik_service_request_duration_seconds_sum / traefik_service_request_duration_seconds_count) * 1000",
|
||||||
|
),
|
||||||
|
promRange('sum(rate(traefik_service_requests_total{code=~"5.."}[1m]))'),
|
||||||
|
]);
|
||||||
|
|
||||||
const links: { url: string; label: string }[] = [
|
const links: { url: string; label: string }[] = [
|
||||||
{ url: '/jaeger', label: 'Jaeger UI' },
|
{ url: "/jaeger", label: "Jaeger UI" },
|
||||||
];
|
];
|
||||||
if (containers.some((c) => c.Names?.some((n) => n.includes('prometheus')))) {
|
if (containers.some((c) => c.Names?.some((n) => n.includes("prometheus")))) {
|
||||||
links.push({ url: '/api/prometheus/targets', label: 'Prometheus' });
|
links.push({ url: "/api/prometheus/targets", label: "Prometheus" });
|
||||||
}
|
}
|
||||||
links.push({ url: 'https://github.com/asepharyana/asepharyana-hub', label: 'GitHub' });
|
links.push({
|
||||||
|
url: "https://github.com/asepharyana/asepharyana-hub",
|
||||||
|
label: "GitHub",
|
||||||
|
});
|
||||||
|
|
||||||
const domains = ['asepharyana.my.id', 'asepharyana.web.id'];
|
const domains = ["asepharyana.my.id", "asepharyana.web.id"];
|
||||||
for (const s of services) {
|
for (const s of services) {
|
||||||
if (s.hasWeb && s.state === 'running') {
|
if (s.hasWeb && s.state === "running") {
|
||||||
links.push({ url: `https://${s.name}.${domains[0]}`, label: s.name });
|
links.push({ url: `https://${s.name}.${domains[0]}`, label: s.name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +232,10 @@ export async function GET() {
|
|||||||
services,
|
services,
|
||||||
traces,
|
traces,
|
||||||
node: { cpu, ram, disk, load1, load5, load15, netIn, netOut },
|
node: { cpu, ram, disk, load1, load5, load15, netIn, netOut },
|
||||||
rps, latency, errors, traceVolume,
|
rps,
|
||||||
|
latency,
|
||||||
|
errors,
|
||||||
|
traceVolume,
|
||||||
links,
|
links,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+466
-202
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
interface Service {
|
interface Service {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -46,50 +48,99 @@ function Donut({ running, degraded }: { running: number; degraded: number }) {
|
|||||||
const total = running + degraded;
|
const total = running + degraded;
|
||||||
if (!total) return <NoData />;
|
if (!total) return <NoData />;
|
||||||
|
|
||||||
const cx = 100, cy = 90, R = 60, circ = 2 * Math.PI * R;
|
const cx = 100,
|
||||||
|
cy = 90,
|
||||||
|
R = 60,
|
||||||
|
circ = 2 * Math.PI * R;
|
||||||
const segs = [
|
const segs = [
|
||||||
{ n: running, c: '#3fb950', l: 'Running' },
|
{ n: running, c: "#3fb950", l: "Running" },
|
||||||
{ n: degraded, c: '#d29922', l: 'Degraded' },
|
{ n: degraded, c: "#d29922", l: "Degraded" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let off = 0;
|
let off = 0;
|
||||||
return (
|
return (
|
||||||
<svg width={200} height={210} viewBox="0 0 200 210">
|
<svg width={200} height={210} viewBox="0 0 200 210">
|
||||||
<style>{`.sl{font-family:system-ui,sans-serif;font-size:10px;fill:#8b949e}`}</style>
|
|
||||||
{segs.map((s) => {
|
{segs.map((s) => {
|
||||||
if (!s.n) return null;
|
if (!s.n) return null;
|
||||||
const frac = s.n / total;
|
const frac = s.n / total;
|
||||||
const ln = frac * circ;
|
const ln = frac * circ;
|
||||||
const el = (
|
const el = (
|
||||||
<circle key={s.l} cx={cx} cy={cy} r={R} fill="none" stroke={s.c} strokeWidth={14}
|
<circle
|
||||||
strokeDasharray={`${ln} ${circ - ln}`} strokeDashoffset={-off}
|
key={s.l}
|
||||||
transform={`rotate(-90 ${cx} ${cy})`} />
|
cx={cx}
|
||||||
|
cy={cy}
|
||||||
|
r={R}
|
||||||
|
fill="none"
|
||||||
|
stroke={s.c}
|
||||||
|
strokeWidth={14}
|
||||||
|
strokeDasharray={`${ln} ${circ - ln}`}
|
||||||
|
strokeDashoffset={-off}
|
||||||
|
transform={`rotate(-90 ${cx} ${cy})`}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
off += ln;
|
off += ln;
|
||||||
return el;
|
return el;
|
||||||
})}
|
})}
|
||||||
<text x={cx} y={cy - 4} textAnchor="middle" fill="#e6edf3" fontSize={26} fontWeight={700} fontFamily="system-ui,sans-serif">{total}</text>
|
<text
|
||||||
<text x={cx} y={cy + 14} textAnchor="middle" className="sl">total</text>
|
x={cx}
|
||||||
{segs.filter(s => s.n).map((s, i) => (
|
y={cy - 4}
|
||||||
<g key={s.l}>
|
textAnchor="middle"
|
||||||
<circle cx={16} cy={165 + i * 16} r={4} fill={s.c} />
|
fill="#e6edf3"
|
||||||
<text x={26} y={168 + i * 16} className="sl">{s.l}: {s.n}</text>
|
fontSize={26}
|
||||||
</g>
|
fontWeight={700}
|
||||||
))}
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
{total}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={cx}
|
||||||
|
y={cy + 14}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill="#8b949e"
|
||||||
|
fontSize={10}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
total
|
||||||
|
</text>
|
||||||
|
{segs
|
||||||
|
.filter((s) => s.n)
|
||||||
|
.map((s, i) => (
|
||||||
|
<g key={s.l}>
|
||||||
|
<circle cx={16} cy={165 + i * 16} r={4} fill={s.c} />
|
||||||
|
<text
|
||||||
|
x={26}
|
||||||
|
y={168 + i * 16}
|
||||||
|
fill="#8b949e"
|
||||||
|
fontSize={10}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
{s.l}: {s.n}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sparkline({ data, color }: { data: number[]; color: string }) {
|
function Sparkline({ data, color }: { data: number[]; color: string }) {
|
||||||
const w = 300, h = 160, pl = 45, pt = 20, pr = 10, pb = 25;
|
const w = 300,
|
||||||
const vw = w - pl - pr, vh = h - pt - pb;
|
h = 160,
|
||||||
|
pl = 45,
|
||||||
|
pt = 20,
|
||||||
|
pr = 10,
|
||||||
|
pb = 25;
|
||||||
|
const vw = w - pl - pr,
|
||||||
|
vh = h - pt - pb;
|
||||||
if (!data.length) return <NoData />;
|
if (!data.length) return <NoData />;
|
||||||
|
|
||||||
const maxV = Math.max(...data.map(Math.abs), 1);
|
const maxV = Math.max(...data.map(Math.abs), 1);
|
||||||
|
|
||||||
const pts = data.map((v, i) =>
|
const pts = data
|
||||||
`${(pl + vw * i / (data.length - 1)).toFixed(1)},${(pt + vh * (1 - v / maxV)).toFixed(1)}`
|
.map(
|
||||||
).join(' ');
|
(v, i) =>
|
||||||
|
`${(pl + (vw * i) / (data.length - 1)).toFixed(1)},${(pt + vh * (1 - v / maxV)).toFixed(1)}`,
|
||||||
|
)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
const area = `M${pl},${pt + vh} L${pts} L${pl + vw},${pt + vh} Z`;
|
const area = `M${pl},${pt + vh} L${pts} L${pl + vw},${pt + vh} Z`;
|
||||||
const lv = data[data.length - 1];
|
const lv = data[data.length - 1];
|
||||||
@@ -97,43 +148,129 @@ function Sparkline({ data, color }: { data: number[]; color: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
||||||
<style>{`.ax{font-family:system-ui,sans-serif;font-size:9px;fill:#6e7681}`}</style>
|
{[0, 1, 2, 3, 4].map((i) => {
|
||||||
{[0, 1, 2, 3, 4].map(i => {
|
const y = pt + (vh * i) / 4;
|
||||||
const y = pt + vh * i / 4;
|
|
||||||
return (
|
return (
|
||||||
<g key={i}>
|
<g key={i}>
|
||||||
<line x1={pl} y1={y} x2={pl + vw} y2={y} stroke="#21262d" strokeWidth={1} />
|
<line
|
||||||
<text x={pl - 6} y={y + 3} textAnchor="end" className="ax">{(maxV * (1 - i / 4)).toFixed(0)}</text>
|
x1={pl}
|
||||||
|
y1={y}
|
||||||
|
x2={pl + vw}
|
||||||
|
y2={y}
|
||||||
|
stroke="#21262d"
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x={pl - 6}
|
||||||
|
y={y + 3}
|
||||||
|
textAnchor="end"
|
||||||
|
fill="#6e7681"
|
||||||
|
fontSize={9}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
{(maxV * (1 - i / 4)).toFixed(0)}
|
||||||
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<path d={area} fill={color} opacity={0.15} />
|
<path d={area} fill={color} opacity={0.15} />
|
||||||
<polyline points={pts} fill="none" stroke={color} strokeWidth={2} strokeLinejoin="round" />
|
<polyline
|
||||||
<text x={pl + vw} y={ly - 10} textAnchor="end" fill={color} fontSize={11} fontWeight={600}
|
points={pts}
|
||||||
fontFamily="system-ui,sans-serif">{lv.toFixed(1)}</text>
|
fill="none"
|
||||||
{data.length > 5 && [...Array(5)].map((_, i) => {
|
stroke={color}
|
||||||
const idx = Math.floor((i + 1) * (data.length - 1) / 5);
|
strokeWidth={2}
|
||||||
const x = pl + vw * idx / (data.length - 1);
|
strokeLinejoin="round"
|
||||||
return <text key={i} x={x} y={pt + vh + 16} textAnchor="middle" className="ax">{idx + 1}</text>;
|
/>
|
||||||
})}
|
<text
|
||||||
|
x={pl + vw}
|
||||||
|
y={ly - 10}
|
||||||
|
textAnchor="end"
|
||||||
|
fill={color}
|
||||||
|
fontSize={11}
|
||||||
|
fontWeight={600}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
{lv.toFixed(1)}
|
||||||
|
</text>
|
||||||
|
{data.length > 5 &&
|
||||||
|
[...Array(5)].map((_, i) => {
|
||||||
|
const idx = Math.floor(((i + 1) * (data.length - 1)) / 5);
|
||||||
|
const x = pl + (vw * idx) / (data.length - 1);
|
||||||
|
return (
|
||||||
|
<text
|
||||||
|
key={i}
|
||||||
|
x={x}
|
||||||
|
y={pt + vh + 16}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill="#6e7681"
|
||||||
|
fontSize={9}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
{idx + 1}
|
||||||
|
</text>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Gauge({ pct, color, label, unit }: { pct: number | null; color: string; label: string; unit: string }) {
|
function Gauge({
|
||||||
|
pct,
|
||||||
|
color,
|
||||||
|
label,
|
||||||
|
unit,
|
||||||
|
}: {
|
||||||
|
pct: number | null;
|
||||||
|
color: string;
|
||||||
|
label: string;
|
||||||
|
unit: string;
|
||||||
|
}) {
|
||||||
if (pct === null || pct <= 0) return <NoData />;
|
if (pct === null || pct <= 0) return <NoData />;
|
||||||
const w = 220, h = 100, bw = 200, bh = 14;
|
const w = 220,
|
||||||
const bx = (w - bw) / 2, by = 30;
|
h = 100,
|
||||||
|
bw = 200,
|
||||||
|
bh = 14;
|
||||||
|
const bx = (w - bw) / 2,
|
||||||
|
by = 30;
|
||||||
const fw = bw * Math.min(pct / 100, 1);
|
const fw = bw * Math.min(pct / 100, 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
||||||
<style>{`.gt{font-family:system-ui,sans-serif;font-size:11px;fill:#8b949e;text-anchor:middle}`}</style>
|
|
||||||
<rect x={bx} y={by} width={bw} height={bh} rx={7} ry={7} fill="#1c2333" />
|
<rect x={bx} y={by} width={bw} height={bh} rx={7} ry={7} fill="#1c2333" />
|
||||||
{fw > 0 && <rect x={bx} y={by} width={fw} height={bh} rx={7} ry={7} fill={color} opacity={0.85} />}
|
{fw > 0 && (
|
||||||
<text x={w / 2} y={14} className="gt" fontWeight={600} fill={color}>{label}</text>
|
<rect
|
||||||
<text x={w / 2} y={by + bh + 24} className="gt" fill="#e6edf3" fontSize={14} fontWeight={700}>
|
x={bx}
|
||||||
{pct.toFixed(1)}{unit}
|
y={by}
|
||||||
|
width={fw}
|
||||||
|
height={bh}
|
||||||
|
rx={7}
|
||||||
|
ry={7}
|
||||||
|
fill={color}
|
||||||
|
opacity={0.85}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<text
|
||||||
|
x={w / 2}
|
||||||
|
y={14}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
fontSize={11}
|
||||||
|
fontWeight={600}
|
||||||
|
fill={color}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={w / 2}
|
||||||
|
y={by + bh + 24}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
fontSize={14}
|
||||||
|
fontWeight={700}
|
||||||
|
fill="#e6edf3"
|
||||||
|
>
|
||||||
|
{pct.toFixed(1)}
|
||||||
|
{unit}
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
@@ -142,29 +279,32 @@ function Gauge({ pct, color, label, unit }: { pct: number | null; color: string;
|
|||||||
function NoData() {
|
function NoData() {
|
||||||
return (
|
return (
|
||||||
<svg width={200} height={100} viewBox="0 0 200 100">
|
<svg width={200} height={100} viewBox="0 0 200 100">
|
||||||
<text x={100} y={50} textAnchor="middle" fill="#6e7681" fontSize={12} fontFamily="system-ui,sans-serif">No data</text>
|
<text
|
||||||
|
x={100}
|
||||||
|
y={50}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill="#6e7681"
|
||||||
|
fontSize={12}
|
||||||
|
fontFamily="system-ui,sans-serif"
|
||||||
|
>
|
||||||
|
No data
|
||||||
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Pill({ state }: { state: string }) {
|
|
||||||
const dot: Record<string, string> = { running: '#3fb950', jaeger: '#58a6ff', exited: '#f85149', restarting: '#d29922' };
|
|
||||||
return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '5px 8px', border: '1px solid #30363d', borderRadius: 5, fontSize: 11, background: '#0d1117' }}>
|
|
||||||
<span style={{ width: 6, height: 6, borderRadius: '50%', background: dot[state] || '#8b949e', flexShrink: 0 }} />
|
|
||||||
<span style={{ fontFamily: "'SF Mono','Fira Code',monospace", fontSize: 10 }}>{state}</span>
|
|
||||||
</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [data, setData] = useState<DashboardData | null>(null);
|
const [data, setData] = useState<DashboardData | null>(null);
|
||||||
const [time, setTime] = useState('');
|
const [time, setTime] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/dashboard');
|
const res = await fetch("/api/dashboard");
|
||||||
if (res.ok) setData(await res.json());
|
if (res.ok) setData(await res.json());
|
||||||
} catch { /* ignore */ }
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
const id = setInterval(fetchData, 15000);
|
const id = setInterval(fetchData, 15000);
|
||||||
@@ -172,194 +312,318 @@ export default function Dashboard() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tick = () => setTime(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
|
const tick = () =>
|
||||||
|
setTime(
|
||||||
|
new Date().toLocaleTimeString([], {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
}),
|
||||||
|
);
|
||||||
tick();
|
tick();
|
||||||
const id = setInterval(tick, 10000);
|
const id = setInterval(tick, 10000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const running = data?.services.filter(s => s.state === 'running').length ?? 0;
|
const running =
|
||||||
|
data?.services.filter((s) => s.state === "running").length ?? 0;
|
||||||
const degraded = (data?.services.length ?? 0) - running;
|
const degraded = (data?.services.length ?? 0) - running;
|
||||||
const hasNode = data?.node.cpu !== null || data?.node.ram !== null;
|
const hasNode = data?.node.cpu !== null || data?.node.ram !== null;
|
||||||
const hasTraffik = (data?.rps?.length ?? 0) > 0 || (data?.latency?.length ?? 0) > 0;
|
const hasTraffik =
|
||||||
const hasOTel = false;
|
(data?.rps?.length ?? 0) > 0 || (data?.latency?.length ?? 0) > 0;
|
||||||
const node = data?.node;
|
const node = data?.node;
|
||||||
const gaugeColor = (v: number | null) => {
|
const gaugeColor = (v: number | null) => {
|
||||||
if (v === null) return '#3fb950';
|
if (v === null) return "#3fb950";
|
||||||
if (v > 80) return '#f85149';
|
if (v > 80) return "#f85149";
|
||||||
if (v > 60) return '#d29922';
|
if (v > 60) return "#d29922";
|
||||||
return '#3fb950';
|
return "#3fb950";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: '#0d1117', color: '#e6edf3', minHeight: '100vh', fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif", lineHeight: 1.5 }}>
|
<div className="min-h-screen bg-background text-foreground">
|
||||||
<style>{`
|
<header className="sticky top-0 z-50 flex items-center justify-between gap-2 border-b bg-background/90 px-5 py-3 backdrop-blur-xl">
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
<div className="flex items-center gap-2.5">
|
||||||
@media(min-width:769px){.grid{grid-template-columns:repeat(auto-fill,minmax(320px,1fr))}}
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-400 to-purple-400 text-xs font-bold text-white">
|
||||||
.card{border:1px solid #30363d;border-radius:8px;padding:14px;background:#161b22;transition:border-color .2s}
|
H
|
||||||
.card:hover{border-color:#3d444d}
|
|
||||||
@media(max-width:480px){.card{padding:12px;border-radius:6px}}
|
|
||||||
.card-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;gap:8px}
|
|
||||||
.card-h h2{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#8b949e}
|
|
||||||
.card-h .badge{font-size:9px;background:#1c2333;padding:2px 8px;border-radius:8px;color:#8b949e;white-space:nowrap}
|
|
||||||
.card-c{display:flex;justify-content:center;align-items:center;width:100%}
|
|
||||||
::-webkit-scrollbar{width:8px}
|
|
||||||
::-webkit-scrollbar-thumb{background:#30363d;border-radius:4px}
|
|
||||||
.link{color:#58a6ff;text-decoration:none;font-size:10px;padding:4px 8px;border:1px solid #30363d;border-radius:5px;transition:all .15s;line-height:1.4}
|
|
||||||
.link:hover{background:#1c2333;border-color:#58a6ff}
|
|
||||||
.stat-box{background:#1c2333;border-radius:6px;padding:10px 6px;text-align:center}
|
|
||||||
.stat-val{font-size:20px;font-weight:700;font-family:'SF Mono','Fira Code',monospace;line-height:1.2}
|
|
||||||
.stat-lbl{font-size:9px;color:#8b949e;margin-top:2px;text-transform:uppercase;letter-spacing:.04em}
|
|
||||||
`}</style>
|
|
||||||
|
|
||||||
<header style={{
|
|
||||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
||||||
padding: '12px 20px', borderBottom: '1px solid #30363d',
|
|
||||||
background: 'rgba(22,27,34,.92)', backdropFilter: 'blur(12px)',
|
|
||||||
position: 'sticky', top: 0, zIndex: 100, gap: 8,
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
||||||
<span style={{
|
|
||||||
width: 28, height: 28, borderRadius: 6,
|
|
||||||
background: 'linear-gradient(135deg,#58a6ff,#bc8cff)',
|
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
fontWeight: 700, fontSize: 12, color: '#fff', flexShrink: 0,
|
|
||||||
}}>H</span>
|
|
||||||
<div>
|
|
||||||
<h1 style={{ fontSize: 16, fontWeight: 600 }}>Hub Dashboard</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
||||||
<span style={{
|
|
||||||
display: 'flex', alignItems: 'center', gap: 5, fontSize: 11,
|
|
||||||
padding: '4px 12px', borderRadius: 20, fontWeight: 500,
|
|
||||||
background: degraded ? 'rgba(210,153,34,.12)' : 'rgba(63,185,80,.12)',
|
|
||||||
color: degraded ? '#d29922' : '#3fb950',
|
|
||||||
}}>
|
|
||||||
<span style={{ width: 7, height: 7, borderRadius: '50%', background: degraded ? '#d29922' : '#3fb950' }} />
|
|
||||||
{degraded ? `${degraded} degraded` : 'All Systems Operational'}
|
|
||||||
</span>
|
</span>
|
||||||
<span style={{ color: '#6e7681', fontSize: 10 }}>{time}</span>
|
<h1 className="text-base font-semibold">Hub Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`h-auto gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${
|
||||||
|
degraded
|
||||||
|
? "border-amber-500/30 bg-amber-900/20 text-amber-500"
|
||||||
|
: "border-emerald-500/30 bg-emerald-900/20 text-emerald-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`h-1.5 w-1.5 rounded-full ${degraded ? "bg-amber-500" : "bg-emerald-400"}`}
|
||||||
|
/>
|
||||||
|
{degraded ? `${degraded} degraded` : "All Systems Operational"}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-[10px] text-muted-foreground">{time}</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div style={{ maxWidth: 1440, margin: '0 auto', padding: '12px 16px' }}>
|
<div className="mx-auto max-w-[1440px] px-4 py-3">
|
||||||
<div className="grid" style={{ display: 'grid', gap: 10 }}>
|
<div className="grid grid-cols-1 gap-2.5 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||||
{/* Services */}
|
{/* Services */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Services</h2><span className="badge">{data?.services.length ?? 0}</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
<CardTitle>Services</CardTitle>
|
||||||
{data?.services.map(s => (
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
<span key={s.name} style={{
|
{data?.services.length ?? 0}
|
||||||
display: 'inline-flex', alignItems: 'center', gap: 5,
|
</Badge>
|
||||||
padding: '5px 8px', border: '1px solid #30363d', borderRadius: 5, fontSize: 11,
|
</CardHeader>
|
||||||
}}>
|
<CardContent>
|
||||||
<span style={{
|
{data?.services.length ? (
|
||||||
width: 6, height: 6, borderRadius: '50%', flexShrink: 0,
|
<div className="flex flex-wrap gap-1">
|
||||||
background: s.state === 'running' ? '#3fb950' : s.state === 'jaeger' ? '#58a6ff' : '#f85149',
|
{data.services.map((s) => (
|
||||||
}} />
|
<Badge
|
||||||
<span style={{ fontFamily: "'SF Mono','Fira Code',monospace", fontSize: 10 }}>{s.name}</span>
|
key={s.name}
|
||||||
</span>
|
variant="outline"
|
||||||
)) || <span style={{ color: '#6e7681', padding: 16, textAlign: 'center', width: '100%', fontSize: 11 }}>No services detected</span>}
|
className="h-auto gap-1.5 px-2 py-1.5 text-xs font-normal"
|
||||||
</div>
|
>
|
||||||
</div>
|
<span
|
||||||
|
className={`h-1.5 w-1.5 shrink-0 rounded-full ${
|
||||||
|
s.state === "running"
|
||||||
|
? "bg-green-400"
|
||||||
|
: s.state === "jaeger"
|
||||||
|
? "bg-blue-400"
|
||||||
|
: "bg-red-400"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span className="font-mono text-[10px]">{s.name}</span>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="py-4 text-center text-[11px] text-muted-foreground">
|
||||||
|
No services detected
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Overview */}
|
{/* Overview */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Overview</h2></div>
|
<CardHeader>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
|
<CardTitle>Overview</CardTitle>
|
||||||
<div className="stat-box"><div className="stat-val" style={{ color: '#58a6ff' }}>{data?.services.length ?? '-'}</div><div className="stat-lbl">Total</div></div>
|
</CardHeader>
|
||||||
<div className="stat-box"><div className="stat-val" style={{ color: '#58a6ff' }}>{running}</div><div className="stat-lbl">Healthy</div></div>
|
<CardContent>
|
||||||
<div className="stat-box"><div className="stat-val" style={{ color: '#3fb950' }}>{data?.traces.length ?? 0}</div><div className="stat-lbl">Traces</div></div>
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
<div className="stat-box"><div className="stat-val" style={{ color: '#f85149' }}>0</div><div className="stat-lbl">Errors</div></div>
|
<div className="rounded-lg bg-muted/50 p-2.5 text-center">
|
||||||
</div>
|
<div className="font-mono text-lg font-bold leading-tight text-blue-400">
|
||||||
</div>
|
{data?.services.length ?? "-"}
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Total
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg bg-muted/50 p-2.5 text-center">
|
||||||
|
<div className="font-mono text-lg font-bold leading-tight text-blue-400">
|
||||||
|
{running}
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Healthy
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg bg-muted/50 p-2.5 text-center">
|
||||||
|
<div className="font-mono text-lg font-bold leading-tight text-green-400">
|
||||||
|
{data?.traces.length ?? 0}
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Traces
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg bg-muted/50 p-2.5 text-center">
|
||||||
|
<div className="font-mono text-lg font-bold leading-tight text-red-400">
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Errors
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Health */}
|
{/* Health */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Health</h2></div>
|
<CardHeader>
|
||||||
<div className="card-c"><Donut running={running} degraded={degraded} /></div>
|
<CardTitle>Health</CardTitle>
|
||||||
</div>
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center">
|
||||||
|
<Donut running={running} degraded={degraded} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Links */}
|
{/* Links */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Links</h2></div>
|
<CardHeader>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
<CardTitle>Links</CardTitle>
|
||||||
{data?.links.map(l => (
|
</CardHeader>
|
||||||
<a key={l.url} href={l.url} target="_blank" className="link">{l.label}</a>
|
<CardContent>
|
||||||
))}
|
<div className="flex flex-wrap gap-1">
|
||||||
</div>
|
{data?.links.map((l) => (
|
||||||
</div>
|
<a
|
||||||
|
key={l.url}
|
||||||
|
href={l.url}
|
||||||
|
target="_blank"
|
||||||
|
className="rounded-md border border-border px-2 py-1 text-[10px] text-blue-400 no-underline transition-colors hover:border-blue-400 hover:bg-muted"
|
||||||
|
>
|
||||||
|
{l.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Node Resources */}
|
{/* System Resources */}
|
||||||
{hasNode && (
|
{hasNode && (
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<h2>System Resources</h2>
|
<CardTitle>System Resources</CardTitle>
|
||||||
<span className="badge">{node?.load1?.toFixed(2)} {node?.load5?.toFixed(2)} {node?.load15?.toFixed(2)}</span>
|
<Badge
|
||||||
</div>
|
variant="secondary"
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(160px,1fr))', gap: 6 }}>
|
className="font-mono text-[10px] font-normal"
|
||||||
<div className="card-c"><Gauge pct={node?.cpu ?? null} color={gaugeColor(node?.cpu ?? null)} label="CPU Usage" unit="%" /></div>
|
>
|
||||||
<div className="card-c"><Gauge pct={node?.ram ?? null} color={gaugeColor(node?.ram ?? null)} label="Memory Usage" unit="%" /></div>
|
{node?.load1?.toFixed(2)} {node?.load5?.toFixed(2)}{" "}
|
||||||
<div className="card-c"><Gauge pct={node?.disk ?? null} color={gaugeColor(node?.disk ?? null)} label="Disk Usage" unit="%" /></div>
|
{node?.load15?.toFixed(2)}
|
||||||
</div>
|
</Badge>
|
||||||
</div>
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap justify-center gap-1.5">
|
||||||
|
<Gauge
|
||||||
|
pct={node?.cpu ?? null}
|
||||||
|
color={gaugeColor(node?.cpu ?? null)}
|
||||||
|
label="CPU Usage"
|
||||||
|
unit="%"
|
||||||
|
/>
|
||||||
|
<Gauge
|
||||||
|
pct={node?.ram ?? null}
|
||||||
|
color={gaugeColor(node?.ram ?? null)}
|
||||||
|
label="Memory Usage"
|
||||||
|
unit="%"
|
||||||
|
/>
|
||||||
|
<Gauge
|
||||||
|
pct={node?.disk ?? null}
|
||||||
|
color={gaugeColor(node?.disk ?? null)}
|
||||||
|
label="Disk Usage"
|
||||||
|
unit="%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Request Rate */}
|
{/* Request Rate */}
|
||||||
{hasTraffik && (
|
{hasTraffik && (
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Request Rate</h2><span className="badge">{data?.rps?.length ? `${data.rps[data.rps.length - 1].toFixed(1)}/s` : '-'}</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="card-c"><Sparkline data={data?.rps ?? []} color="#58a6ff" /></div>
|
<CardTitle>Request Rate</CardTitle>
|
||||||
</div>
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
|
{data?.rps?.length
|
||||||
|
? `${data.rps[data.rps.length - 1].toFixed(1)}/s`
|
||||||
|
: "-"}
|
||||||
|
</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center">
|
||||||
|
<Sparkline data={data?.rps ?? []} color="#58a6ff" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Latency */}
|
{/* Latency */}
|
||||||
{hasTraffik && (
|
{hasTraffik && (
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Latency</h2><span className="badge">{data?.latency?.length ? `${data.latency[data.latency.length - 1].toFixed(0)}ms` : '-'}</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="card-c"><Sparkline data={data?.latency ?? []} color="#bc8cff" /></div>
|
<CardTitle>Latency</CardTitle>
|
||||||
</div>
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
|
{data?.latency?.length
|
||||||
|
? `${data.latency[data.latency.length - 1].toFixed(0)}ms`
|
||||||
|
: "-"}
|
||||||
|
</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center">
|
||||||
|
<Sparkline data={data?.latency ?? []} color="#bc8cff" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Error Rate */}
|
{/* Error Rate */}
|
||||||
{hasTraffik && (
|
{hasTraffik && (
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Error Rate</h2><span className="badge">{data?.errors?.length ? `${data.errors[data.errors.length - 1].toFixed(1)}/s` : '-'}</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="card-c"><Sparkline data={data?.errors ?? []} color="#f85149" /></div>
|
<CardTitle>Error Rate</CardTitle>
|
||||||
</div>
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
|
{data?.errors?.length
|
||||||
|
? `${data.errors[data.errors.length - 1].toFixed(1)}/s`
|
||||||
|
: "-"}
|
||||||
|
</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center">
|
||||||
|
<Sparkline data={data?.errors ?? []} color="#f85149" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Trace Volume */}
|
{/* Trace Volume */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Trace Volume</h2><span className="badge">{data?.traces.length ?? 0} traces</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="card-c"><Sparkline data={data?.traceVolume ?? []} color="#3fb950" /></div>
|
<CardTitle>Trace Volume</CardTitle>
|
||||||
</div>
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
|
{data?.traces.length ?? 0} traces
|
||||||
|
</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center">
|
||||||
|
<Sparkline data={data?.traceVolume ?? []} color="#3fb950" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Traces */}
|
{/* Recent Traces */}
|
||||||
<div className="card">
|
<Card>
|
||||||
<div className="card-h"><h2>Recent Traces</h2><span className="badge">{data?.traces.length ?? 0}</span></div>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
{data?.traces.length ? (
|
<CardTitle>Recent Traces</CardTitle>
|
||||||
<ul style={{ listStyle: 'none' }}>
|
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||||
{data.traces.map((t, i) => (
|
{data?.traces.length ?? 0}
|
||||||
<li key={i} style={{
|
</Badge>
|
||||||
padding: '6px 0', borderBottom: '1px solid #21262d',
|
</CardHeader>
|
||||||
fontSize: 11, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6,
|
<CardContent>
|
||||||
}}>
|
{data?.traces.length ? (
|
||||||
<div style={{ overflow: 'hidden', minWidth: 0 }}>
|
<ul className="list-none">
|
||||||
<div style={{ fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.service}</div>
|
{data.traces.map((t, i) => (
|
||||||
<div style={{ color: '#8b949e', fontSize: 10, fontFamily: "'SF Mono','Fira Code',monospace", overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 200 }}>{t.operation}</div>
|
<li
|
||||||
</div>
|
key={i}
|
||||||
<div style={{ display: 'flex', gap: 8, fontSize: 10, color: '#8b949e', flexShrink: 0, alignItems: 'center' }}>
|
className="flex items-center justify-between gap-1.5 border-b border-border py-1.5 text-[11px] last:border-0"
|
||||||
<span style={{ fontFamily: "'SF Mono',monospace", fontWeight: 500, color: '#58a6ff' }}>{safeDur(t.duration)}</span>
|
>
|
||||||
<span>{t.spans}</span>
|
<div className="min-w-0">
|
||||||
{t.hasError && <span style={{ color: '#f85149', padding: '1px 5px', borderRadius: 3, background: 'rgba(248,81,73,.1)', fontSize: 9 }}>err</span>}
|
<div className="truncate font-medium">{t.service}</div>
|
||||||
</div>
|
<div className="max-w-[200px] truncate font-mono text-[10px] text-muted-foreground">
|
||||||
</li>
|
{t.operation}
|
||||||
))}
|
</div>
|
||||||
</ul>
|
</div>
|
||||||
) : <div style={{ textAlign: 'center', padding: 16, color: '#6e7681', fontSize: 11 }}>No traces — data appears once services send OTel telemetry</div>}
|
<div className="flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground">
|
||||||
</div>
|
<span className="font-mono font-medium text-blue-400">
|
||||||
|
{safeDur(t.duration)}
|
||||||
|
</span>
|
||||||
|
<span>{t.spans}</span>
|
||||||
|
{t.hasError && (
|
||||||
|
<span className="rounded-sm bg-red-500/10 px-1.5 py-0.5 text-[9px] text-red-500">
|
||||||
|
err
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p className="py-4 text-center text-[11px] text-muted-foreground">
|
||||||
|
No traces — data appears once services send OTel telemetry
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,25 +1,25 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
|
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
||||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
|
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogOverlay({
|
function AlertDialogOverlay({
|
||||||
@@ -31,11 +31,11 @@ function AlertDialogOverlay({
|
|||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogContent({
|
function AlertDialogContent({
|
||||||
@@ -43,7 +43,7 @@ function AlertDialogContent({
|
|||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: AlertDialogPrimitive.Popup.Props & {
|
}: AlertDialogPrimitive.Popup.Props & {
|
||||||
size?: "default" | "sm"
|
size?: "default" | "sm";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
@@ -53,12 +53,12 @@ function AlertDialogContent({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</AlertDialogPortal>
|
</AlertDialogPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogHeader({
|
function AlertDialogHeader({
|
||||||
@@ -70,11 +70,11 @@ function AlertDialogHeader({
|
|||||||
data-slot="alert-dialog-header"
|
data-slot="alert-dialog-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogFooter({
|
function AlertDialogFooter({
|
||||||
@@ -86,11 +86,11 @@ function AlertDialogFooter({
|
|||||||
data-slot="alert-dialog-footer"
|
data-slot="alert-dialog-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogMedia({
|
function AlertDialogMedia({
|
||||||
@@ -102,11 +102,11 @@ function AlertDialogMedia({
|
|||||||
data-slot="alert-dialog-media"
|
data-slot="alert-dialog-media"
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogTitle({
|
function AlertDialogTitle({
|
||||||
@@ -118,11 +118,11 @@ function AlertDialogTitle({
|
|||||||
data-slot="alert-dialog-title"
|
data-slot="alert-dialog-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogDescription({
|
function AlertDialogDescription({
|
||||||
@@ -134,11 +134,11 @@ function AlertDialogDescription({
|
|||||||
data-slot="alert-dialog-description"
|
data-slot="alert-dialog-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogAction({
|
function AlertDialogAction({
|
||||||
@@ -151,7 +151,7 @@ function AlertDialogAction({
|
|||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogCancel({
|
function AlertDialogCancel({
|
||||||
@@ -168,7 +168,7 @@ function AlertDialogCancel({
|
|||||||
render={<Button variant={variant} size={size} />}
|
render={<Button variant={variant} size={size} />}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -184,4 +184,4 @@ export {
|
|||||||
AlertDialogPortal,
|
AlertDialogPortal,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
}
|
};
|
||||||
|
|||||||
+12
-12
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
||||||
@@ -16,8 +16,8 @@ const alertVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Alert({
|
function Alert({
|
||||||
className,
|
className,
|
||||||
@@ -31,7 +31,7 @@ function Alert({
|
|||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
|
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDescription({
|
function AlertDescription({
|
||||||
@@ -56,11 +56,11 @@ function AlertDescription({
|
|||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -70,7 +70,7 @@ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("absolute top-2 right-2", className)}
|
className={cn("absolute top-2 right-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription, AlertAction }
|
export { Alert, AlertTitle, AlertDescription, AlertAction };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function AspectRatio({
|
function AspectRatio({
|
||||||
ratio,
|
ratio,
|
||||||
@@ -16,7 +16,7 @@ function AspectRatio({
|
|||||||
className={cn("relative aspect-(--ratio)", className)}
|
className={cn("relative aspect-(--ratio)", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AspectRatio }
|
export { AspectRatio };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const attachmentVariants = cva(
|
const attachmentVariants = cva(
|
||||||
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
|
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
|
||||||
@@ -21,8 +21,8 @@ const attachmentVariants = cva(
|
|||||||
vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",
|
vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Attachment({
|
function Attachment({
|
||||||
className,
|
className,
|
||||||
@@ -32,7 +32,7 @@ function Attachment({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
VariantProps<typeof attachmentVariants> & {
|
VariantProps<typeof attachmentVariants> & {
|
||||||
state?: "idle" | "uploading" | "processing" | "error" | "done"
|
state?: "idle" | "uploading" | "processing" | "error" | "done";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -43,7 +43,7 @@ function Attachment({
|
|||||||
className={cn(attachmentVariants({ size, orientation }), className)}
|
className={cn(attachmentVariants({ size, orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentMediaVariants = cva(
|
const attachmentMediaVariants = cva(
|
||||||
@@ -59,8 +59,8 @@ const attachmentMediaVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "icon",
|
variant: "icon",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function AttachmentMedia({
|
function AttachmentMedia({
|
||||||
className,
|
className,
|
||||||
@@ -74,7 +74,7 @@ function AttachmentMedia({
|
|||||||
className={cn(attachmentMediaVariants({ variant }), className)}
|
className={cn(attachmentMediaVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentContent({
|
function AttachmentContent({
|
||||||
@@ -86,11 +86,11 @@ function AttachmentContent({
|
|||||||
data-slot="attachment-content"
|
data-slot="attachment-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
|
"max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentTitle({
|
function AttachmentTitle({
|
||||||
@@ -102,11 +102,11 @@ function AttachmentTitle({
|
|||||||
data-slot="attachment-title"
|
data-slot="attachment-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
|
"block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentDescription({
|
function AttachmentDescription({
|
||||||
@@ -119,11 +119,11 @@ function AttachmentDescription({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
|
"mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
|
||||||
"max-w-full",
|
"max-w-full",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentActions({
|
function AttachmentActions({
|
||||||
@@ -135,11 +135,11 @@ function AttachmentActions({
|
|||||||
data-slot="attachment-actions"
|
data-slot="attachment-actions"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",
|
"relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentAction({
|
function AttachmentAction({
|
||||||
@@ -156,7 +156,7 @@ function AttachmentAction({
|
|||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentTrigger({
|
function AttachmentTrigger({
|
||||||
@@ -172,13 +172,13 @@ function AttachmentTrigger({
|
|||||||
type: render ? type : (type ?? "button"),
|
type: render ? type : (type ?? "button"),
|
||||||
className: cn("absolute inset-0 z-10 outline-none", className),
|
className: cn("absolute inset-0 z-10 outline-none", className),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "attachment-trigger",
|
slot: "attachment-trigger",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -187,11 +187,11 @@ function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="attachment-group"
|
data-slot="attachment-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
|
"flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -204,4 +204,4 @@ export {
|
|||||||
AttachmentActions,
|
AttachmentActions,
|
||||||
AttachmentAction,
|
AttachmentAction,
|
||||||
AttachmentTrigger,
|
AttachmentTrigger,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
|
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Avatar({
|
function Avatar({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: AvatarPrimitive.Root.Props & {
|
}: AvatarPrimitive.Root.Props & {
|
||||||
size?: "default" | "sm" | "lg"
|
size?: "default" | "sm" | "lg";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
@@ -18,11 +18,11 @@ function Avatar({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
|
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
||||||
@@ -31,11 +31,11 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|||||||
data-slot="avatar-image"
|
data-slot="avatar-image"
|
||||||
className={cn(
|
className={cn(
|
||||||
"aspect-square size-full rounded-full object-cover",
|
"aspect-square size-full rounded-full object-cover",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarFallback({
|
function AvatarFallback({
|
||||||
@@ -47,11 +47,11 @@ function AvatarFallback({
|
|||||||
data-slot="avatar-fallback"
|
data-slot="avatar-fallback"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -63,11 +63,11 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
||||||
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
||||||
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -76,11 +76,11 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="avatar-group"
|
data-slot="avatar-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarGroupCount({
|
function AvatarGroupCount({
|
||||||
@@ -92,11 +92,11 @@ function AvatarGroupCount({
|
|||||||
data-slot="avatar-group-count"
|
data-slot="avatar-group-count"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -106,4 +106,4 @@ export {
|
|||||||
AvatarGroup,
|
AvatarGroup,
|
||||||
AvatarGroupCount,
|
AvatarGroupCount,
|
||||||
AvatarBadge,
|
AvatarBadge,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||||
@@ -24,8 +24,8 @@ const badgeVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Badge({
|
function Badge({
|
||||||
className,
|
className,
|
||||||
@@ -39,14 +39,14 @@ function Badge({
|
|||||||
{
|
{
|
||||||
className: cn(badgeVariants({ variant }), className),
|
className: cn(badgeVariants({ variant }), className),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "badge",
|
slot: "badge",
|
||||||
variant,
|
variant,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
export { Badge, badgeVariants };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
|
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
|
||||||
|
|
||||||
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return (
|
return (
|
||||||
@@ -13,7 +13,7 @@ function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
|||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
@@ -22,11 +22,11 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
|||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground",
|
"flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
@@ -36,7 +36,7 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
|||||||
className={cn("inline-flex items-center gap-1", className)}
|
className={cn("inline-flex items-center gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbLink({
|
function BreadcrumbLink({
|
||||||
@@ -50,13 +50,13 @@ function BreadcrumbLink({
|
|||||||
{
|
{
|
||||||
className: cn("transition-colors hover:text-foreground", className),
|
className: cn("transition-colors hover:text-foreground", className),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "breadcrumb-link",
|
slot: "breadcrumb-link",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -69,7 +69,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
className={cn("font-normal text-foreground", className)}
|
className={cn("font-normal text-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbSeparator({
|
function BreadcrumbSeparator({
|
||||||
@@ -85,11 +85,9 @@ function BreadcrumbSeparator({
|
|||||||
className={cn("[&>svg]:size-3.5", className)}
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? <ChevronRightIcon />}
|
||||||
<ChevronRightIcon />
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbEllipsis({
|
function BreadcrumbEllipsis({
|
||||||
@@ -103,15 +101,14 @@ function BreadcrumbEllipsis({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex size-5 items-center justify-center [&>svg]:size-4",
|
"flex size-5 items-center justify-center [&>svg]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon
|
<MoreHorizontalIcon />
|
||||||
/>
|
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">More</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -122,4 +119,4 @@ export {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -12,7 +12,7 @@ function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex min-w-0 flex-col gap-2", className)}
|
className={cn("flex min-w-0 flex-col gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bubbleVariants = cva(
|
const bubbleVariants = cva(
|
||||||
@@ -39,8 +39,8 @@ const bubbleVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Bubble({
|
function Bubble({
|
||||||
variant = "default",
|
variant = "default",
|
||||||
@@ -49,7 +49,7 @@ function Bubble({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
VariantProps<typeof bubbleVariants> & {
|
VariantProps<typeof bubbleVariants> & {
|
||||||
align?: "start" | "end"
|
align?: "start" | "end";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -59,7 +59,7 @@ function Bubble({
|
|||||||
className={cn(bubbleVariants({ variant }), className)}
|
className={cn(bubbleVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BubbleContent({
|
function BubbleContent({
|
||||||
@@ -73,16 +73,16 @@ function BubbleContent({
|
|||||||
{
|
{
|
||||||
className: cn(
|
className: cn(
|
||||||
"w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
"w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
||||||
className
|
className,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "bubble-content",
|
slot: "bubble-content",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bubbleReactionsVariants = cva(
|
const bubbleReactionsVariants = cva(
|
||||||
@@ -102,8 +102,8 @@ const bubbleReactionsVariants = cva(
|
|||||||
side: "bottom",
|
side: "bottom",
|
||||||
align: "end",
|
align: "end",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function BubbleReactions({
|
function BubbleReactions({
|
||||||
side = "bottom",
|
side = "bottom",
|
||||||
@@ -111,8 +111,8 @@ function BubbleReactions({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
align?: "start" | "end"
|
align?: "start" | "end";
|
||||||
side?: "top" | "bottom"
|
side?: "top" | "bottom";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -122,7 +122,7 @@ function BubbleReactions({
|
|||||||
className={cn(bubbleReactionsVariants({ side, align }), className)}
|
className={cn(bubbleReactionsVariants({ side, align }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }
|
export { BubbleGroup, Bubble, BubbleContent, BubbleReactions };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
const buttonGroupVariants = cva(
|
const buttonGroupVariants = cva(
|
||||||
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
||||||
@@ -19,8 +19,8 @@ const buttonGroupVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
orientation: "horizontal",
|
orientation: "horizontal",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function ButtonGroup({
|
function ButtonGroup({
|
||||||
className,
|
className,
|
||||||
@@ -35,7 +35,7 @@ function ButtonGroup({
|
|||||||
className={cn(buttonGroupVariants({ orientation }), className)}
|
className={cn(buttonGroupVariants({ orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupText({
|
function ButtonGroupText({
|
||||||
@@ -49,16 +49,16 @@ function ButtonGroupText({
|
|||||||
{
|
{
|
||||||
className: cn(
|
className: cn(
|
||||||
"flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "button-group-text",
|
slot: "button-group-text",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupSeparator({
|
function ButtonGroupSeparator({
|
||||||
@@ -72,11 +72,11 @@ function ButtonGroupSeparator({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
|
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -84,4 +84,4 @@ export {
|
|||||||
ButtonGroupSeparator,
|
ButtonGroupSeparator,
|
||||||
ButtonGroupText,
|
ButtonGroupText,
|
||||||
buttonGroupVariants,
|
buttonGroupVariants,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15
-15
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Card({
|
function Card({
|
||||||
className,
|
className,
|
||||||
@@ -13,11 +13,11 @@ function Card({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -26,11 +26,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -39,11 +39,11 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-title"
|
data-slot="card-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -53,7 +53,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -62,11 +62,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-action"
|
data-slot="card-action"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -76,7 +76,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("px-(--card-spacing)", className)}
|
className={cn("px-(--card-spacing)", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -85,11 +85,11 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
|
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -100,4 +100,4 @@ export {
|
|||||||
CardAction,
|
CardAction,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react"
|
} from "embla-carousel-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselApi = UseEmblaCarouselType[1];
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||||
type CarouselOptions = UseCarouselParameters[0]
|
type CarouselOptions = UseCarouselParameters[0];
|
||||||
type CarouselPlugin = UseCarouselParameters[1]
|
type CarouselPlugin = UseCarouselParameters[1];
|
||||||
|
|
||||||
type CarouselProps = {
|
type CarouselProps = {
|
||||||
opts?: CarouselOptions
|
opts?: CarouselOptions;
|
||||||
plugins?: CarouselPlugin
|
plugins?: CarouselPlugin;
|
||||||
orientation?: "horizontal" | "vertical"
|
orientation?: "horizontal" | "vertical";
|
||||||
setApi?: (api: CarouselApi) => void
|
setApi?: (api: CarouselApi) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
type CarouselContextProps = {
|
type CarouselContextProps = {
|
||||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
||||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
api: ReturnType<typeof useEmblaCarousel>[1];
|
||||||
scrollPrev: () => void
|
scrollPrev: () => void;
|
||||||
scrollNext: () => void
|
scrollNext: () => void;
|
||||||
canScrollPrev: boolean
|
canScrollPrev: boolean;
|
||||||
canScrollNext: boolean
|
canScrollNext: boolean;
|
||||||
} & CarouselProps
|
} & CarouselProps;
|
||||||
|
|
||||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
||||||
|
|
||||||
function useCarousel() {
|
function useCarousel() {
|
||||||
const context = React.useContext(CarouselContext)
|
const context = React.useContext(CarouselContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useCarousel must be used within a <Carousel />")
|
throw new Error("useCarousel must be used within a <Carousel />");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Carousel({
|
function Carousel({
|
||||||
@@ -56,53 +56,53 @@ function Carousel({
|
|||||||
...opts,
|
...opts,
|
||||||
axis: orientation === "horizontal" ? "x" : "y",
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
},
|
},
|
||||||
plugins
|
plugins,
|
||||||
)
|
);
|
||||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||||
|
|
||||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
if (!api) return
|
if (!api) return;
|
||||||
setCanScrollPrev(api.canScrollPrev())
|
setCanScrollPrev(api.canScrollPrev());
|
||||||
setCanScrollNext(api.canScrollNext())
|
setCanScrollNext(api.canScrollNext());
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const scrollPrev = React.useCallback(() => {
|
const scrollPrev = React.useCallback(() => {
|
||||||
api?.scrollPrev()
|
api?.scrollPrev();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const scrollNext = React.useCallback(() => {
|
const scrollNext = React.useCallback(() => {
|
||||||
api?.scrollNext()
|
api?.scrollNext();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const handleKeyDown = React.useCallback(
|
const handleKeyDown = React.useCallback(
|
||||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === "ArrowLeft") {
|
if (event.key === "ArrowLeft") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollPrev()
|
scrollPrev();
|
||||||
} else if (event.key === "ArrowRight") {
|
} else if (event.key === "ArrowRight") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollNext()
|
scrollNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollPrev, scrollNext]
|
[scrollPrev, scrollNext],
|
||||||
)
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api || !setApi) return
|
if (!api || !setApi) return;
|
||||||
setApi(api)
|
setApi(api);
|
||||||
}, [api, setApi])
|
}, [api, setApi]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api) return
|
if (!api) return;
|
||||||
onSelect(api)
|
onSelect(api);
|
||||||
api.on("reInit", onSelect)
|
api.on("reInit", onSelect);
|
||||||
api.on("select", onSelect)
|
api.on("select", onSelect);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
api?.off("select", onSelect)
|
api?.off("select", onSelect);
|
||||||
}
|
};
|
||||||
}, [api, onSelect])
|
}, [api, onSelect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarouselContext.Provider
|
<CarouselContext.Provider
|
||||||
@@ -129,11 +129,11 @@ function Carousel({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CarouselContext.Provider>
|
</CarouselContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { carouselRef, orientation } = useCarousel()
|
const { carouselRef, orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -145,16 +145,16 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex",
|
"flex",
|
||||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { orientation } = useCarousel()
|
const { orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -164,11 +164,11 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 shrink-0 grow-0 basis-full",
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselPrevious({
|
function CarouselPrevious({
|
||||||
@@ -177,7 +177,7 @@ function CarouselPrevious({
|
|||||||
size = "icon-sm",
|
size = "icon-sm",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -189,7 +189,7 @@ function CarouselPrevious({
|
|||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "inset-y-0 -left-12 my-auto"
|
? "inset-y-0 -left-12 my-auto"
|
||||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollPrev}
|
disabled={!canScrollPrev}
|
||||||
onClick={scrollPrev}
|
onClick={scrollPrev}
|
||||||
@@ -198,7 +198,7 @@ function CarouselPrevious({
|
|||||||
<ChevronLeftIcon />
|
<ChevronLeftIcon />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselNext({
|
function CarouselNext({
|
||||||
@@ -207,7 +207,7 @@ function CarouselNext({
|
|||||||
size = "icon-sm",
|
size = "icon-sm",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -219,7 +219,7 @@ function CarouselNext({
|
|||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "inset-y-0 -right-12 my-auto"
|
? "inset-y-0 -right-12 my-auto"
|
||||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollNext}
|
disabled={!canScrollNext}
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
@@ -228,7 +228,7 @@ function CarouselNext({
|
|||||||
<ChevronRightIcon />
|
<ChevronRightIcon />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -239,4 +239,4 @@ export {
|
|||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
useCarousel,
|
useCarousel,
|
||||||
}
|
};
|
||||||
|
|||||||
+80
-80
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,21 +1,21 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"
|
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
|
||||||
|
|
||||||
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
||||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
|
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
|
||||||
return (
|
return (
|
||||||
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
|
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
|
||||||
return (
|
return (
|
||||||
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
|
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,18 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
|
||||||
InputGroup,
|
import { SearchIcon, CheckIcon } from "lucide-react";
|
||||||
InputGroupAddon,
|
|
||||||
} from "@/components/ui/input-group"
|
|
||||||
import { SearchIcon, CheckIcon } from "lucide-react"
|
|
||||||
|
|
||||||
function Command({
|
function Command({
|
||||||
className,
|
className,
|
||||||
@@ -26,11 +23,11 @@ function Command({
|
|||||||
data-slot="command"
|
data-slot="command"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground",
|
"flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandDialog({
|
function CommandDialog({
|
||||||
@@ -41,11 +38,11 @@ function CommandDialog({
|
|||||||
showCloseButton = false,
|
showCloseButton = false,
|
||||||
...props
|
...props
|
||||||
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
||||||
title?: string
|
title?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
className?: string
|
className?: string;
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean;
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
@@ -56,14 +53,14 @@ function CommandDialog({
|
|||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0",
|
"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
showCloseButton={showCloseButton}
|
showCloseButton={showCloseButton}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandInput({
|
function CommandInput({
|
||||||
@@ -77,7 +74,7 @@ function CommandInput({
|
|||||||
data-slot="command-input"
|
data-slot="command-input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -86,7 +83,7 @@ function CommandInput({
|
|||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandList({
|
function CommandList({
|
||||||
@@ -98,11 +95,11 @@ function CommandList({
|
|||||||
data-slot="command-list"
|
data-slot="command-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
|
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandEmpty({
|
function CommandEmpty({
|
||||||
@@ -115,7 +112,7 @@ function CommandEmpty({
|
|||||||
className={cn("py-6 text-center text-sm", className)}
|
className={cn("py-6 text-center text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandGroup({
|
function CommandGroup({
|
||||||
@@ -127,11 +124,11 @@ function CommandGroup({
|
|||||||
data-slot="command-group"
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandSeparator({
|
function CommandSeparator({
|
||||||
@@ -144,7 +141,7 @@ function CommandSeparator({
|
|||||||
className={cn("-mx-1 h-px bg-border", className)}
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandItem({
|
function CommandItem({
|
||||||
@@ -157,14 +154,14 @@ function CommandItem({
|
|||||||
data-slot="command-item"
|
data-slot="command-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
|
"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
|
<CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
|
||||||
</CommandPrimitive.Item>
|
</CommandPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandShortcut({
|
function CommandShortcut({
|
||||||
@@ -176,11 +173,11 @@ function CommandShortcut({
|
|||||||
data-slot="command-shortcut"
|
data-slot="command-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
|
"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -193,4 +190,4 @@ export {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,26 +1,26 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { XIcon } from "lucide-react"
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
||||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
||||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogOverlay({
|
function DialogOverlay({
|
||||||
@@ -32,11 +32,11 @@ function DialogOverlay({
|
|||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogContent({
|
function DialogContent({
|
||||||
@@ -45,7 +45,7 @@ function DialogContent({
|
|||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: DialogPrimitive.Popup.Props & {
|
}: DialogPrimitive.Popup.Props & {
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
@@ -54,7 +54,7 @@ function DialogContent({
|
|||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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 bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"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 bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -70,14 +70,13 @@ function DialogContent({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XIcon
|
<XIcon />
|
||||||
/>
|
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</DialogPrimitive.Popup>
|
</DialogPrimitive.Popup>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -87,7 +86,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex flex-col gap-2", className)}
|
className={cn("flex flex-col gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogFooter({
|
function DialogFooter({
|
||||||
@@ -96,14 +95,14 @@ function DialogFooter({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-footer"
|
data-slot="dialog-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
|
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -114,7 +113,7 @@ function DialogFooter({
|
|||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
||||||
@@ -123,11 +122,11 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|||||||
data-slot="dialog-title"
|
data-slot="dialog-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-heading text-base leading-none font-medium",
|
"font-heading text-base leading-none font-medium",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogDescription({
|
function DialogDescription({
|
||||||
@@ -139,11 +138,11 @@ function DialogDescription({
|
|||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -157,4 +156,4 @@ export {
|
|||||||
DialogPortal,
|
DialogPortal,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DirectionProvider,
|
DirectionProvider,
|
||||||
useDirection,
|
useDirection,
|
||||||
} from "@base-ui/react/direction-provider"
|
} from "@base-ui/react/direction-provider";
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15
-15
@@ -1,6 +1,6 @@
|
|||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,11 +8,11 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty"
|
data-slot="empty"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
|
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -22,7 +22,7 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex max-w-sm flex-col items-center gap-2", className)}
|
className={cn("flex max-w-sm flex-col items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyMediaVariants = cva(
|
const emptyMediaVariants = cva(
|
||||||
@@ -37,8 +37,8 @@ const emptyMediaVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function EmptyMedia({
|
function EmptyMedia({
|
||||||
className,
|
className,
|
||||||
@@ -52,7 +52,7 @@ function EmptyMedia({
|
|||||||
className={cn(emptyMediaVariants({ variant, className }))}
|
className={cn(emptyMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -61,11 +61,11 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty-title"
|
data-slot="empty-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-heading text-sm font-medium tracking-tight",
|
"font-heading text-sm font-medium tracking-tight",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -74,11 +74,11 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
data-slot="empty-description"
|
data-slot="empty-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -87,11 +87,11 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty-content"
|
data-slot="empty-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance",
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -101,4 +101,4 @@ export {
|
|||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
EmptyContent,
|
EmptyContent,
|
||||||
EmptyMedia,
|
EmptyMedia,
|
||||||
}
|
};
|
||||||
|
|||||||
+37
-37
@@ -1,11 +1,11 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
||||||
return (
|
return (
|
||||||
@@ -13,11 +13,11 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|||||||
data-slot="field-set"
|
data-slot="field-set"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldLegend({
|
function FieldLegend({
|
||||||
@@ -31,11 +31,11 @@ function FieldLegend({
|
|||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -44,11 +44,11 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-group"
|
data-slot="field-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldVariants = cva(
|
const fieldVariants = cva(
|
||||||
@@ -66,8 +66,8 @@ const fieldVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
orientation: "vertical",
|
orientation: "vertical",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Field({
|
function Field({
|
||||||
className,
|
className,
|
||||||
@@ -82,7 +82,7 @@ function Field({
|
|||||||
className={cn(fieldVariants({ orientation }), className)}
|
className={cn(fieldVariants({ orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -91,11 +91,11 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-content"
|
data-slot="field-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldLabel({
|
function FieldLabel({
|
||||||
@@ -108,11 +108,11 @@ function FieldLabel({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
||||||
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -121,11 +121,11 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-label"
|
data-slot="field-label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
|
"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -136,11 +136,11 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
||||||
"last:mt-0 nth-last-2:-mt-1",
|
"last:mt-0 nth-last-2:-mt-1",
|
||||||
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldSeparator({
|
function FieldSeparator({
|
||||||
@@ -148,7 +148,7 @@ function FieldSeparator({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -156,7 +156,7 @@ function FieldSeparator({
|
|||||||
data-content={!!children}
|
data-content={!!children}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -170,7 +170,7 @@ function FieldSeparator({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldError({
|
function FieldError({
|
||||||
@@ -179,37 +179,37 @@ function FieldError({
|
|||||||
errors,
|
errors,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
errors?: Array<{ message?: string } | undefined>
|
errors?: Array<{ message?: string } | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (children) {
|
if (children) {
|
||||||
return children
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errors?.length) {
|
if (!errors?.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueErrors = [
|
const uniqueErrors = [
|
||||||
...new Map(errors.map((error) => [error?.message, error])).values(),
|
...new Map(errors.map((error) => [error?.message, error])).values(),
|
||||||
]
|
];
|
||||||
|
|
||||||
if (uniqueErrors?.length == 1) {
|
if (uniqueErrors?.length == 1) {
|
||||||
return uniqueErrors[0]?.message
|
return uniqueErrors[0]?.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="ml-4 flex list-disc flex-col gap-1">
|
<ul className="ml-4 flex list-disc flex-col gap-1">
|
||||||
{uniqueErrors.map(
|
{uniqueErrors.map(
|
||||||
(error, index) =>
|
(error, index) =>
|
||||||
error?.message && <li key={index}>{error.message}</li>
|
error?.message && <li key={index}>{error.message}</li>,
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)
|
);
|
||||||
}, [children, errors])
|
}, [children, errors]);
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -221,7 +221,7 @@ function FieldError({
|
|||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -235,4 +235,4 @@ export {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
FieldContent,
|
FieldContent,
|
||||||
FieldTitle,
|
FieldTitle,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+28
-28
@@ -1,10 +1,10 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -13,11 +13,11 @@ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-group"
|
data-slot="item-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
|
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemSeparator({
|
function ItemSeparator({
|
||||||
@@ -31,7 +31,7 @@ function ItemSeparator({
|
|||||||
className={cn("my-2", className)}
|
className={cn("my-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemVariants = cva(
|
const itemVariants = cva(
|
||||||
@@ -53,8 +53,8 @@ const itemVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Item({
|
function Item({
|
||||||
className,
|
className,
|
||||||
@@ -69,7 +69,7 @@ function Item({
|
|||||||
{
|
{
|
||||||
className: cn(itemVariants({ variant, size, className })),
|
className: cn(itemVariants({ variant, size, className })),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
@@ -77,7 +77,7 @@ function Item({
|
|||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemMediaVariants = cva(
|
const itemMediaVariants = cva(
|
||||||
@@ -94,8 +94,8 @@ const itemMediaVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function ItemMedia({
|
function ItemMedia({
|
||||||
className,
|
className,
|
||||||
@@ -109,7 +109,7 @@ function ItemMedia({
|
|||||||
className={cn(itemMediaVariants({ variant, className }))}
|
className={cn(itemMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -118,11 +118,11 @@ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-content"
|
data-slot="item-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
|
"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -131,11 +131,11 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-title"
|
data-slot="item-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
|
"line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -144,11 +144,11 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
data-slot="item-description"
|
data-slot="item-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
"line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -158,7 +158,7 @@ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex items-center gap-2", className)}
|
className={cn("flex items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -167,11 +167,11 @@ function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-header"
|
data-slot="item-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -180,11 +180,11 @@ function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-footer"
|
data-slot="item-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -198,4 +198,4 @@ export {
|
|||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemHeader,
|
ItemHeader,
|
||||||
ItemFooter,
|
ItemFooter,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
||||||
return (
|
return (
|
||||||
@@ -6,11 +6,11 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|||||||
data-slot="kbd"
|
data-slot="kbd"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
|
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -20,7 +20,7 @@ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("inline-flex items-center gap-1", className)}
|
className={cn("inline-flex items-center gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Kbd, KbdGroup }
|
export { Kbd, KbdGroup };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
||||||
return (
|
return (
|
||||||
@@ -10,11 +10,11 @@ function Label({ className, ...props }: React.ComponentProps<"label">) {
|
|||||||
data-slot="label"
|
data-slot="label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Label }
|
export { Label };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { mergeProps } from "@base-ui/react/merge-props"
|
import { mergeProps } from "@base-ui/react/merge-props";
|
||||||
import { useRender } from "@base-ui/react/use-render"
|
import { useRender } from "@base-ui/react/use-render";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const markerVariants = cva(
|
const markerVariants = cva(
|
||||||
"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
|
"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
|
||||||
@@ -16,8 +16,8 @@ const markerVariants = cva(
|
|||||||
border: "border-b border-border pb-2",
|
border: "border-b border-border pb-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Marker({
|
function Marker({
|
||||||
className,
|
className,
|
||||||
@@ -31,14 +31,14 @@ function Marker({
|
|||||||
{
|
{
|
||||||
className: cn(markerVariants({ variant, className })),
|
className: cn(markerVariants({ variant, className })),
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
),
|
),
|
||||||
render,
|
render,
|
||||||
state: {
|
state: {
|
||||||
slot: "marker",
|
slot: "marker",
|
||||||
variant,
|
variant,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
|
function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -48,11 +48,11 @@ function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
|
function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -61,11 +61,11 @@ function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
data-slot="marker-content"
|
data-slot="marker-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
"min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Marker, MarkerIcon, MarkerContent, markerVariants }
|
export { Marker, MarkerIcon, MarkerContent, markerVariants };
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -9,7 +9,7 @@ function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex min-w-0 flex-col gap-2", className)}
|
className={cn("flex min-w-0 flex-col gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Message({
|
function Message({
|
||||||
@@ -23,11 +23,11 @@ function Message({
|
|||||||
data-align={align}
|
data-align={align}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
|
"group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
|
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -36,11 +36,11 @@ function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="message-avatar"
|
data-slot="message-avatar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8",
|
"flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
|
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -49,11 +49,11 @@ function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="message-content"
|
data-slot="message-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
|
"flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -62,11 +62,11 @@ function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="message-header"
|
data-slot="message-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
|
"flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -75,11 +75,11 @@ function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="message-footer"
|
data-slot="message-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
|
"flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -89,4 +89,4 @@ export {
|
|||||||
MessageContent,
|
MessageContent,
|
||||||
MessageFooter,
|
MessageFooter,
|
||||||
MessageHeader,
|
MessageHeader,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,8 +1,12 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return (
|
return (
|
||||||
@@ -13,7 +17,7 @@ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
|||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationContent({
|
function PaginationContent({
|
||||||
@@ -26,17 +30,17 @@ function PaginationContent({
|
|||||||
className={cn("flex items-center gap-0.5", className)}
|
className={cn("flex items-center gap-0.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
||||||
return <li data-slot="pagination-item" {...props} />
|
return <li data-slot="pagination-item" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
||||||
React.ComponentProps<"a">
|
React.ComponentProps<"a">;
|
||||||
|
|
||||||
function PaginationLink({
|
function PaginationLink({
|
||||||
className,
|
className,
|
||||||
@@ -59,7 +63,7 @@ function PaginationLink({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationPrevious({
|
function PaginationPrevious({
|
||||||
@@ -77,7 +81,7 @@ function PaginationPrevious({
|
|||||||
<ChevronLeftIcon data-icon="inline-start" />
|
<ChevronLeftIcon data-icon="inline-start" />
|
||||||
<span className="hidden sm:block">{text}</span>
|
<span className="hidden sm:block">{text}</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationNext({
|
function PaginationNext({
|
||||||
@@ -95,7 +99,7 @@ function PaginationNext({
|
|||||||
<span className="hidden sm:block">{text}</span>
|
<span className="hidden sm:block">{text}</span>
|
||||||
<ChevronRightIcon data-icon="inline-end" />
|
<ChevronRightIcon data-icon="inline-end" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationEllipsis({
|
function PaginationEllipsis({
|
||||||
@@ -108,15 +112,14 @@ function PaginationEllipsis({
|
|||||||
data-slot="pagination-ellipsis"
|
data-slot="pagination-ellipsis"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex size-8 items-center justify-center [&_svg:not([class*='size-'])]:size-4",
|
"flex size-8 items-center justify-center [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon
|
<MoreHorizontalIcon />
|
||||||
/>
|
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">More pages</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -127,4 +130,4 @@ export {
|
|||||||
PaginationLink,
|
PaginationLink,
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
|
import { Progress as ProgressPrimitive } from "@base-ui/react/progress";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Progress({
|
function Progress({
|
||||||
className,
|
className,
|
||||||
@@ -22,7 +22,7 @@ function Progress({
|
|||||||
<ProgressIndicator />
|
<ProgressIndicator />
|
||||||
</ProgressTrack>
|
</ProgressTrack>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
|
function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
|
||||||
@@ -30,12 +30,12 @@ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
|
|||||||
<ProgressPrimitive.Track
|
<ProgressPrimitive.Track
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted",
|
"relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="progress-track"
|
data-slot="progress-track"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressIndicator({
|
function ProgressIndicator({
|
||||||
@@ -48,7 +48,7 @@ function ProgressIndicator({
|
|||||||
className={cn("h-full bg-primary transition-all", className)}
|
className={cn("h-full bg-primary transition-all", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
|
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
|
||||||
@@ -58,7 +58,7 @@ function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
|
|||||||
data-slot="progress-label"
|
data-slot="progress-label"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
|
function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
|
||||||
@@ -66,12 +66,12 @@ function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
|
|||||||
<ProgressPrimitive.Value
|
<ProgressPrimitive.Value
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-sm text-muted-foreground tabular-nums",
|
"ml-auto text-sm text-muted-foreground tabular-nums",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="progress-value"
|
data-slot="progress-value"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -80,4 +80,4 @@ export {
|
|||||||
ProgressIndicator,
|
ProgressIndicator,
|
||||||
ProgressLabel,
|
ProgressLabel,
|
||||||
ProgressValue,
|
ProgressValue,
|
||||||
}
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"
|
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function ScrollArea({
|
function ScrollArea({
|
||||||
className,
|
className,
|
||||||
@@ -25,7 +25,7 @@ function ScrollArea({
|
|||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScrollBar({
|
function ScrollBar({
|
||||||
@@ -40,7 +40,7 @@ function ScrollBar({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
|
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -49,7 +49,7 @@ function ScrollBar({
|
|||||||
className="relative flex-1 rounded-full bg-border"
|
className="relative flex-1 rounded-full bg-border"
|
||||||
/>
|
/>
|
||||||
</ScrollAreaPrimitive.Scrollbar>
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
export { ScrollArea, ScrollBar };
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Separator({
|
function Separator({
|
||||||
className,
|
className,
|
||||||
@@ -15,11 +15,11 @@ function Separator({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Separator }
|
export { Separator };
|
||||||
|
|||||||
+23
-24
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user