fix(ui): show workspace readiness dot and bounded session accordions
This commit is contained in:
@@ -308,6 +308,18 @@ default, hover, focus, active, disabled, loading, and error behavior where those
|
|||||||
|
|
||||||
### Navigation
|
### Navigation
|
||||||
|
|
||||||
|
- **Workspace readiness:** the Workspace navigation button carries an 8px dot to
|
||||||
|
the right of its label. Green means a selected workspace with confirmed ready
|
||||||
|
preprocessing and no query error; all other states are red. The button's
|
||||||
|
tooltip and accessible description retain the translated exact state. Do not
|
||||||
|
add a separate readiness text row or change the backend readiness gate.
|
||||||
|
- **Session groups:** one accessible multi-open accordion contains Active sessions
|
||||||
|
(initially open) and Archive (initially closed). Preserve the existing archived
|
||||||
|
flag as the grouping rule, independent of whether a Pi process is running.
|
||||||
|
Open sections share the rail's remaining height; each list scrolls independently
|
||||||
|
with a cap of `min(18rem, 35dvh)`, while its trigger remains outside that scroll
|
||||||
|
area. The mobile navigation dialog supplies a bounded viewport-height container.
|
||||||
|
Keyboard users can focus and scroll each labelled panel.
|
||||||
- **Session entry:** one Session button returns to the current unfinished session,
|
- **Session entry:** one Session button returns to the current unfinished session,
|
||||||
including provisional creation, without resetting or reconnecting it. Otherwise
|
including provisional creation, without resetting or reconnecting it. Otherwise
|
||||||
it prepares a new question using the normal readiness and unsaved-work guards.
|
it prepares a new question using the normal readiness and unsaved-work guards.
|
||||||
|
|||||||
@@ -39,6 +39,27 @@ for standalone OIDC, not Omics upstream. No runtime configuration or deployment
|
|||||||
was changed by this documentation pass. Closing/merging the branch and actual
|
was changed by this documentation pass. Closing/merging the branch and actual
|
||||||
PSD acceptance remain separate, unperformed steps.
|
PSD acceptance remain separate, unperformed steps.
|
||||||
|
|
||||||
|
### Navigation readiness and session accordions — 2026-09-13
|
||||||
|
|
||||||
|
The Workspace navigation button now carries an accessible green/red readiness
|
||||||
|
dot instead of a separate text row. Green requires a selected workspace and a
|
||||||
|
successful, current `ready` response; checking, unavailable and other states are
|
||||||
|
red, with the state exposed through the tooltip and accessible description.
|
||||||
|
The backend readiness gate is unchanged.
|
||||||
|
|
||||||
|
Active sessions (non-archived) start expanded; Archive starts collapsed. Both
|
||||||
|
accordion sections can remain open and share the remaining sidebar height,
|
||||||
|
with independently scrolling content capped at `min(18rem, 35dvh)`. The mobile
|
||||||
|
navigation dialog also provides a bounded height. Keyboard controls and labels
|
||||||
|
are retained. See `DESIGN.md` and `docs/guida-utente.md` for the UI contract.
|
||||||
|
|
||||||
|
Verification: 763 frontend unit tests, 20 browser scenarios (including 80 mocked
|
||||||
|
sessions at 390/1280px), TypeScript and the production frontend build passed.
|
||||||
|
Only the Mac frontend was recreated; it is healthy at `127.0.0.1:8080`.
|
||||||
|
Core, catalog, Qdrant and embedding containers were not changed. The prior
|
||||||
|
frontend image is retained as
|
||||||
|
`thothii-frontend:before-navigation-accordions-20260913` for rollback.
|
||||||
|
|
||||||
### Agreed Omics delivery route — 2026-09-13
|
### Agreed Omics delivery route — 2026-09-13
|
||||||
|
|
||||||
The owner approved GitHub as an intermediate transport for Omics: publish only
|
The owner approved GitHub as an intermediate transport for Omics: publish only
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ The workflow phases are fixed:
|
|||||||
|
|
||||||
## Resume, archive, and the meaning of saved state
|
## Resume, archive, and the meaning of saved state
|
||||||
|
|
||||||
|
In Administration, the dot beside **Workspace** is green when readiness is
|
||||||
|
confirmed and red otherwise. Hover the button for the exact state; assistive
|
||||||
|
technology receives the same description. Select Workspace to inspect preparation.
|
||||||
|
|
||||||
|
The session sidebar has two accordion sections: **Active sessions** starts open,
|
||||||
|
**Archive** starts closed. Both can be open together; long lists scroll inside
|
||||||
|
their own panels. Here active means not archived, not necessarily a running model
|
||||||
|
process. Existing groups and session actions remain inside those sections.
|
||||||
|
|
||||||
The sidebar lists sessions and their current lifecycle. Resuming returns to the last incomplete
|
The sidebar lists sessions and their current lifecycle. Resuming returns to the last incomplete
|
||||||
phase. A finalized or archived session cannot be resumed.
|
phase. A finalized or archived session cannot be resumed.
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,55 @@ async function inspect(page: Page) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const width of [390, 1280]) {
|
||||||
|
test(`session navigation has readiness dot and bounded accordion lists at ${width}px`, async ({ page }, testInfo) => {
|
||||||
|
await page.setViewportSize({ width, height: 1000 });
|
||||||
|
const writes = await fixtures(page);
|
||||||
|
const sessions = Array.from({ length: 80 }, (_, index) => ({
|
||||||
|
id: `nav-${index}`, name: `Session ${index}`, question: `Question ${index}`,
|
||||||
|
archived: index >= 40, active: false, status: index >= 40 ? "finalized" : "open",
|
||||||
|
workspace, group: null, author: null, summary: null,
|
||||||
|
created_at: "2026-09-01T12:00:00Z", updated_at: null,
|
||||||
|
}));
|
||||||
|
await page.route("**/api/sessions?*", route => route.fulfill({ json: sessions }));
|
||||||
|
await page.route("**/api/sessions", route => route.fulfill({ json: sessions }));
|
||||||
|
await page.goto("/");
|
||||||
|
await navigation(page);
|
||||||
|
await page.getByRole("button", { name: "Administration", exact: true }).click();
|
||||||
|
const workspaceButton = page.getByRole("button", { name: "Workspace", exact: true });
|
||||||
|
await expect(workspaceButton.getByRole("img")).toHaveAttribute("data-ready", "true");
|
||||||
|
await expect(workspaceButton).toHaveAccessibleDescription("Workspace readiness: ready");
|
||||||
|
await expect(page.getByText("Workspace readiness: ready", { exact: true })).toHaveCount(0);
|
||||||
|
const green = await workspaceButton.getByRole("img").evaluate(el => getComputedStyle(el).backgroundColor);
|
||||||
|
const active = page.getByRole("button", { name: "Active sessions", exact: true });
|
||||||
|
const archive = page.getByRole("button", { name: "Archive (40)", exact: true });
|
||||||
|
await expect(active).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await expect(archive).toHaveAttribute("aria-expanded", "false");
|
||||||
|
await archive.click();
|
||||||
|
for (const name of ["Active sessions", "Archive (40)"]) {
|
||||||
|
const panel = page.getByRole("region", { name, exact: true });
|
||||||
|
await expect(panel).toHaveCSS("overflow-y", "auto");
|
||||||
|
const dimensions = await panel.evaluate(el => ({ height: el.getBoundingClientRect().height, scroll: el.scrollHeight, client: el.clientHeight }));
|
||||||
|
expect(dimensions.height).toBeLessThanOrEqual(288);
|
||||||
|
expect(dimensions.client).toBeGreaterThan(30);
|
||||||
|
expect(dimensions.scroll).toBeGreaterThan(dimensions.client);
|
||||||
|
const box = await panel.boundingBox();
|
||||||
|
expect(box!.y + box!.height).toBeLessThanOrEqual(1000);
|
||||||
|
await panel.evaluate(el => { el.scrollTop = el.scrollHeight; });
|
||||||
|
expect(await panel.evaluate(el => el.scrollTop)).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
await page.screenshot({ path: testInfo.outputPath("session-accordions.png"), animations: "disabled" });
|
||||||
|
await expect(archive).toBeInViewport();
|
||||||
|
expect((await inspect(page)).overflow).toBe(0);
|
||||||
|
// A refetch failure must not keep a stale green signal.
|
||||||
|
await page.route("**/api/workspaces/*/preprocessing", route => route.fulfill({ status: 503, json: { error: "Fixture unavailable" } }));
|
||||||
|
await expect(workspaceButton.getByRole("img")).toHaveAttribute("data-ready", "false", { timeout: 15000 });
|
||||||
|
await expect(workspaceButton).toHaveAccessibleDescription("Workspace readiness: unavailable");
|
||||||
|
expect(await workspaceButton.getByRole("img").evaluate(el => getComputedStyle(el).backgroundColor)).not.toBe(green);
|
||||||
|
expect(writes).toEqual([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const width of [390, 1280, 2400]) {
|
for (const width of [390, 1280, 2400]) {
|
||||||
test(`knowledge readers use available width and readable paragraphs at ${width}px`, async ({ page }, testInfo) => {
|
test(`knowledge readers use available width and readable paragraphs at ${width}px`, async ({ page }, testInfo) => {
|
||||||
await page.setViewportSize({ width, height: 1100 });
|
await page.setViewportSize({ width, height: 1100 });
|
||||||
|
|||||||
@@ -319,6 +319,46 @@ body.thot-full-document { margin: 0; }
|
|||||||
padding: 0 1rem 0.75rem;
|
padding: 0 1rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thot-session-accordion { display: flex; flex-direction: column; }
|
||||||
|
.thot-session-accordion__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-top: 1px solid oklch(var(--border));
|
||||||
|
}
|
||||||
|
.thot-session-accordion__item[data-open] {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
max-height: calc(min(18rem, 35dvh) + 2.5rem);
|
||||||
|
}
|
||||||
|
.thot-session-accordion__item > h3 { flex: none; }
|
||||||
|
.thot-session-accordion__trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font: 600 var(--text-control)/1.5 var(--font-sans);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.thot-session-accordion__trigger:hover { background: oklch(var(--muted)); }
|
||||||
|
.thot-session-accordion__panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: min(18rem, 35dvh);
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
.thot-session-accordion__trigger:focus-visible,
|
||||||
|
.thot-session-accordion__panel:focus-visible {
|
||||||
|
outline: 2px solid oklch(var(--ring));
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* UI labels use the same family as controls; monospace is for technical data. */
|
/* UI labels use the same family as controls; monospace is for technical data. */
|
||||||
.thot-label {
|
.thot-label {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
|
|||||||
@@ -159,7 +159,9 @@ describe("authenticated shell permissions", () => {
|
|||||||
expect(panel.children[4]).toBe(workspace);
|
expect(panel.children[4]).toBe(workspace);
|
||||||
expect(panel.children[5]).toBe(pi);
|
expect(panel.children[5]).toBe(pi);
|
||||||
expect(within(panel).queryByRole("region", { name: "Workspace preprocessing" })).not.toBeInTheDocument();
|
expect(within(panel).queryByRole("region", { name: "Workspace preprocessing" })).not.toBeInTheDocument();
|
||||||
expect(within(panel).getByText(/Workspace readiness/)).toBeInTheDocument();
|
expect(within(panel).queryByText(/Workspace readiness/)).not.toBeInTheDocument();
|
||||||
|
expect(within(workspace).getByRole("img", { name: /Workspace readiness/ })).toBeInTheDocument();
|
||||||
|
expect(workspace).toHaveAccessibleDescription(/Workspace readiness/);
|
||||||
expect(screen.getByRole("tab", { name: "All sessions" })).toBeInTheDocument();
|
expect(screen.getByRole("tab", { name: "All sessions" })).toBeInTheDocument();
|
||||||
|
|
||||||
await user.keyboard(" ");
|
await user.keyboard(" ");
|
||||||
|
|||||||
@@ -423,6 +423,26 @@ test("Archive accordion expands to reveal archived sessions", async () => {
|
|||||||
expect(screen.getByText("Attiva uno")).toBeInTheDocument();
|
expect(screen.getByText("Attiva uno")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("session accordion exposes labelled panels and supports independent keyboard toggles", async () => {
|
||||||
|
wrap();
|
||||||
|
await screen.findByText("Attiva uno");
|
||||||
|
const active = screen.getByRole("button", { name: "Active sessions" });
|
||||||
|
const archive = screen.getByRole("button", { name: "Archive (1)" });
|
||||||
|
expect(active).toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(archive).toHaveAttribute("aria-expanded", "false");
|
||||||
|
expect(screen.getByRole("region", { name: "Active sessions" })).toHaveClass("thot-session-accordion__panel");
|
||||||
|
archive.focus();
|
||||||
|
await userEvent.keyboard("{Enter}");
|
||||||
|
expect(archive).toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(screen.getByRole("region", { name: "Archive (1)" })).toHaveAttribute("tabindex", "0");
|
||||||
|
expect(active).toHaveAttribute("aria-expanded", "true");
|
||||||
|
active.focus();
|
||||||
|
await userEvent.keyboard(" ");
|
||||||
|
expect(active).toHaveAttribute("aria-expanded", "false");
|
||||||
|
expect(screen.queryByText("Attiva uno")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Archiviata due")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
test("Resume from the panel activates the session and closes the panel", async () => {
|
test("Resume from the panel activates the session and closes the panel", async () => {
|
||||||
let resumed: string | null = null;
|
let resumed: string | null = null;
|
||||||
useSessionStore.setState({
|
useSessionStore.setState({
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
&& preprocessingQuery.data
|
&& preprocessingQuery.data
|
||||||
&& preprocessingQuery.data.state !== "ready",
|
&& preprocessingQuery.data.state !== "ready",
|
||||||
);
|
);
|
||||||
|
const workspaceReady = Boolean(selectedWorkspaceId && !preprocessingQuery.isError && preprocessingQuery.data?.state === "ready");
|
||||||
|
const workspaceReadinessLabel = t("Workspace readiness: {state}", {
|
||||||
|
state: t(preprocessingQuery.isError ? "unavailable" : preprocessingQuery.data?.state ?? "checking"),
|
||||||
|
});
|
||||||
const { data: sessions = [], isSuccess: sessionsLoaded } = useQuery<SessionSummary[]>({
|
const { data: sessions = [], isSuccess: sessionsLoaded } = useQuery<SessionSummary[]>({
|
||||||
queryKey: ["sessions", sessionScope],
|
queryKey: ["sessions", sessionScope],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -951,7 +955,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
{/* Right session rail */}
|
{/* Right session rail */}
|
||||||
{(
|
{(
|
||||||
<ArchiveNavigation surface={activeSurface} forceDrawer={activeSurface === "core" && showActivity}>
|
<ArchiveNavigation surface={activeSurface} forceDrawer={activeSurface === "core" && showActivity}>
|
||||||
<aside aria-label={t("Session navigation")} className="flex w-64 shrink-0 flex-col bg-sidebar">
|
<aside aria-label={t("Session navigation")} className="flex min-h-0 w-64 shrink-0 flex-col bg-sidebar">
|
||||||
<div className="thot-session-navigation__header relative text-center">
|
<div className="thot-session-navigation__header relative text-center">
|
||||||
<h1 className="thot-wordmark--sidebar font-heading text-[2rem] font-semibold leading-none tracking-tight text-foreground">
|
<h1 className="thot-wordmark--sidebar font-heading text-[2rem] font-semibold leading-none tracking-tight text-foreground">
|
||||||
Thoth<span className="text-primary">II</span>
|
Thoth<span className="text-primary">II</span>
|
||||||
@@ -1046,12 +1050,16 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="w-full min-w-0 max-w-full gap-[0.25rem] px-[0.75rem]"
|
className="w-full min-w-0 max-w-full gap-[0.25rem] px-[0.75rem]"
|
||||||
aria-current={currentNavigation === "workspace" ? "page" : undefined}
|
aria-current={currentNavigation === "workspace" ? "page" : undefined}
|
||||||
|
aria-label={t("Workspace")}
|
||||||
|
aria-describedby="workspace-readiness-indicator"
|
||||||
|
title={workspaceReadinessLabel}
|
||||||
data-navigation-state={currentNavigation === "workspace" ? "current" : "available"}
|
data-navigation-state={currentNavigation === "workspace" ? "current" : "available"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!canLeaveDatabaseManagement()) return;
|
if (!canLeaveDatabaseManagement()) return;
|
||||||
navigate({ surface: "workspace-management" }, true);
|
navigate({ surface: "workspace-management" }, true);
|
||||||
}}
|
}}
|
||||||
>{t("Workspace")}</Button>
|
>{t("Workspace")}<span id="workspace-readiness-indicator" role="img" aria-label={workspaceReadinessLabel}
|
||||||
|
data-ready={workspaceReady} className={`ml-1 size-2 shrink-0 rounded-full ${workspaceReady ? "bg-[oklch(var(--success))]" : "bg-destructive"}`} /></Button>
|
||||||
<Button
|
<Button
|
||||||
variant={currentNavigation === "pi" ? "navigationActive" : "outline"}
|
variant={currentNavigation === "pi" ? "navigationActive" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -1065,8 +1073,6 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
navigate({ surface: "pi-management" }, true);
|
navigate({ surface: "pi-management" }, true);
|
||||||
}}
|
}}
|
||||||
>{t("Pi configuration")}</Button>
|
>{t("Pi configuration")}</Button>
|
||||||
<Button variant="ghost" size="sm" className="w-full min-w-0 whitespace-normal" onClick={() => navigate({ surface: "workspace-management", workspace: selectedWorkspaceId })}>{t("Workspace readiness: {state}", { state: t(preprocessingQuery.isError ? "unavailable" : preprocessingQuery.data?.state ?? "checking") })}
|
|
||||||
</Button>
|
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
</Accordion.Root>
|
</Accordion.Root>
|
||||||
@@ -1154,18 +1160,18 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto px-2 pb-4">
|
<Accordion.Root multiple
|
||||||
{/* L2 — section toggle */}
|
value={[...(activeOpen ? ["active"] : []), ...(archiveOpen ? ["archive"] : [])]}
|
||||||
<button
|
onValueChange={value => { setActiveOpen(value.includes("active")); setArchiveOpen(value.includes("archive")); }}
|
||||||
type="button"
|
className="thot-session-accordion min-h-0 flex-1 overflow-y-auto px-2 pb-4">
|
||||||
onClick={() => setActiveOpen((v) => !v)}
|
<Accordion.Item value="active" className="thot-session-accordion__item">
|
||||||
aria-expanded={activeOpen}
|
<Accordion.Header className="m-0">
|
||||||
className="thot-label flex w-full items-center gap-1 px-1 pb-1 pt-1 text-left text-foreground/65 hover:text-foreground"
|
<Accordion.Trigger className="thot-session-accordion__trigger">
|
||||||
>
|
<span>{t("Active sessions")}</span>
|
||||||
<span className="select-none">{activeOpen ? "▾" : "▸"}</span>
|
<ChevronDown aria-hidden="true" className={`size-4 shrink-0 ${activeOpen ? "rotate-180" : ""}`} />
|
||||||
<span>{t("Active sessions")}</span>
|
</Accordion.Trigger>
|
||||||
</button>
|
</Accordion.Header>
|
||||||
{activeOpen && (
|
<Accordion.Panel className="thot-session-accordion__panel" tabIndex={0}>
|
||||||
<div className="flex flex-col gap-3 pb-2">
|
<div className="flex flex-col gap-3 pb-2">
|
||||||
{groups.map((g) => (
|
{groups.map((g) => (
|
||||||
<div key={g}>
|
<div key={g}>
|
||||||
@@ -1217,19 +1223,16 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
{/* L2 — section toggle */}
|
<Accordion.Item value="archive" className="thot-session-accordion__item">
|
||||||
<button
|
<Accordion.Header className="m-0">
|
||||||
type="button"
|
<Accordion.Trigger className="thot-session-accordion__trigger">
|
||||||
onClick={() => setArchiveOpen((v) => !v)}
|
<span className="tabular-nums">{t("Archive ({count})", { count: archivedList.length })}</span>
|
||||||
aria-expanded={archiveOpen}
|
<ChevronDown aria-hidden="true" className={`size-4 shrink-0 ${archiveOpen ? "rotate-180" : ""}`} />
|
||||||
className="thot-label mt-2 flex w-full items-center gap-1 px-1 pb-1 pt-1 text-left text-foreground/65 hover:text-foreground"
|
</Accordion.Trigger>
|
||||||
>
|
</Accordion.Header>
|
||||||
<span className="select-none">{archiveOpen ? "▾" : "▸"}</span>
|
<Accordion.Panel className="thot-session-accordion__panel" tabIndex={0}>
|
||||||
<span className="tabular-nums">{t("Archive ({count})", { count: archivedList.length })}</span>
|
|
||||||
</button>
|
|
||||||
{archiveOpen && (
|
|
||||||
<NavSessions
|
<NavSessions
|
||||||
sessions={archivedList}
|
sessions={archivedList}
|
||||||
activeSessionId={activeSessionId}
|
activeSessionId={activeSessionId}
|
||||||
@@ -1239,8 +1242,9 @@ export function AppShell({ canLogout }: AppShellProps) {
|
|||||||
onSelectionChange={setSessionSelected}
|
onSelectionChange={setSessionSelected}
|
||||||
showOwner={showingAllSessions}
|
showOwner={showingAllSessions}
|
||||||
/>
|
/>
|
||||||
)}
|
</Accordion.Panel>
|
||||||
</div>
|
</Accordion.Item>
|
||||||
|
</Accordion.Root>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</ArchiveNavigation>
|
</ArchiveNavigation>
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ export function ArchiveNavigation({ surface, forceDrawer = false, children }: {
|
|||||||
<span className="font-heading font-semibold">ThothII</span>
|
<span className="font-heading font-semibold">ThothII</span>
|
||||||
<DialogTrigger render={<Button variant="outline" size="sm" />}>{translate("Navigation")}</DialogTrigger>
|
<DialogTrigger render={<Button variant="outline" size="sm" />}>{translate("Navigation")}</DialogTrigger>
|
||||||
</div>
|
</div>
|
||||||
<DialogContent className="flex max-h-[calc(100dvh-2rem)] flex-col">
|
<DialogContent className="flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] flex-col">
|
||||||
<DialogTitle>{translate("Navigation")}</DialogTitle>
|
<DialogTitle>{translate("Navigation")}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">{translate("Choose an administration page or a session.")}</DialogDescription>
|
<DialogDescription className="sr-only">{translate("Choose an administration page or a session.")}</DialogDescription>
|
||||||
<div className="min-h-0 overflow-y-auto [&>aside]:w-full">{children}</div>
|
<div className="min-h-0 flex-1 overflow-y-auto [&>aside]:h-full [&>aside]:w-full">{children}</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog></>;
|
</Dialog></>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user