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.
Files changed (119) hide show
  1. package/.next/standalone/.github/workflows/publish.yml +37 -0
  2. package/.next/standalone/DESIGN.md +521 -0
  3. package/.next/standalone/README.md +283 -0
  4. package/.next/standalone/bin/wiki-viewer.js +158 -0
  5. package/.next/standalone/next.config.ts +11 -0
  6. package/.next/standalone/package.json +120 -0
  7. package/.next/standalone/pnpm-lock.yaml +5654 -0
  8. package/.next/standalone/pnpm-workspace.yaml +2 -0
  9. package/.next/standalone/postcss.config.mjs +2 -0
  10. package/.next/standalone/public/logo.svg +11 -0
  11. package/.next/standalone/public/public/logo.svg +11 -0
  12. package/.next/standalone/server.js +44 -0
  13. package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +247 -0
  14. package/.next/standalone/src/app/api/assets/[...path]/route.ts +70 -0
  15. package/.next/standalone/src/app/api/system/browse/route.ts +78 -0
  16. package/.next/standalone/src/app/api/system/clear-root/route.ts +9 -0
  17. package/.next/standalone/src/app/api/system/config/route.ts +10 -0
  18. package/.next/standalone/src/app/api/system/pins/route.ts +31 -0
  19. package/.next/standalone/src/app/api/system/reveal/route.ts +28 -0
  20. package/.next/standalone/src/app/api/system/root-status/route.ts +9 -0
  21. package/.next/standalone/src/app/api/system/set-root/route.ts +23 -0
  22. package/.next/standalone/src/app/api/upload/[...path]/route.ts +80 -0
  23. package/.next/standalone/src/app/api/wiki/app/route.ts +41 -0
  24. package/.next/standalone/src/app/api/wiki/content/route.ts +60 -0
  25. package/.next/standalone/src/app/api/wiki/file/route.ts +52 -0
  26. package/.next/standalone/src/app/api/wiki/folder/route.ts +26 -0
  27. package/.next/standalone/src/app/api/wiki/move/route.ts +45 -0
  28. package/.next/standalone/src/app/api/wiki/page/route.ts +84 -0
  29. package/.next/standalone/src/app/api/wiki/route.ts +98 -0
  30. package/.next/standalone/src/app/api/wiki/slugs/route.ts +50 -0
  31. package/.next/standalone/src/app/api/wiki/upload/route.ts +87 -0
  32. package/.next/standalone/src/app/api/wiki/watch/route.ts +86 -0
  33. package/.next/standalone/src/app/globals.css +716 -0
  34. package/.next/standalone/src/app/icon.svg +11 -0
  35. package/.next/standalone/src/app/layout.tsx +35 -0
  36. package/.next/standalone/src/app/page.tsx +1509 -0
  37. package/.next/standalone/src/components/confirm-dialog.tsx +56 -0
  38. package/.next/standalone/src/components/dir-picker.tsx +393 -0
  39. package/.next/standalone/src/components/editor/bubble-menu.tsx +286 -0
  40. package/.next/standalone/src/components/editor/callout-extension.ts +36 -0
  41. package/.next/standalone/src/components/editor/csv-viewer.tsx +335 -0
  42. package/.next/standalone/src/components/editor/editor-toolbar.tsx +564 -0
  43. package/.next/standalone/src/components/editor/editor.tsx +633 -0
  44. package/.next/standalone/src/components/editor/extensions/drag-handle.ts +325 -0
  45. package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +55 -0
  46. package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +159 -0
  47. package/.next/standalone/src/components/editor/extensions.ts +148 -0
  48. package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +74 -0
  49. package/.next/standalone/src/components/editor/folder-index.tsx +235 -0
  50. package/.next/standalone/src/components/editor/google-doc-viewer.tsx +73 -0
  51. package/.next/standalone/src/components/editor/image-viewer.tsx +56 -0
  52. package/.next/standalone/src/components/editor/link-popover.tsx +111 -0
  53. package/.next/standalone/src/components/editor/media-popover.tsx +211 -0
  54. package/.next/standalone/src/components/editor/media-viewer.tsx +69 -0
  55. package/.next/standalone/src/components/editor/mermaid-viewer.tsx +285 -0
  56. package/.next/standalone/src/components/editor/node-app-viewer.tsx +269 -0
  57. package/.next/standalone/src/components/editor/notebook-viewer.tsx +370 -0
  58. package/.next/standalone/src/components/editor/office/docx-viewer.tsx +92 -0
  59. package/.next/standalone/src/components/editor/office/office-chrome.tsx +82 -0
  60. package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +103 -0
  61. package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +116 -0
  62. package/.next/standalone/src/components/editor/pdf-viewer.tsx +31 -0
  63. package/.next/standalone/src/components/editor/slash-commands.tsx +454 -0
  64. package/.next/standalone/src/components/editor/source-viewer.tsx +212 -0
  65. package/.next/standalone/src/components/editor/table-menu.tsx +232 -0
  66. package/.next/standalone/src/components/editor/website-viewer.tsx +74 -0
  67. package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +188 -0
  68. package/.next/standalone/src/components/editor/wiki-link-decorator.ts +79 -0
  69. package/.next/standalone/src/components/editor/wiki-link-extension.ts +120 -0
  70. package/.next/standalone/src/components/editor/wiki-link-picker.tsx +250 -0
  71. package/.next/standalone/src/components/layout/viewer-toolbar.tsx +54 -0
  72. package/.next/standalone/src/components/theme-provider.tsx +16 -0
  73. package/.next/standalone/src/components/theme-toggle.tsx +24 -0
  74. package/.next/standalone/src/components/ui/badge.tsx +35 -0
  75. package/.next/standalone/src/components/ui/button.tsx +56 -0
  76. package/.next/standalone/src/components/ui/card.tsx +63 -0
  77. package/.next/standalone/src/components/ui/collapsible.tsx +9 -0
  78. package/.next/standalone/src/components/ui/command.tsx +150 -0
  79. package/.next/standalone/src/components/ui/context-menu.tsx +191 -0
  80. package/.next/standalone/src/components/ui/dialog.tsx +119 -0
  81. package/.next/standalone/src/components/ui/dropdown-menu.tsx +194 -0
  82. package/.next/standalone/src/components/ui/input.tsx +22 -0
  83. package/.next/standalone/src/components/ui/label.tsx +25 -0
  84. package/.next/standalone/src/components/ui/popover.tsx +33 -0
  85. package/.next/standalone/src/components/ui/scroll-area.tsx +47 -0
  86. package/.next/standalone/src/components/ui/select.tsx +156 -0
  87. package/.next/standalone/src/components/ui/separator.tsx +30 -0
  88. package/.next/standalone/src/components/ui/sheet.tsx +140 -0
  89. package/.next/standalone/src/components/ui/skeleton.tsx +15 -0
  90. package/.next/standalone/src/components/ui/switch.tsx +28 -0
  91. package/.next/standalone/src/components/ui/tabs.tsx +54 -0
  92. package/.next/standalone/src/components/ui/textarea.tsx +21 -0
  93. package/.next/standalone/src/components/ui/tip.tsx +23 -0
  94. package/.next/standalone/src/components/ui/toggle-group.tsx +41 -0
  95. package/.next/standalone/src/components/ui/tooltip.tsx +27 -0
  96. package/.next/standalone/src/components/wiki/frontmatter-header.tsx +164 -0
  97. package/.next/standalone/src/lib/app-runner.ts +231 -0
  98. package/.next/standalone/src/lib/cabinets/tree.ts +75 -0
  99. package/.next/standalone/src/lib/config.ts +32 -0
  100. package/.next/standalone/src/lib/embeds/detect.ts +142 -0
  101. package/.next/standalone/src/lib/google/detect.ts +107 -0
  102. package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +84 -0
  103. package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +100 -0
  104. package/.next/standalone/src/lib/markdown/to-html.ts +168 -0
  105. package/.next/standalone/src/lib/markdown/to-markdown.ts +238 -0
  106. package/.next/standalone/src/lib/root-dir.ts +35 -0
  107. package/.next/standalone/src/lib/toast.ts +9 -0
  108. package/.next/standalone/src/lib/utils.ts +6 -0
  109. package/.next/standalone/src/lib/wiki-helpers.ts +36 -0
  110. package/.next/standalone/src/stores/ai-panel-store.ts +23 -0
  111. package/.next/standalone/src/stores/editor-store.ts +244 -0
  112. package/.next/standalone/src/stores/tree-store.ts +52 -0
  113. package/.next/standalone/src/stores/wiki-slugs-store.ts +74 -0
  114. package/.next/standalone/src/types/index.ts +47 -0
  115. package/.next/standalone/tailwind.config.ts +201 -0
  116. package/.next/standalone/tsconfig.json +41 -0
  117. package/README.md +283 -0
  118. package/bin/wiki-viewer.js +158 -0
  119. 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 &ldquo;{query}&rdquo;
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
+ };