docs(design): redesign game-folder button as icon + label + dot
The previous design squeezed the full game-directory path into the
top-bar button as truncated `ui-monospace` (e.g.
`…s/Desktop/eti_games_AFTER_LAN_2025`). In practice the leading-ellipsis
truncation rarely showed the meaningful part of the path on real-world
configurations, ate horizontal space the new 3-zone top bar needs for
its actual primary controls, and competed with the filter / search /
sort cluster for attention.
Replace the inline path with an icon + short label + colored status
dot. The full path moves into the tooltip and `aria-label`, where it's
still one mouseover (and screen-reader friendly) away. The button now
communicates the *state* of the configuration at a glance — which is
what users actually need.
Two visual states, both 36 px tall with the same surface as the other
top-bar controls:
- **Set & valid** — label `Game folder`, green dot (`--ok`) with a
soft glow, default border, tooltip = full path.
- **Not set / invalid** — label `Set game folder`, red dot (`--danger`)
with a soft glow, a red-tinted border, and a faint red wash on
hover so the bad state reads as "this is what you need to fix".
Tooltip = `Please select a game folder`.
"Invalid" (a path is stored but doesn't exist on disk) is collapsed
into the same visual state as "not set" — the user's required action
is identical (open the picker, pick a folder), so a third state
isn't worth the visual budget yet. If we later want to surface a
*last-known* path so the user can re-attach an external drive,
introduce a distinct missing state then.
Implementation notes:
- `DirectoryButton` now takes a single `path: string | null` prop and
picks state from `!!(path && path.trim())`. Children are
`Icon.folder`, the label, and an 8 px `.dirbtn-status-dot` sibling
— the dot is an inline flex sibling, not a corner badge, because
the button is now wider than tall and a corner pin would feel
misplaced.
- `.dirbtn` is `inline-flex` with `padding: 0 14px 0 12px`, gap 8 px,
`white-space: nowrap`, and `flex-shrink: 0`. The `max-width: 360px`
cap from the path-truncation era is gone — the button is now
intrinsically sized.
- Dot glow uses `box-shadow: 0 0 6px color-mix(...)` so it still
reads through the launcher's translucent top-bar background.
- The Tweaks panel grows a dev-only `Game folder set` toggle (under
the new *Library* section) that flips a `gameFolderSet` flag wired
into the Launcher, so reviewers can see both states without
fiddling with real filesystem state. The README explicitly calls
this out as **dev-only** — production state comes from the
settings store, not a user-facing toggle.
The README gains a new *Game-folder button* section with the full
spec, a state table, and a rationale paragraph; the "Changes since v2"
list and the interactions list are updated to reflect the new label
and behavior.
Test Plan
- Open `design_reference/SoftLAN Launcher.html` and locate the Tweaks
panel's *Library → Game folder set* toggle.
- With the toggle **on**: the top-bar button shows `Game folder`
with a green dot; hovering the button reveals the full mock path
in the native tooltip.
- With the toggle **off**: the label switches to `Set game folder`,
the dot turns red, the border picks up a red tint, and hovering
the button reveals `Please select a game folder`.
- Inspect the button with a screen reader / DevTools accessibility
pane: the `aria-label` should read `Game folder: <path>` when set
and `Set game folder` when unset.
This commit is contained in:
+38
-2
@@ -40,6 +40,11 @@ The HTML mock includes two chrome variants — **A (single-row)** and **B (two-r
|
||||
- **Right:** game-folder button + kebab menu.
|
||||
- Below ~1100 px of launcher width (container query), the three zones collapse into a single left-to-right flowing row (no wrap, no centering). Implement via container query on the launcher root; viewport media query is acceptable if your codebase doesn't use container queries yet.
|
||||
- See "Top bar (variant A)" below for the full spec and rationale.
|
||||
- **Game-directory button redesigned.** No more inline path display. The button is now an icon + short label + status dot, with the full path moved to the tooltip. Two states:
|
||||
- **Set & valid:** label `Game folder`, green status dot, tooltip = full configured path.
|
||||
- **Not set / invalid path:** label `Set game folder`, red status dot, subtle red border, slightly red-tinted hover, tooltip = `Please select a game folder`. "Invalid" (a path is stored but doesn't exist on disk) is treated identically to "not set" — no separate warning state for now.
|
||||
- Click behavior unchanged — opens the native folder picker via Tauri.
|
||||
- See "Game-folder button" below for the full spec.
|
||||
|
||||
## Changes since v1
|
||||
|
||||
@@ -77,7 +82,7 @@ The default screen. A grid of game cards over a dark, gradient-tinted background
|
||||
|
||||
- **Right zone (col 3, flex space-between):**
|
||||
- **Sort menu** (pinned left, hugging search) — 36 px button, same surface style as search. Label `Sort: <bold value>` plus 13 px sort-bars icon and 11 px chevron. Click reveals dropdown menu below. Options: `Name (A–Z)`, `Size (largest)`, `Recently Played`, `Status`.
|
||||
- **Game directory button** (pinned right of itself, before the kebab) — 36 px button, max-width 360 px. Folder icon, "Game directory" label (600 weight `--t-1`), then the current path in `ui-monospace` 11.5 px `--t-3` truncated with leading ellipsis when long (e.g. `…s/Desktop/eti_games_AFTER_LAN_2025`).
|
||||
- **Game-folder button** (pinned right of itself, before the kebab) — see "Game-folder button" below.
|
||||
- **Kebab menu** (`⋮`, pinned far-right) — 36×36 button with same surface. Menu items: `Settings` (opens Settings dialog), `Refresh library`, separator, `Unpack logs`, `About SoftLAN`.
|
||||
|
||||
**Narrow-window fallback** (container width < 1100 px): the grid is replaced by a single `display: flex; flex-wrap: nowrap; gap: 16px` row. All items align left-to-right in source order (brand → filter → search → sort → game-folder → kebab). The search field becomes `flex: 1 1 auto` so it absorbs remaining slack. The geometric centering is abandoned at narrow widths because there isn't enough horizontal slack for it to read cleanly. Implement via container query (`@container launcher (max-width: 1100px)`) on the launcher root; a viewport media query is an acceptable fallback if you're not using container queries yet.
|
||||
@@ -211,6 +216,37 @@ Persisted settings (write through to local storage / Tauri config):
|
||||
|
||||
---
|
||||
|
||||
## Game-folder button
|
||||
|
||||
The top-bar control that exposes the user's currently-configured game folder (the parent directory under which all per-game subfolders live). Sits in the right zone of the top bar, between the sort menu and the kebab menu. Click anywhere on it → native folder picker via Tauri; on selection, rescan library.
|
||||
|
||||
Two visual states, driven by whether `settings.gameFolder` resolves to an accessible directory:
|
||||
|
||||
| State | Trigger | Label | Status dot | Border | Tooltip |
|
||||
|---|---|---|---|---|---|
|
||||
| **Set & valid** | path is configured and exists on disk | `Game folder` | green (`--ok` `#22c55e`) | default `--bd-1` | full configured path |
|
||||
| **Not set / invalid** | path is `null`/empty, or path is set but the directory no longer exists | `Set game folder` | red (`--danger` `#ef4444`) | tinted red (`color-mix(in srgb, var(--danger) 35%, var(--bd-1))`) | `Please select a game folder` |
|
||||
|
||||
"Invalid" is intentionally collapsed into the same visual state as "not set" — the user's job is identical (open the picker and pick a folder), so we don't differentiate. If we later need a distinct "missing" state (e.g. to show the *last known* path so the user can re-attach an external drive), introduce a third state then; for now, keep it simple.
|
||||
|
||||
**Anatomy:** `inline-flex`, `height: 36px`, `padding: 0 14px 0 12px`, `gap: 8px`. `background: var(--bg-2)`, `border-radius: 8px`. Children, left to right:
|
||||
|
||||
1. **Folder icon** — `Icon.folder` from `components.jsx`, 14×14, `currentColor`.
|
||||
2. **Label** — 12.5 px / 600, `var(--t-1)`, `white-space: nowrap`, `line-height: 1`. Text content depends on state (see table above).
|
||||
3. **Status dot** — `8×8` px circle, `border-radius: 999px`, `margin-left: 2px`. Background `var(--ok)` (set) or `var(--danger)` (unset). Glow via `box-shadow: 0 0 6px color-mix(in srgb, <dot-color> 70%, transparent)`. The dot is **inline** (a flex sibling next to the label), not corner-pinned over the icon — because the button is now wider than tall, a corner badge would feel misplaced.
|
||||
|
||||
**Hover:** border darkens to `--bd-2` (set state) or to `color-mix(in srgb, var(--danger) 55%, var(--bd-2))` with a faint `color-mix(in srgb, var(--danger) 8%, var(--bg-2))` background tint (unset state) so the bad-state hover reads as "this is the thing you need to fix".
|
||||
|
||||
**Accessibility:** the `aria-label` carries the full state in words — `"Game folder: <path>"` when set, `"Set game folder"` when unset — so screen readers don't have to interpret the colored dot.
|
||||
|
||||
**Why no inline path anymore?** The previous design squeezed the full path into the button as truncated monospace (`…/eti_games_AFTER_LAN_2025`). It rarely showed the meaningful part of the path on real-world configurations, ate horizontal space, and competed with the actual primary controls (filter / search / sort) for the top bar's attention budget. The information is still one mouseover away in the tooltip; on the surface, the dot + short label communicate the *state* of the configuration, which is what users actually need to glance at.
|
||||
|
||||
**Data:** the component takes a `path: string | null` prop. `null` (or empty/whitespace string) renders the unset state; any non-empty string renders the set state. In production, derive `path` from your settings store; if you want to additionally validate existence, do the `fs.metadata` check in the store / a hook and pass `null` when the directory is missing.
|
||||
|
||||
**Dev preview:** the prototype's Tweaks panel exposes a `Game folder set` toggle (under the *Library* section) that flips the `gameFolderSet` flag wired into the Launcher. This is dev-only — in the real app the state comes from the settings store, **not** from a user-facing toggle. Don't ship it.
|
||||
|
||||
---
|
||||
|
||||
## Game card
|
||||
|
||||
The unit element of the library grid.
|
||||
@@ -389,7 +425,7 @@ Implement only if you decide variant A doesn't work after building.
|
||||
- **Click filter tab / segmented pill** → change filter.
|
||||
- **Click sort button** → opens dropdown; click an option → re-sort grid; clicking outside the menu closes it.
|
||||
- **Hover game card** → lift + accent border glow + cover image scale 1.03.
|
||||
- **Click "Game directory" button** → open native folder picker via Tauri; on selection, rescan library.
|
||||
- **Click "Game folder" button** → open native folder picker via Tauri; on selection, rescan library. The button itself indicates whether a valid folder is currently configured (green dot + `Game folder`) or not (red dot + `Set game folder`) — see "Game-folder button" above.
|
||||
- **Click "Settings"** in kebab → open Settings dialog. Changes apply live and persist immediately (no Apply button — Done just closes).
|
||||
- **Click "Unpack logs"** in kebab → opens a logs viewer (separate window or modal — out of scope for this design).
|
||||
- **Click "Refresh library"** in kebab → re-runs the library scan.
|
||||
|
||||
Reference in New Issue
Block a user