PR: #573: feat(app): import drawer v2 states — per-side accents, disabled dropzone, Done [Story-2296] by @pramaan-board-bot[bot]
View PR →

Story-2296: Import drawer v2 states (B, EPIC-2270 — final link)

The drawer already knew how to upload. This Story makes it say whose — loudly enough to see from across the desk, and honestly enough that a counsel who has picked nothing is never staring at a dead dropzone with no explanation.

The idea in one line

An armed drawer reads as its side. Pick "Opponent's" and the drop target turns crimson and says Drop opponent documents; pick nothing and it is visibly, explicably closed.

What was already there (and stayed untouched)

The armed-dropzone flow shipped in Story-2111: the five source chips (Client's / Opponent's / Other / Strategy / Privileged), the presign+finalize upload mechanics, the background-upload footer, the per-row side chips, and the privileged refusal. None of that moved. This ticket is colours, states, and copy — a surgical S.

The four deltas against the canonical frame

#Frame saidDrawer didNow
1 An eyebrow above the sources: SELECT SOURCE TO BEGIN, settling to SOURCE SELECTED once armed. No eyebrow. The ask lived inside the dropzone, so the disabled zone had to carry two jobs at once. The eyebrow states the ask up front; the zone is freed to describe itself.
2 Disabled zone reads Drag files here, greyed. Read "Select a source to begin" — the instruction, in the place where the invitation belongs. dragFilesHere, in ink-meta-muted. The zone now looks like what it is: a dropzone that is currently shut.
3 The armed zone takes the side's ink — label and icon, not just border and fill. Border and fill recoloured; the label stayed ink-strong for every side. Each armed source carries an ink class. Green-8 / crimson / ink-strong / blue-3.
4 A Done affordance, bottom-right of the footer. Missing entirely. The only exit was the ✕. A subtle SecondaryButton that calls onClose.
Done is never disabled — and that is the point. The frame draws it greyed, but a Done that blocks while files upload would contradict the trust line sitting six inches to its left ("safe to leave — processing continues"). Uploads run in the background; closing the drawer does not cancel them. So Done closes, always. A promise the UI makes in words and breaks in behaviour is worse than no promise.

Why "Other" is deliberately colourless

Client's is green, Opponent's is crimson — those are the two poles of the adversarial axis. "Other" is not a third pole; it is the absence of a claim about the axis. Giving it a colour of its own would imply a side it does not have, so it stays neutral: surface-soft fill, ink-strong label. The register's side badges already read this way; the drawer now agrees with them.

Tokens (no literals)

Every colour resolves through design/v1/colors_and_type.css. The frame's hexes map onto stops that already exist — no token proposal was needed:

FrameTokenUsed for
#1D9E75--green-9Client's zone border (vivid accent — the selection has to read as on)
#085041--green-8Client's zone ink
#A83A4A--crimson-13Opponent's zone border
#8B2332--crimsonOpponent's zone ink
#F5F5F5 / #1A1A1A--surface-soft / ink-strongOther (neutral)

Semantic green and crimson here are carrying side, not status — which is exactly the axis the Record register already colours by, so the drawer and the register now speak one language rather than two.

Copy — both locales

Two new keys under Matters.import, added to messages/en.json and messages/hi.json in the same change: sourceSelected and dragFilesHere. The per-side drop copy (dropClient / dropOpponent / dropOther) and the trust line already existed in both locales and were reused, not re-authored. Done reuses Common.done.

Stories — every state, co-located

src/components/matters/import-into-matter-sheet-v2.stories.tsx:

StoryState
EmptyNo source — dropzone visibly disabled, eyebrow asks
ArmedClientGreen accent, "Drop client documents"
ArmedOpponent (new)Crimson accent, "Drop opponent documents"
ArmedOther (new)Neutral, "Drop other documents"
ImportingUploading / reading / registered / failed rows + background-upload footer
ArmedStrategy, Privileged, ReadOnly, AllRegistered, RegisteredExit, FromTheRailPre-existing, unchanged
Verified. pnpm lint (0 errors) · pnpm test (1158 passed) · pnpm storybook:check (drift + build green) · pnpm build green. All five states screenshotted from the built Storybook and attached to the PR.
Deliberately not done. The frame tints the Browse files / Choose folder buttons with the side accent too. That would mean bypassing SecondaryButton and re-hardcoding brand classes on a raw button — the exact thing the button doctrine forbids. The zone's border, fill, icon and label already carry the side unmistakably; the buttons stay on the shared primitive. If founder review wants the tint, the right move is an accent variant on the primitive, not a local one-off.

Files changed

4 file(s), +57 / -14 lines

Path Role + -
messages/en.jsonConfig+2-0
messages/hi.jsonConfig+2-0
src/components/matters/import-into-matter-sheet-v2.stories.tsxComponent+14-0
src/components/matters/import-into-matter-sheet-v2.tsxComponent+39-14

One-sentence recall

Pick a side and the drawer becomes that side; pick nothing and it says so.