Files
Stirling-PDF/frontend/editor/src/proprietary/components/shared/FileSidebarGroupControls.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

110 lines
3.9 KiB
TypeScript
Raw Normal View History

// The Files-sidebar category picker: a "tune" button opening a modal that lists the fixed, shared
// categories and lets the user show or hide each one in their own sidebar. Device-local and
// presentational only — it never changes the shared categories or the label vocabulary; files in a
// hidden (or no) category fall back to "Other".
2026-07-09 12:47:37 +01:00
import { useMemo, useState, useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import TuneIcon from "@mui/icons-material/Tune";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import { Modal } from "@app/ui/Modal";
import { Button } from "@app/ui/Button";
import { ActionIcon } from "@app/ui/ActionIcon";
import { ClassificationCategoryManager } from "@app/components/policies/ClassificationCategoryManager";
2026-07-09 12:47:37 +01:00
import { useClassificationEnabled } from "@app/hooks/useClassificationEnabled";
import { bucketStubsByLabel } from "@app/components/shared/fileSidebarGroupingLogic";
import {
getSidebarCategories,
resetHiddenCategories,
2026-07-09 12:47:37 +01:00
setCategoryHidden,
subscribeSidebarCategories,
} from "@app/services/fileSidebarCategories";
import type { StirlingFileStub } from "@app/types/fileContext";
import "@app/components/shared/FileSidebarGroupControls.css";
interface FileSidebarGroupControlsProps {
/** The files currently listed, for live per-category counts. */
2026-07-09 12:47:37 +01:00
stubs: StirlingFileStub[];
}
export function FileSidebarGroupControls({
stubs,
}: FileSidebarGroupControlsProps) {
const { t } = useTranslation();
const enabled = useClassificationEnabled();
const [open, setOpen] = useState(false);
const categories = useSyncExternalStore(
subscribeSidebarCategories,
getSidebarCategories,
);
// Files per category (deduped across its labels), from the same bucketing the sidebar groups use.
2026-07-09 12:47:37 +01:00
const categoryCounts = useMemo(() => {
const byLabel = bucketStubsByLabel(stubs);
2026-07-09 12:47:37 +01:00
const counts = new Map<string, number>();
for (const category of categories) {
const ids = new Set<string>();
for (const key of category.labelKeys) {
for (const stub of byLabel.get(key)?.stubs ?? []) {
ids.add(stub.id);
2026-07-09 12:47:37 +01:00
}
}
counts.set(category.id, ids.size);
}
return counts;
}, [stubs, categories]);
2026-07-09 12:47:37 +01:00
// Classification off (non-AI SaaS tenant) → no "customize groups" affordance,
// matching the flat, ungrouped list. (All hooks above run unconditionally.)
if (!enabled) return null;
return (
<>
{/* -external: revealed on section-header hover, like the Browse button. */}
<ActionIcon
variant="quiet"
className="file-sidebar-section-btn file-sidebar-section-btn-external"
onClick={() => setOpen(true)}
2026-07-09 12:47:37 +01:00
aria-label={t("fileSidebar.customizeGroups", "Customize groups")}
data-testid="customize-groups"
>
<TuneIcon sx={{ fontSize: "1rem" }} />
</ActionIcon>
<Modal
open={open}
onClose={() => setOpen(false)}
width="md"
2026-07-09 12:47:37 +01:00
title={t("fileSidebar.groupsModal.title", "Sidebar categories")}
subtitle={t(
"fileSidebar.groupsModal.subtitle",
"Show or hide categories in the files sidebar.",
2026-07-09 12:47:37 +01:00
)}
footer={
<div className="fsg-footer">
<Button
variant="tertiary"
size="sm"
leftSection={<RestartAltIcon sx={{ fontSize: "1rem" }} />}
onClick={resetHiddenCategories}
2026-07-09 12:47:37 +01:00
>
{t("fileSidebar.groupsModal.reset", "Show all")}
2026-07-09 12:47:37 +01:00
</Button>
<Button variant="primary" size="sm" onClick={() => setOpen(false)}>
{t("fileSidebar.groupsModal.done", "Done")}
</Button>
</div>
}
>
<div className="fsg-body">
<ClassificationCategoryManager
categories={categories}
onToggleHidden={setCategoryHidden}
counts={categoryCounts}
2026-07-09 12:47:37 +01:00
/>
</div>
</Modal>
</>
);
}