wiki-viewer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.github/workflows/publish.yml +37 -0
- package/.next/standalone/DESIGN.md +521 -0
- package/.next/standalone/README.md +283 -0
- package/.next/standalone/bin/wiki-viewer.js +158 -0
- package/.next/standalone/next.config.ts +11 -0
- package/.next/standalone/package.json +120 -0
- package/.next/standalone/pnpm-lock.yaml +5654 -0
- package/.next/standalone/pnpm-workspace.yaml +2 -0
- package/.next/standalone/postcss.config.mjs +2 -0
- package/.next/standalone/public/logo.svg +11 -0
- package/.next/standalone/public/public/logo.svg +11 -0
- package/.next/standalone/server.js +44 -0
- package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +247 -0
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +70 -0
- package/.next/standalone/src/app/api/system/browse/route.ts +78 -0
- package/.next/standalone/src/app/api/system/clear-root/route.ts +9 -0
- package/.next/standalone/src/app/api/system/config/route.ts +10 -0
- package/.next/standalone/src/app/api/system/pins/route.ts +31 -0
- package/.next/standalone/src/app/api/system/reveal/route.ts +28 -0
- package/.next/standalone/src/app/api/system/root-status/route.ts +9 -0
- package/.next/standalone/src/app/api/system/set-root/route.ts +23 -0
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +80 -0
- package/.next/standalone/src/app/api/wiki/app/route.ts +41 -0
- package/.next/standalone/src/app/api/wiki/content/route.ts +60 -0
- package/.next/standalone/src/app/api/wiki/file/route.ts +52 -0
- package/.next/standalone/src/app/api/wiki/folder/route.ts +26 -0
- package/.next/standalone/src/app/api/wiki/move/route.ts +45 -0
- package/.next/standalone/src/app/api/wiki/page/route.ts +84 -0
- package/.next/standalone/src/app/api/wiki/route.ts +98 -0
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +50 -0
- package/.next/standalone/src/app/api/wiki/upload/route.ts +87 -0
- package/.next/standalone/src/app/api/wiki/watch/route.ts +86 -0
- package/.next/standalone/src/app/globals.css +716 -0
- package/.next/standalone/src/app/icon.svg +11 -0
- package/.next/standalone/src/app/layout.tsx +35 -0
- package/.next/standalone/src/app/page.tsx +1509 -0
- package/.next/standalone/src/components/confirm-dialog.tsx +56 -0
- package/.next/standalone/src/components/dir-picker.tsx +393 -0
- package/.next/standalone/src/components/editor/bubble-menu.tsx +286 -0
- package/.next/standalone/src/components/editor/callout-extension.ts +36 -0
- package/.next/standalone/src/components/editor/csv-viewer.tsx +335 -0
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +564 -0
- package/.next/standalone/src/components/editor/editor.tsx +633 -0
- package/.next/standalone/src/components/editor/extensions/drag-handle.ts +325 -0
- package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +55 -0
- package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +159 -0
- package/.next/standalone/src/components/editor/extensions.ts +148 -0
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +74 -0
- package/.next/standalone/src/components/editor/folder-index.tsx +235 -0
- package/.next/standalone/src/components/editor/google-doc-viewer.tsx +73 -0
- package/.next/standalone/src/components/editor/image-viewer.tsx +56 -0
- package/.next/standalone/src/components/editor/link-popover.tsx +111 -0
- package/.next/standalone/src/components/editor/media-popover.tsx +211 -0
- package/.next/standalone/src/components/editor/media-viewer.tsx +69 -0
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +285 -0
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +269 -0
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +370 -0
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +92 -0
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +82 -0
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +103 -0
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +116 -0
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +31 -0
- package/.next/standalone/src/components/editor/slash-commands.tsx +454 -0
- package/.next/standalone/src/components/editor/source-viewer.tsx +212 -0
- package/.next/standalone/src/components/editor/table-menu.tsx +232 -0
- package/.next/standalone/src/components/editor/website-viewer.tsx +74 -0
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +188 -0
- package/.next/standalone/src/components/editor/wiki-link-decorator.ts +79 -0
- package/.next/standalone/src/components/editor/wiki-link-extension.ts +120 -0
- package/.next/standalone/src/components/editor/wiki-link-picker.tsx +250 -0
- package/.next/standalone/src/components/layout/viewer-toolbar.tsx +54 -0
- package/.next/standalone/src/components/theme-provider.tsx +16 -0
- package/.next/standalone/src/components/theme-toggle.tsx +24 -0
- package/.next/standalone/src/components/ui/badge.tsx +35 -0
- package/.next/standalone/src/components/ui/button.tsx +56 -0
- package/.next/standalone/src/components/ui/card.tsx +63 -0
- package/.next/standalone/src/components/ui/collapsible.tsx +9 -0
- package/.next/standalone/src/components/ui/command.tsx +150 -0
- package/.next/standalone/src/components/ui/context-menu.tsx +191 -0
- package/.next/standalone/src/components/ui/dialog.tsx +119 -0
- package/.next/standalone/src/components/ui/dropdown-menu.tsx +194 -0
- package/.next/standalone/src/components/ui/input.tsx +22 -0
- package/.next/standalone/src/components/ui/label.tsx +25 -0
- package/.next/standalone/src/components/ui/popover.tsx +33 -0
- package/.next/standalone/src/components/ui/scroll-area.tsx +47 -0
- package/.next/standalone/src/components/ui/select.tsx +156 -0
- package/.next/standalone/src/components/ui/separator.tsx +30 -0
- package/.next/standalone/src/components/ui/sheet.tsx +140 -0
- package/.next/standalone/src/components/ui/skeleton.tsx +15 -0
- package/.next/standalone/src/components/ui/switch.tsx +28 -0
- package/.next/standalone/src/components/ui/tabs.tsx +54 -0
- package/.next/standalone/src/components/ui/textarea.tsx +21 -0
- package/.next/standalone/src/components/ui/tip.tsx +23 -0
- package/.next/standalone/src/components/ui/toggle-group.tsx +41 -0
- package/.next/standalone/src/components/ui/tooltip.tsx +27 -0
- package/.next/standalone/src/components/wiki/frontmatter-header.tsx +164 -0
- package/.next/standalone/src/lib/app-runner.ts +231 -0
- package/.next/standalone/src/lib/cabinets/tree.ts +75 -0
- package/.next/standalone/src/lib/config.ts +32 -0
- package/.next/standalone/src/lib/embeds/detect.ts +142 -0
- package/.next/standalone/src/lib/google/detect.ts +107 -0
- package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +84 -0
- package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +100 -0
- package/.next/standalone/src/lib/markdown/to-html.ts +168 -0
- package/.next/standalone/src/lib/markdown/to-markdown.ts +238 -0
- package/.next/standalone/src/lib/root-dir.ts +35 -0
- package/.next/standalone/src/lib/toast.ts +9 -0
- package/.next/standalone/src/lib/utils.ts +6 -0
- package/.next/standalone/src/lib/wiki-helpers.ts +36 -0
- package/.next/standalone/src/stores/ai-panel-store.ts +23 -0
- package/.next/standalone/src/stores/editor-store.ts +244 -0
- package/.next/standalone/src/stores/tree-store.ts +52 -0
- package/.next/standalone/src/stores/wiki-slugs-store.ts +74 -0
- package/.next/standalone/src/types/index.ts +47 -0
- package/.next/standalone/tailwind.config.ts +201 -0
- package/.next/standalone/tsconfig.json +41 -0
- package/README.md +283 -0
- package/bin/wiki-viewer.js +158 -0
- package/package.json +120 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Editor } from "@tiptap/react";
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { useWikiSlugsStore } from "@/stores/wiki-slugs-store";
|
|
7
|
+
import type { WikiCreateResult } from "./wiki-link-create-dialog";
|
|
8
|
+
|
|
9
|
+
interface WikiLinkPickerProps {
|
|
10
|
+
editor: Editor | null;
|
|
11
|
+
onCreateRequest: (slug: string) => Promise<WikiCreateResult>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SLUG_RE = /^[a-z0-9-]+$/;
|
|
15
|
+
const MAX_RESULTS = 20;
|
|
16
|
+
|
|
17
|
+
function fuzzyMatch(slug: string, query: string): boolean {
|
|
18
|
+
if (!query) return true;
|
|
19
|
+
return slug.includes(query.toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function WikiLinkPicker({
|
|
23
|
+
editor,
|
|
24
|
+
onCreateRequest,
|
|
25
|
+
}: WikiLinkPickerProps) {
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
const [query, setQuery] = useState("");
|
|
28
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
29
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
30
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
|
|
32
|
+
// triggerFrom: doc position immediately after `[[` is fully inserted.
|
|
33
|
+
// Calculated as `from + 1` at the moment the second `[` keydown fires.
|
|
34
|
+
const triggerFromRef = useRef<number>(0);
|
|
35
|
+
|
|
36
|
+
const slugs = useWikiSlugsStore((s) => s.slugs);
|
|
37
|
+
const slugDir = useWikiSlugsStore((s) => s.slugDir);
|
|
38
|
+
const has = useWikiSlugsStore((s) => s.has);
|
|
39
|
+
|
|
40
|
+
const allSlugs = Array.from(slugs).sort();
|
|
41
|
+
const filtered = allSlugs
|
|
42
|
+
.filter((s) => fuzzyMatch(s, query))
|
|
43
|
+
.slice(0, MAX_RESULTS);
|
|
44
|
+
|
|
45
|
+
const canCreate = query.length > 0 && SLUG_RE.test(query) && !has(query);
|
|
46
|
+
const totalItems = filtered.length + (canCreate ? 1 : 0);
|
|
47
|
+
|
|
48
|
+
const handleClose = useCallback(() => {
|
|
49
|
+
setOpen(false);
|
|
50
|
+
setQuery("");
|
|
51
|
+
setSelectedIndex(0);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const insertWikiLink = useCallback(
|
|
55
|
+
(slug: string) => {
|
|
56
|
+
if (!editor) return;
|
|
57
|
+
const { from } = editor.state.selection;
|
|
58
|
+
// Delete the `[[query` range and replace with the wiki-link mark.
|
|
59
|
+
const start = triggerFromRef.current - 2; // back over [[
|
|
60
|
+
const markType = editor.state.schema.marks.wikiLink;
|
|
61
|
+
if (!markType) return;
|
|
62
|
+
const mark = markType.create({ slug, alias: null, anchor: null });
|
|
63
|
+
const node = editor.state.schema.text(slug, [mark]);
|
|
64
|
+
const tr = editor.state.tr.replaceWith(start, from, node);
|
|
65
|
+
editor.view.dispatch(tr);
|
|
66
|
+
editor.commands.focus();
|
|
67
|
+
handleClose();
|
|
68
|
+
},
|
|
69
|
+
[editor, handleClose],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleSelect = useCallback(
|
|
73
|
+
async (index: number) => {
|
|
74
|
+
if (!editor) return;
|
|
75
|
+
if (index < filtered.length) {
|
|
76
|
+
insertWikiLink(filtered[index] ?? "");
|
|
77
|
+
} else if (canCreate) {
|
|
78
|
+
// Delegate to create dialog; insert mark on success.
|
|
79
|
+
const result = await onCreateRequest(query);
|
|
80
|
+
if (result.ok) {
|
|
81
|
+
insertWikiLink(result.slug);
|
|
82
|
+
} else {
|
|
83
|
+
handleClose();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[
|
|
88
|
+
editor,
|
|
89
|
+
filtered,
|
|
90
|
+
canCreate,
|
|
91
|
+
query,
|
|
92
|
+
insertWikiLink,
|
|
93
|
+
onCreateRequest,
|
|
94
|
+
handleClose,
|
|
95
|
+
],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!editor) return;
|
|
100
|
+
|
|
101
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
102
|
+
if (!open) {
|
|
103
|
+
// Detect second `[` — trigger is `[[`
|
|
104
|
+
if (event.key === "[") {
|
|
105
|
+
const { from } = editor.state.selection;
|
|
106
|
+
const textBefore = editor.state.doc.textBetween(
|
|
107
|
+
Math.max(0, from - 1),
|
|
108
|
+
from,
|
|
109
|
+
);
|
|
110
|
+
if (textBefore !== "[") return;
|
|
111
|
+
|
|
112
|
+
// Skip if cursor is inside an existing wikiLink mark
|
|
113
|
+
const $pos = editor.state.doc.resolve(from);
|
|
114
|
+
const insideWikiLink = $pos
|
|
115
|
+
.marks()
|
|
116
|
+
.some((m) => m.type.name === "wikiLink");
|
|
117
|
+
if (insideWikiLink) return;
|
|
118
|
+
|
|
119
|
+
const coords = editor.view.coordsAtPos(from);
|
|
120
|
+
const editorRect = editor.view.dom.getBoundingClientRect();
|
|
121
|
+
setPosition({
|
|
122
|
+
top: coords.bottom - editorRect.top + 4,
|
|
123
|
+
left: coords.left - editorRect.left,
|
|
124
|
+
});
|
|
125
|
+
// After keydown resolves, the second `[` will be inserted
|
|
126
|
+
// moving the cursor to `from + 1`.
|
|
127
|
+
triggerFromRef.current = from + 1;
|
|
128
|
+
setOpen(true);
|
|
129
|
+
setQuery("");
|
|
130
|
+
setSelectedIndex(0);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Picker is open — handle navigation keys
|
|
136
|
+
if (event.key === "ArrowDown") {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
setSelectedIndex((i) => (totalItems > 0 ? (i + 1) % totalItems : 0));
|
|
139
|
+
} else if (event.key === "ArrowUp") {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
setSelectedIndex((i) =>
|
|
142
|
+
totalItems > 0 ? (i - 1 + totalItems) % totalItems : 0,
|
|
143
|
+
);
|
|
144
|
+
} else if (event.key === "Enter") {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
event.stopPropagation();
|
|
147
|
+
if (totalItems > 0) {
|
|
148
|
+
void handleSelect(selectedIndex);
|
|
149
|
+
}
|
|
150
|
+
} else if (event.key === "Escape") {
|
|
151
|
+
handleClose();
|
|
152
|
+
} else if (event.key === "]") {
|
|
153
|
+
// Closing bracket dismisses without inserting
|
|
154
|
+
handleClose();
|
|
155
|
+
} else if (event.key === "Backspace") {
|
|
156
|
+
if (query.length === 0) {
|
|
157
|
+
handleClose();
|
|
158
|
+
} else {
|
|
159
|
+
setQuery((q) => q.slice(0, -1));
|
|
160
|
+
setSelectedIndex(0);
|
|
161
|
+
}
|
|
162
|
+
} else if (event.key.length === 1 && !event.metaKey && !event.ctrlKey) {
|
|
163
|
+
setQuery((q) => q + event.key);
|
|
164
|
+
setSelectedIndex(0);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
window.addEventListener("keydown", handleKeyDown, true);
|
|
169
|
+
return () => window.removeEventListener("keydown", handleKeyDown, true);
|
|
170
|
+
}, [
|
|
171
|
+
editor,
|
|
172
|
+
open,
|
|
173
|
+
query,
|
|
174
|
+
selectedIndex,
|
|
175
|
+
totalItems,
|
|
176
|
+
handleClose,
|
|
177
|
+
handleSelect,
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
// Close on outside click
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!open) return;
|
|
183
|
+
const handleMouseDown = (e: MouseEvent) => {
|
|
184
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
185
|
+
handleClose();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
window.addEventListener("mousedown", handleMouseDown);
|
|
189
|
+
return () => window.removeEventListener("mousedown", handleMouseDown);
|
|
190
|
+
}, [open, handleClose]);
|
|
191
|
+
|
|
192
|
+
if (!open || totalItems === 0) return null;
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
ref={menuRef}
|
|
197
|
+
className="absolute z-50 w-[280px] bg-popover border border-border rounded-lg shadow-lg py-1 overflow-hidden max-h-[300px] overflow-y-auto"
|
|
198
|
+
style={{ top: position.top, left: position.left }}
|
|
199
|
+
>
|
|
200
|
+
{filtered.map((slug, i) => {
|
|
201
|
+
const dir = slugDir.get(slug);
|
|
202
|
+
return (
|
|
203
|
+
<button
|
|
204
|
+
key={slug}
|
|
205
|
+
type="button"
|
|
206
|
+
onMouseDown={(e) => {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
void handleSelect(i);
|
|
209
|
+
}}
|
|
210
|
+
onMouseEnter={() => setSelectedIndex(i)}
|
|
211
|
+
className={cn(
|
|
212
|
+
"flex items-center justify-between w-full px-3 py-1.5 text-left text-sm transition-colors",
|
|
213
|
+
i === selectedIndex
|
|
214
|
+
? "bg-accent text-accent-foreground"
|
|
215
|
+
: "hover:bg-accent/50",
|
|
216
|
+
)}
|
|
217
|
+
>
|
|
218
|
+
<span className="truncate">{slug}</span>
|
|
219
|
+
{dir && (
|
|
220
|
+
<span className="ml-2 shrink-0 text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
|
|
221
|
+
{dir}
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
</button>
|
|
225
|
+
);
|
|
226
|
+
})}
|
|
227
|
+
|
|
228
|
+
{canCreate && (
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onMouseDown={(e) => {
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
void handleSelect(filtered.length);
|
|
234
|
+
}}
|
|
235
|
+
onMouseEnter={() => setSelectedIndex(filtered.length)}
|
|
236
|
+
className={cn(
|
|
237
|
+
"flex items-center w-full px-3 py-1.5 text-left text-sm transition-colors",
|
|
238
|
+
filtered.length === selectedIndex
|
|
239
|
+
? "bg-accent text-accent-foreground"
|
|
240
|
+
: "hover:bg-accent/50",
|
|
241
|
+
)}
|
|
242
|
+
>
|
|
243
|
+
<span className="text-muted-foreground">
|
|
244
|
+
+ Create new “{query}”
|
|
245
|
+
</span>
|
|
246
|
+
</button>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
// Simplified toolbar stub used by the ported viewer components.
|
|
7
|
+
// Replace with a fuller implementation if navigation breadcrumbs are needed.
|
|
8
|
+
|
|
9
|
+
export function ViewerToolbar({
|
|
10
|
+
path,
|
|
11
|
+
badge,
|
|
12
|
+
sublabel,
|
|
13
|
+
showBreadcrumb: _showBreadcrumb = true,
|
|
14
|
+
leading,
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
}: {
|
|
18
|
+
path?: string;
|
|
19
|
+
badge?: string;
|
|
20
|
+
sublabel?: string;
|
|
21
|
+
showBreadcrumb?: boolean;
|
|
22
|
+
leading?: ReactNode;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cn(
|
|
29
|
+
"flex shrink-0 items-center justify-between gap-3 border-b border-border bg-background px-4 py-2",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
34
|
+
{leading}
|
|
35
|
+
{path && (
|
|
36
|
+
<span className="text-xs text-muted-foreground truncate" title={path}>
|
|
37
|
+
{path.split("/").pop() ?? path}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
{badge && (
|
|
41
|
+
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground/50">
|
|
42
|
+
{badge}
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
{sublabel && (
|
|
46
|
+
<span className="shrink-0 text-xs text-muted-foreground/40">
|
|
47
|
+
{sublabel}
|
|
48
|
+
</span>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex shrink-0 items-center gap-1">{children}</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
4
|
+
|
|
5
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
6
|
+
return (
|
|
7
|
+
<NextThemesProvider
|
|
8
|
+
attribute="class"
|
|
9
|
+
defaultTheme="system"
|
|
10
|
+
enableSystem
|
|
11
|
+
disableTransitionOnChange
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</NextThemesProvider>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Moon, Sun } from "lucide-react";
|
|
4
|
+
import { useTheme } from "next-themes";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
export function ThemeToggle({ className }: { className?: string }) {
|
|
8
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
9
|
+
return (
|
|
10
|
+
<Button
|
|
11
|
+
size="sm"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
className={`h-7 w-7 p-0 ${className ?? ""}`}
|
|
14
|
+
title={resolvedTheme === "dark" ? "Switch to light" : "Switch to dark"}
|
|
15
|
+
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
|
|
16
|
+
>
|
|
17
|
+
{resolvedTheme === "dark" ? (
|
|
18
|
+
<Sun className="h-3.5 w-3.5" />
|
|
19
|
+
) : (
|
|
20
|
+
<Moon className="h-3.5 w-3.5" />
|
|
21
|
+
)}
|
|
22
|
+
</Button>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-sm border px-2.5 py-0.5 text-xs font-normal transition-colors focus:outline-none focus:ring-2 focus:ring-ring",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"border-transparent bg-primary text-primary-foreground shadow-e-1",
|
|
12
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
13
|
+
destructive:
|
|
14
|
+
"border-transparent bg-destructive text-destructive-foreground shadow-e-1",
|
|
15
|
+
outline: "text-foreground",
|
|
16
|
+
accent: "border-transparent bg-accent text-accent-foreground",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export interface BadgeProps
|
|
26
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
27
|
+
VariantProps<typeof badgeVariants> {}
|
|
28
|
+
|
|
29
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium normal-case transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"bg-primary text-primary-foreground shadow-e-1 hover:bg-primary/90",
|
|
13
|
+
destructive:
|
|
14
|
+
"bg-destructive text-destructive-foreground shadow-e-1 hover:bg-destructive/90",
|
|
15
|
+
outline:
|
|
16
|
+
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
secondary:
|
|
18
|
+
"bg-secondary text-secondary-foreground shadow-e-1 hover:bg-secondary/80",
|
|
19
|
+
ghost: "bg-transparent hover:bg-accent/10 text-foreground",
|
|
20
|
+
link: "text-foreground underline underline-offset-4 hover:text-accent",
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default: "h-10 p-3 rounded-full",
|
|
24
|
+
sm: "h-8 rounded-full px-3 text-xs",
|
|
25
|
+
lg: "h-10 rounded-full px-8",
|
|
26
|
+
icon: "h-9 w-9 rounded-full",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
size: "default",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface ButtonProps
|
|
37
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
38
|
+
VariantProps<typeof buttonVariants> {
|
|
39
|
+
asChild?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
43
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
44
|
+
const Comp = asChild ? Slot : "button";
|
|
45
|
+
return (
|
|
46
|
+
<Comp
|
|
47
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
+
ref={ref}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
Button.displayName = "Button";
|
|
55
|
+
|
|
56
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
const Card = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
"rounded-md border border-border bg-card text-card-foreground shadow-e-2",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
));
|
|
17
|
+
Card.displayName = "Card";
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
CardHeader.displayName = "CardHeader";
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLDivElement,
|
|
33
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<div
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn("text-lg font-normal leading-none tracking-tight", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
41
|
+
CardTitle.displayName = "CardTitle";
|
|
42
|
+
|
|
43
|
+
const CardDescription = React.forwardRef<
|
|
44
|
+
HTMLDivElement,
|
|
45
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<div
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
));
|
|
53
|
+
CardDescription.displayName = "CardDescription";
|
|
54
|
+
|
|
55
|
+
const CardContent = React.forwardRef<
|
|
56
|
+
HTMLDivElement,
|
|
57
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
60
|
+
));
|
|
61
|
+
CardContent.displayName = "CardContent";
|
|
62
|
+
|
|
63
|
+
export { Card, CardContent, CardDescription, CardHeader, CardTitle };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
4
|
+
|
|
5
|
+
const Collapsible = CollapsiblePrimitive.Root;
|
|
6
|
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
|
7
|
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
|
8
|
+
|
|
9
|
+
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
4
|
+
import { Search } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
const Command = React.forwardRef<
|
|
10
|
+
React.ComponentRef<typeof CommandPrimitive>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<CommandPrimitive
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex h-full w-full flex-col overflow-hidden rounded-sm bg-popover text-popover-foreground",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
Command.displayName = CommandPrimitive.displayName;
|
|
23
|
+
|
|
24
|
+
function CommandDialog({
|
|
25
|
+
children,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof Dialog>) {
|
|
28
|
+
return (
|
|
29
|
+
<Dialog {...props}>
|
|
30
|
+
<DialogContent className="overflow-hidden p-0 shadow-golden max-w-lg">
|
|
31
|
+
<DialogTitle className="sr-only">Search</DialogTitle>
|
|
32
|
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
33
|
+
{children}
|
|
34
|
+
</Command>
|
|
35
|
+
</DialogContent>
|
|
36
|
+
</Dialog>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CommandInput = React.forwardRef<
|
|
41
|
+
React.ComponentRef<typeof CommandPrimitive.Input>,
|
|
42
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
43
|
+
>(({ className, ...props }, ref) => (
|
|
44
|
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
45
|
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
46
|
+
<CommandPrimitive.Input
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(
|
|
49
|
+
"flex h-11 w-full rounded-sm bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
));
|
|
56
|
+
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
|
57
|
+
|
|
58
|
+
const CommandList = React.forwardRef<
|
|
59
|
+
React.ComponentRef<typeof CommandPrimitive.List>,
|
|
60
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<CommandPrimitive.List
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
));
|
|
68
|
+
CommandList.displayName = CommandPrimitive.List.displayName;
|
|
69
|
+
|
|
70
|
+
const CommandEmpty = React.forwardRef<
|
|
71
|
+
React.ComponentRef<typeof CommandPrimitive.Empty>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
73
|
+
>((props, ref) => (
|
|
74
|
+
<CommandPrimitive.Empty
|
|
75
|
+
ref={ref}
|
|
76
|
+
className="py-6 text-center text-sm text-muted-foreground"
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
));
|
|
80
|
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
|
81
|
+
|
|
82
|
+
const CommandGroup = React.forwardRef<
|
|
83
|
+
React.ComponentRef<typeof CommandPrimitive.Group>,
|
|
84
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
85
|
+
>(({ className, ...props }, ref) => (
|
|
86
|
+
<CommandPrimitive.Group
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn(
|
|
89
|
+
"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-normal [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
));
|
|
95
|
+
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
|
96
|
+
|
|
97
|
+
const CommandSeparator = React.forwardRef<
|
|
98
|
+
React.ComponentRef<typeof CommandPrimitive.Separator>,
|
|
99
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
100
|
+
>(({ className, ...props }, ref) => (
|
|
101
|
+
<CommandPrimitive.Separator
|
|
102
|
+
ref={ref}
|
|
103
|
+
className={cn("-mx-1 h-px bg-border", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
));
|
|
107
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
|
108
|
+
|
|
109
|
+
const CommandItem = React.forwardRef<
|
|
110
|
+
React.ComponentRef<typeof CommandPrimitive.Item>,
|
|
111
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
112
|
+
>(({ className, ...props }, ref) => (
|
|
113
|
+
<CommandPrimitive.Item
|
|
114
|
+
ref={ref}
|
|
115
|
+
className={cn(
|
|
116
|
+
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
117
|
+
className,
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
));
|
|
122
|
+
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
|
123
|
+
|
|
124
|
+
function CommandShortcut({
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}: React.HTMLAttributes<HTMLSpanElement>) {
|
|
128
|
+
return (
|
|
129
|
+
<span
|
|
130
|
+
className={cn(
|
|
131
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
CommandShortcut.displayName = "CommandShortcut";
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
Command,
|
|
142
|
+
CommandDialog,
|
|
143
|
+
CommandEmpty,
|
|
144
|
+
CommandGroup,
|
|
145
|
+
CommandInput,
|
|
146
|
+
CommandItem,
|
|
147
|
+
CommandList,
|
|
148
|
+
CommandSeparator,
|
|
149
|
+
CommandShortcut,
|
|
150
|
+
};
|