Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit f48262f

Browse files
committed
docs(spec): ContentListToolbar layout rebalance
1 parent be75d26 commit f48262f

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# ContentListToolbar Layout Rebalance — Design
2+
3+
**Status:** Draft
4+
**Date:** 2026-05-27
5+
**Scope:** `apps/admin/src/features/_shared/components/content-list-toolbar.tsx`
6+
**Consumers:** `PostsRouteViewContent.tsx`, `NotesRouteViewContent.tsx`
7+
**Author:** Innei
8+
9+
## Problem
10+
11+
The current `ContentListToolbar` packs search, filter, sort, refresh, and selection controls onto a single 40px row. Two specific imbalances make the row feel uncoordinated:
12+
13+
1. **Search bar dominates the left.** It is `max-w-80` (320px) wide and ends with a heavy `border-r` separator, visually pinning it as a "compartment" that does not belong with the action chips that follow.
14+
2. **Right-side cluster has uneven weight.** When no rows are selected, the right edge holds a tiny grey checkbox plus the text "选择当前页". The moment any row is selected, that area becomes a black-on-white inverted chip (`bg-neutral-950 text-white`) that bundles checkbox + count + bulk-action button. The visual weight swings sharply between the two states, and neither state matches the lightweight filter/sort chips next to it.
15+
16+
The goal is to rebalance positioning so the search bar reads as a peer to the action chips, and to harmonise the selection control with the rest of the toolbar regardless of state. The component API stays the same; both consumers must continue to render without changes.
17+
18+
## Goals
19+
20+
- Search bar reads as a peer to filter/sort chips, not a separate compartment.
21+
- All non-search controls form a single right-aligned cluster with visible grouping.
22+
- Selection control has the same visual weight whether empty or populated.
23+
- No changes to `ContentListToolbarProps` or to either route view file.
24+
25+
## Non-Goals
26+
27+
- No structural rework (no floating bulk-action bar, no mode-switching toolbar).
28+
- No mobile-specific collapse strategy beyond what already exists.
29+
- No changes to `ContentListHeader`, `SortMenu`, or the underlying `Checkbox` primitive.
30+
- No additional props on the selection slot.
31+
32+
## Design
33+
34+
### Toolbar layout
35+
36+
Left to right:
37+
38+
1. **Search form**`flex-1 max-w-60` (240px ceiling, shrinks below). Removes `border-r border-neutral-200 pr-3`. No separator after.
39+
2. **Spacer**`min-w-0 flex-1` pushes the rest to the right.
40+
3. **Filters slot** — unchanged (currently a `SelectField` styled as a chip).
41+
4. **Sort menu** — unchanged.
42+
5. **Vertical separator**`span.w-px.h-3.5.bg-neutral-200.dark:bg-neutral-800`, separating sort+filter from utility actions.
43+
6. **Extra actions slot** — refresh button etc.
44+
7. **Selection slot** — see below.
45+
46+
Container metrics (`h-10`, `gap-1.5`, padding `px-4`, border-bottom) stay the same.
47+
48+
### Selection control
49+
50+
Replaces the current "empty → tiny checkbox+text" / "selected → inverted black chip" pair with two consistent states sharing the same visual register as filter/sort chips.
51+
52+
**Empty state.** A bare `Checkbox` (no text label) wrapped in a label element. `aria-label` is taken from `selection.selectAllLabel` so screen readers still announce "选择当前页". The sm-and-up text is removed.
53+
54+
**Selected state.** Two side-by-side chips in place of the single inverted pill:
55+
56+
- **Count chip** (`h-7`, neutral surface):
57+
- `inline-flex items-center gap-1.5 h-7 px-2 rounded text-xs tabular-nums`
58+
- `bg-neutral-100 text-neutral-700 dark:bg-neutral-900 dark:text-neutral-200`
59+
- Contents: checkbox + `selection.selectedLabel`.
60+
- **Bulk action chip** (`h-7`, danger tone — both current consumers use it for batch delete, so the styling encodes destructive intent at the component level):
61+
- `inline-flex items-center gap-1.5 h-7 px-2 rounded text-xs`
62+
- `border border-red-200 bg-red-50/60 text-red-700 hover:bg-red-100`
63+
- `dark:border-red-900 dark:bg-red-950/30 dark:text-red-300 dark:hover:bg-red-950/50`
64+
- `focus-visible:ring-2 focus-visible:ring-[var(--color-primary-shallow)]`
65+
- `disabled:pointer-events-none disabled:opacity-50`
66+
- Contents: `selection.bulkActionIcon` + `selection.bulkActionLabel`.
67+
68+
Bulk action chip remains a `<button>`. Count chip stays a `<label>` wrapping the checkbox so clicking the count still toggles select-all.
69+
70+
### What changes in the file
71+
72+
`apps/admin/src/features/_shared/components/content-list-toolbar.tsx` only.
73+
74+
| Section | Change |
75+
|---|---|
76+
| Search `<form>` | Drop `max-w-80 border-r border-neutral-200 pr-3 dark:border-neutral-800`. Set `flex-1 max-w-60`. |
77+
| Toolbar body | Move spacer (`min-w-0 flex-1`) from its current position (between `extraActions` and `selection`) to sit immediately after the search form, so search anchors the left and all other controls pack right. Insert a `w-px h-3.5` vertical separator between `sortMenu` and `extraActions`. |
78+
| `ContentListToolbarSelectionControls` (empty branch) | Replace the `inline-flex h-7 ... gap-3 text-xs` block with a label that contains only `<Checkbox>` (no `<span>`). Keep `aria-label`. |
79+
| `ContentListToolbarSelectionControls` (selected branch) | Replace the single black `inline-flex h-7 ... bg-neutral-950 text-white` chip with two adjacent chips per the spec above. |
80+
81+
### What stays the same
82+
83+
- `ContentListToolbarProps` and `ContentListToolbarSelection` interfaces.
84+
- All callers in `PostsRouteViewContent.tsx` and `NotesRouteViewContent.tsx`.
85+
- `ContentListHeader`, `SortMenu`, `SortOrderButton` exports.
86+
- Keyboard focus order (search → filter → sort → refresh → select-all → bulk-action).
87+
- Behaviour for `hasVisibleItems === false` (right side renders an empty container).
88+
89+
## Risks
90+
91+
- **Hardcoding danger tone for the bulk action.** Today both consumers pass "批量删除". A future non-destructive bulk action would look wrong in red. Mitigation: add a `bulkActionTone?: 'default' | 'danger'` prop only when that need actually appears. YAGNI for now.
92+
- **Empty-state checkbox without visible label.** Discoverability drops slightly. Mitigation: `aria-label` and `title` carry the meaning; the count chip appears the moment one row is selected.
93+
94+
## Validation
95+
96+
- Visual: open `/posts` and `/notes` at desktop and `tablet:` breakpoints, verify spacing and that selection states transition without layout shift.
97+
- Lint: `pnpm -C apps/admin exec oxlint apps/admin/src/features/_shared/components/content-list-toolbar.tsx`.
98+
- Typecheck: `pnpm -C apps/admin exec tsc --noEmit --pretty false`.
99+
- Manual: select rows on both views, exercise bulk-delete confirmation, exercise clear-search, refresh, sort menu, filter dropdown.

0 commit comments

Comments
 (0)