aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui-new/src/components
diff options
context:
space:
mode:
author苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
committer苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
commit66668d85d603c5841d755a6023aa1925559fc6d4 (patch)
tree485464148c76b0021efb55b7d2afd1c3004ceee0 /packages/ui-new/src/components
parenta6773bd092db654360c599ca6b0108ea0e456e8c (diff)
downloadDropOut-66668d85d603c5841d755a6023aa1925559fc6d4.tar.gz
DropOut-66668d85d603c5841d755a6023aa1925559fc6d4.zip
chore(workspace): replace legacy codes
Diffstat (limited to 'packages/ui-new/src/components')
-rw-r--r--packages/ui-new/src/components/bottom-bar.tsx231
-rw-r--r--packages/ui-new/src/components/config-editor.tsx111
-rw-r--r--packages/ui-new/src/components/download-monitor.tsx62
-rw-r--r--packages/ui-new/src/components/game-console.tsx290
-rw-r--r--packages/ui-new/src/components/instance-creation-modal.tsx552
-rw-r--r--packages/ui-new/src/components/instance-editor-modal.tsx548
-rw-r--r--packages/ui-new/src/components/login-modal.tsx188
-rw-r--r--packages/ui-new/src/components/particle-background.tsx63
-rw-r--r--packages/ui-new/src/components/sidebar.tsx185
-rw-r--r--packages/ui-new/src/components/ui/avatar.tsx107
-rw-r--r--packages/ui-new/src/components/ui/badge.tsx52
-rw-r--r--packages/ui-new/src/components/ui/button.tsx56
-rw-r--r--packages/ui-new/src/components/ui/card.tsx103
-rw-r--r--packages/ui-new/src/components/ui/checkbox.tsx27
-rw-r--r--packages/ui-new/src/components/ui/dialog.tsx155
-rw-r--r--packages/ui-new/src/components/ui/dropdown-menu.tsx269
-rw-r--r--packages/ui-new/src/components/ui/field.tsx238
-rw-r--r--packages/ui-new/src/components/ui/input.tsx20
-rw-r--r--packages/ui-new/src/components/ui/label.tsx19
-rw-r--r--packages/ui-new/src/components/ui/scroll-area.tsx53
-rw-r--r--packages/ui-new/src/components/ui/select.tsx199
-rw-r--r--packages/ui-new/src/components/ui/separator.tsx25
-rw-r--r--packages/ui-new/src/components/ui/sonner.tsx43
-rw-r--r--packages/ui-new/src/components/ui/spinner.tsx10
-rw-r--r--packages/ui-new/src/components/ui/switch.tsx32
-rw-r--r--packages/ui-new/src/components/ui/tabs.tsx80
-rw-r--r--packages/ui-new/src/components/ui/textarea.tsx18
-rw-r--r--packages/ui-new/src/components/user-avatar.tsx23
28 files changed, 0 insertions, 3759 deletions
diff --git a/packages/ui-new/src/components/bottom-bar.tsx b/packages/ui-new/src/components/bottom-bar.tsx
deleted file mode 100644
index 32eb852..0000000
--- a/packages/ui-new/src/components/bottom-bar.tsx
+++ /dev/null
@@ -1,231 +0,0 @@
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { Play, User } from "lucide-react";
-import { useCallback, useEffect, useMemo, useState } from "react";
-import { toast } from "sonner";
-import { listInstalledVersions, startGame } from "@/client";
-import { cn } from "@/lib/utils";
-import { useAuthStore } from "@/models/auth";
-import { useInstancesStore } from "@/models/instances";
-import { useGameStore } from "@/stores/game-store";
-import { LoginModal } from "./login-modal";
-import { Button } from "./ui/button";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "./ui/select";
-
-interface InstalledVersion {
- id: string;
- type: string;
-}
-
-export function BottomBar() {
- const authStore = useAuthStore();
- const gameStore = useGameStore();
- const instancesStore = useInstancesStore();
-
- const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
- const [installedVersions, setInstalledVersions] = useState<
- InstalledVersion[]
- >([]);
- const [isLoadingVersions, setIsLoadingVersions] = useState(true);
- const [showLoginModal, setShowLoginModal] = useState(false);
-
- const loadInstalledVersions = useCallback(async () => {
- if (!instancesStore.activeInstance) {
- setInstalledVersions([]);
- setIsLoadingVersions(false);
- return;
- }
-
- setIsLoadingVersions(true);
- try {
- const versions = await listInstalledVersions(
- instancesStore.activeInstance.id,
- );
-
- const installed = versions || [];
- setInstalledVersions(installed);
-
- // If no version is selected but we have installed versions, select the first one
- if (!gameStore.selectedVersion && installed.length > 0) {
- gameStore.setSelectedVersion(installed[0].id);
- }
- } catch (error) {
- console.error("Failed to load installed versions:", error);
- } finally {
- setIsLoadingVersions(false);
- }
- }, [
- instancesStore.activeInstance,
- gameStore.selectedVersion,
- gameStore.setSelectedVersion,
- ]);
-
- useEffect(() => {
- loadInstalledVersions();
-
- // Listen for backend events that should refresh installed versions.
- let unlistenDownload: UnlistenFn | null = null;
- let unlistenVersionDeleted: UnlistenFn | null = null;
-
- (async () => {
- try {
- unlistenDownload = await listen("download-complete", () => {
- loadInstalledVersions();
- });
- } catch (err) {
- // best-effort: do not break UI if listening fails
- // eslint-disable-next-line no-console
- console.warn("Failed to attach download-complete listener:", err);
- }
-
- try {
- unlistenVersionDeleted = await listen("version-deleted", () => {
- loadInstalledVersions();
- });
- } catch (err) {
- // eslint-disable-next-line no-console
- console.warn("Failed to attach version-deleted listener:", err);
- }
- })();
-
- return () => {
- try {
- if (unlistenDownload) unlistenDownload();
- } catch {
- // ignore
- }
- try {
- if (unlistenVersionDeleted) unlistenVersionDeleted();
- } catch {
- // ignore
- }
- };
- }, [loadInstalledVersions]);
-
- const handleStartGame = async () => {
- if (!selectedVersion) {
- toast.info("Please select a version!");
- return;
- }
-
- if (!instancesStore.activeInstance) {
- toast.info("Please select an instance first!");
- return;
- }
- // await gameStore.startGame(
- // authStore.currentAccount,
- // authStore.openLoginModal,
- // instancesStore.activeInstanceId,
- // uiStore.setView,
- // );
- await startGame(instancesStore.activeInstance?.id, selectedVersion);
- };
-
- const getVersionTypeColor = (type: string) => {
- switch (type) {
- case "release":
- return "bg-emerald-500";
- case "snapshot":
- return "bg-amber-500";
- case "old_beta":
- return "bg-rose-500";
- case "old_alpha":
- return "bg-violet-500";
- default:
- return "bg-gray-500";
- }
- };
-
- const versionOptions = useMemo(
- () =>
- installedVersions.map((v) => ({
- label: `${v.id}${v.type !== "release" ? ` (${v.type})` : ""}`,
- value: v.id,
- type: v.type,
- })),
- [installedVersions],
- );
-
- return (
- <div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/30 via-transparent to-transparent p-4 z-10">
- <div className="max-w-7xl mx-auto">
- <div className="flex items-center justify-between bg-white/5 dark:bg-black/20 backdrop-blur-xl border border-white/10 dark:border-white/5 p-3 shadow-lg">
- <div className="flex items-center gap-4">
- <div className="flex flex-col">
- <span className="text-xs font-mono text-zinc-400 uppercase tracking-wider">
- Active Instance
- </span>
- <span className="text-sm font-medium text-white">
- {instancesStore.activeInstance?.name || "No instance selected"}
- </span>
- </div>
-
- <Select
- items={versionOptions}
- onValueChange={setSelectedVersion}
- disabled={isLoadingVersions}
- >
- <SelectTrigger className="max-w-48">
- <SelectValue
- placeholder={
- isLoadingVersions
- ? "Loading versions..."
- : "Please select a version"
- }
- />
- </SelectTrigger>
- <SelectContent>
- <SelectGroup>
- {versionOptions.map((item) => (
- <SelectItem
- key={item.value}
- value={item.value}
- className={getVersionTypeColor(item.type)}
- >
- {item.label}
- </SelectItem>
- ))}
- </SelectGroup>
- </SelectContent>
- </Select>
- </div>
-
- <div className="flex items-center gap-3">
- {authStore.account ? (
- <Button
- className={cn(
- "px-4 py-2 shadow-xl",
- "bg-emerald-600! hover:bg-emerald-500!",
- )}
- size="lg"
- onClick={handleStartGame}
- >
- <Play />
- Start
- </Button>
- ) : (
- <Button
- className="px-4 py-2"
- size="lg"
- onClick={() => setShowLoginModal(true)}
- >
- <User /> Login
- </Button>
- )}
- </div>
- </div>
- </div>
-
- <LoginModal
- open={showLoginModal}
- onOpenChange={() => setShowLoginModal(false)}
- />
- </div>
- );
-}
diff --git a/packages/ui-new/src/components/config-editor.tsx b/packages/ui-new/src/components/config-editor.tsx
deleted file mode 100644
index 129b8f7..0000000
--- a/packages/ui-new/src/components/config-editor.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import type React from "react";
-import { useEffect, useState } from "react";
-import { type ZodType, z } from "zod";
-import { useSettingsStore } from "@/models/settings";
-import type { LauncherConfig } from "@/types";
-import { Button } from "./ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "./ui/dialog";
-import { FieldError } from "./ui/field";
-import { Spinner } from "./ui/spinner";
-import { Textarea } from "./ui/textarea";
-
-const launcherConfigSchema: ZodType<LauncherConfig> = z.object({
- minMemory: z.number(),
- maxMemory: z.number(),
- javaPath: z.string(),
- width: z.number(),
- height: z.number(),
- downloadThreads: z.number(),
- customBackgroundPath: z.string().nullable(),
- enableGpuAcceleration: z.boolean(),
- enableVisualEffects: z.boolean(),
- activeEffect: z.string(),
- theme: z.string(),
- logUploadService: z.string(),
- pastebinApiKey: z.string().nullable(),
- assistant: z.any(), // TODO: AssistantConfig schema
- useSharedCaches: z.boolean(),
- keepLegacyPerInstanceStorage: z.boolean(),
- featureFlags: z.any(), // TODO: FeatureFlags schema
-});
-
-export interface ConfigEditorProps
- extends Omit<React.ComponentPropsWithoutRef<typeof Dialog>, "onOpenChange"> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function ConfigEditor({ onOpenChange, ...props }: ConfigEditorProps) {
- const settings = useSettingsStore();
-
- const [errorMessage, setErrorMessage] = useState<string | null>(null);
- const [rawConfigContent, setRawConfigContent] = useState(
- JSON.stringify(settings.config, null, 2),
- );
- const [isSaving, setIsSaving] = useState(false);
-
- useEffect(() => {
- setRawConfigContent(JSON.stringify(settings.config, null, 2));
- }, [settings.config]);
-
- const handleSave = async () => {
- setIsSaving(true);
- setErrorMessage(null);
- try {
- const validatedConfig = launcherConfigSchema.parse(
- JSON.parse(rawConfigContent),
- );
- settings.config = validatedConfig;
- await settings.save();
- onOpenChange?.(false);
- } catch (error) {
- setErrorMessage(error instanceof Error ? error.message : String(error));
- } finally {
- setIsSaving(false);
- }
- };
-
- return (
- <Dialog onOpenChange={onOpenChange} {...props}>
- <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Edit Configuration</DialogTitle>
- <DialogDescription>
- Edit the raw JSON configuration file.
- </DialogDescription>
- </DialogHeader>
-
- <Textarea
- value={rawConfigContent}
- onChange={(e) => setRawConfigContent(e.target.value)}
- className="font-mono text-sm h-100 resize-none"
- spellCheck={false}
- aria-invalid={!!errorMessage}
- />
-
- {errorMessage && <FieldError errors={[{ message: errorMessage }]} />}
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => onOpenChange?.(false)}
- disabled={isSaving}
- >
- Cancel
- </Button>
- <Button onClick={handleSave} disabled={isSaving}>
- {isSaving && <Spinner />}
- Save Changes
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/packages/ui-new/src/components/download-monitor.tsx b/packages/ui-new/src/components/download-monitor.tsx
deleted file mode 100644
index f3902d9..0000000
--- a/packages/ui-new/src/components/download-monitor.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { X } from "lucide-react";
-import { useState } from "react";
-
-export function DownloadMonitor() {
- const [isVisible, setIsVisible] = useState(true);
-
- if (!isVisible) return null;
-
- return (
- <div className="bg-zinc-900/80 backdrop-blur-md border border-zinc-700 rounded-lg shadow-2xl overflow-hidden">
- {/* Header */}
- <div className="flex items-center justify-between px-4 py-3 bg-zinc-800/50 border-b border-zinc-700">
- <div className="flex items-center gap-2">
- <div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
- <span className="text-sm font-medium text-white">Downloads</span>
- </div>
- <button
- type="button"
- onClick={() => setIsVisible(false)}
- className="text-zinc-400 hover:text-white transition-colors p-1"
- >
- <X size={16} />
- </button>
- </div>
-
- {/* Content */}
- <div className="p-4">
- <div className="space-y-3">
- {/* Download Item */}
- <div className="space-y-1">
- <div className="flex justify-between text-xs">
- <span className="text-zinc-300">Minecraft 1.20.4</span>
- <span className="text-zinc-400">65%</span>
- </div>
- <div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
- <div
- className="h-full bg-emerald-500 rounded-full transition-all duration-300"
- style={{ width: "65%" }}
- ></div>
- </div>
- <div className="flex justify-between text-[10px] text-zinc-500">
- <span>142 MB / 218 MB</span>
- <span>2.1 MB/s • 36s remaining</span>
- </div>
- </div>
-
- {/* Download Item */}
- <div className="space-y-1">
- <div className="flex justify-between text-xs">
- <span className="text-zinc-300">Java 17</span>
- <span className="text-zinc-400">100%</span>
- </div>
- <div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
- <div className="h-full bg-emerald-500 rounded-full"></div>
- </div>
- <div className="text-[10px] text-emerald-400">Completed</div>
- </div>
- </div>
- </div>
- </div>
- );
-}
diff --git a/packages/ui-new/src/components/game-console.tsx b/packages/ui-new/src/components/game-console.tsx
deleted file mode 100644
index 6980c8c..0000000
--- a/packages/ui-new/src/components/game-console.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-import { Copy, Download, Filter, Search, Trash2, X } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { useLogsStore } from "@/stores/logs-store";
-import { useUIStore } from "@/stores/ui-store";
-
-export function GameConsole() {
- const uiStore = useUIStore();
- const logsStore = useLogsStore();
-
- const [searchTerm, setSearchTerm] = useState("");
- const [selectedLevels, setSelectedLevels] = useState<Set<string>>(
- new Set(["info", "warn", "error", "debug", "fatal"]),
- );
- const [autoScroll, setAutoScroll] = useState(true);
- const consoleEndRef = useRef<HTMLDivElement>(null);
- const logsContainerRef = useRef<HTMLDivElement>(null);
-
- const levelColors: Record<string, string> = {
- info: "text-blue-400",
- warn: "text-amber-400",
- error: "text-red-400",
- debug: "text-purple-400",
- fatal: "text-rose-400",
- };
-
- const levelBgColors: Record<string, string> = {
- info: "bg-blue-400/10",
- warn: "bg-amber-400/10",
- error: "bg-red-400/10",
- debug: "bg-purple-400/10",
- fatal: "bg-rose-400/10",
- };
-
- // Filter logs based on search term and selected levels
- const filteredLogs = logsStore.logs.filter((log) => {
- const matchesSearch =
- searchTerm === "" ||
- log.message.toLowerCase().includes(searchTerm.toLowerCase()) ||
- log.source.toLowerCase().includes(searchTerm.toLowerCase());
-
- const matchesLevel = selectedLevels.has(log.level);
-
- return matchesSearch && matchesLevel;
- });
-
- // Auto-scroll to bottom when new logs arrive or autoScroll is enabled
- useEffect(() => {
- if (autoScroll && consoleEndRef.current && filteredLogs.length > 0) {
- consoleEndRef.current.scrollIntoView({ behavior: "smooth" });
- }
- }, [filteredLogs, autoScroll]);
-
- // Handle keyboard shortcuts
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- // Ctrl/Cmd + K to focus search
- if ((e.ctrlKey || e.metaKey) && e.key === "k") {
- e.preventDefault();
- // Focus search input
- const searchInput = document.querySelector(
- 'input[type="text"]',
- ) as HTMLInputElement;
- if (searchInput) searchInput.focus();
- }
- // Escape to close console
- if (e.key === "Escape") {
- uiStore.toggleConsole();
- }
- };
-
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [uiStore.toggleConsole]);
-
- const toggleLevel = (level: string) => {
- const newLevels = new Set(selectedLevels);
- if (newLevels.has(level)) {
- newLevels.delete(level);
- } else {
- newLevels.add(level);
- }
- setSelectedLevels(newLevels);
- };
-
- const handleCopyAll = () => {
- const logsText = logsStore.exportLogs(filteredLogs);
- navigator.clipboard.writeText(logsText);
- };
-
- const handleExport = () => {
- const logsText = logsStore.exportLogs(filteredLogs);
- const blob = new Blob([logsText], { type: "text/plain" });
- const url = URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.href = url;
- a.download = `dropout_logs_${new Date().toISOString().replace(/[:.]/g, "-")}.txt`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- };
-
- const handleClear = () => {
- logsStore.clear();
- };
-
- return (
- <>
- {/* Header */}
- <div className="flex items-center justify-between p-4 border-b border-zinc-700 bg-[#252526]">
- <div className="flex items-center gap-3">
- <h2 className="text-lg font-bold text-white">Game Console</h2>
- <div className="flex items-center gap-1">
- <span className="text-xs text-zinc-400">Logs:</span>
- <span className="text-xs font-medium text-emerald-400">
- {filteredLogs.length}
- </span>
- <span className="text-xs text-zinc-400">/</span>
- <span className="text-xs text-zinc-400">
- {logsStore.logs.length}
- </span>
- </div>
- </div>
- <button
- type="button"
- onClick={() => uiStore.toggleConsole()}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- >
- <X size={20} />
- </button>
- </div>
-
- {/* Toolbar */}
- <div className="flex items-center gap-3 p-3 border-b border-zinc-700 bg-[#2D2D30]">
- {/* Search */}
- <div className="relative flex-1">
- <Search
- className="absolute left-3 top-1/2 transform -translate-y-1/2 text-zinc-500"
- size={16}
- />
- <input
- type="text"
- value={searchTerm}
- onChange={(e) => setSearchTerm(e.target.value)}
- placeholder="Search logs..."
- className="w-full pl-10 pr-4 py-2 bg-[#3E3E42] border border-zinc-600 rounded text-sm text-white placeholder:text-zinc-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
- />
- {searchTerm && (
- <button
- type="button"
- onClick={() => setSearchTerm("")}
- className="absolute right-3 top-1/2 transform -translate-y-1/2 text-zinc-400 hover:text-white"
- >
- ×
- </button>
- )}
- </div>
-
- {/* Level Filters */}
- <div className="flex items-center gap-1">
- {Object.entries(levelColors).map(([level, colorClass]) => (
- <button
- type="button"
- key={level}
- onClick={() => toggleLevel(level)}
- className={`px-3 py-1.5 text-xs font-medium rounded transition-colors ${
- selectedLevels.has(level)
- ? `${levelBgColors[level]} ${colorClass}`
- : "bg-[#3E3E42] text-zinc-400 hover:text-white"
- }`}
- >
- {level.toUpperCase()}
- </button>
- ))}
- </div>
-
- {/* Actions */}
- <div className="flex items-center gap-1">
- <button
- type="button"
- onClick={handleCopyAll}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Copy all logs"
- >
- <Copy size={16} />
- </button>
- <button
- type="button"
- onClick={handleExport}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Export logs"
- >
- <Download size={16} />
- </button>
- <button
- type="button"
- onClick={handleClear}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Clear logs"
- >
- <Trash2 size={16} />
- </button>
- </div>
-
- {/* Auto-scroll Toggle */}
- <div className="flex items-center gap-2 pl-2 border-l border-zinc-700">
- <label className="inline-flex items-center cursor-pointer">
- <input
- type="checkbox"
- checked={autoScroll}
- onChange={(e) => setAutoScroll(e.target.checked)}
- className="sr-only peer"
- />
- <div className="relative w-9 h-5 bg-zinc-700 peer-focus:outline-none peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-0.5 after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"></div>
- <span className="ml-2 text-xs text-zinc-400">Auto-scroll</span>
- </label>
- </div>
- </div>
-
- {/* Logs Container */}
- <div
- ref={logsContainerRef}
- className="flex-1 overflow-y-auto font-mono text-sm bg-[#1E1E1E]"
- style={{ fontFamily: "'Cascadia Code', 'Consolas', monospace" }}
- >
- {filteredLogs.length === 0 ? (
- <div className="flex items-center justify-center h-full">
- <div className="text-center text-zinc-500">
- <Filter className="mx-auto mb-2" size={24} />
- <p>No logs match the current filters</p>
- </div>
- </div>
- ) : (
- <div className="p-4 space-y-1">
- {filteredLogs.map((log) => (
- <div
- key={log.id}
- className="group hover:bg-white/5 p-2 rounded transition-colors"
- >
- <div className="flex items-start gap-3">
- <div
- className={`px-2 py-0.5 rounded text-xs font-medium ${levelBgColors[log.level]} ${levelColors[log.level]}`}
- >
- {log.level.toUpperCase()}
- </div>
- <div className="text-zinc-400 text-xs shrink-0">
- {log.timestamp}
- </div>
- <div className="text-amber-300 text-xs shrink-0">
- [{log.source}]
- </div>
- <div className="text-gray-300 flex-1">{log.message}</div>
- </div>
- </div>
- ))}
- <div ref={consoleEndRef} />
- </div>
- )}
- </div>
-
- {/* Footer */}
- <div className="flex items-center justify-between p-3 border-t border-zinc-700 bg-[#252526] text-xs text-zinc-400">
- <div className="flex items-center gap-4">
- <div>
- <span>Total: </span>
- <span className="text-white">{logsStore.logs.length}</span>
- <span> | Filtered: </span>
- <span className="text-emerald-400">{filteredLogs.length}</span>
- </div>
- <div className="flex items-center gap-2">
- <kbd className="px-1.5 py-0.5 bg-[#3E3E42] rounded text-xs">
- Ctrl+K
- </kbd>
- <span>to search</span>
- </div>
- </div>
- <div>
- <span>Updated: </span>
- <span>
- {new Date().toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- })}
- </span>
- </div>
- </div>
- </>
- );
-}
diff --git a/packages/ui-new/src/components/instance-creation-modal.tsx b/packages/ui-new/src/components/instance-creation-modal.tsx
deleted file mode 100644
index 8a2b1b4..0000000
--- a/packages/ui-new/src/components/instance-creation-modal.tsx
+++ /dev/null
@@ -1,552 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { Loader2, Search } from "lucide-react";
-import { useCallback, useEffect, useMemo, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { useInstancesStore } from "@/models/instances";
-import { useGameStore } from "@/stores/game-store";
-import type { Version } from "@/types/bindings/manifest";
-import type { FabricLoaderEntry } from "../types/bindings/fabric";
-import type { ForgeVersion as ForgeVersionEntry } from "../types/bindings/forge";
-import type { Instance } from "../types/bindings/instance";
-
-interface Props {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function InstanceCreationModal({ open, onOpenChange }: Props) {
- const gameStore = useGameStore();
- const instancesStore = useInstancesStore();
-
- // Steps: 1 = name, 2 = version, 3 = mod loader
- const [step, setStep] = useState<number>(1);
-
- // Step 1
- const [instanceName, setInstanceName] = useState<string>("");
-
- // Step 2
- const [versionSearch, setVersionSearch] = useState<string>("");
- const [versionFilter, setVersionFilter] = useState<
- "all" | "release" | "snapshot"
- >("release");
- const [selectedVersionUI, setSelectedVersionUI] = useState<Version | null>(
- null,
- );
-
- // Step 3
- const [modLoaderType, setModLoaderType] = useState<
- "vanilla" | "fabric" | "forge"
- >("vanilla");
- const [fabricLoaders, setFabricLoaders] = useState<FabricLoaderEntry[]>([]);
- const [forgeVersions, setForgeVersions] = useState<ForgeVersionEntry[]>([]);
- const [selectedFabricLoader, setSelectedFabricLoader] = useState<string>("");
- const [selectedForgeLoader, setSelectedForgeLoader] = useState<string>("");
- const [loadingLoaders, setLoadingLoaders] = useState(false);
-
- const loadModLoaders = useCallback(async () => {
- if (!selectedVersionUI) return;
- setLoadingLoaders(true);
- setFabricLoaders([]);
- setForgeVersions([]);
- try {
- if (modLoaderType === "fabric") {
- const loaders = await invoke<FabricLoaderEntry[]>(
- "get_fabric_loaders_for_version",
- {
- gameVersion: selectedVersionUI.id,
- },
- );
- setFabricLoaders(loaders || []);
- if (loaders && loaders.length > 0) {
- setSelectedFabricLoader(loaders[0].loader.version);
- } else {
- setSelectedFabricLoader("");
- }
- } else if (modLoaderType === "forge") {
- const versions = await invoke<ForgeVersionEntry[]>(
- "get_forge_versions_for_game",
- {
- gameVersion: selectedVersionUI.id,
- },
- );
- setForgeVersions(versions || []);
- if (versions && versions.length > 0) {
- // Binding `ForgeVersion` uses `version` (not `id`) — use `.version` here.
- setSelectedForgeLoader(versions[0].version);
- } else {
- setSelectedForgeLoader("");
- }
- }
- } catch (e) {
- console.error("Failed to load mod loaders:", e);
- toast.error("Failed to fetch mod loader versions");
- } finally {
- setLoadingLoaders(false);
- }
- }, [modLoaderType, selectedVersionUI]);
-
- // When entering step 3 and a base version exists, fetch loaders if needed
- useEffect(() => {
- if (step === 3 && modLoaderType !== "vanilla" && selectedVersionUI) {
- loadModLoaders();
- }
- }, [step, modLoaderType, selectedVersionUI, loadModLoaders]);
-
- // Creating state
- const [creating, setCreating] = useState(false);
- const [errorMessage, setErrorMessage] = useState<string>("");
-
- // Derived filtered versions
- const filteredVersions = useMemo(() => {
- const all = gameStore.versions || [];
- let list = all.slice();
- if (versionFilter !== "all") {
- list = list.filter((v) => v.type === versionFilter);
- }
- if (versionSearch.trim()) {
- const q = versionSearch.trim().toLowerCase().replace(/。/g, ".");
- list = list.filter((v) => v.id.toLowerCase().includes(q));
- }
- return list;
- }, [gameStore.versions, versionFilter, versionSearch]);
-
- // Reset when opened/closed
- useEffect(() => {
- if (open) {
- // ensure versions are loaded
- gameStore.loadVersions();
- setStep(1);
- setInstanceName("");
- setVersionSearch("");
- setVersionFilter("release");
- setSelectedVersionUI(null);
- setModLoaderType("vanilla");
- setFabricLoaders([]);
- setForgeVersions([]);
- setSelectedFabricLoader("");
- setSelectedForgeLoader("");
- setErrorMessage("");
- setCreating(false);
- }
- }, [open, gameStore.loadVersions]);
-
- function validateStep1(): boolean {
- if (!instanceName.trim()) {
- setErrorMessage("Please enter an instance name");
- return false;
- }
- setErrorMessage("");
- return true;
- }
-
- function validateStep2(): boolean {
- if (!selectedVersionUI) {
- setErrorMessage("Please select a Minecraft version");
- return false;
- }
- setErrorMessage("");
- return true;
- }
-
- async function handleNext() {
- setErrorMessage("");
- if (step === 1) {
- if (!validateStep1()) return;
- setStep(2);
- } else if (step === 2) {
- if (!validateStep2()) return;
- setStep(3);
- }
- }
-
- function handleBack() {
- setErrorMessage("");
- setStep((s) => Math.max(1, s - 1));
- }
-
- async function handleCreate() {
- if (!validateStep1() || !validateStep2()) return;
- setCreating(true);
- setErrorMessage("");
-
- try {
- // Step 1: create instance
- const instance = await invoke<Instance>("create_instance", {
- name: instanceName.trim(),
- });
-
- // If selectedVersion provided, install it
- if (selectedVersionUI) {
- try {
- await invoke("install_version", {
- instanceId: instance.id,
- versionId: selectedVersionUI.id,
- });
- } catch (err) {
- console.error("Failed to install base version:", err);
- // continue - instance created but version install failed
- toast.error(
- `Failed to install version ${selectedVersionUI.id}: ${String(err)}`,
- );
- }
- }
-
- // If mod loader selected, install it
- if (modLoaderType === "fabric" && selectedFabricLoader) {
- try {
- await invoke("install_fabric", {
- instanceId: instance.id,
- gameVersion: selectedVersionUI?.id ?? "",
- loaderVersion: selectedFabricLoader,
- });
- } catch (err) {
- console.error("Failed to install Fabric:", err);
- toast.error(`Failed to install Fabric: ${String(err)}`);
- }
- } else if (modLoaderType === "forge" && selectedForgeLoader) {
- try {
- await invoke("install_forge", {
- instanceId: instance.id,
- gameVersion: selectedVersionUI?.id ?? "",
- installerVersion: selectedForgeLoader,
- });
- } catch (err) {
- console.error("Failed to install Forge:", err);
- toast.error(`Failed to install Forge: ${String(err)}`);
- }
- }
-
- // Refresh instances list
- await instancesStore.refresh();
-
- toast.success("Instance created successfully");
- onOpenChange(false);
- } catch (e) {
- console.error("Failed to create instance:", e);
- setErrorMessage(String(e));
- toast.error(`Failed to create instance: ${e}`);
- } finally {
- setCreating(false);
- }
- }
-
- // UI pieces
- const StepIndicator = () => (
- <div className="flex gap-2 w-full">
- <div
- className={`flex-1 h-1 rounded ${step >= 1 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- <div
- className={`flex-1 h-1 rounded ${step >= 2 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- <div
- className={`flex-1 h-1 rounded ${step >= 3 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- </div>
- );
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="w-full max-w-3xl max-h-[90vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Create New Instance</DialogTitle>
- <DialogDescription>
- Multi-step wizard — create an instance and optionally install a
- version or mod loader.
- </DialogDescription>
- </DialogHeader>
-
- <div className="px-6">
- <div className="pt-4 pb-6">
- <StepIndicator />
- </div>
-
- {/* Step 1 - Name */}
- {step === 1 && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="instance-name"
- className="block text-sm font-medium mb-2"
- >
- Instance Name
- </label>
- <Input
- id="instance-name"
- placeholder="My Minecraft Instance"
- value={instanceName}
- onChange={(e) => setInstanceName(e.target.value)}
- disabled={creating}
- />
- </div>
- <p className="text-xs text-muted-foreground">
- Give your instance a memorable name.
- </p>
- </div>
- )}
-
- {/* Step 2 - Version selection */}
- {step === 2 && (
- <div className="space-y-4">
- <div className="flex gap-3">
- <div className="relative flex-1">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
- <Input
- value={versionSearch}
- onChange={(e) => setVersionSearch(e.target.value)}
- placeholder="Search versions..."
- className="pl-9"
- />
- </div>
-
- <div className="flex gap-2">
- <Button
- type="button"
- variant={versionFilter === "all" ? "default" : "outline"}
- onClick={() => setVersionFilter("all")}
- >
- All
- </Button>
- <Button
- type="button"
- variant={
- versionFilter === "release" ? "default" : "outline"
- }
- onClick={() => setVersionFilter("release")}
- >
- Release
- </Button>
- <Button
- type="button"
- variant={
- versionFilter === "snapshot" ? "default" : "outline"
- }
- onClick={() => setVersionFilter("snapshot")}
- >
- Snapshot
- </Button>
- </div>
- </div>
-
- <ScrollArea className="max-h-[36vh]">
- <div className="space-y-2 py-2">
- {gameStore.versions.length === 0 ? (
- <div className="flex items-center justify-center py-8 text-muted-foreground">
- <Loader2 className="animate-spin mr-2" />
- Loading versions...
- </div>
- ) : filteredVersions.length === 0 ? (
- <div className="text-center py-8 text-muted-foreground">
- No matching versions found
- </div>
- ) : (
- filteredVersions.map((v) => {
- const isSelected = selectedVersionUI?.id === v.id;
- return (
- <button
- key={v.id}
- type="button"
- onClick={() => setSelectedVersionUI(v)}
- className={`w-full text-left p-3 rounded-lg border transition-colors ${
- isSelected
- ? "bg-indigo-50 dark:bg-indigo-600/20 border-indigo-200"
- : "bg-white/40 dark:bg-white/5 border-black/5 dark:border-white/5 hover:bg-white/60"
- }`}
- >
- <div className="flex items-center justify-between">
- <div>
- <div className="font-mono font-bold">{v.id}</div>
- <div className="text-xs text-muted-foreground mt-1">
- {v.type}{" "}
- {v.releaseTime
- ? ` • ${new Date(v.releaseTime).toLocaleDateString()}`
- : ""}
- </div>
- </div>
- {v.javaVersion && (
- <div className="text-sm">
- Java {v.javaVersion}
- </div>
- )}
- </div>
- </button>
- );
- })
- )}
- </div>
- </ScrollArea>
- </div>
- )}
-
- {/* Step 3 - Mod loader */}
- {step === 3 && (
- <div className="space-y-4">
- <div>
- <div className="text-sm font-medium mb-2">Mod Loader Type</div>
- <div className="flex gap-3">
- <Button
- type="button"
- variant={
- modLoaderType === "vanilla" ? "default" : "outline"
- }
- onClick={() => setModLoaderType("vanilla")}
- >
- Vanilla
- </Button>
- <Button
- type="button"
- variant={modLoaderType === "fabric" ? "default" : "outline"}
- onClick={() => setModLoaderType("fabric")}
- >
- Fabric
- </Button>
- <Button
- type="button"
- variant={modLoaderType === "forge" ? "default" : "outline"}
- onClick={() => setModLoaderType("forge")}
- >
- Forge
- </Button>
- </div>
- </div>
-
- {modLoaderType === "fabric" && (
- <div>
- {loadingLoaders ? (
- <div className="flex items-center gap-2">
- <Loader2 className="animate-spin" />
- Loading Fabric versions...
- </div>
- ) : fabricLoaders.length > 0 ? (
- <div className="space-y-2">
- <select
- value={selectedFabricLoader}
- onChange={(e) =>
- setSelectedFabricLoader(e.target.value)
- }
- className="w-full px-3 py-2 rounded border bg-transparent"
- >
- {fabricLoaders.map((f) => (
- <option
- key={f.loader.version}
- value={f.loader.version}
- >
- {f.loader.version}{" "}
- {f.loader.stable ? "(Stable)" : "(Beta)"}
- </option>
- ))}
- </select>
- </div>
- ) : (
- <p className="text-sm text-muted-foreground">
- No Fabric loaders available for this version
- </p>
- )}
- </div>
- )}
-
- {modLoaderType === "forge" && (
- <div>
- {loadingLoaders ? (
- <div className="flex items-center gap-2">
- <Loader2 className="animate-spin" />
- Loading Forge versions...
- </div>
- ) : forgeVersions.length > 0 ? (
- <div className="space-y-2">
- <select
- value={selectedForgeLoader}
- onChange={(e) => setSelectedForgeLoader(e.target.value)}
- className="w-full px-3 py-2 rounded border bg-transparent"
- >
- {forgeVersions.map((f) => (
- // binding ForgeVersion uses `version` as the identifier
- <option key={f.version} value={f.version}>
- {f.version}
- </option>
- ))}
- </select>
- </div>
- ) : (
- <p className="text-sm text-muted-foreground">
- No Forge versions available for this version
- </p>
- )}
- </div>
- )}
- </div>
- )}
-
- {errorMessage && (
- <div className="text-sm text-red-400 mt-3">{errorMessage}</div>
- )}
- </div>
-
- <DialogFooter>
- <div className="w-full flex justify-between items-center">
- <div>
- <Button
- type="button"
- variant="ghost"
- onClick={() => {
- // cancel
- onOpenChange(false);
- }}
- disabled={creating}
- >
- Cancel
- </Button>
- </div>
-
- <div className="flex gap-2">
- {step > 1 && (
- <Button
- type="button"
- variant="outline"
- onClick={handleBack}
- disabled={creating}
- >
- Back
- </Button>
- )}
-
- {step < 3 ? (
- <Button type="button" onClick={handleNext} disabled={creating}>
- Next
- </Button>
- ) : (
- <Button
- type="button"
- onClick={handleCreate}
- disabled={creating}
- >
- {creating ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- Creating...
- </>
- ) : (
- "Create"
- )}
- </Button>
- )}
- </div>
- </div>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
-
-export default InstanceCreationModal;
diff --git a/packages/ui-new/src/components/instance-editor-modal.tsx b/packages/ui-new/src/components/instance-editor-modal.tsx
deleted file mode 100644
index f880c20..0000000
--- a/packages/ui-new/src/components/instance-editor-modal.tsx
+++ /dev/null
@@ -1,548 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { Folder, Loader2, Save, Trash2, X } from "lucide-react";
-import { useCallback, useEffect, useState } from "react";
-import { toast } from "sonner";
-
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-
-import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/models/instances";
-import { useSettingsStore } from "@/models/settings";
-import type { FileInfo } from "../types/bindings/core";
-import type { Instance } from "../types/bindings/instance";
-
-type Props = {
- open: boolean;
- instance: Instance | null;
- onOpenChange: (open: boolean) => void;
-};
-
-export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
- const instancesStore = useInstancesStore();
- const { config } = useSettingsStore();
-
- const [activeTab, setActiveTab] = useState<
- "info" | "version" | "files" | "settings"
- >("info");
- const [saving, setSaving] = useState(false);
- const [errorMessage, setErrorMessage] = useState("");
-
- // Info tab fields
- const [editName, setEditName] = useState("");
- const [editNotes, setEditNotes] = useState("");
-
- // Files tab state
- const [selectedFileFolder, setSelectedFileFolder] = useState<
- "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots"
- >("mods");
- const [fileList, setFileList] = useState<FileInfo[]>([]);
- const [loadingFiles, setLoadingFiles] = useState(false);
- const [deletingPath, setDeletingPath] = useState<string | null>(null);
-
- // Version tab state (placeholder - the Svelte implementation used a ModLoaderSelector component)
- // React versions-view/instance-creation handle mod loader installs; here we show basic current info.
-
- // Settings tab fields
- const [editMemoryMin, setEditMemoryMin] = useState<number>(0);
- const [editMemoryMax, setEditMemoryMax] = useState<number>(0);
- const [editJavaArgs, setEditJavaArgs] = useState<string>("");
-
- // initialize when open & instance changes
- useEffect(() => {
- if (open && instance) {
- setActiveTab("info");
- setSaving(false);
- setErrorMessage("");
- setEditName(instance.name || "");
- setEditNotes(instance.notes ?? "");
- setEditMemoryMin(
- (instance.memoryOverride && toNumber(instance.memoryOverride.min)) ??
- config?.minMemory ??
- 512,
- );
- setEditMemoryMax(
- (instance.memoryOverride && toNumber(instance.memoryOverride.max)) ??
- config?.maxMemory ??
- 2048,
- );
- setEditJavaArgs(instance.jvmArgsOverride ?? "");
- setFileList([]);
- setSelectedFileFolder("mods");
- }
- }, [open, instance, config?.minMemory, config?.maxMemory]);
-
- // load files when switching to files tab
- const loadFileList = useCallback(
- async (
- folder:
- | "mods"
- | "resourcepacks"
- | "shaderpacks"
- | "saves"
- | "screenshots",
- ) => {
- if (!instance) return;
- setLoadingFiles(true);
- try {
- const files = await invoke<FileInfo[]>("list_instance_directory", {
- instanceId: instance.id,
- folder,
- });
- setFileList(files || []);
- } catch (err) {
- console.error("Failed to load files:", err);
- toast.error("Failed to load files: " + String(err));
- setFileList([]);
- } finally {
- setLoadingFiles(false);
- }
- },
- [instance],
- );
-
- useEffect(() => {
- if (open && instance && activeTab === "files") {
- // explicitly pass the selected folder so loadFileList doesn't rely on stale closures
- loadFileList(selectedFileFolder);
- }
- }, [activeTab, open, instance, selectedFileFolder, loadFileList]);
-
- async function changeFolder(
- folder: "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots",
- ) {
- setSelectedFileFolder(folder);
- // reload the list for the newly selected folder
- if (open && instance) await loadFileList(folder);
- }
-
- async function deleteFile(filePath: string) {
- if (
- !confirm(
- `Are you sure you want to delete "${filePath.split("/").pop()}"?`,
- )
- ) {
- return;
- }
- setDeletingPath(filePath);
- try {
- await invoke("delete_instance_file", { path: filePath });
- // refresh the currently selected folder
- await loadFileList(selectedFileFolder);
- toast.success("Deleted");
- } catch (err) {
- console.error("Failed to delete file:", err);
- toast.error("Failed to delete file: " + String(err));
- } finally {
- setDeletingPath(null);
- }
- }
-
- async function openInExplorer(filePath: string) {
- try {
- await invoke("open_file_explorer", { path: filePath });
- } catch (err) {
- console.error("Failed to open in explorer:", err);
- toast.error("Failed to open file explorer: " + String(err));
- }
- }
-
- async function saveChanges() {
- if (!instance) return;
- if (!editName.trim()) {
- setErrorMessage("Instance name cannot be empty");
- return;
- }
- setSaving(true);
- setErrorMessage("");
- try {
- // Build updated instance shape compatible with backend
- const updatedInstance: Instance = {
- ...instance,
- name: editName.trim(),
- // some bindings may use camelCase; set optional string fields to null when empty
- notes: editNotes.trim() ? editNotes.trim() : null,
- memoryOverride: {
- min: editMemoryMin,
- max: editMemoryMax,
- },
- jvmArgsOverride: editJavaArgs.trim() ? editJavaArgs.trim() : null,
- };
-
- await instancesStore.update(updatedInstance as Instance);
- toast.success("Instance saved");
- onOpenChange(false);
- } catch (err) {
- console.error("Failed to save instance:", err);
- setErrorMessage(String(err));
- toast.error("Failed to save instance: " + String(err));
- } finally {
- setSaving(false);
- }
- }
-
- function formatFileSize(bytesBig: FileInfo["size"]): string {
- const bytes = Number(bytesBig ?? 0);
- if (bytes === 0) return "0 B";
- const k = 1024;
- const sizes = ["B", "KB", "MB", "GB", "TB"];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
- }
-
- function formatDate(
- tsBig?:
- | FileInfo["modified"]
- | Instance["createdAt"]
- | Instance["lastPlayed"],
- ) {
- if (tsBig === undefined || tsBig === null) return "";
- const n = toNumber(tsBig);
- // tsrs bindings often use seconds for createdAt/lastPlayed; if value looks like seconds use *1000
- const maybeMs = n > 1e12 ? n : n * 1000;
- return new Date(maybeMs).toLocaleDateString();
- }
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="w-full max-w-4xl max-h-[90vh] overflow-hidden">
- <DialogHeader>
- <div className="flex items-center justify-between gap-4">
- <div>
- <DialogTitle>Edit Instance</DialogTitle>
- <DialogDescription>{instance?.name ?? ""}</DialogDescription>
- </div>
- <div className="flex items-center gap-2">
- <button
- type="button"
- onClick={() => onOpenChange(false)}
- disabled={saving}
- className="p-2 rounded hover:bg-zinc-800 text-zinc-400"
- aria-label="Close"
- >
- <X />
- </button>
- </div>
- </div>
- </DialogHeader>
-
- {/* Tab Navigation */}
- <div className="flex gap-1 px-6 pt-2 border-b border-zinc-700">
- {[
- { id: "info", label: "Info" },
- { id: "version", label: "Version" },
- { id: "files", label: "Files" },
- { id: "settings", label: "Settings" },
- ].map((tab) => (
- <button
- type="button"
- key={tab.id}
- onClick={() =>
- setActiveTab(
- tab.id as "info" | "version" | "files" | "settings",
- )
- }
- className={`px-4 py-2 text-sm font-medium transition-colors rounded-t-lg ${
- activeTab === tab.id
- ? "bg-indigo-600 text-white"
- : "bg-zinc-800 text-zinc-400 hover:text-white"
- }`}
- >
- {tab.label}
- </button>
- ))}
- </div>
-
- {/* Content */}
- <div className="p-6 overflow-y-auto max-h-[60vh]">
- {activeTab === "info" && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="instance-name-edit"
- className="block text-sm font-medium mb-2"
- >
- Instance Name
- </label>
- <Input
- id="instance-name-edit"
- value={editName}
- onChange={(e) => setEditName(e.target.value)}
- disabled={saving}
- />
- </div>
-
- <div>
- <label
- htmlFor="instance-notes-edit"
- className="block text-sm font-medium mb-2"
- >
- Notes
- </label>
- <Textarea
- id="instance-notes-edit"
- value={editNotes}
- onChange={(e) => setEditNotes(e.target.value)}
- rows={4}
- disabled={saving}
- />
- </div>
-
- <div className="grid grid-cols-2 gap-4 text-sm">
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Created</p>
- <p className="text-white font-medium">
- {instance?.createdAt ? formatDate(instance.createdAt) : "-"}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Last Played</p>
- <p className="text-white font-medium">
- {instance?.lastPlayed
- ? formatDate(instance.lastPlayed)
- : "Never"}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Game Directory</p>
- <p
- className="text-white font-medium text-xs truncate"
- title={instance?.gameDir ?? ""}
- >
- {instance?.gameDir
- ? String(instance.gameDir).split("/").pop()
- : ""}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Current Version</p>
- <p className="text-white font-medium">
- {instance?.versionId ?? "None"}
- </p>
- </div>
- </div>
- </div>
- )}
-
- {activeTab === "version" && (
- <div className="space-y-4">
- {instance?.versionId ? (
- <div className="p-4 bg-indigo-500/10 border border-indigo-500/30 rounded-lg">
- <p className="text-sm text-indigo-400">
- Currently playing:{" "}
- <span className="font-medium">{instance.versionId}</span>
- {instance.modLoader && (
- <>
- {" "}
- with{" "}
- <span className="capitalize">{instance.modLoader}</span>
- {instance.modLoaderVersion
- ? ` ${instance.modLoaderVersion}`
- : ""}
- </>
- )}
- </p>
- </div>
- ) : (
- <div className="text-sm text-zinc-400">
- No version selected for this instance
- </div>
- )}
-
- <div>
- <p className="text-sm font-medium mb-2">
- Change Version / Mod Loader
- </p>
- <p className="text-xs text-zinc-400">
- Use the Versions page to install new game versions or mod
- loaders, then set them here.
- </p>
- </div>
- </div>
- )}
-
- {activeTab === "files" && (
- <div className="space-y-4">
- <div className="flex gap-2 flex-wrap">
- {(
- [
- "mods",
- "resourcepacks",
- "shaderpacks",
- "saves",
- "screenshots",
- ] as const
- ).map((folder) => (
- <button
- type="button"
- key={folder}
- onClick={() => changeFolder(folder)}
- className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
- selectedFileFolder === folder
- ? "bg-indigo-600 text-white"
- : "bg-zinc-800 text-zinc-400 hover:text-white"
- }`}
- >
- {folder}
- </button>
- ))}
- </div>
-
- {loadingFiles ? (
- <div className="flex items-center gap-2 text-zinc-400 py-8 justify-center">
- <Loader2 className="animate-spin" />
- Loading files...
- </div>
- ) : fileList.length === 0 ? (
- <div className="text-center py-8 text-zinc-500">
- No files in this folder
- </div>
- ) : (
- <div className="space-y-2">
- {fileList.map((file) => (
- <div
- key={file.path}
- className="flex items-center justify-between p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors"
- >
- <div className="flex-1 min-w-0">
- <p className="font-medium text-white truncate">
- {file.name}
- </p>
- <p className="text-xs text-zinc-400">
- {file.isDirectory
- ? "Folder"
- : formatFileSize(file.size)}{" "}
- • {formatDate(file.modified)}
- </p>
- </div>
- <div className="flex gap-2 ml-4">
- <button
- type="button"
- onClick={() => openInExplorer(file.path)}
- title="Open in explorer"
- className="p-2 rounded-lg hover:bg-zinc-600 text-zinc-400 hover:text-white transition-colors"
- >
- <Folder />
- </button>
- <button
- type="button"
- onClick={() => deleteFile(file.path)}
- disabled={deletingPath === file.path}
- title="Delete"
- className="p-2 rounded-lg hover:bg-red-600/20 text-red-400 hover:text-red-300 transition-colors disabled:opacity-50"
- >
- {deletingPath === file.path ? (
- <Loader2 className="animate-spin" />
- ) : (
- <Trash2 />
- )}
- </button>
- </div>
- </div>
- ))}
- </div>
- )}
- </div>
- )}
-
- {activeTab === "settings" && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="min-memory-edit"
- className="block text-sm font-medium mb-2"
- >
- Minimum Memory (MB)
- </label>
- <Input
- id="min-memory-edit"
- type="number"
- value={String(editMemoryMin)}
- onChange={(e) => setEditMemoryMin(Number(e.target.value))}
- disabled={saving}
- />
- <p className="text-xs text-zinc-400 mt-1">
- Default: {config?.minMemory} MB
- </p>
- </div>
-
- <div>
- <label
- htmlFor="max-memory-edit"
- className="block text-sm font-medium mb-2"
- >
- Maximum Memory (MB)
- </label>
- <Input
- id="max-memory-edit"
- type="number"
- value={String(editMemoryMax)}
- onChange={(e) => setEditMemoryMax(Number(e.target.value))}
- disabled={saving}
- />
- <p className="text-xs text-zinc-400 mt-1">
- Default: {config?.maxMemory} MB
- </p>
- </div>
-
- <div>
- <label
- htmlFor="jvm-args-edit"
- className="block text-sm font-medium mb-2"
- >
- JVM Arguments (Advanced)
- </label>
- <Textarea
- id="jvm-args-edit"
- value={editJavaArgs}
- onChange={(e) => setEditJavaArgs(e.target.value)}
- rows={4}
- disabled={saving}
- />
- </div>
- </div>
- )}
- </div>
-
- {errorMessage && (
- <div className="px-6 text-sm text-red-400">{errorMessage}</div>
- )}
-
- <DialogFooter>
- <div className="flex items-center justify-between w-full">
- <div />
- <div className="flex gap-2">
- <Button
- variant="outline"
- onClick={() => {
- onOpenChange(false);
- }}
- >
- Cancel
- </Button>
- <Button onClick={saveChanges} disabled={saving}>
- {saving ? (
- <Loader2 className="animate-spin mr-2" />
- ) : (
- <Save className="mr-2" />
- )}
- Save
- </Button>
- </div>
- </div>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
-
-export default InstanceEditorModal;
diff --git a/packages/ui-new/src/components/login-modal.tsx b/packages/ui-new/src/components/login-modal.tsx
deleted file mode 100644
index 49596da..0000000
--- a/packages/ui-new/src/components/login-modal.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { Mail, User } from "lucide-react";
-import { useCallback, useState } from "react";
-import { toast } from "sonner";
-import { useAuthStore } from "@/models/auth";
-import { Button } from "./ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "./ui/dialog";
-import {
- Field,
- FieldDescription,
- FieldError,
- FieldGroup,
- FieldLabel,
-} from "./ui/field";
-import { Input } from "./ui/input";
-
-export interface LoginModalProps
- extends Omit<React.ComponentPropsWithoutRef<typeof Dialog>, "onOpenChange"> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function LoginModal({ onOpenChange, ...props }: LoginModalProps) {
- const authStore = useAuthStore();
-
- const [offlineUsername, setOfflineUsername] = useState<string>("");
- const [errorMessage, setErrorMessage] = useState<string>("");
- const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
-
- const handleMicrosoftLogin = useCallback(async () => {
- setIsLoggingIn(true);
- authStore.setLoginMode("microsoft");
- try {
- await authStore.loginOnline(() => onOpenChange?.(false));
- } catch (error) {
- const err = error as Error;
- console.error("Failed to login with Microsoft:", err);
- setErrorMessage(err.message);
- } finally {
- setIsLoggingIn(false);
- }
- }, [authStore.loginOnline, authStore.setLoginMode, onOpenChange]);
-
- const handleOfflineLogin = useCallback(async () => {
- setIsLoggingIn(true);
- try {
- await authStore.loginOffline(offlineUsername);
- toast.success("Logged in offline successfully");
- onOpenChange?.(false);
- } catch (error) {
- const err = error as Error;
- console.error("Failed to login offline:", err);
- setErrorMessage(err.message);
- } finally {
- setIsLoggingIn(false);
- }
- }, [authStore, offlineUsername, onOpenChange]);
-
- return (
- <Dialog onOpenChange={onOpenChange} {...props}>
- <DialogContent className="md:max-w-md">
- <DialogHeader>
- <DialogTitle>Login</DialogTitle>
- <DialogDescription>
- Login to your Minecraft account or play offline
- </DialogDescription>
- </DialogHeader>
- <div className="p-4 w-full overflow-hidden">
- {!authStore.loginMode && (
- <div className="flex flex-col space-y-4">
- <Button size="lg" onClick={handleMicrosoftLogin}>
- <Mail />
- Login with Microsoft
- </Button>
- <Button
- variant="secondary"
- onClick={() => authStore.setLoginMode("offline")}
- size="lg"
- >
- <User />
- Login Offline
- </Button>
- </div>
- )}
- {authStore.loginMode === "microsoft" && (
- <div className="flex flex-col space-y-4">
- <button
- type="button"
- className="text-4xl font-bold text-center bg-accent p-4 cursor-pointer"
- onClick={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- toast.success("Copied to clipboard");
- }
- }}
- >
- {authStore.deviceCode?.userCode}
- </button>
- <span className="text-muted-foreground w-full overflow-hidden text-ellipsis">
- To sign in, use a web browser to open the page{" "}
- <a href={authStore.deviceCode?.verificationUri}>
- {authStore.deviceCode?.verificationUri}
- </a>{" "}
- and enter the code{" "}
- <code
- className="font-semibold cursor-pointer"
- onClick={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- }
- }}
- onKeyDown={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- }
- }}
- >
- {authStore.deviceCode?.userCode}
- </code>{" "}
- to authenticate, this code will be expired in{" "}
- {authStore.deviceCode?.expiresIn} seconds.
- </span>
- <FieldError>{errorMessage}</FieldError>
- </div>
- )}
- {authStore.loginMode === "offline" && (
- <FieldGroup>
- <Field>
- <FieldLabel>Username</FieldLabel>
- <FieldDescription>
- Enter a username to play offline
- </FieldDescription>
- <Input
- value={offlineUsername}
- onChange={(e) => {
- setOfflineUsername(e.target.value);
- setErrorMessage("");
- }}
- aria-invalid={!!errorMessage}
- />
- <FieldError>{errorMessage}</FieldError>
- </Field>
- </FieldGroup>
- )}
- </div>
- <DialogFooter>
- <div className="flex flex-col justify-center items-center">
- <span className="text-xs text-muted-foreground ">
- {authStore.statusMessage}
- </span>
- </div>
- <Button
- variant="outline"
- onClick={() => {
- if (authStore.loginMode) {
- if (authStore.loginMode === "microsoft") {
- authStore.cancelLoginOnline();
- }
- authStore.setLoginMode(null);
- } else {
- onOpenChange?.(false);
- }
- }}
- >
- Cancel
- </Button>
- {authStore.loginMode === "offline" && (
- <Button onClick={handleOfflineLogin} disabled={isLoggingIn}>
- Login
- </Button>
- )}
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/packages/ui-new/src/components/particle-background.tsx b/packages/ui-new/src/components/particle-background.tsx
deleted file mode 100644
index 2e0b15a..0000000
--- a/packages/ui-new/src/components/particle-background.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useEffect, useRef } from "react";
-import { SaturnEffect } from "../lib/effects/SaturnEffect";
-
-export function ParticleBackground() {
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
- const effectRef = useRef<SaturnEffect | null>(null);
-
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- // Instantiate SaturnEffect and attach to canvas
- let effect: SaturnEffect | null = null;
- try {
- effect = new SaturnEffect(canvas);
- effectRef.current = effect;
- } catch (err) {
- // If effect fails, silently degrade (keep background blank)
- // eslint-disable-next-line no-console
- console.warn("SaturnEffect initialization failed:", err);
- }
-
- const resizeHandler = () => {
- if (effectRef.current) {
- try {
- effectRef.current.resize(window.innerWidth, window.innerHeight);
- } catch {
- // ignore
- }
- }
- };
-
- window.addEventListener("resize", resizeHandler);
-
- // Expose getter for HomeView interactions (getSaturnEffect)
- // HomeView will call window.getSaturnEffect()?.handleMouseDown/Move/Up
- (
- window as unknown as { getSaturnEffect?: () => SaturnEffect | null }
- ).getSaturnEffect = () => effectRef.current;
-
- return () => {
- window.removeEventListener("resize", resizeHandler);
- if (effectRef.current) {
- try {
- effectRef.current.destroy();
- } catch {
- // ignore
- }
- }
- effectRef.current = null;
- (
- window as unknown as { getSaturnEffect?: () => SaturnEffect | null }
- ).getSaturnEffect = undefined;
- };
- }, []);
-
- return (
- <canvas
- ref={canvasRef}
- className="absolute inset-0 z-0 pointer-events-none"
- />
- );
-}
diff --git a/packages/ui-new/src/components/sidebar.tsx b/packages/ui-new/src/components/sidebar.tsx
deleted file mode 100644
index 0147b0a..0000000
--- a/packages/ui-new/src/components/sidebar.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-import { Folder, Home, LogOutIcon, Settings } from "lucide-react";
-import { useLocation, useNavigate } from "react-router";
-import { cn } from "@/lib/utils";
-import { useAuthStore } from "@/models/auth";
-import { Button } from "./ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "./ui/dropdown-menu";
-import { UserAvatar } from "./user-avatar";
-
-interface NavItemProps {
- Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
- label: string;
- to: string;
-}
-
-function NavItem({ Icon, label, to }: NavItemProps) {
- const navigate = useNavigate();
- const location = useLocation();
- const isActive = location.pathname === to;
-
- const handleClick = () => {
- navigate(to);
- };
-
- return (
- <Button
- variant="ghost"
- className={cn(
- "w-fit lg:w-full justify-center lg:justify-start",
- isActive && "relative bg-accent",
- )}
- size="lg"
- onClick={handleClick}
- >
- <Icon className="size-5" strokeWidth={isActive ? 2.5 : 2} />
- <span className="hidden lg:block text-sm relative z-10">{label}</span>
- {isActive && (
- <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-4 bg-black dark:bg-white rounded-r-full hidden lg:block"></div>
- )}
- </Button>
- );
-}
-
-export function Sidebar() {
- const authStore = useAuthStore();
-
- return (
- <aside
- className={cn(
- "flex flex-col items-center lg:items-start",
- "bg-sidebar transition-all duration-300",
- "w-20 lg:w-64 shrink-0 py-6 h-full",
- )}
- >
- {/* Logo Area */}
- <div className="h-16 w-full flex items-center justify-center lg:justify-start lg:px-6 mb-6">
- {/* Icon Logo (Small) */}
- <div className="lg:hidden text-black dark:text-white">
- <svg
- width="32"
- height="32"
- viewBox="0 0 100 100"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <title>Logo</title>
- <path
- d="M25 25 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M25 75 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M50 50 L75 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <circle cx="25" cy="25" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="50" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="75" r="8" fill="currentColor" stroke="none" />
- <circle cx="50" cy="50" r="10" fill="currentColor" stroke="none" />
- <circle cx="75" cy="50" r="8" fill="currentColor" stroke="none" />
- </svg>
- </div>
- {/* Full Logo (Large) */}
- <div className="hidden lg:flex items-center gap-3 font-bold text-xl tracking-tighter dark:text-white text-black">
- <svg
- width="42"
- height="42"
- viewBox="0 0 100 100"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- className="shrink-0"
- >
- <title>Logo</title>
- <path
- d="M25 25 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M25 75 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M50 50 L75 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
-
- <circle cx="25" cy="25" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="50" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="75" r="8" fill="currentColor" stroke="none" />
-
- <circle
- cx="50"
- cy="25"
- r="7"
- stroke="currentColor"
- strokeWidth="2"
- strokeDasharray="4 2"
- fill="none"
- className="opacity-30"
- />
- <circle
- cx="50"
- cy="75"
- r="7"
- stroke="currentColor"
- strokeWidth="2"
- strokeDasharray="4 2"
- fill="none"
- className="opacity-30"
- />
- <circle cx="50" cy="50" r="10" fill="currentColor" stroke="none" />
- <circle cx="75" cy="50" r="8" fill="currentColor" stroke="none" />
- </svg>
-
- <span>DROPOUT</span>
- </div>
- </div>
-
- <nav className="w-full flex flex-col space-y-1 px-3 items-center">
- <NavItem Icon={Home} label="Overview" to="/" />
- <NavItem Icon={Folder} label="Instances" to="/instances" />
- <NavItem Icon={Settings} label="Settings" to="/settings" />
- </nav>
-
- <div className="flex-1 flex flex-col justify-end">
- <DropdownMenu>
- <DropdownMenuTrigger render={<UserAvatar />}>
- Open
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" side="right" sideOffset={20}>
- <DropdownMenuGroup>
- <DropdownMenuItem
- variant="destructive"
- onClick={authStore.logout}
- >
- <LogOutIcon />
- Logout
- </DropdownMenuItem>
- </DropdownMenuGroup>
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- </aside>
- );
-}
diff --git a/packages/ui-new/src/components/ui/avatar.tsx b/packages/ui-new/src/components/ui/avatar.tsx
deleted file mode 100644
index 9fd72a2..0000000
--- a/packages/ui-new/src/components/ui/avatar.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Avatar({
- className,
- size = "default",
- ...props
-}: AvatarPrimitive.Root.Props & {
- size?: "default" | "sm" | "lg";
-}) {
- return (
- <AvatarPrimitive.Root
- data-slot="avatar"
- data-size={size}
- className={cn(
- "size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
- return (
- <AvatarPrimitive.Image
- data-slot="avatar-image"
- className={cn(
- "rounded-full aspect-square size-full object-cover",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarFallback({
- className,
- ...props
-}: AvatarPrimitive.Fallback.Props) {
- return (
- <AvatarPrimitive.Fallback
- data-slot="avatar-fallback"
- className={cn(
- "bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
- return (
- <span
- data-slot="avatar-badge"
- className={cn(
- "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
- "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
- "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
- "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="avatar-group"
- className={cn(
- "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarGroupCount({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="avatar-group-count"
- className={cn(
- "bg-muted text-muted-foreground size-8 rounded-full text-xs group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Avatar,
- AvatarImage,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarBadge,
-};
diff --git a/packages/ui-new/src/components/ui/badge.tsx b/packages/ui-new/src/components/ui/badge.tsx
deleted file mode 100644
index 425ab9e..0000000
--- a/packages/ui-new/src/components/ui/badge.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { mergeProps } from "@base-ui/react/merge-props";
-import { useRender } from "@base-ui/react/use-render";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const badgeVariants = cva(
- "h-5 gap-1 rounded-none border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- secondary:
- "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
- destructive:
- "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
- outline:
- "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
- ghost:
- "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
- link: "text-primary underline-offset-4 hover:underline",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function Badge({
- className,
- variant = "default",
- render,
- ...props
-}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
- return useRender({
- defaultTagName: "span",
- props: mergeProps<"span">(
- {
- className: cn(badgeVariants({ variant }), className),
- },
- props,
- ),
- render,
- state: {
- slot: "badge",
- variant,
- },
- });
-}
-
-export { Badge, badgeVariants };
diff --git a/packages/ui-new/src/components/ui/button.tsx b/packages/ui-new/src/components/ui/button.tsx
deleted file mode 100644
index 7dee494..0000000
--- a/packages/ui-new/src/components/ui/button.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Button as ButtonPrimitive } from "@base-ui/react/button";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const buttonVariants = cva(
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- outline:
- "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
- secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
- ghost:
- "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
- destructive:
- "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default:
- "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
- xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
- sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
- lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
- icon: "size-8",
- "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
- "icon-sm": "size-7 rounded-none",
- "icon-lg": "size-9",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-function Button({
- className,
- variant = "default",
- size = "default",
- ...props
-}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
- return (
- <ButtonPrimitive
- data-slot="button"
- className={cn(buttonVariants({ variant, size, className }))}
- {...props}
- />
- );
-}
-
-export { Button, buttonVariants };
diff --git a/packages/ui-new/src/components/ui/card.tsx b/packages/ui-new/src/components/ui/card.tsx
deleted file mode 100644
index b7084a0..0000000
--- a/packages/ui-new/src/components/ui/card.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Card({
- className,
- size = "default",
- ...props
-}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
- return (
- <div
- data-slot="card"
- data-size={size}
- className={cn(
- "ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-header"
- className={cn(
- "gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-title"
- className={cn(
- "text-sm font-medium group-data-[size=sm]/card:text-sm",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-description"
- className={cn("text-muted-foreground text-xs/relaxed", className)}
- {...props}
- />
- );
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-action"
- className={cn(
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-content"
- className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
- {...props}
- />
- );
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-footer"
- className={cn(
- "rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-};
diff --git a/packages/ui-new/src/components/ui/checkbox.tsx b/packages/ui-new/src/components/ui/checkbox.tsx
deleted file mode 100644
index 9f22cea..0000000
--- a/packages/ui-new/src/components/ui/checkbox.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-"use client";
-
-import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
-import { CheckIcon } from "lucide-react";
-import { cn } from "@/lib/utils";
-
-function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
- return (
- <CheckboxPrimitive.Root
- data-slot="checkbox"
- className={cn(
- "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-none border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-1 aria-invalid:ring-1 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- >
- <CheckboxPrimitive.Indicator
- data-slot="checkbox-indicator"
- className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
- >
- <CheckIcon />
- </CheckboxPrimitive.Indicator>
- </CheckboxPrimitive.Root>
- );
-}
-
-export { Checkbox };
diff --git a/packages/ui-new/src/components/ui/dialog.tsx b/packages/ui-new/src/components/ui/dialog.tsx
deleted file mode 100644
index 033b47c..0000000
--- a/packages/ui-new/src/components/ui/dialog.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-"use client";
-
-import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
-import { XIcon } from "lucide-react";
-import type * as React from "react";
-import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-
-function Dialog({ ...props }: DialogPrimitive.Root.Props) {
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
-}
-
-function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
-}
-
-function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
-}
-
-function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
-}
-
-function DialogOverlay({
- className,
- ...props
-}: DialogPrimitive.Backdrop.Props) {
- return (
- <DialogPrimitive.Backdrop
- data-slot="dialog-overlay"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DialogContent({
- className,
- children,
- showCloseButton = true,
- ...props
-}: DialogPrimitive.Popup.Props & {
- showCloseButton?: boolean;
-}) {
- return (
- <DialogPortal>
- <DialogOverlay />
- <DialogPrimitive.Popup
- data-slot="dialog-content"
- className={cn(
- "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-none p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
- className,
- )}
- {...props}
- >
- {children}
- {showCloseButton && (
- <DialogPrimitive.Close
- data-slot="dialog-close"
- render={
- <Button
- variant="ghost"
- className="absolute top-2 right-2"
- size="icon-sm"
- />
- }
- >
- <XIcon />
- <span className="sr-only">Close</span>
- </DialogPrimitive.Close>
- )}
- </DialogPrimitive.Popup>
- </DialogPortal>
- );
-}
-
-function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="dialog-header"
- className={cn("gap-1 text-left flex flex-col", className)}
- {...props}
- />
- );
-}
-
-function DialogFooter({
- className,
- showCloseButton = false,
- children,
- ...props
-}: React.ComponentProps<"div"> & {
- showCloseButton?: boolean;
-}) {
- return (
- <div
- data-slot="dialog-footer"
- className={cn(
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className,
- )}
- {...props}
- >
- {children}
- {showCloseButton && (
- <DialogPrimitive.Close render={<Button variant="outline" />}>
- Close
- </DialogPrimitive.Close>
- )}
- </div>
- );
-}
-
-function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
- return (
- <DialogPrimitive.Title
- data-slot="dialog-title"
- className={cn("text-sm font-medium", className)}
- {...props}
- />
- );
-}
-
-function DialogDescription({
- className,
- ...props
-}: DialogPrimitive.Description.Props) {
- return (
- <DialogPrimitive.Description
- data-slot="dialog-description"
- className={cn(
- "text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogOverlay,
- DialogPortal,
- DialogTitle,
- DialogTrigger,
-};
diff --git a/packages/ui-new/src/components/ui/dropdown-menu.tsx b/packages/ui-new/src/components/ui/dropdown-menu.tsx
deleted file mode 100644
index ee97374..0000000
--- a/packages/ui-new/src/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { Menu as MenuPrimitive } from "@base-ui/react/menu";
-import { CheckIcon, ChevronRightIcon } from "lucide-react";
-import type * as React from "react";
-import { cn } from "@/lib/utils";
-
-function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
- return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
-}
-
-function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
- return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
-}
-
-function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
- return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
-}
-
-function DropdownMenuContent({
- align = "start",
- alignOffset = 0,
- side = "bottom",
- sideOffset = 4,
- className,
- ...props
-}: MenuPrimitive.Popup.Props &
- Pick<
- MenuPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset"
- >) {
- return (
- <MenuPrimitive.Portal>
- <MenuPrimitive.Positioner
- className="isolate z-50 outline-none"
- align={align}
- alignOffset={alignOffset}
- side={side}
- sideOffset={sideOffset}
- >
- <MenuPrimitive.Popup
- data-slot="dropdown-menu-content"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
- className,
- )}
- {...props}
- />
- </MenuPrimitive.Positioner>
- </MenuPrimitive.Portal>
- );
-}
-
-function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
- return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
-}
-
-function DropdownMenuLabel({
- className,
- inset,
- ...props
-}: MenuPrimitive.GroupLabel.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.GroupLabel
- data-slot="dropdown-menu-label"
- data-inset={inset}
- className={cn(
- "text-muted-foreground px-2 py-2 text-xs data-inset:pl-7",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DropdownMenuItem({
- className,
- inset,
- variant = "default",
- ...props
-}: MenuPrimitive.Item.Props & {
- inset?: boolean;
- variant?: "default" | "destructive";
-}) {
- return (
- <MenuPrimitive.Item
- data-slot="dropdown-menu-item"
- data-inset={inset}
- data-variant={variant}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
- return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
-}
-
-function DropdownMenuSubTrigger({
- className,
- inset,
- children,
- ...props
-}: MenuPrimitive.SubmenuTrigger.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.SubmenuTrigger
- data-slot="dropdown-menu-sub-trigger"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 data-popup-open:bg-accent data-popup-open:text-accent-foreground flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- {children}
- <ChevronRightIcon className="ml-auto" />
- </MenuPrimitive.SubmenuTrigger>
- );
-}
-
-function DropdownMenuSubContent({
- align = "start",
- alignOffset = -3,
- side = "right",
- sideOffset = 0,
- className,
- ...props
-}: React.ComponentProps<typeof DropdownMenuContent>) {
- return (
- <DropdownMenuContent
- data-slot="dropdown-menu-sub-content"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-24 rounded-none shadow-lg ring-1 duration-100 w-auto",
- className,
- )}
- align={align}
- alignOffset={alignOffset}
- side={side}
- sideOffset={sideOffset}
- {...props}
- />
- );
-}
-
-function DropdownMenuCheckboxItem({
- className,
- children,
- checked,
- inset,
- ...props
-}: MenuPrimitive.CheckboxItem.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.CheckboxItem
- data-slot="dropdown-menu-checkbox-item"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- checked={checked}
- {...props}
- >
- <span
- className="absolute right-2 flex items-center justify-center pointer-events-none"
- data-slot="dropdown-menu-checkbox-item-indicator"
- >
- <MenuPrimitive.CheckboxItemIndicator>
- <CheckIcon />
- </MenuPrimitive.CheckboxItemIndicator>
- </span>
- {children}
- </MenuPrimitive.CheckboxItem>
- );
-}
-
-function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
- return (
- <MenuPrimitive.RadioGroup
- data-slot="dropdown-menu-radio-group"
- {...props}
- />
- );
-}
-
-function DropdownMenuRadioItem({
- className,
- children,
- inset,
- ...props
-}: MenuPrimitive.RadioItem.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.RadioItem
- data-slot="dropdown-menu-radio-item"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- <span
- className="absolute right-2 flex items-center justify-center pointer-events-none"
- data-slot="dropdown-menu-radio-item-indicator"
- >
- <MenuPrimitive.RadioItemIndicator>
- <CheckIcon />
- </MenuPrimitive.RadioItemIndicator>
- </span>
- {children}
- </MenuPrimitive.RadioItem>
- );
-}
-
-function DropdownMenuSeparator({
- className,
- ...props
-}: MenuPrimitive.Separator.Props) {
- return (
- <MenuPrimitive.Separator
- data-slot="dropdown-menu-separator"
- className={cn("bg-border -mx-1 h-px", className)}
- {...props}
- />
- );
-}
-
-function DropdownMenuShortcut({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
- <span
- data-slot="dropdown-menu-shortcut"
- className={cn(
- "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- DropdownMenu,
- DropdownMenuPortal,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuLabel,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuSub,
- DropdownMenuSubTrigger,
- DropdownMenuSubContent,
-};
diff --git a/packages/ui-new/src/components/ui/field.tsx b/packages/ui-new/src/components/ui/field.tsx
deleted file mode 100644
index ab9fb71..0000000
--- a/packages/ui-new/src/components/ui/field.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { cva, type VariantProps } from "class-variance-authority";
-import { useMemo } from "react";
-import { Label } from "@/components/ui/label";
-import { Separator } from "@/components/ui/separator";
-import { cn } from "@/lib/utils";
-
-function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
- return (
- <fieldset
- data-slot="field-set"
- className={cn(
- "gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldLegend({
- className,
- variant = "legend",
- ...props
-}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
- return (
- <legend
- data-slot="field-legend"
- data-variant={variant}
- className={cn(
- "mb-2.5 font-medium data-[variant=label]:text-xs data-[variant=legend]:text-sm",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-group"
- className={cn(
- "gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-const fieldVariants = cva(
- "data-[invalid=true]:text-destructive gap-2 group/field flex w-full",
- {
- variants: {
- orientation: {
- vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
- horizontal:
- "flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- responsive:
- "flex-col *:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:*:data-[slot=field-label]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- },
- },
- defaultVariants: {
- orientation: "vertical",
- },
- },
-);
-
-function Field({
- className,
- orientation = "vertical",
- ...props
-}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
- return (
- <div
- data-slot="field"
- data-orientation={orientation}
- className={cn(fieldVariants({ orientation }), className)}
- {...props}
- />
- );
-}
-
-function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-content"
- className={cn(
- "gap-0.5 group/field-content flex flex-1 flex-col leading-snug",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldLabel({
- className,
- ...props
-}: React.ComponentProps<typeof Label>) {
- return (
- <Label
- data-slot="field-label"
- className={cn(
- "has-data-checked:bg-primary/5 has-data-checked:border-primary/30 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-2 group/field-label peer/field-label flex w-fit leading-snug",
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-label"
- className={cn(
- "gap-2 text-xs/relaxed group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
- return (
- <p
- data-slot="field-description"
- className={cn(
- "text-muted-foreground text-left text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-horizontal/field:text-balance",
- "last:mt-0 nth-last-2:-mt-1",
- "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"div"> & {
- children?: React.ReactNode;
-}) {
- return (
- <div
- data-slot="field-separator"
- data-content={!!children}
- className={cn(
- "-my-2 h-5 text-xs group-data-[variant=outline]/field-group:-mb-2 relative",
- className,
- )}
- {...props}
- >
- <Separator className="absolute inset-0 top-1/2" />
- {children && (
- <span
- className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
- data-slot="field-separator-content"
- >
- {children}
- </span>
- )}
- </div>
- );
-}
-
-function FieldError({
- className,
- children,
- errors,
- ...props
-}: React.ComponentProps<"div"> & {
- errors?: Array<{ message?: string } | undefined>;
-}) {
- const content = useMemo(() => {
- if (children) {
- return children;
- }
-
- if (!errors?.length) {
- return null;
- }
-
- const uniqueErrors = [
- ...new Map(errors.map((error) => [error?.message, error])).values(),
- ];
-
- if (uniqueErrors?.length === 1) {
- return uniqueErrors[0]?.message;
- }
-
- return (
- <ul className="ml-4 flex list-disc flex-col gap-1">
- {uniqueErrors.map(
- (error, index) =>
- error?.message && (
- <li key={`${error.message.slice(6)}-${index}`}>
- {error.message}
- </li>
- ),
- )}
- </ul>
- );
- }, [children, errors]);
-
- if (!content) {
- return null;
- }
-
- return (
- <div
- role="alert"
- data-slot="field-error"
- className={cn("text-destructive text-xs font-normal", className)}
- {...props}
- >
- {content}
- </div>
- );
-}
-
-export {
- Field,
- FieldLabel,
- FieldDescription,
- FieldError,
- FieldGroup,
- FieldLegend,
- FieldSeparator,
- FieldSet,
- FieldContent,
- FieldTitle,
-};
diff --git a/packages/ui-new/src/components/ui/input.tsx b/packages/ui-new/src/components/ui/input.tsx
deleted file mode 100644
index bb0390a..0000000
--- a/packages/ui-new/src/components/ui/input.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Input as InputPrimitive } from "@base-ui/react/input";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
- return (
- <InputPrimitive
- type={type}
- data-slot="input"
- className={cn(
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Input };
diff --git a/packages/ui-new/src/components/ui/label.tsx b/packages/ui-new/src/components/ui/label.tsx
deleted file mode 100644
index 9a998c7..0000000
--- a/packages/ui-new/src/components/ui/label.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Label({ className, ...props }: React.ComponentProps<"label">) {
- return (
- // biome-ignore lint/a11y/noLabelWithoutControl: shadcn component
- <label
- data-slot="label"
- className={cn(
- "gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Label };
diff --git a/packages/ui-new/src/components/ui/scroll-area.tsx b/packages/ui-new/src/components/ui/scroll-area.tsx
deleted file mode 100644
index 4a68eb2..0000000
--- a/packages/ui-new/src/components/ui/scroll-area.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
-import { cn } from "@/lib/utils";
-
-function ScrollArea({
- className,
- children,
- ...props
-}: ScrollAreaPrimitive.Root.Props) {
- return (
- <ScrollAreaPrimitive.Root
- data-slot="scroll-area"
- className={cn("relative", className)}
- {...props}
- >
- <ScrollAreaPrimitive.Viewport
- data-slot="scroll-area-viewport"
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
- >
- {children}
- </ScrollAreaPrimitive.Viewport>
- <ScrollBar />
- <ScrollAreaPrimitive.Corner />
- </ScrollAreaPrimitive.Root>
- );
-}
-
-function ScrollBar({
- className,
- orientation = "vertical",
- ...props
-}: ScrollAreaPrimitive.Scrollbar.Props) {
- return (
- <ScrollAreaPrimitive.Scrollbar
- data-slot="scroll-area-scrollbar"
- data-orientation={orientation}
- orientation={orientation}
- className={cn(
- "data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none",
- className,
- )}
- {...props}
- >
- <ScrollAreaPrimitive.Thumb
- data-slot="scroll-area-thumb"
- className="rounded-none bg-border relative flex-1"
- />
- </ScrollAreaPrimitive.Scrollbar>
- );
-}
-
-export { ScrollArea, ScrollBar };
diff --git a/packages/ui-new/src/components/ui/select.tsx b/packages/ui-new/src/components/ui/select.tsx
deleted file mode 100644
index 210adba..0000000
--- a/packages/ui-new/src/components/ui/select.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import { Select as SelectPrimitive } from "@base-ui/react/select";
-import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
-import type * as React from "react";
-import { cn } from "@/lib/utils";
-
-const Select = SelectPrimitive.Root;
-
-function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
- return (
- <SelectPrimitive.Group
- data-slot="select-group"
- className={cn("scroll-my-1", className)}
- {...props}
- />
- );
-}
-
-function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
- return (
- <SelectPrimitive.Value
- data-slot="select-value"
- className={cn("flex flex-1 text-left", className)}
- {...props}
- />
- );
-}
-
-function SelectTrigger({
- className,
- size = "default",
- children,
- ...props
-}: SelectPrimitive.Trigger.Props & {
- size?: "sm" | "default";
-}) {
- return (
- <SelectPrimitive.Trigger
- data-slot="select-trigger"
- data-size={size}
- className={cn(
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-none border bg-transparent py-2 pr-2 pl-2.5 text-xs transition-colors select-none focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- {children}
- <SelectPrimitive.Icon
- render={
- <ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
- }
- />
- </SelectPrimitive.Trigger>
- );
-}
-
-function SelectContent({
- className,
- children,
- side = "bottom",
- sideOffset = 4,
- align = "center",
- alignOffset = 0,
- alignItemWithTrigger = true,
- ...props
-}: SelectPrimitive.Popup.Props &
- Pick<
- SelectPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
- >) {
- return (
- <SelectPrimitive.Portal>
- <SelectPrimitive.Positioner
- side={side}
- sideOffset={sideOffset}
- align={align}
- alignOffset={alignOffset}
- alignItemWithTrigger={alignItemWithTrigger}
- className="isolate z-50"
- >
- <SelectPrimitive.Popup
- data-slot="select-content"
- data-align-trigger={alignItemWithTrigger}
- className={cn(
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-none shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none",
- className,
- )}
- {...props}
- >
- <SelectScrollUpButton />
- <SelectPrimitive.List>{children}</SelectPrimitive.List>
- <SelectScrollDownButton />
- </SelectPrimitive.Popup>
- </SelectPrimitive.Positioner>
- </SelectPrimitive.Portal>
- );
-}
-
-function SelectLabel({
- className,
- ...props
-}: SelectPrimitive.GroupLabel.Props) {
- return (
- <SelectPrimitive.GroupLabel
- data-slot="select-label"
- className={cn("text-muted-foreground px-2 py-2 text-xs", className)}
- {...props}
- />
- );
-}
-
-function SelectItem({
- className,
- children,
- ...props
-}: SelectPrimitive.Item.Props) {
- return (
- <SelectPrimitive.Item
- data-slot="select-item"
- className={cn(
- "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- <SelectPrimitive.ItemText className="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
- {children}
- </SelectPrimitive.ItemText>
- <SelectPrimitive.ItemIndicator
- render={
- <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
- }
- >
- <CheckIcon className="pointer-events-none" />
- </SelectPrimitive.ItemIndicator>
- </SelectPrimitive.Item>
- );
-}
-
-function SelectSeparator({
- className,
- ...props
-}: SelectPrimitive.Separator.Props) {
- return (
- <SelectPrimitive.Separator
- data-slot="select-separator"
- className={cn("bg-border -mx-1 h-px pointer-events-none", className)}
- {...props}
- />
- );
-}
-
-function SelectScrollUpButton({
- className,
- ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
- return (
- <SelectPrimitive.ScrollUpArrow
- data-slot="select-scroll-up-button"
- className={cn(
- "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full",
- className,
- )}
- {...props}
- >
- <ChevronUpIcon />
- </SelectPrimitive.ScrollUpArrow>
- );
-}
-
-function SelectScrollDownButton({
- className,
- ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
- return (
- <SelectPrimitive.ScrollDownArrow
- data-slot="select-scroll-down-button"
- className={cn(
- "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full",
- className,
- )}
- {...props}
- >
- <ChevronDownIcon />
- </SelectPrimitive.ScrollDownArrow>
- );
-}
-
-export {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectScrollDownButton,
- SelectScrollUpButton,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-};
diff --git a/packages/ui-new/src/components/ui/separator.tsx b/packages/ui-new/src/components/ui/separator.tsx
deleted file mode 100644
index e91a862..0000000
--- a/packages/ui-new/src/components/ui/separator.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-"use client";
-
-import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
-
-import { cn } from "@/lib/utils";
-
-function Separator({
- className,
- orientation = "horizontal",
- ...props
-}: SeparatorPrimitive.Props) {
- return (
- <SeparatorPrimitive
- data-slot="separator"
- orientation={orientation}
- className={cn(
- "bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Separator };
diff --git a/packages/ui-new/src/components/ui/sonner.tsx b/packages/ui-new/src/components/ui/sonner.tsx
deleted file mode 100644
index d6e293d..0000000
--- a/packages/ui-new/src/components/ui/sonner.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import {
- CircleCheckIcon,
- InfoIcon,
- Loader2Icon,
- OctagonXIcon,
- TriangleAlertIcon,
-} from "lucide-react";
-import { useTheme } from "next-themes";
-import { Toaster as Sonner, type ToasterProps } from "sonner";
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
-
- return (
- <Sonner
- theme={theme as ToasterProps["theme"]}
- className="toaster group"
- icons={{
- success: <CircleCheckIcon className="size-4" />,
- info: <InfoIcon className="size-4" />,
- warning: <TriangleAlertIcon className="size-4" />,
- error: <OctagonXIcon className="size-4" />,
- loading: <Loader2Icon className="size-4 animate-spin" />,
- }}
- style={
- {
- "--normal-bg": "var(--popover)",
- "--normal-text": "var(--popover-foreground)",
- "--normal-border": "var(--border)",
- "--border-radius": "var(--radius)",
- } as React.CSSProperties
- }
- toastOptions={{
- classNames: {
- toast: "cn-toast",
- },
- }}
- {...props}
- />
- );
-};
-
-export { Toaster };
diff --git a/packages/ui-new/src/components/ui/spinner.tsx b/packages/ui-new/src/components/ui/spinner.tsx
deleted file mode 100644
index 91f6a63..0000000
--- a/packages/ui-new/src/components/ui/spinner.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { cn } from "@/lib/utils"
-import { Loader2Icon } from "lucide-react"
-
-function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
- return (
- <Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
- )
-}
-
-export { Spinner }
diff --git a/packages/ui-new/src/components/ui/switch.tsx b/packages/ui-new/src/components/ui/switch.tsx
deleted file mode 100644
index fef14e3..0000000
--- a/packages/ui-new/src/components/ui/switch.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client";
-
-import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
-
-import { cn } from "@/lib/utils";
-
-function Switch({
- className,
- size = "default",
- ...props
-}: SwitchPrimitive.Root.Props & {
- size?: "sm" | "default";
-}) {
- return (
- <SwitchPrimitive.Root
- data-slot="switch"
- data-size={size}
- className={cn(
- "data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50",
- className,
- )}
- {...props}
- >
- <SwitchPrimitive.Thumb
- data-slot="switch-thumb"
- className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform"
- />
- </SwitchPrimitive.Root>
- );
-}
-
-export { Switch };
diff --git a/packages/ui-new/src/components/ui/tabs.tsx b/packages/ui-new/src/components/ui/tabs.tsx
deleted file mode 100644
index c66893f..0000000
--- a/packages/ui-new/src/components/ui/tabs.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-function Tabs({
- className,
- orientation = "horizontal",
- ...props
-}: TabsPrimitive.Root.Props) {
- return (
- <TabsPrimitive.Root
- data-slot="tabs"
- data-orientation={orientation}
- className={cn(
- "gap-2 group/tabs flex data-horizontal:flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-const tabsListVariants = cva(
- "rounded-none p-0.75 group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
- {
- variants: {
- variant: {
- default: "bg-muted",
- line: "gap-1 bg-transparent",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function TabsList({
- className,
- variant = "default",
- ...props
-}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
- return (
- <TabsPrimitive.List
- data-slot="tabs-list"
- data-variant={variant}
- className={cn(tabsListVariants({ variant }), className)}
- {...props}
- />
- );
-}
-
-function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
- return (
- <TabsPrimitive.Tab
- data-slot="tabs-trigger"
- className={cn(
- "gap-1.5 rounded-none border border-transparent px-1.5 py-0.5 text-xs font-medium group-data-vertical/tabs:py-[calc(--spacing(1.25))] [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
- "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
- "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:-bottom-1.25 group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
- className,
- )}
- {...props}
- />
- );
-}
-
-function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
- return (
- <TabsPrimitive.Panel
- data-slot="tabs-content"
- className={cn("text-xs/relaxed flex-1 outline-none", className)}
- {...props}
- />
- );
-}
-
-export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
diff --git a/packages/ui-new/src/components/ui/textarea.tsx b/packages/ui-new/src/components/ui/textarea.tsx
deleted file mode 100644
index 3c3e5d0..0000000
--- a/packages/ui-new/src/components/ui/textarea.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
- return (
- <textarea
- data-slot="textarea"
- className={cn(
- "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-none border bg-transparent px-2.5 py-2 text-xs transition-colors focus-visible:ring-1 aria-invalid:ring-1 md:text-xs placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Textarea };
diff --git a/packages/ui-new/src/components/user-avatar.tsx b/packages/ui-new/src/components/user-avatar.tsx
deleted file mode 100644
index bbdb84c..0000000
--- a/packages/ui-new/src/components/user-avatar.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useAuthStore } from "@/models/auth";
-import { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from "./ui/avatar";
-
-export function UserAvatar({
- className,
- ...props
-}: React.ComponentProps<typeof Avatar>) {
- const authStore = useAuthStore();
-
- if (!authStore.account) {
- return null;
- }
-
- return (
- <Avatar {...props}>
- <AvatarImage
- src={`https://minotar.net/helm/${authStore.account.username}/100.png`}
- />
- <AvatarFallback>{authStore.account.username.slice(0, 2)}</AvatarFallback>
- <AvatarBadge />
- </Avatar>
- );
-}