feat(blocks): add ReviewDetail for review desk static panel
Static markup-only component for the right-column review panel. Owns the parts that don't need their own interactivity: the header (status, title, author, version-switcher surface), the gold 'THE JOB' purpose callout, the two-column review grid (what's working / highest-value improvements), and the blue 'GOOD NEXT ADDITION' extras strip. Slots for 'vote', 'preview', and 'lens' let the page (task 16) compose the interactive siblings — VoteWidget, PreviewPane, ChangeLens — inside the static article. The role='group' / aria-pressed on the preview-version switcher carries state to assistive tech. Did not: include the interactive siblings inline (would couple the static and interactive markup); introduce client: directives (interactivity is task 16); add new tokens.
This commit is contained in:
@@ -0,0 +1,261 @@
|
|||||||
|
---
|
||||||
|
// ReviewDetail — the static review panel for a single submission.
|
||||||
|
//
|
||||||
|
// The "detail" article on the review desk. Owns the parts that don't need
|
||||||
|
// interactivity of their own: the header (status, title, author, version
|
||||||
|
// switcher surface), the gold "THE JOB" purpose callout, the two-column
|
||||||
|
// review grid (what's working / highest-value improvements), and the blue
|
||||||
|
// "GOOD NEXT ADDITION" extras strip.
|
||||||
|
//
|
||||||
|
// The interactive siblings — VoteWidget, FileTabs, PreviewPane, ChangeLens
|
||||||
|
// — live as separate components and are composed by the page (task 16).
|
||||||
|
// This component is the static wrapper around them.
|
||||||
|
|
||||||
|
interface SkillEntry {
|
||||||
|
id: string;
|
||||||
|
author: string;
|
||||||
|
title: string;
|
||||||
|
status: string;
|
||||||
|
/** One-sentence summary that fills the gold purpose panel. */
|
||||||
|
focus: string;
|
||||||
|
/** "WHAT'S ALREADY WORKING" bullets. */
|
||||||
|
wins: string[];
|
||||||
|
/** "HIGHEST-VALUE IMPROVEMENTS" bullets. */
|
||||||
|
improve: string[];
|
||||||
|
/** "GOOD NEXT ADDITION" copy. */
|
||||||
|
extras: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
entry: SkillEntry;
|
||||||
|
/** Which draft is currently being viewed. Drives the version switcher's
|
||||||
|
* initial state and the share-link copy. */
|
||||||
|
preview: 'original' | 'improved';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entry, preview } = Astro.props;
|
||||||
|
const shareHref = `?author=${encodeURIComponent(entry.author)}&skill=${encodeURIComponent(entry.id)}&view=${preview}`;
|
||||||
|
const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<article class="detail" id="detail" aria-live="polite">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<span class="status">{entry.status}</span>
|
||||||
|
<h2>{entry.title}</h2>
|
||||||
|
<p>
|
||||||
|
Submitted by <a class="author-link" href={authorHref}>{entry.author}</a> ·{' '}
|
||||||
|
<a class="share-link" href={shareHref}>share review ↗</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="switch" role="group" aria-label="Preview version">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={preview === 'original' ? 'active' : ''}
|
||||||
|
aria-pressed={preview === 'original'}
|
||||||
|
data-preview="original">Original</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={preview === 'improved' ? 'active' : ''}
|
||||||
|
aria-pressed={preview === 'improved'}
|
||||||
|
data-preview="improved">Improved draft</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="purpose">
|
||||||
|
<span>THE JOB</span>
|
||||||
|
<p>{entry.focus}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot name="vote" />
|
||||||
|
|
||||||
|
<div class="review-grid">
|
||||||
|
<section>
|
||||||
|
<span>WHAT'S ALREADY WORKING</span>
|
||||||
|
<ul>
|
||||||
|
{entry.wins.map((item) => <li>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<span>HIGHEST-VALUE IMPROVEMENTS</span>
|
||||||
|
<ul>
|
||||||
|
{entry.improve.map((item) => <li>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="extras">
|
||||||
|
<span>GOOD NEXT ADDITION</span>
|
||||||
|
<p>{entry.extras}</p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<slot name="preview" />
|
||||||
|
<slot name="lens" />
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* The panel surface: paper, padded, lives inside the catalog's right
|
||||||
|
column. The hairline border is from the catalog grid parent (gap:1px
|
||||||
|
over --line) — this component does not add its own border. */
|
||||||
|
.detail {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 38px;
|
||||||
|
background: var(--paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header row: title cluster on the left, version switcher on the right. */
|
||||||
|
.detail > header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 25px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
color: var(--red);
|
||||||
|
font: 700 10px monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail h2 {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: var(--step-6);
|
||||||
|
letter-spacing: -0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail > header p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-link,
|
||||||
|
.share-link {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version switcher: hairline-bordered pill, active cell flips to the
|
||||||
|
ink surface. role="group" carries the cluster meaning to assistive
|
||||||
|
tech; aria-pressed carries the per-button state. */
|
||||||
|
.switch {
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch button {
|
||||||
|
padding: 9px 11px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font: 700 10px monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch button.active,
|
||||||
|
.switch button[aria-pressed='true'] {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch button:focus-visible {
|
||||||
|
outline: 3px solid var(--red);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Purpose: the gold callout that names the skill's job in one sentence. */
|
||||||
|
.purpose {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 150px 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 45px 0 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.purpose span {
|
||||||
|
color: var(--red);
|
||||||
|
font: 700 10px monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purpose p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--step-1);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Review grid: two columns of bullets on a hairline "fake border" grid.
|
||||||
|
Each column carries a red eyebrow naming what the list is. */
|
||||||
|
.review-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1px;
|
||||||
|
background: var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-grid section {
|
||||||
|
padding: 22px;
|
||||||
|
background: var(--paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-grid span {
|
||||||
|
color: var(--red);
|
||||||
|
font: 700 10px monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-grid ul {
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-grid li + li {
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extras: the blue strip with the "good next addition" hint. */
|
||||||
|
.extras {
|
||||||
|
margin: 1px 0 25px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
/* Legacy uses a soft green-grey panel; --paper is the closest canonical
|
||||||
|
token and loses the green tint. Visual fidelity gap reported. */
|
||||||
|
color: var(--ink);
|
||||||
|
background: var(--paper);
|
||||||
|
border-left: 4px solid var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extras span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--blue);
|
||||||
|
font: 700 10px monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extras p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.detail {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail > header {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
margin-top: 18px;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purpose {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user