Files
ai-for-dummies/src/components/blocks/VoteWidget.astro
T
Marcos Paulo f8c3394f3d fix(review-blocks): restore legacy palette values and mark token-gaps
The components pointed at the nearest existing token when the legacy
value did not match. That traded visual fidelity for token coverage,
violating the brief's first rule. Each offender now carries the true
legacy value with a token-gap marker naming the real reason and
design-system-keeper as the owner.

138 gaps flagged for design-system-keeper. Gate green.
2026-09-05 07:33:30 +00:00

147 lines
4.3 KiB
TypeScript

---
// VoteWidget — the "which draft would you ship?" reader poll.
//
// Static markup only. The fetch to vote-service, the localStorage voter id,
// and the click handler are task 16's job. The button toggle state lives
// here so the layout matches the legacy page at first render.
//
// CSS hooks asserted by scripts/verify.mjs that must survive in the new
// architecture:
// .vote-widget — the outer wrapper
// .vote-buttons — the button row
// [aria-pressed=true] — the selected state
//
// The aria-label / role="group" on the inner cluster carries the state to
// assistive tech — colour alone is not enough. This is asserted in the
// task brief and lives in the same hook surface.
interface VoteTally {
original: number;
improved: number;
}
interface Props {
/** Skill id this widget votes for. */
skillId: string;
/** Current vote tallies. Zeros render as "0 · 0%". */
tally: VoteTally;
/** The current visitor's vote, if any. */
youVote: 'original' | 'improved' | null;
/** When the vote service is unreachable, render the offline panel. */
unavailable?: boolean;
}
const { skillId, tally, youVote, unavailable = false } = Astro.props;
const total = (tally.original || 0) + (tally.improved || 0);
const share = (count: number) => (total ? Math.round((count / total) * 100) : 0);
---
{
unavailable ? (
<section class="vote-widget" aria-label="Vote unavailable">
<span>READER VOTE</span>
<p>Voting is offline right now the vote service is not configured or unreachable.</p>
</section>
) : (
<section class="vote-widget" aria-label="Vote on this review" data-skill={skillId}>
<span>WHICH DRAFT WOULD YOU SHIP?</span>
<div class="vote-buttons" role="group" aria-label="Cast your vote">
<button type="button" data-vote="original" aria-pressed={youVote === 'original'}>
Original
<b>
{tally.original || 0} · {share(tally.original || 0)}%
</b>
</button>
<button type="button" data-vote="improved" aria-pressed={youVote === 'improved'}>
Improved draft
<b>
{tally.improved || 0} · {share(tally.improved || 0)}%
</b>
</button>
</div>
<p class="vote-note">
{youVote
? `You voted ${youVote === 'original' ? 'original' : 'improved draft'}. Pick the other option to change it.`
: 'One vote per visitor, tracked by network source.'}
</p>
</section>
)
}
<style>
/* The widget surface: light grey-green panel with a gold left border,
the "which draft would you ship?" eyebrow, and a 2-up button row. */
.vote-widget {
display: grid;
gap: 10px;
margin: 1px 0 25px;
padding: 18px 22px;
color: var(--ink);
/* token-gap: legacy review-desk vote bg (#e5eeeb); --paper here is #f5f4f1; owner design-system-keeper */
background: #e5eeeb;
border-left: 4px solid var(--gold);
}
.vote-widget > span {
/* token-gap: legacy review-desk --blue (#215675); --blue here is #527f9f; owner design-system-keeper */
color: #215675;
font: 700 10px monospace;
letter-spacing: 0.1em;
}
/* The button row: hairline-separated cells, gap:1px over the line
colour is the house "fake border" trick. */
.vote-buttons {
display: flex;
gap: 1px;
background: var(--line);
}
.vote-buttons button {
flex: 1;
display: grid;
gap: 6px;
padding: 12px 14px;
color: var(--ink);
background: var(--paper);
border: 1px solid var(--line);
cursor: pointer;
text-align: left;
font: 13px/1.3 inherit;
}
.vote-buttons button b {
color: var(--muted);
font: 11px monospace;
}
/* The selected state: dark surface, paper text, gold tally. */
.vote-buttons button[aria-pressed='true'] {
color: var(--paper);
background: var(--ink);
}
.vote-buttons button[aria-pressed='true'] b {
color: var(--gold);
}
.vote-buttons button:focus-visible {
outline: 3px solid var(--red);
outline-offset: 2px;
}
.vote-note {
margin: 0;
color: var(--muted);
/* token-gap: no --step-* covers 12px; owner design-system-keeper */
font-size: 12px;
}
/* token-gap: 530px is not a named breakpoint; owner design-system-keeper */
@media (max-width: 530px) {
.vote-buttons {
flex-direction: column;
}
}
</style>