feat(files): add a name filter to My Files with a no-results state

Reconnects the existing (orphaned) name-filter pipeline with an input next
to the sort controls. When a filter returns nothing, the page shows a
dedicated 'no matching files' empty state instead of the generic per-tab
message (e.g. 'Nothing modified yet').
This commit is contained in:
Reece
2026-06-19 14:48:00 +01:00
parent e1912847d6
commit 4ae26167af
2 changed files with 58 additions and 0 deletions
@@ -13,6 +13,7 @@ import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutli
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import UploadFileIcon from "@mui/icons-material/UploadFile";
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
import SearchIcon from "@mui/icons-material/Search";
import { FileId } from "@app/types/file";
import { FolderId, FolderRecord, ROOT_FOLDER_ID } from "@app/types/folder";
@@ -85,6 +86,8 @@ interface FileGridProps {
onChangeSortMode?: (mode: FilesPageSortMode) => void;
/** Drives the empty-state copy. */
currentTab?: "all" | "local" | "cloud" | "recent" | "shared" | "sharedByMe";
/** A filter is applied; an empty result then means "no matches", not "no files". */
searchActive?: boolean;
/** Cloud reachability; switches the cloud empty-state copy. */
serverReachable?: boolean;
/** Empty-state CTA handlers; if absent the matching button hides. */
@@ -100,6 +103,7 @@ export function FileGrid(props: FileGridProps & { loading?: boolean }) {
entries,
loading,
currentTab,
searchActive,
serverReachable,
onEmptyUpload,
onEmptyCreateFolder,
@@ -114,6 +118,7 @@ export function FileGrid(props: FileGridProps & { loading?: boolean }) {
return (
<EmptyState
tab={currentTab}
searchActive={searchActive}
serverReachable={serverReachable}
onUpload={onEmptyUpload}
onCreateFolder={onEmptyCreateFolder}
@@ -183,6 +188,8 @@ function SkeletonGrid({ viewMode }: { viewMode: FilesPageViewMode }) {
interface EmptyStateProps {
/** Drives copy + iconography. */
tab?: "all" | "local" | "cloud" | "recent" | "shared" | "sharedByMe";
/** When true the empty list is the result of a filter, not a bare folder. */
searchActive?: boolean;
/** Switches the cloud empty-state copy. */
serverReachable?: boolean;
/** CTA handlers; absent => button hidden. */
@@ -194,12 +201,35 @@ interface EmptyStateProps {
function EmptyState({
tab = "all",
searchActive = false,
serverReachable = true,
onUpload,
onCreateFolder,
newFolderDisabledReason,
}: EmptyStateProps) {
const { t } = useTranslation();
// A filter with no matches isn't an empty folder - say so, and skip the
// upload / new-folder CTAs since clearing the filter is the way out.
if (searchActive) {
return (
<div className="files-page-empty">
<span className="files-page-empty-icon">
<SearchIcon style={{ fontSize: "2.5rem" }} />
</span>
<div className="files-page-empty-title">
{t("filesPage.empty.noResults.title", "No matching files")}
</div>
<div className="files-page-empty-hint">
{t(
"filesPage.empty.noResults.hint",
"No files in this folder match your filter. Try a different term or clear the filter.",
)}
</div>
</div>
);
}
const { titleKey, titleFallback, hintKey, hintFallback } = (() => {
switch (tab) {
case "local":
@@ -15,10 +15,12 @@ import {
MultiSelect,
SegmentedControl,
Select,
TextInput,
Tooltip,
} from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import CloseIcon from "@mui/icons-material/Close";
import SearchIcon from "@mui/icons-material/Search";
import UploadFileIcon from "@mui/icons-material/UploadFile";
import QrCode2Icon from "@mui/icons-material/QrCode2";
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
@@ -136,6 +138,7 @@ export default function FileManagerView() {
sortMode,
setSortMode,
search,
setSearch,
originFilter,
setOriginFilter,
typeFilter,
@@ -1360,6 +1363,30 @@ export default function FileManagerView() {
aria-label={t("filesPage.typeFilter.label", "Filter by type")}
/>
)}
<TextInput
size="xs"
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder={t("filesPage.search.placeholder", "Filter files…")}
leftSection={<SearchIcon sx={{ fontSize: "1rem" }} />}
rightSection={
search ? (
<ActionIcon
variant="subtle"
size="sm"
onClick={() => setSearch("")}
aria-label={t("filesPage.search.clear", "Clear filter")}
>
<CloseIcon sx={{ fontSize: "0.9rem" }} />
</ActionIcon>
) : null
}
aria-label={t(
"filesPage.search.label",
"Filter files by name",
)}
style={{ width: 180 }}
/>
<Select
size="xs"
value={sortMode}
@@ -1451,6 +1478,7 @@ export default function FileManagerView() {
entries={entries}
loading={loading}
currentTab={currentTab}
searchActive={search.trim().length > 0}
serverReachable={folders.serverReachable}
selectedFileIds={selectedFileIds}
activeWorkspaceFileIds={activeWorkspaceFileIdSet}