mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
The Files app had two layouts (folders inline or in a tree), a back bar and a permanent warning above the list, a toolbar of ten unlabelled icons, and a Recent list whose entries did nothing. In the tree layout folders could not be renamed or deleted at all, and the default view, default sort and hidden-files settings were ignored. It now follows the contacts and calendar layout: - a full-height sidebar with a "+" New menu, My files with the folder tree, Starred, Recent and Shared with me; folders accept dropped items - a header with collapsing breadcrumbs (also drop targets), search, sort, list/grid switch and a details toggle; selecting items swaps it for an action bar - list rows open from the name and select from the row, with hover actions; the grid shows folders as chips and files as cards with lazy, cached thumbnails - a details panel for the selection or the current folder, empty-folder and no-results states, a floating upload card, a dismissible stability warning, and keyboard navigation with the arrow keys - touch: tap opens, long-press selects, a "+" button in narrow panes The tree is built from the listing the store already fetches instead of one full fetch per folder, so it is current after every change. Recent files open in their folder, and the settings for view, sort, hidden files and icons now apply. The inline/sidebar setting is gone.
54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { useTranslations } from "next-intl";
|
|
import { Upload, FolderPlus, FolderOpen, SearchX } from "@/components/icons";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
interface EmptyFolderProps {
|
|
isRoot: boolean;
|
|
canCreate: boolean;
|
|
onUpload: () => void;
|
|
onCreateFolder: () => void;
|
|
}
|
|
|
|
/** What an empty folder shows: what to do next, and that dropping files works. */
|
|
export function EmptyFolder({ isRoot, canCreate, onUpload, onCreateFolder }: EmptyFolderProps) {
|
|
const t = useTranslations("files");
|
|
return (
|
|
<div className="flex items-center justify-center h-full min-h-[18rem] p-8">
|
|
<div className="flex flex-col items-center text-center max-w-sm">
|
|
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center mb-4">
|
|
{isRoot ? <Upload className="w-7 h-7 text-muted-foreground" /> : <FolderOpen className="w-7 h-7 text-muted-foreground" />}
|
|
</div>
|
|
<h3 className="text-base font-medium">{isRoot ? t("empty_state_title") : t("empty_folder_title")}</h3>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{canCreate ? t("empty_folder_description") : t("empty_folder_read_only")}
|
|
</p>
|
|
{canCreate && (
|
|
<div className="flex gap-2 flex-wrap justify-center mt-5">
|
|
<Button size="sm" onClick={onUpload} className="gap-1.5">
|
|
<Upload className="w-4 h-4" />
|
|
{t("upload_files")}
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={onCreateFolder} className="gap-1.5">
|
|
<FolderPlus className="w-4 h-4" />
|
|
{t("new_folder")}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function NoSearchResults({ query, onClear }: { query: string; onClear: () => void }) {
|
|
const t = useTranslations("files");
|
|
return (
|
|
<div className="flex flex-col items-center justify-center text-center h-full min-h-[14rem] p-8">
|
|
<SearchX className="w-10 h-10 text-muted-foreground/50 mb-3" />
|
|
<p className="text-sm font-medium">{t("no_results_for", { query })}</p>
|
|
<Button variant="ghost" size="sm" className="mt-2" onClick={onClear}>{t("clear_search")}</Button>
|
|
</div>
|
|
);
|
|
}
|