feat: Enhance file manager with mobile support and UI improvements
Some checks failed
GBCI / build (push) Failing after 10m3s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-29 11:58:23 -03:00
parent 21a8236516
commit fbe6db37a6
4 changed files with 277 additions and 130 deletions

View file

@ -1,14 +1,30 @@
"use client"; "use client";
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { import {
Search, Download, Trash2, Share, Star, Search, Download, Trash2, Share, Star,
MoreVertical, Home, ChevronRight, MoreVertical, Home, ChevronRight, ChevronLeft,
Folder, File, Image, Video, Music, FileText, Code, Database, Folder, File, Image, Video, Music, FileText, Code, Database,
Clock, Users, Eye, Edit3, Copy, Scissors, Clock, Users, Eye, Edit3, Copy, Scissors,
FolderPlus, Info, Lock, FolderPlus, Info, Lock, Menu,
ExternalLink, History} from 'lucide-react'; ExternalLink, History, X
} from 'lucide-react';
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Footer from '../footer'; import Footer from '../footer';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { ScrollArea } from '@/components/ui/scroll-area';
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from '@/components/ui/context-menu';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
// Simple date formatting functions // Simple date formatting functions
const formatDate = (dateString) => { const formatDate = (dateString) => {
@ -46,46 +62,6 @@ const formatDistanceToNow = (date) => {
return formatDate(date); return formatDate(date);
}; };
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
} from "@/components/ui/context-menu";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider,
} from "@/components/ui/tooltip";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
// File system data // File system data
const fileSystemData = { const fileSystemData = {
"": { "": {
@ -193,7 +169,7 @@ const fileSystemData = {
const getFileIcon = (item) => { const getFileIcon = (item) => {
if (item.is_dir) { if (item.is_dir) {
return <Folder className="w-4 h-4 text-blue-600" />; return <Folder className="w-4 h-4 text-yellow-500" />;
} }
const iconMap = { const iconMap = {
@ -209,7 +185,7 @@ const getFileIcon = (item) => {
mp3: <Music className="w-4 h-4 text-green-600" /> mp3: <Music className="w-4 h-4 text-green-600" />
}; };
return iconMap[item.type] || <File className="w-4 h-4 text-gray-500" />; return iconMap[item.type] || <File className="w-4 h-4 text-muted-foreground" />;
}; };
const formatFileSize = (bytes) => { const formatFileSize = (bytes) => {
@ -264,7 +240,7 @@ const FileContextMenu = ({ file, children, onAction }) => {
<item.icon className="w-4 h-4" /> <item.icon className="w-4 h-4" />
{item.label} {item.label}
</ContextMenuSubTrigger> </ContextMenuSubTrigger>
<ContextMenuSubContent> <ContextMenuSubContent className="bg-popover">
{item.submenu.map((subItem, subIndex) => ( {item.submenu.map((subItem, subIndex) => (
<ContextMenuItem <ContextMenuItem
key={subIndex} key={subIndex}
@ -300,14 +276,14 @@ const FileContextMenu = ({ file, children, onAction }) => {
<ContextMenuTrigger asChild> <ContextMenuTrigger asChild>
{children} {children}
</ContextMenuTrigger> </ContextMenuTrigger>
<ContextMenuContent className="w-56"> <ContextMenuContent className="w-56 bg-popover">
{contextMenuItems.map(renderContextMenuItem)} {contextMenuItems.map(renderContextMenuItem)}
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
); );
}; };
const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => { const FolderTree = ({ onSelect, selectedPath = undefined, isCollapsed = undefined, isMobile = undefined }) => {
const [expanded, setExpanded] = useState({ "": true, "projects": true }); const [expanded, setExpanded] = useState({ "": true, "projects": true });
const toggleExpand = (path) => { const toggleExpand = (path) => {
@ -336,7 +312,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
onClick={() => toggleExpand(path)} onClick={() => toggleExpand(path)}
className={cn( className={cn(
"flex items-center w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent transition-colors", "flex items-center w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent transition-colors",
isSelected && "bg-muted", isSelected && "bg-accent",
isCollapsed ? "justify-center" : "justify-start" isCollapsed ? "justify-center" : "justify-start"
)} )}
> >
@ -363,7 +339,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
}; };
return ( return (
<div className="flex flex-col h-full"> <div className={cn("flex flex-col h-full bg-background border-r border-border", isMobile ? "w-full" : "")}>
<div className={cn("p-2", isCollapsed ? "px-1" : "")}> <div className={cn("p-2", isCollapsed ? "px-1" : "")}>
<nav className="space-y-1"> <nav className="space-y-1">
{navLinks.map((link) => {navLinks.map((link) =>
@ -373,19 +349,19 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
<Button <Button
variant={selectedPath === link.path ? "default" : "ghost"} variant={selectedPath === link.path ? "default" : "ghost"}
size="icon" size="icon"
className="w-9 h-9" className="w-9 h-9 bg-background hover:bg-accent"
onClick={() => onSelect(link.path)} onClick={() => onSelect(link.path)}
> >
<link.icon className="h-4 w-4" /> <link.icon className="h-4 w-4" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right">{link.title}</TooltipContent> <TooltipContent side="right" className="bg-popover">{link.title}</TooltipContent>
</Tooltip> </Tooltip>
) : ( ) : (
<Button <Button
key={link.path} key={link.path}
variant={selectedPath === link.path ? "default" : "ghost"} variant={selectedPath === link.path ? "default" : "ghost"}
className="w-full justify-start" className="w-full justify-start bg-background hover:bg-accent"
onClick={() => onSelect(link.path)} onClick={() => onSelect(link.path)}
> >
<link.icon className="mr-2 h-4 w-4" /> <link.icon className="mr-2 h-4 w-4" />
@ -397,7 +373,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
</div> </div>
{!isCollapsed && ( {!isCollapsed && (
<> <>
<Separator /> <Separator className="bg-border" />
<ScrollArea className="flex-1 p-2"> <ScrollArea className="flex-1 p-2">
{renderTreeItem("")} {renderTreeItem("")}
</ScrollArea> </ScrollArea>
@ -407,7 +383,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
); );
}; };
const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile, onContextAction }) => { const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile, onContextAction, isMobile }) => {
const files = useMemo(() => { const files = useMemo(() => {
const currentItem = fileSystemData[path]; const currentItem = fileSystemData[path];
if (!currentItem || !currentItem.is_dir || !currentItem.children) return []; if (!currentItem || !currentItem.is_dir || !currentItem.children) return [];
@ -440,7 +416,7 @@ const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile,
}, [path, searchTerm, filterType]); }, [path, searchTerm, filterType]);
return ( return (
<ScrollArea className="h-full"> <ScrollArea className={cn("h-full bg-background", isMobile ? "w-full" : "")}>
<div className="flex flex-col"> <div className="flex flex-col">
{files.map((item) => ( {files.map((item) => (
<FileContextMenu <FileContextMenu
@ -450,8 +426,8 @@ const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile,
> >
<button <button
className={cn( className={cn(
"flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b", "flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b border-border",
selectedFile?.id === item.id && "bg-muted" selectedFile?.id === item.id && "bg-accent"
)} )}
onClick={() => setSelectedFile(item)} onClick={() => setSelectedFile(item)}
> >
@ -479,10 +455,10 @@ const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile,
); );
}; };
const FileDisplay = ({ file }) => { const FileDisplay = ({ file, isMobile }) => {
if (!file) { if (!file) {
return ( return (
<div className="flex flex-col items-center justify-center h-full text-center text-muted-foreground"> <div className={cn("flex flex-col items-center justify-center h-full text-center text-muted-foreground bg-background", isMobile ? "w-full" : "")}>
<File className="w-12 h-12 mb-4" /> <File className="w-12 h-12 mb-4" />
<div className="text-lg font-medium">No file selected</div> <div className="text-lg font-medium">No file selected</div>
<div className="text-sm">Select a file to view details</div> <div className="text-sm">Select a file to view details</div>
@ -491,53 +467,59 @@ const FileDisplay = ({ file }) => {
} }
return ( return (
<div className="flex flex-col h-full"> <div className={cn("flex flex-col h-full bg-background", isMobile ? "w-full" : "")}>
<div className="flex items-center p-2"> <div className="flex items-center p-2 bg-secondary border-b border-border">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Tooltip> <TooltipProvider>
<TooltipTrigger asChild> <Tooltip>
<Button variant="ghost" size="icon"> <TooltipTrigger asChild>
<Download className="h-4 w-4" /> <Button variant="ghost" size="icon" className="hover:bg-accent">
</Button> <Download className="h-4 w-4" />
</TooltipTrigger> </Button>
<TooltipContent>Download</TooltipContent> </TooltipTrigger>
</Tooltip> <TooltipContent className="bg-popover">Download</TooltipContent>
<Tooltip> </Tooltip>
<TooltipTrigger asChild> </TooltipProvider>
<Button variant="ghost" size="icon"> <TooltipProvider>
<Share className="h-4 w-4" /> <Tooltip>
</Button> <TooltipTrigger asChild>
</TooltipTrigger> <Button variant="ghost" size="icon" className="hover:bg-accent">
<TooltipContent>Share</TooltipContent> <Share className="h-4 w-4" />
</Tooltip> </Button>
<Tooltip> </TooltipTrigger>
<TooltipTrigger asChild> <TooltipContent className="bg-popover">Share</TooltipContent>
<Button variant="ghost" size="icon"> </Tooltip>
<Star className="h-4 w-4" /> </TooltipProvider>
</Button> <TooltipProvider>
</TooltipTrigger> <Tooltip>
<TooltipContent>Star</TooltipContent> <TooltipTrigger asChild>
</Tooltip> <Button variant="ghost" size="icon" className="hover:bg-accent">
<Star className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent className="bg-popover">Star</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
<div className="ml-auto"> <div className="ml-auto">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon" className="hover:bg-accent">
<MoreVertical className="h-4 w-4" /> <MoreVertical className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end" className="bg-popover">
<DropdownMenuItem>Rename</DropdownMenuItem> <DropdownMenuItem>Rename</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem> <DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Move to trash</DropdownMenuItem> <DropdownMenuItem className="text-destructive">Move to trash</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<Separator /> <Separator className="bg-border" />
<div className="flex-1 flex-col"> <div className="flex-1 flex-col">
<div className="flex items-start gap-4 p-4"> <div className="flex items-start gap-4 p-4">
<div className="p-2 rounded-lg bg-accent"> <div className="p-2 rounded-lg bg-secondary">
{getFileIcon(file)} {getFileIcon(file)}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@ -552,7 +534,7 @@ const FileDisplay = ({ file }) => {
</div> </div>
)} )}
</div> </div>
<Separator /> <Separator className="bg-border" />
<div className="p-4 space-y-4"> <div className="p-4 space-y-4">
<div> <div>
<div className="text-sm font-medium">Location</div> <div className="text-sm font-medium">Location</div>
@ -582,16 +564,13 @@ export default function FileManager() {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [filterType, setFilterType] = useState('all'); const [filterType, setFilterType] = useState('all');
const [selectedFile, setSelectedFile] = useState(null); const [selectedFile, setSelectedFile] = useState(null);
const [isMobile, setIsMobile] = useState(false);
const [showMobileMenu, setShowMobileMenu] = useState(false);
const [activePanel, setActivePanel] = useState('files'); // 'tree', 'files', 'preview'
const currentItem = fileSystemData[currentPath]; const currentItem = fileSystemData[currentPath];
// Drive-specific keyboard shortcuts
// XTreeGold classic shortcut layout - two rows
// XTree/NC-style: only unique, non-browser, non-reserved keys for file ops
// No F1-F12, Ctrl+F, Ctrl+T, Ctrl+W, Ctrl+N, Ctrl+R, Ctrl+P, etc.
// Use Q, W, E, R, T, Y, U, I, O, P, A, S, D, G, H, J, K, L, Z, X, C, V, B, M with Ctrl/Shift if needed
const shortcuts = [ const shortcuts = [
// File operations row (unique qkeys)
[ [
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') }, { key: 'Q', label: 'Rename', action: () => console.log('Rename') },
{ key: 'W', label: 'View', action: () => console.log('View') }, { key: 'W', label: 'View', action: () => console.log('View') },
@ -604,7 +583,6 @@ export default function FileManager() {
{ key: 'O', label: 'Paste', action: () => console.log('Paste') }, { key: 'O', label: 'Paste', action: () => console.log('Paste') },
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') }, { key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
], ],
// Navigation/info row (unique qkeys)
[ [
{ key: 'A', label: 'Select', action: () => console.log('Select') }, { key: 'A', label: 'Select', action: () => console.log('Select') },
{ key: 'S', label: 'Select All', action: () => console.log('Select All') }, { key: 'S', label: 'Select All', action: () => console.log('Select All') },
@ -619,13 +597,10 @@ export default function FileManager() {
] ]
]; ];
const handleContextAction = (action, file) => { const handleContextAction = (action, file) => {
console.log(`Context action: ${action}`, file); console.log(`Context action: ${action}`, file);
// Handle context menu actions here
switch (action) { switch (action) {
case 'star': case 'star':
// Toggle star status
console.log('Toggle star for', file.name); console.log('Toggle star for', file.name);
break; break;
case 'share': case 'share':
@ -652,14 +627,24 @@ export default function FileManager() {
case 'details': case 'details':
console.log('Show details for', file.name); console.log('Show details for', file.name);
setSelectedFile(file); setSelectedFile(file);
if (isMobile) setActivePanel('preview');
break; break;
default: default:
console.log('Unknown action:', action); console.log('Unknown action:', action);
} }
}; };
// Keyboard shortcut handler useEffect(() => {
React.useEffect(() => { const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useEffect(() => {
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
const isCtrl = e.ctrlKey || e.metaKey; const isCtrl = e.ctrlKey || e.metaKey;
const isShift = e.shiftKey; const isShift = e.shiftKey;
@ -675,7 +660,7 @@ export default function FileManager() {
console.log('Upload shortcut'); console.log('Upload shortcut');
} else if (isCtrl && e.key === 'f') { } else if (isCtrl && e.key === 'f') {
e.preventDefault(); e.preventDefault();
(document.querySelector('input[placeholder="Search files"]') as HTMLInputElement | null)?.focus(); document.querySelector('input[placeholder="Search files"]').focus();
} else if (e.key === 'Delete' && selectedFile) { } else if (e.key === 'Delete' && selectedFile) {
e.preventDefault(); e.preventDefault();
console.log('Delete shortcut'); console.log('Delete shortcut');
@ -689,11 +674,93 @@ export default function FileManager() {
return () => document.removeEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown);
}, [selectedFile]); }, [selectedFile]);
if (isMobile) {
return (
<TooltipProvider>
<div className="flex flex-col h-screen bg-background">
<div className="flex items-center justify-between p-2 bg-secondary border-b border-border">
<div className="flex items-center gap-2">
<Sheet open={showMobileMenu} onOpenChange={setShowMobileMenu} >
<SheetTrigger asChild>
<Button variant="ghost" size="icon">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-[280px] p-0 bg-background">
<FolderTree
onSelect={(path) => {
setCurrentPath(path);
setShowMobileMenu(false);
setActivePanel('files');
}}
selectedPath={currentPath}
isMobile={true}
/>
</SheetContent>
</Sheet>
<h1 className="text-lg font-bold truncate">{currentItem?.name || 'My Drive'}</h1>
</div>
<div className="flex items-center gap-2">
{activePanel !== 'preview' && (
<Button variant="ghost" size="icon" onClick={() => setActivePanel('preview')}>
<Info className="h-4 w-4" />
</Button>
)}
{activePanel === 'preview' && (
<Button variant="ghost" size="icon" onClick={() => setActivePanel('files')}>
<ChevronLeft className="h-4 w-4" />
</Button>
)}
</div>
</div>
<div className="flex-1 overflow-hidden">
{activePanel === 'files' && (
<div className="flex flex-col h-full">
<div className="p-2 bg-secondary border-b border-border">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search files"
className="pl-8 w-full bg-background border border-border"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<FileList
path={currentPath}
searchTerm={searchTerm}
filterType={filterType}
selectedFile={selectedFile}
setSelectedFile={(file) => {
setSelectedFile(file);
setActivePanel('preview');
}}
onContextAction={handleContextAction}
isMobile={true}
/>
</div>
)}
{activePanel === 'preview' && (
<FileDisplay
file={selectedFile}
isMobile={true}
/>
)}
</div>
<Footer shortcuts={shortcuts} isMobile={true} />
</div>
</TooltipProvider>
);
}
return ( return (
<div className="flex flex-col h-[calc(100vh-40px)]"> {/* Adjust based on your nav height */} <TooltipProvider>
<TooltipProvider delayDuration={0}> <div className="flex flex-col h-[calc(100vh-40px)] bg-background">
<ResizablePanelGroup direction="horizontal" className="flex-1 min-h-0"> <ResizablePanelGroup direction="horizontal" className="flex-1 min-h-0">
{/* Left Sidebar */}
<ResizablePanel <ResizablePanel
defaultSize={20} defaultSize={20}
collapsedSize={4} collapsedSize={4}
@ -711,35 +778,34 @@ export default function FileManager() {
/> />
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle className="bg-border" />
{/* Middle File List */} <ResizablePanel defaultSize={50} minSize={30} className="bg-background border-r border-border">
<ResizablePanel defaultSize={50} minSize={30}>
<Tabs defaultValue="all" className="flex flex-col h-full"> <Tabs defaultValue="all" className="flex flex-col h-full">
<div className="flex items-center px-4 py-2"> <div className="flex items-center px-4 py-2 bg-secondary border-b border-border">
<h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1> <h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1>
<TabsList className="ml-auto"> <TabsList className="ml-auto bg-background">
<TabsTrigger value="all">All</TabsTrigger> <TabsTrigger value="all" className="data-[state=active]:bg-accent">All</TabsTrigger>
<TabsTrigger value="starred">Starred</TabsTrigger> <TabsTrigger value="starred" className="data-[state=active]:bg-accent">Starred</TabsTrigger>
</TabsList> </TabsList>
</div> </div>
<Separator /> <Separator className="bg-border" />
<div className="bg-background/95 p-4 backdrop-blur"> <div className="bg-secondary p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" /> <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input <Input
placeholder="Search files" placeholder="Search files"
className="pl-8" className="pl-8 bg-background border border-border"
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
</div> </div>
<Select value={filterType} onValueChange={setFilterType}> <Select value={filterType} onValueChange={setFilterType}>
<SelectTrigger className="w-[140px]"> <SelectTrigger className="w-[140px] bg-background border border-border">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent className="bg-popover">
<SelectItem value="all">All items</SelectItem> <SelectItem value="all">All items</SelectItem>
<SelectItem value="folders">Folders</SelectItem> <SelectItem value="folders">Folders</SelectItem>
<SelectItem value="files">Files</SelectItem> <SelectItem value="files">Files</SelectItem>
@ -771,17 +837,14 @@ export default function FileManager() {
</Tabs> </Tabs>
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle className="bg-border" />
{/* Right File Details */} <ResizablePanel defaultSize={30} minSize={25} className="bg-background">
<ResizablePanel defaultSize={30} minSize={25}>
<FileDisplay file={selectedFile} /> <FileDisplay file={selectedFile} />
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
</TooltipProvider> <Footer shortcuts={shortcuts} />
</div>
{/* Footer with Status Bar */} </TooltipProvider>
<Footer shortcuts={shortcuts} />
</div>
); );
} }

64
app/prompt.txt Normal file
View file

@ -0,0 +1,64 @@
Pages
=====
Use themes variables
--------------------
background
foreground
card
card
popover
popover
primary
primary
secondary
secondary
muted
muted
accent
accent
destructive
destructive
border
input
ring
radius
chart
chart
chart
chart
chart
Use resizable where is possible
--------------------------------
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
Use shortcuts on footer
-----------------------
const shortcuts = [
[
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
{ key: 'W', label: 'View', action: () => console.log('View') },
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
],
[
{ key: 'K', label: 'Star', action: () => console.log('Star') },
{ key: 'L', label: 'Download', action: () => console.log('Download') },
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') },
]
];
<Footer shortcuts={shortcuts} />

View file

@ -2197,6 +2197,11 @@ body {
background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: rgb(255 255 255 / var(--tw-bg-opacity));
} }
.bg-yellow-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-gradient-to-br { .bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
} }
@ -2693,6 +2698,11 @@ body {
color: hsl(var(--sidebar-foreground) / 0.7); color: hsl(var(--sidebar-foreground) / 0.7);
} }
.text-teal-600 {
--tw-text-opacity: 1;
color: rgb(13 148 136 / var(--tw-text-opacity));
}
.text-white { .text-white {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
@ -2708,6 +2718,11 @@ body {
color: rgb(202 138 4 / var(--tw-text-opacity)); color: rgb(202 138 4 / var(--tw-text-opacity));
} }
.text-yellow-800 {
--tw-text-opacity: 1;
color: rgb(133 77 14 / var(--tw-text-opacity));
}
.underline { .underline {
text-decoration-line: underline; text-decoration-line: underline;
} }
@ -2971,6 +2986,10 @@ body {
animation-timing-function: linear; animation-timing-function: linear;
} }
.running {
animation-play-state: running;
}
.dark .scrollbar-thin::-webkit-scrollbar-thumb { .dark .scrollbar-thin::-webkit-scrollbar-thumb {
background: #4b5563; background: #4b5563;
} }

View file

@ -59,6 +59,7 @@ const SheetContent = React.forwardRef<
>(({ side = "right", className, children, ...props }, ref) => ( >(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal> <SheetPortal>
<SheetOverlay /> <SheetOverlay />
<SheetPrimitive.DialogTitle></SheetPrimitive.DialogTitle>
<SheetPrimitive.Content <SheetPrimitive.Content
ref={ref} ref={ref}
className={cn(sheetVariants({ side }), className)} className={cn(sheetVariants({ side }), className)}