Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ These follow Calendar's rules (#238).

These follow the PDF recipes (#241).

- **Toast (p.16).** The feedback of an action in a folder is a toast at the bottom right of the window (full width less the margin on the phone): surface fill, hairline border, tile radius, no shadow, and a 3 px left edge. The edge is teal (`--data`) for a success and `--danger` for an outcome with failures, such as "0 of 1 copied". A close X sits in the toast's bottom-right corner. A success fades after 5 s; an error stays until it is closed or replaced, and its details stay in the failure box above the list. The text is the page's `role="status"` live region, and an empty toast has no chrome and no size.
- **Toast (p.16).** The feedback of an action in a folder is a toast at the bottom right of the window (full width less the margin on the phone, where it stands above the selection sheet while a selection exists, #405): surface fill, hairline border, tile radius, no shadow, and a 3 px left edge. The edge is teal (`--data`) for a success and `--danger` for an outcome with failures, such as "0 of 1 copied". A close X sits in the toast's bottom-right corner. A success fades after 5 s; an error stays until it is closed or replaced, and its details stay in the failure box above the list. The text is the page's `role="status"` live region, and an empty toast has no chrome and no size.
- **Dropdown menu (p.18).** The item menu lists what opens or looks at the item first ("Download", "Share", "Details"), then what changes it ("Rename", "Move", "Copy", the favourite toggle). The destructive "Move to trash" comes last, in `--danger`, under a hairline. The active entry (hover or focus) rests on `--ink` at 5 %, never teal or amber.
- **Segmented control (p.19).** "List / Gallery" is one control: a shared hairline around both segments. The current segment takes the surface fill and a hairline of its own; the other is transparent with muted text. On phone width the segments show line icons (lines with dots, four squares) at 44 px width and keep their names as `aria-label`.
- **Status pill (data table, p.15).** A status, an access level or a role is an outline pill (hairline, pill radius, no fill) with a 6 px dot. The dot is teal for a working connection, `--danger` (also the text) for one that needs signing in again and for an orphaned share, and `--ink-muted` where the pill only labels: "Can view", "Can edit", "Admin", and a connection that is unavailable or has no library yet.
Expand Down
37 changes: 37 additions & 0 deletions e2e/toast-selection-bar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect, test, type Page, type TestInfo } from '@playwright/test';

// #405: on the phone the toast sits above the fixed selection sheet, so every action in the sheet stays reachable
// while it shows.
const tag = (info: TestInfo) => `${info.project.name}-${info.repeatEachIndex}-${info.retry}`;
const crumbs = (page: Page) => page.getByRole('navigation', { name: /Pfad|Path/ });
const bar = (page: Page) => page.getByRole('group', { name: /^(Aktionen für die Auswahl|Actions for the selection)$/ });

test('#405 the toast does not cover the selection sheet on the phone', async ({ page }, info) => {
test.skip(!info.project.name.startsWith('mobile'), 'the sheet is fixed at the bottom only on the phone');
const dir = `toast-sheet-${tag(info)}`;
await page.goto('/');
await expect(crumbs(page)).toBeVisible();
const { csrfToken } = await (await page.request.get('/api/v1/me')).json();
const headers = { 'x-csrf-token': csrfToken, origin: new URL(page.url()).origin };
expect((await page.request.post('/api/v1/sources/home/mkdir', { headers, data: { path: `/${dir}` } })).status()).toBe(201);
await page.goto(`/home/${encodeURIComponent(dir)}`);
await expect(crumbs(page)).toContainText(dir);

await page.getByRole('button', { name: /^(Hochladen|Upload)$/ }).and(page.locator('[aria-haspopup="menu"]')).click();
await page.getByRole('menuitem', { name: /^(Neuer Ordner|New folder)$/ }).click();
const d = page.getByRole('dialog', { name: /Neuer Ordner|New folder/ });
await d.getByRole('textbox').fill('Sub');
await d.getByRole('textbox').press('Enter');
await expect(d).toHaveCount(0);

const toast = page.locator('.toast.shown');
await expect(toast).toContainText(/Sub/);
await expect(bar(page)).toBeVisible();
const trash = bar(page).getByRole('button', { name: /^(In den Papierkorb|Move to trash)$/ });
await expect(trash).toBeVisible();

const t = (await toast.boundingBox())!;
const b = (await bar(page).boundingBox())!;
expect(t.y + t.height).toBeLessThanOrEqual(b.y + 0.5);
await trash.click({ trial: true, timeout: 2000 });
});
7 changes: 6 additions & 1 deletion web/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -907,8 +907,13 @@ a:where(:hover) { text-decoration: underline; }
.toast-text { margin: 0; }
.toast.shown .toast-text { margin: 0 0 var(--space-1); font-size: var(--fs-small); color: var(--ink); overflow-wrap: anywhere; }
.toast-close .icon { width: 16px; height: 16px; background: var(--ink-muted); }
/* On the phone the toast stands above the fixed selection sheet (#405); the folder measures the sheet into
--selection-bar-height, 0 without a selection. */
@media (max-width: 600px) {
.toast { left: var(--space-3); right: var(--space-3); bottom: var(--space-3); max-width: none; }
.toast {
left: var(--space-3); right: var(--space-3); bottom: calc(var(--selection-bar-height, 0px) + var(--space-3));
max-width: none;
}
.toast-text { flex: 1; }
}

Expand Down
Loading