docs: add design spec for file card / language switcher polish
Covers flag-icons for the language switcher, a shared header layout across the choose/convert/download states, human-readable file sizes, and restyled format/quality controls and download button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
# File card / language switcher polish
|
||||||
|
|
||||||
|
Date: 2026-07-31
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Polish pass on top of the existing frontend redesign ([[2026-07-31-ombrora-frontend-redesign-design]]): flag icons for the language switcher, a friendlier destination-format/quality picker, a consistent visual skeleton across the three file-lifecycle states (choosing format, converting, downloading), and human-readable file size shown alongside the filename in both the choosing and downloading states.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
- `LanguageSwitcher.jsx` renders a plain text link ("English"/"Français") instead of a flag.
|
||||||
|
- `FileConfigCard` (choosing step) shows a generic `FileIcon` + filename; `FileCard` (download step) shows only the filename with no icon — different markup between the two, which reads as the icon "disappearing" when a file moves from one list to the other.
|
||||||
|
- Neither step shows file size.
|
||||||
|
- The target-format `<select>`, quality slider, and download link use only default browser/plain styling.
|
||||||
|
|
||||||
|
## Scope decisions (from brainstorming)
|
||||||
|
|
||||||
|
- **Flags via the `flag-icons` npm package**, not emoji or hand-drawn SVG. Chosen over emoji (unreliable flag emoji rendering on some Windows/Linux font configs) and over hand-rolled SVG (user preferred pulling in the library). `flag-icons` ships CSS + font/SVG assets only, no native binary — safe for o2switch. Per the o2switch single-package.json constraint documented in `CLAUDE.md`, it must be added to **both** `frontend/package.json` and root `package.json` dependencies, with `npm install` run in both places.
|
||||||
|
- **`FileConfigCard` and `FileCard` remain separate components** (different responsibilities: one edits pending config, one polls job status) rather than merging into one mega-component. Instead, both adopt the same header markup shape and shared CSS classes so they render identically at the "icon + name + size" level, with only the right-hand, state-specific content differing (controls / spinner / download button / error).
|
||||||
|
- **Target-format picker stays a native `<select>`**, not a custom chip grid. Chosen for lower implementation cost and to keep native accessibility/keyboard behavior, restyled with border/focus/theme-token styling plus a small badge next to it showing the currently selected extension.
|
||||||
|
- **File size is computed and carried client-side, no backend change.** `File.size` is already available at selection time in `HomePage.handleFilesSelected`. For the download step, `uploadFiles(items)` returns jobs in the same order as the request array (confirmed in `src/app.js`'s `POST /api/jobs` handler, which builds jobs by iterating `req.files` index-for-index) — so `handleConvert` can zip the returned jobs with the original `validItems` by index to attach `size` onto each job object before it's stored in `submittedJobs`, with no API contract change.
|
||||||
|
- **"Converting" state gets a spinning icon**, not a progress bar (no real progress percentage exists to drive one) — `CircleNotch` from the already-installed `@phosphor-icons/react`, animated via a CSS `@keyframes spin`, respecting `prefers-reduced-motion` (existing design-system rule from the foundation spec).
|
||||||
|
- **Download link becomes a filled button** matching `.convert-button`'s visual language (same accent color, radius, min-height), not just a restyled inline link.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### New dependency
|
||||||
|
|
||||||
|
Add `flag-icons` to `dependencies` in both `package.json` (root) and `frontend/package.json`, then `npm install` in both locations to keep both lockfiles in sync (per existing o2switch mirroring rule).
|
||||||
|
|
||||||
|
### New util
|
||||||
|
|
||||||
|
`frontend/src/utils/formatBytes.js` — pure function `formatBytes(bytes)` returning a human-readable string (`"2.4 MB"`, `"512 KB"`, `"0 B"` for falsy/zero/undefined input). Used by both `FileConfigCard` and `FileCard`.
|
||||||
|
|
||||||
|
### Shared header markup/CSS
|
||||||
|
|
||||||
|
Both components render the same header shape:
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="file-tile-header">
|
||||||
|
<FileIcon />
|
||||||
|
<span class="file-tile-name">{name}</span>
|
||||||
|
<span class="file-tile-size">{formatBytes(size)}</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
CSS classes `.file-tile-header`, `.file-tile-name`, `.file-tile-size` added to `frontend/src/styles/home.css` (renaming/superseding today's `.file-config-name`, which only exists in that file today and is used by both components already — checked: `FileCard.jsx` currently reuses `.file-config-name` for its filename span, so this is a rename/extension of an existing shared class, not a net-new pattern). `FileCard` gains the icon it's currently missing.
|
||||||
|
|
||||||
|
### `FileConfigCard.jsx` changes
|
||||||
|
|
||||||
|
- Add size display next to filename (via `item.file.size`).
|
||||||
|
- Restyle the target-format `<select>` (new `.format-select` class: themed border, focus ring, padding) and add a small `.format-badge` span next to it showing `item.targetFormat.toUpperCase()`.
|
||||||
|
- Quality slider, PNG compression slider, ICO size select, PDF checkbox: consistent spacing/label styling under a shared `.file-config-controls` refinement — no logic changes, styling only.
|
||||||
|
|
||||||
|
### `FileCard.jsx` changes
|
||||||
|
|
||||||
|
- Add `FileIcon` to the header (parity with `FileConfigCard`).
|
||||||
|
- Add size display — needs `size` passed as a new prop from `HomePage`.
|
||||||
|
- "Converting" state: `CircleNotch` icon (spin animation) next to the existing translated text.
|
||||||
|
- "Done" state: download link restyled as a filled button (new `.download-button` class reusing `.convert-button`'s visual tokens), keeping the existing `<a href={downloadUrl(jobId)}>` semantics.
|
||||||
|
|
||||||
|
### `HomePage.jsx` changes
|
||||||
|
|
||||||
|
- `handleConvert`: after `uploadFiles(validItems)` resolves, zip the returned `jobs` array with `validItems` by index to attach `size: validItems[i].file.size` onto each job object before appending to `submittedJobs`.
|
||||||
|
- Pass `size={job.size}` down to `FileCard`.
|
||||||
|
|
||||||
|
### `LanguageSwitcher.jsx` changes
|
||||||
|
|
||||||
|
- Import `flag-icons/css/flag-icons.min.css` once (in `main.jsx`, alongside other global CSS imports — checked: `main.jsx` currently imports `./index.css`, matching the existing pattern for global stylesheet imports).
|
||||||
|
- Replace the text `label` with `<span class={`fi fi-${otherLang === 'en' ? 'gb' : 'fr'}`} />` (flag-icons uses `gb` for the English/UK flag, not `en` — there's no ISO country code "en"), keeping an `aria-label`/`title` set to the existing translated label (`t('nav.switchToEnglish')`/`t('nav.switchToFrench')`) for accessibility, since a flag alone doesn't convey the target language to screen readers.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Replacing the native `<select>` with a custom chip/grid picker (considered, declined — see scope decisions).
|
||||||
|
- Any backend/API changes — file size is entirely a client-side, already-available value.
|
||||||
|
- A progress-percentage-driven progress bar for the "converting" state (no real progress data exists).
|
||||||
|
- Automated frontend tests (no test harness exists yet, per the foundation spec's own out-of-scope note).
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Manual only, consistent with the foundation spec's approach (no frontend test harness exists):
|
||||||
|
- `npm run dev` in `frontend/`, both `/fr/` and `/en/`.
|
||||||
|
- Upload one or more files, verify: file icon + name + size shown in the choosing step; format select is restyled with the extension badge visible; quality/PNG/ICO/PDF controls still function exactly as before.
|
||||||
|
- Convert, verify: converting state shows the spinning icon; on completion the download step shows the same icon + name + size layout as the choosing step, plus a filled download button; clicking it downloads the converted file.
|
||||||
|
- Verify the language switcher shows the correct flag for the *other* language (FR page shows GB flag and vice versa) and that keyboard/screen-reader users still get a meaningful accessible name.
|
||||||
|
- Verify at 375/768/1024/1440px widths that the new header layout doesn't break wrapping (existing `flex-wrap` behavior in `.file-config-card`/`.file-card` should still apply).
|
||||||
|
- Verify dark mode: flag icons, badges, and the new button render with sufficient contrast in both themes.
|
||||||
Reference in New Issue
Block a user