ruler-components 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +458 -0
- package/dist/index.d.ts +458 -0
- package/dist/index.js +3618 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3467 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -2
- package/AGENTS.md +0 -9
- package/CLAUDE.md +0 -1
- package/app/docs/[slug]/page.tsx +0 -147
- package/app/docs/layout.tsx +0 -26
- package/app/docs/page.tsx +0 -148
- package/app/favicon.ico +0 -0
- package/app/globals.css +0 -168
- package/app/icon.png +0 -0
- package/app/layout.tsx +0 -90
- package/app/opengraph-image.tsx +0 -10
- package/app/page.tsx +0 -34
- package/app/twitter-image.tsx +0 -10
- package/components/docs/accordion-doc-page.tsx +0 -352
- package/components/docs/alert-doc-page.tsx +0 -293
- package/components/docs/allinput-doc-page.tsx +0 -183
- package/components/docs/avatar-doc-page.tsx +0 -424
- package/components/docs/badge-doc-page.tsx +0 -363
- package/components/docs/button-doc-page.tsx +0 -406
- package/components/docs/card-doc-page.tsx +0 -364
- package/components/docs/checkbox-doc-page.tsx +0 -277
- package/components/docs/colors-doc-page.tsx +0 -430
- package/components/docs/dialog-doc-page.tsx +0 -364
- package/components/docs/docs-footer.tsx +0 -11
- package/components/docs/docs-mobile-menu-bar.tsx +0 -22
- package/components/docs/docs-mobile-nav-context.tsx +0 -27
- package/components/docs/docs-page.tsx +0 -263
- package/components/docs/docs-search.tsx +0 -303
- package/components/docs/docs-sidebar.tsx +0 -165
- package/components/docs/docs-topbar.tsx +0 -32
- package/components/docs/dropdown-doc-page.tsx +0 -522
- package/components/docs/getting-started-doc-page.tsx +0 -205
- package/components/docs/label-doc-page.tsx +0 -173
- package/components/docs/loaders-doc-page.tsx +0 -244
- package/components/docs/menu-doc-page.tsx +0 -373
- package/components/docs/modal-doc-page.tsx +0 -436
- package/components/docs/next-steps.tsx +0 -53
- package/components/docs/pagination-doc-page.tsx +0 -316
- package/components/docs/progress-doc-page.tsx +0 -387
- package/components/docs/radio-doc-page.tsx +0 -270
- package/components/docs/skeleton-doc-page.tsx +0 -230
- package/components/docs/stepper-doc-page.tsx +0 -309
- package/components/docs/switch-doc-page.tsx +0 -264
- package/components/docs/table-doc-page.tsx +0 -689
- package/components/docs/tabs-doc-page.tsx +0 -359
- package/components/docs/theming-doc-page.tsx +0 -243
- package/components/docs/tooltip-doc-page.tsx +0 -357
- package/components/docs/usage-section.tsx +0 -21
- package/components/icons/discord.tsx +0 -9
- package/components/icons/github.tsx +0 -9
- package/components/icons/x.tsx +0 -9
- package/components/installation-section.tsx +0 -34
- package/components/landing/feature-highlights.tsx +0 -105
- package/components/landing/footer-strip.tsx +0 -45
- package/components/landing/hero-card.tsx +0 -14
- package/components/landing/hero-spotlight.tsx +0 -126
- package/components/landing/hero.tsx +0 -64
- package/components/landing/navbar.tsx +0 -44
- package/components/landing/quick-start-section.tsx +0 -59
- package/components/landing/site-footer.tsx +0 -55
- package/components/theme-provider.tsx +0 -60
- package/components/ui/accordion.tsx +0 -174
- package/components/ui/alert.tsx +0 -103
- package/components/ui/allinput.tsx +0 -210
- package/components/ui/avatar.tsx +0 -179
- package/components/ui/badge.tsx +0 -52
- package/components/ui/button.tsx +0 -75
- package/components/ui/card.tsx +0 -105
- package/components/ui/checkbox.tsx +0 -130
- package/components/ui/code-block.tsx +0 -60
- package/components/ui/data-table.tsx +0 -235
- package/components/ui/dialog.tsx +0 -173
- package/components/ui/dropdown.tsx +0 -78
- package/components/ui/feature-card.tsx +0 -38
- package/components/ui/input.tsx +0 -44
- package/components/ui/label.tsx +0 -110
- package/components/ui/loaders.tsx +0 -139
- package/components/ui/menu.tsx +0 -118
- package/components/ui/modal.tsx +0 -399
- package/components/ui/package-manager-tabs.tsx +0 -40
- package/components/ui/pagination.tsx +0 -200
- package/components/ui/progress.tsx +0 -134
- package/components/ui/radio.tsx +0 -262
- package/components/ui/skeleton.tsx +0 -102
- package/components/ui/steppers.tsx +0 -309
- package/components/ui/switch.tsx +0 -153
- package/components/ui/table.tsx +0 -58
- package/components/ui/tabs.tsx +0 -166
- package/components/ui/theme-toggle.tsx +0 -60
- package/components/ui/tooltip.tsx +0 -238
- package/data/color-palette.ts +0 -35
- package/data/docs-nav.ts +0 -84
- package/eslint.config.mjs +0 -18
- package/index.ts +0 -8
- package/lib/cn.ts +0 -4
- package/lib/color.ts +0 -85
- package/lib/og-image.tsx +0 -138
- package/lib/theme.ts +0 -33
- package/next.config.ts +0 -7
- package/postcss.config.mjs +0 -7
- package/public/dark.png +0 -0
- package/public/dark1.png +0 -0
- package/public/dark2.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/light.png +0 -0
- package/public/light1.png +0 -0
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/video/hero-demo.mp4 +0 -0
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -34
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Check } from "lucide-react";
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
const checkboxControlVariants = cva(
|
|
7
|
-
[
|
|
8
|
-
"pointer-events-none relative flex shrink-0 items-center justify-center rounded-[3px] border-2 bg-white transition-all duration-200",
|
|
9
|
-
"border-zinc-500 group-hover:border-zinc-700",
|
|
10
|
-
"peer-checked:border-zinc-900 peer-checked:bg-zinc-900",
|
|
11
|
-
"peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-zinc-900/30 peer-focus-visible:ring-offset-2",
|
|
12
|
-
"peer-disabled:border-zinc-300 peer-disabled:bg-zinc-100",
|
|
13
|
-
"peer-disabled:peer-checked:border-zinc-300 peer-disabled:peer-checked:bg-zinc-300",
|
|
14
|
-
"dark:border-zinc-500 dark:bg-zinc-900 dark:group-hover:border-zinc-400",
|
|
15
|
-
"dark:peer-checked:border-zinc-100 dark:peer-checked:bg-zinc-100",
|
|
16
|
-
"dark:peer-disabled:border-zinc-700 dark:peer-disabled:bg-zinc-800",
|
|
17
|
-
"dark:peer-disabled:peer-checked:border-zinc-600 dark:peer-disabled:peer-checked:bg-zinc-600",
|
|
18
|
-
"peer-checked:[&_svg]:scale-100 peer-checked:[&_svg]:opacity-100",
|
|
19
|
-
],
|
|
20
|
-
{
|
|
21
|
-
variants: {
|
|
22
|
-
size: {
|
|
23
|
-
sm: "h-4 w-4 [&_svg]:h-2.5 [&_svg]:w-2.5",
|
|
24
|
-
md: "h-[18px] w-[18px] [&_svg]:h-3 [&_svg]:w-3",
|
|
25
|
-
lg: "h-5 w-5 [&_svg]:h-3.5 [&_svg]:w-3.5",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
defaultVariants: { size: "md" },
|
|
29
|
-
},
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const checkboxWrapperVariants = cva("group flex w-full transition-colors duration-150", {
|
|
33
|
-
variants: {
|
|
34
|
-
variant: {
|
|
35
|
-
inline: "gap-3",
|
|
36
|
-
card: [
|
|
37
|
-
"gap-3 rounded-lg border border-zinc-200 bg-white px-3.5 py-3",
|
|
38
|
-
"hover:border-zinc-300 hover:bg-zinc-50/80",
|
|
39
|
-
"has-[:checked]:border-zinc-900",
|
|
40
|
-
"dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700 dark:hover:bg-zinc-900/50",
|
|
41
|
-
"dark:has-[:checked]:border-zinc-500",
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
defaultVariants: { variant: "inline" },
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export interface CheckboxProps
|
|
49
|
-
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size">,
|
|
50
|
-
VariantProps<typeof checkboxControlVariants> {
|
|
51
|
-
label?: string;
|
|
52
|
-
description?: string;
|
|
53
|
-
variant?: "inline" | "card";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
57
|
-
(
|
|
58
|
-
{
|
|
59
|
-
className,
|
|
60
|
-
size,
|
|
61
|
-
variant = "inline",
|
|
62
|
-
label,
|
|
63
|
-
description,
|
|
64
|
-
disabled,
|
|
65
|
-
id,
|
|
66
|
-
...props
|
|
67
|
-
},
|
|
68
|
-
ref,
|
|
69
|
-
) => {
|
|
70
|
-
const generatedId = React.useId();
|
|
71
|
-
const inputId = id ?? generatedId;
|
|
72
|
-
const resolvedVariant = variant === "card" || description ? "card" : "inline";
|
|
73
|
-
const alignStart = resolvedVariant === "card" || Boolean(description);
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<label
|
|
77
|
-
htmlFor={inputId}
|
|
78
|
-
className={cn(
|
|
79
|
-
checkboxWrapperVariants({ variant: resolvedVariant }),
|
|
80
|
-
alignStart ? "items-start" : "items-center",
|
|
81
|
-
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
82
|
-
className,
|
|
83
|
-
)}
|
|
84
|
-
>
|
|
85
|
-
<span
|
|
86
|
-
className={cn(
|
|
87
|
-
"relative flex shrink-0 items-center justify-center p-2.5 -m-2.5",
|
|
88
|
-
alignStart ? "mt-0.5" : undefined,
|
|
89
|
-
)}
|
|
90
|
-
>
|
|
91
|
-
<input
|
|
92
|
-
ref={ref}
|
|
93
|
-
id={inputId}
|
|
94
|
-
type="checkbox"
|
|
95
|
-
disabled={disabled}
|
|
96
|
-
className="peer absolute inset-0 z-10 cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
97
|
-
{...props}
|
|
98
|
-
/>
|
|
99
|
-
<span
|
|
100
|
-
aria-hidden
|
|
101
|
-
className="pointer-events-none absolute inset-0 rounded-full bg-zinc-900/0 transition-colors duration-200 group-hover:bg-zinc-900/[0.08] group-active:bg-zinc-900/[0.12] peer-disabled:group-hover:bg-transparent dark:group-hover:bg-white/[0.08] dark:group-active:bg-white/[0.12]"
|
|
102
|
-
/>
|
|
103
|
-
<span className={checkboxControlVariants({ size })}>
|
|
104
|
-
<Check
|
|
105
|
-
className="text-white opacity-0 transition-all duration-200 dark:text-zinc-900"
|
|
106
|
-
strokeWidth={3}
|
|
107
|
-
/>
|
|
108
|
-
</span>
|
|
109
|
-
</span>
|
|
110
|
-
{(label || description) && (
|
|
111
|
-
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
112
|
-
{label && (
|
|
113
|
-
<span className="text-sm leading-snug text-zinc-800 dark:text-zinc-100">
|
|
114
|
-
{label}
|
|
115
|
-
</span>
|
|
116
|
-
)}
|
|
117
|
-
{description && (
|
|
118
|
-
<span className="text-xs leading-relaxed text-zinc-500 dark:text-zinc-400">
|
|
119
|
-
{description}
|
|
120
|
-
</span>
|
|
121
|
-
)}
|
|
122
|
-
</span>
|
|
123
|
-
)}
|
|
124
|
-
</label>
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
Checkbox.displayName = "Checkbox";
|
|
129
|
-
|
|
130
|
-
export { Checkbox, checkboxControlVariants };
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { Check, Copy } from "lucide-react";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
|
|
7
|
-
export function CodeBlock({
|
|
8
|
-
code,
|
|
9
|
-
filename,
|
|
10
|
-
className,
|
|
11
|
-
}: {
|
|
12
|
-
code: string;
|
|
13
|
-
filename?: string;
|
|
14
|
-
className?: string;
|
|
15
|
-
}) {
|
|
16
|
-
const [copied, setCopied] = useState(false);
|
|
17
|
-
|
|
18
|
-
const onCopy = async () => {
|
|
19
|
-
await navigator.clipboard.writeText(code);
|
|
20
|
-
setCopied(true);
|
|
21
|
-
setTimeout(() => setCopied(false), 1500);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const copyButton = (
|
|
25
|
-
<button
|
|
26
|
-
type="button"
|
|
27
|
-
onClick={onCopy}
|
|
28
|
-
aria-label="Copy code"
|
|
29
|
-
className="flex h-7 w-7 items-center justify-center rounded-md text-zinc-500 transition-colors hover:bg-white/10 hover:text-zinc-200"
|
|
30
|
-
>
|
|
31
|
-
{copied ? (
|
|
32
|
-
<Check className="h-3.5 w-3.5 text-white" />
|
|
33
|
-
) : (
|
|
34
|
-
<Copy className="h-3.5 w-3.5" />
|
|
35
|
-
)}
|
|
36
|
-
</button>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div
|
|
41
|
-
className={cn(
|
|
42
|
-
"group relative overflow-x-auto rounded-xl bg-zinc-900 ring-1 ring-white/10",
|
|
43
|
-
className,
|
|
44
|
-
)}
|
|
45
|
-
>
|
|
46
|
-
{filename ? (
|
|
47
|
-
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2.5">
|
|
48
|
-
<span className="font-mono text-xs text-zinc-500">{filename}</span>
|
|
49
|
-
{copyButton}
|
|
50
|
-
</div>
|
|
51
|
-
) : null}
|
|
52
|
-
<pre className={cn("max-w-full overflow-x-auto px-4 py-3.5 text-[13px] leading-relaxed text-zinc-100", !filename && "pr-12")}>
|
|
53
|
-
<code className="font-mono">{code}</code>
|
|
54
|
-
</pre>
|
|
55
|
-
{filename ? null : (
|
|
56
|
-
<div className="absolute right-3 top-3">{copyButton}</div>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useMemo, useState } from "react";
|
|
4
|
-
import { ArrowDown, ArrowUp, ArrowUpDown, Filter, Search, X } from "lucide-react";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
import { Button } from "./button";
|
|
7
|
-
import { Input } from "./input";
|
|
8
|
-
import { Pagination } from "./pagination";
|
|
9
|
-
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table";
|
|
10
|
-
import { Dropdown, DropdownContent, DropdownDivider, DropdownItem, DropdownLabel, DropdownTrigger } from "./dropdown";
|
|
11
|
-
|
|
12
|
-
export interface DataTableColumn<T> {
|
|
13
|
-
key: string;
|
|
14
|
-
header: string;
|
|
15
|
-
sortable?: boolean;
|
|
16
|
-
align?: "left" | "center" | "right";
|
|
17
|
-
filterOptions?: string[];
|
|
18
|
-
accessor?: (row: T) => string | number;
|
|
19
|
-
render?: (row: T) => React.ReactNode;
|
|
20
|
-
className?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface DataTableProps<T> {
|
|
24
|
-
data: T[];
|
|
25
|
-
columns: DataTableColumn<T>[];
|
|
26
|
-
searchPlaceholder?: string;
|
|
27
|
-
pageSize?: number;
|
|
28
|
-
className?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type SortDirection = "asc" | "desc" | null;
|
|
32
|
-
|
|
33
|
-
export function DataTable<T extends object>({
|
|
34
|
-
data,
|
|
35
|
-
columns,
|
|
36
|
-
searchPlaceholder = "Search...",
|
|
37
|
-
pageSize = 5,
|
|
38
|
-
className,
|
|
39
|
-
}: DataTableProps<T>) {
|
|
40
|
-
const [search, setSearch] = useState("");
|
|
41
|
-
const [sortKey, setSortKey] = useState<string | null>(null);
|
|
42
|
-
const [sortDir, setSortDir] = useState<SortDirection>(null);
|
|
43
|
-
const [activeFilters, setActiveFilters] = useState<Record<string, string>>({});
|
|
44
|
-
const [page, setPage] = useState(1);
|
|
45
|
-
|
|
46
|
-
const getValue = (row: T, col: DataTableColumn<T>) =>
|
|
47
|
-
col.accessor ? col.accessor(row) : ((row as Record<string, unknown>)[col.key] as string | number);
|
|
48
|
-
|
|
49
|
-
const filterableColumns = columns.filter((c) => c.filterOptions?.length);
|
|
50
|
-
const activeFilterCount = Object.values(activeFilters).filter(Boolean).length;
|
|
51
|
-
|
|
52
|
-
const filtered = useMemo(() => {
|
|
53
|
-
let rows = data;
|
|
54
|
-
|
|
55
|
-
if (search.trim()) {
|
|
56
|
-
const q = search.trim().toLowerCase();
|
|
57
|
-
rows = rows.filter((row) => columns.some((col) => String(getValue(row, col) ?? "").toLowerCase().includes(q)));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
Object.entries(activeFilters).forEach(([key, value]) => {
|
|
61
|
-
if (!value) return;
|
|
62
|
-
const col = columns.find((c) => c.key === key);
|
|
63
|
-
if (!col) return;
|
|
64
|
-
rows = rows.filter((row) => String(getValue(row, col)) === value);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return rows;
|
|
68
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
|
-
}, [data, search, activeFilters]);
|
|
70
|
-
|
|
71
|
-
const sorted = useMemo(() => {
|
|
72
|
-
if (!sortKey || !sortDir) return filtered;
|
|
73
|
-
const col = columns.find((c) => c.key === sortKey);
|
|
74
|
-
if (!col) return filtered;
|
|
75
|
-
return [...filtered].sort((a, b) => {
|
|
76
|
-
const av = getValue(a, col);
|
|
77
|
-
const bv = getValue(b, col);
|
|
78
|
-
if (typeof av === "number" && typeof bv === "number") return sortDir === "asc" ? av - bv : bv - av;
|
|
79
|
-
return sortDir === "asc" ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
|
|
80
|
-
});
|
|
81
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
82
|
-
}, [filtered, sortKey, sortDir]);
|
|
83
|
-
|
|
84
|
-
const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
|
|
85
|
-
const currentPage = Math.min(page, totalPages);
|
|
86
|
-
const paged = sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
|
87
|
-
|
|
88
|
-
const handleSort = (col: DataTableColumn<T>) => {
|
|
89
|
-
if (!col.sortable) return;
|
|
90
|
-
if (sortKey !== col.key) {
|
|
91
|
-
setSortKey(col.key);
|
|
92
|
-
setSortDir("asc");
|
|
93
|
-
} else if (sortDir === "asc") {
|
|
94
|
-
setSortDir("desc");
|
|
95
|
-
} else {
|
|
96
|
-
setSortKey(null);
|
|
97
|
-
setSortDir(null);
|
|
98
|
-
}
|
|
99
|
-
setPage(1);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const clearAll = () => {
|
|
103
|
-
setSearch("");
|
|
104
|
-
setActiveFilters({});
|
|
105
|
-
setPage(1);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div className={cn("w-full", className)}>
|
|
110
|
-
<div className="flex flex-col gap-3 pb-4 sm:flex-row sm:items-center sm:justify-between">
|
|
111
|
-
<div className="relative w-full sm:max-w-xs">
|
|
112
|
-
<Search size={15} className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" />
|
|
113
|
-
<Input
|
|
114
|
-
value={search}
|
|
115
|
-
onChange={(e) => {
|
|
116
|
-
setSearch(e.target.value);
|
|
117
|
-
setPage(1);
|
|
118
|
-
}}
|
|
119
|
-
placeholder={searchPlaceholder}
|
|
120
|
-
className="pl-9"
|
|
121
|
-
/>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
{(filterableColumns.length > 0 || activeFilterCount > 0) && (
|
|
125
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
126
|
-
{filterableColumns.map((col) => (
|
|
127
|
-
<Dropdown key={col.key}>
|
|
128
|
-
<DropdownTrigger>
|
|
129
|
-
<Button
|
|
130
|
-
variant="outline"
|
|
131
|
-
size="sm"
|
|
132
|
-
className={cn(activeFilters[col.key] && "border-zinc-900 text-zinc-900 dark:border-zinc-100 dark:text-white")}
|
|
133
|
-
>
|
|
134
|
-
<Filter size={13} />
|
|
135
|
-
{activeFilters[col.key] || col.header}
|
|
136
|
-
</Button>
|
|
137
|
-
</DropdownTrigger>
|
|
138
|
-
<DropdownContent align="right">
|
|
139
|
-
<DropdownLabel>Filter by {col.header}</DropdownLabel>
|
|
140
|
-
<DropdownItem label="All" selected={!activeFilters[col.key]} onClick={() => setActiveFilters((f) => ({ ...f, [col.key]: "" }))} />
|
|
141
|
-
<DropdownDivider />
|
|
142
|
-
{col.filterOptions!.map((opt) => (
|
|
143
|
-
<DropdownItem
|
|
144
|
-
key={opt}
|
|
145
|
-
label={opt}
|
|
146
|
-
selected={activeFilters[col.key] === opt}
|
|
147
|
-
onClick={() => {
|
|
148
|
-
setActiveFilters((f) => ({ ...f, [col.key]: opt }));
|
|
149
|
-
setPage(1);
|
|
150
|
-
}}
|
|
151
|
-
/>
|
|
152
|
-
))}
|
|
153
|
-
</DropdownContent>
|
|
154
|
-
</Dropdown>
|
|
155
|
-
))}
|
|
156
|
-
|
|
157
|
-
{(activeFilterCount > 0 || search) && (
|
|
158
|
-
<Button variant="ghost" size="sm" onClick={clearAll}>
|
|
159
|
-
<X size={13} />
|
|
160
|
-
Clear
|
|
161
|
-
</Button>
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<div className="overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
|
|
168
|
-
<Table>
|
|
169
|
-
<TableHeader>
|
|
170
|
-
<TableRow>
|
|
171
|
-
{columns.map((col) => (
|
|
172
|
-
<TableHead
|
|
173
|
-
key={col.key}
|
|
174
|
-
onClick={() => handleSort(col)}
|
|
175
|
-
className={cn(
|
|
176
|
-
col.align === "right" && "text-right",
|
|
177
|
-
col.align === "center" && "text-center",
|
|
178
|
-
col.sortable && "cursor-pointer select-none hover:text-zinc-700 dark:hover:text-zinc-200",
|
|
179
|
-
col.className,
|
|
180
|
-
)}
|
|
181
|
-
>
|
|
182
|
-
<span
|
|
183
|
-
className={cn(
|
|
184
|
-
"inline-flex items-center gap-1",
|
|
185
|
-
col.align === "right" && "flex-row-reverse",
|
|
186
|
-
col.align === "center" && "justify-center",
|
|
187
|
-
)}
|
|
188
|
-
>
|
|
189
|
-
{col.header}
|
|
190
|
-
{col.sortable &&
|
|
191
|
-
(sortKey === col.key ? (
|
|
192
|
-
sortDir === "asc" ? (
|
|
193
|
-
<ArrowUp size={12} />
|
|
194
|
-
) : (
|
|
195
|
-
<ArrowDown size={12} />
|
|
196
|
-
)
|
|
197
|
-
) : (
|
|
198
|
-
<ArrowUpDown size={12} className="opacity-30" />
|
|
199
|
-
))}
|
|
200
|
-
</span>
|
|
201
|
-
</TableHead>
|
|
202
|
-
))}
|
|
203
|
-
</TableRow>
|
|
204
|
-
</TableHeader>
|
|
205
|
-
<TableBody>
|
|
206
|
-
{paged.length === 0 ? (
|
|
207
|
-
<TableRow className="hover:bg-transparent">
|
|
208
|
-
<TableCell colSpan={columns.length} className="py-10 text-center text-zinc-400">
|
|
209
|
-
No results found.
|
|
210
|
-
</TableCell>
|
|
211
|
-
</TableRow>
|
|
212
|
-
) : (
|
|
213
|
-
paged.map((row, i) => (
|
|
214
|
-
<TableRow key={i}>
|
|
215
|
-
{columns.map((col) => (
|
|
216
|
-
<TableCell key={col.key} className={cn(col.align === "right" && "text-right", col.align === "center" && "text-center", col.className)}>
|
|
217
|
-
{col.render ? col.render(row) : String(getValue(row, col) ?? "")}
|
|
218
|
-
</TableCell>
|
|
219
|
-
))}
|
|
220
|
-
</TableRow>
|
|
221
|
-
))
|
|
222
|
-
)}
|
|
223
|
-
</TableBody>
|
|
224
|
-
</Table>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
<div className="flex flex-col-reverse items-center justify-between gap-3 pt-4 sm:flex-row">
|
|
228
|
-
<p className="text-xs text-zinc-500 dark:text-zinc-400">
|
|
229
|
-
{sorted.length === 0 ? "0 results" : `Showing ${(currentPage - 1) * pageSize + 1}–${Math.min(currentPage * pageSize, sorted.length)} of ${sorted.length}`}
|
|
230
|
-
</p>
|
|
231
|
-
<Pagination page={currentPage} totalPages={totalPages} onPageChange={setPage} />
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
package/components/ui/dialog.tsx
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useState, useEffect } from "react";
|
|
4
|
-
import { createPortal } from "react-dom";
|
|
5
|
-
import { User, Plus } from "lucide-react";
|
|
6
|
-
import { cn } from "@/lib/cn";
|
|
7
|
-
|
|
8
|
-
type DialogContextType = {
|
|
9
|
-
isOpen: boolean;
|
|
10
|
-
setIsOpen: (open: boolean) => void;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const DialogContext = createContext<DialogContextType | null>(null);
|
|
14
|
-
|
|
15
|
-
export function Dialog({ children, open, onOpenChange }: { children: React.ReactNode; open?: boolean; onOpenChange?: (open: boolean) => void }) {
|
|
16
|
-
const [isOpenState, setIsOpenState] = useState(false);
|
|
17
|
-
const isOpen = open !== undefined ? open : isOpenState;
|
|
18
|
-
const setIsOpen = onOpenChange || setIsOpenState;
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (isOpen) document.body.style.overflow = "hidden";
|
|
22
|
-
else document.body.style.overflow = "unset";
|
|
23
|
-
return () => {
|
|
24
|
-
document.body.style.overflow = "unset";
|
|
25
|
-
};
|
|
26
|
-
}, [isOpen]);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<DialogContext.Provider value={{ isOpen, setIsOpen }}>
|
|
30
|
-
{children}
|
|
31
|
-
</DialogContext.Provider>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function DialogTrigger({ children }: { children: React.ReactElement<any> }) {
|
|
36
|
-
const { setIsOpen } = useContext(DialogContext)!;
|
|
37
|
-
|
|
38
|
-
return React.cloneElement(children, {
|
|
39
|
-
onClick: (e: React.MouseEvent) => {
|
|
40
|
-
if (children.props?.onClick) children.props.onClick(e);
|
|
41
|
-
setIsOpen(true);
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function DialogContent({ children, className = "" }: { children: React.ReactNode; className?: string }) {
|
|
47
|
-
const { isOpen, setIsOpen } = useContext(DialogContext)!;
|
|
48
|
-
const [visible, setVisible] = useState(false);
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!isOpen) {
|
|
52
|
-
setVisible(false);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const frame = requestAnimationFrame(() => setVisible(true));
|
|
56
|
-
return () => cancelAnimationFrame(frame);
|
|
57
|
-
}, [isOpen]);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (!isOpen) return;
|
|
61
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
62
|
-
if (e.key === "Escape") setIsOpen(false);
|
|
63
|
-
};
|
|
64
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
65
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
66
|
-
}, [isOpen, setIsOpen]);
|
|
67
|
-
|
|
68
|
-
if (!isOpen) return null;
|
|
69
|
-
|
|
70
|
-
return createPortal(
|
|
71
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
72
|
-
<div
|
|
73
|
-
className={cn(
|
|
74
|
-
"fixed inset-0 bg-zinc-900/45 backdrop-blur-[2px] transition-opacity duration-200 ease-out dark:bg-black/65",
|
|
75
|
-
visible ? "opacity-100" : "opacity-0",
|
|
76
|
-
)}
|
|
77
|
-
onClick={() => setIsOpen(false)}
|
|
78
|
-
aria-hidden
|
|
79
|
-
/>
|
|
80
|
-
<div
|
|
81
|
-
role="dialog"
|
|
82
|
-
aria-modal="true"
|
|
83
|
-
onClick={(e) => e.stopPropagation()}
|
|
84
|
-
className={cn(
|
|
85
|
-
"relative z-10 w-full max-w-md rounded-2xl border border-zinc-200/90 bg-white p-6 text-zinc-900",
|
|
86
|
-
"shadow-[0_20px_60px_rgba(0,0,0,0.12),0_0_0_1px_rgba(0,0,0,0.04)]",
|
|
87
|
-
"dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100",
|
|
88
|
-
"dark:shadow-[0_20px_60px_rgba(0,0,0,0.5),0_0_0_1px_rgba(255,255,255,0.06)]",
|
|
89
|
-
"transition-[opacity,transform] duration-200 ease-out",
|
|
90
|
-
visible ? "translate-y-0 scale-100 opacity-100" : "translate-y-1 scale-[0.97] opacity-0",
|
|
91
|
-
className,
|
|
92
|
-
)}
|
|
93
|
-
>
|
|
94
|
-
{children}
|
|
95
|
-
</div>
|
|
96
|
-
</div>,
|
|
97
|
-
document.body,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function DialogHeader({ title, description }: { title?: string; description?: string }) {
|
|
102
|
-
return (
|
|
103
|
-
<div className="mb-5">
|
|
104
|
-
{title && <h2 className="text-[17px] font-semibold leading-snug tracking-tight text-zinc-900 dark:text-white">{title}</h2>}
|
|
105
|
-
{description && <p className="mt-2 text-[14px] leading-relaxed text-zinc-500 dark:text-zinc-400">{description}</p>}
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function DialogFooter({ children }: { children: React.ReactNode }) {
|
|
111
|
-
return <div className="mt-6 flex items-center justify-end gap-1">{children}</div>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function DialogAction({ label, onClick, variant = "primary" }: { label: string; onClick?: () => void; variant?: "primary" | "destructive" }) {
|
|
115
|
-
const { setIsOpen } = useContext(DialogContext)!;
|
|
116
|
-
|
|
117
|
-
const handleClick = () => {
|
|
118
|
-
if (onClick) onClick();
|
|
119
|
-
setIsOpen(false);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const colorClass =
|
|
123
|
-
variant === "destructive"
|
|
124
|
-
? "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-500/10"
|
|
125
|
-
: "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-800";
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<button
|
|
129
|
-
onClick={handleClick}
|
|
130
|
-
className={cn(
|
|
131
|
-
"rounded-lg px-3.5 py-2 text-[13px] font-semibold tracking-tight outline-none transition-colors duration-150",
|
|
132
|
-
"focus-visible:ring-2 focus-visible:ring-zinc-900/15 dark:focus-visible:ring-white/20",
|
|
133
|
-
colorClass,
|
|
134
|
-
)}
|
|
135
|
-
>
|
|
136
|
-
{label}
|
|
137
|
-
</button>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function DialogItem({
|
|
142
|
-
label,
|
|
143
|
-
icon,
|
|
144
|
-
onClick,
|
|
145
|
-
variant = "account",
|
|
146
|
-
}: {
|
|
147
|
-
label: string;
|
|
148
|
-
icon?: React.ReactNode;
|
|
149
|
-
onClick?: () => void;
|
|
150
|
-
variant?: "account" | "action";
|
|
151
|
-
}) {
|
|
152
|
-
const { setIsOpen } = useContext(DialogContext)!;
|
|
153
|
-
|
|
154
|
-
const handleClick = () => {
|
|
155
|
-
if (onClick) onClick();
|
|
156
|
-
setIsOpen(false);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const iconBg = variant === "account" ? "bg-[#0070BA]/10 dark:bg-[#0070BA]/20" : "bg-zinc-100 dark:bg-zinc-800";
|
|
160
|
-
const iconColor = variant === "account" ? "text-[#0070BA] dark:text-[#4DA3E0]" : "text-zinc-500 dark:text-zinc-400";
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<button
|
|
164
|
-
onClick={handleClick}
|
|
165
|
-
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left outline-none transition-colors duration-150 hover:bg-zinc-50 dark:hover:bg-zinc-900/60"
|
|
166
|
-
>
|
|
167
|
-
<div className={cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-full", iconBg, iconColor)}>
|
|
168
|
-
{icon || (variant === "account" ? <User size={18} /> : <Plus size={18} />)}
|
|
169
|
-
</div>
|
|
170
|
-
<span className="text-[14px] font-medium text-zinc-900 dark:text-white">{label}</span>
|
|
171
|
-
</button>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useState, useRef, useEffect } from "react";
|
|
4
|
-
import { Check, ChevronDown, Search, User, Settings, LogOut, Filter, ArrowUpDown, Calendar, ChevronRight, X } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
// --- Base Components ---
|
|
7
|
-
export function Dropdown({ children }: { children: React.ReactNode }) {
|
|
8
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
9
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
13
|
-
if (containerRef.current && !containerRef.current.contains(e.target as Node)) setIsOpen(false);
|
|
14
|
-
};
|
|
15
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
16
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
17
|
-
}, []);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!isOpen) return;
|
|
21
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
22
|
-
if (e.key === "Escape") setIsOpen(false);
|
|
23
|
-
};
|
|
24
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
25
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
26
|
-
}, [isOpen]);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="relative inline-block text-left" ref={containerRef}>
|
|
30
|
-
{React.Children.map(children, (child) => {
|
|
31
|
-
if (React.isValidElement(child)) {
|
|
32
|
-
return React.cloneElement(child as React.ReactElement<any>, { isOpen, setIsOpen });
|
|
33
|
-
}
|
|
34
|
-
return child;
|
|
35
|
-
})}
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function DropdownTrigger({ children, isOpen, setIsOpen }: any) {
|
|
41
|
-
return React.cloneElement(children, {
|
|
42
|
-
onClick: () => setIsOpen(!isOpen),
|
|
43
|
-
"aria-haspopup": "menu",
|
|
44
|
-
"aria-expanded": isOpen,
|
|
45
|
-
className: `${children.props.className} ${isOpen ? "ring-2 ring-zinc-200 dark:ring-zinc-800" : ""}`
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function DropdownContent({ children, isOpen, className = "", align = "left" }: any) {
|
|
50
|
-
if (!isOpen) return null;
|
|
51
|
-
const alignClass = align === "right" ? "right-0" : "left-0";
|
|
52
|
-
return (
|
|
53
|
-
<div role="menu" className={`absolute ${alignClass} z-50 mt-2 w-56 origin-top-right rounded-lg border border-zinc-200 bg-white p-1 shadow-xl outline-none dark:border-zinc-800 dark:bg-zinc-950 ${className} animate-in fade-in zoom-in-95 duration-200`}>
|
|
54
|
-
{children}
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function DropdownItem({ label, icon: Icon, selected, onClick, className = "", variant = "default" }: any) {
|
|
60
|
-
const baseClass = "flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors outline-none cursor-pointer";
|
|
61
|
-
const variantClass = variant === "destructive" ? "text-red-600 hover:bg-red-50 dark:hover:bg-red-950/30" : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-900";
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<button type="button" role="menuitem" className={`${baseClass} ${variantClass} ${className}`} onClick={onClick}>
|
|
65
|
-
{Icon && <Icon size={16} className="opacity-70" />}
|
|
66
|
-
<span className="flex-1">{label}</span>
|
|
67
|
-
{selected && <Check size={14} className="text-zinc-900 dark:text-white" />}
|
|
68
|
-
</button>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function DropdownLabel({ children }: { children: React.ReactNode }) {
|
|
73
|
-
return <div className="px-3 py-2 text-[11px] font-bold uppercase tracking-wider text-zinc-400">{children}</div>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function DropdownDivider() {
|
|
77
|
-
return <div role="separator" className="my-1 h-px bg-zinc-100 dark:bg-zinc-800" />;
|
|
78
|
-
}
|