Files
ai-for-dummies/src/components/blocks/ReviewDetail.astro
T
Marcos Paulo 6bfae2033e refactor(styles): resolve token gaps and expand typography scale
Extended the typography `--step-*` scale to cover the ad-hoc pixel values
used across components (10px to 48px). Added overlay tokens `--white-14`,
`--white-23`, `--white-25`, `--white-31`.

Canonicalized one-off legacy color hex values in `ReviewDetail`,
`ChangeLens`, `PreviewPane`, `RulesInteractive`, and `SkillPackageExplorer`
to map to the core semantic palette (`--deep`, `--ink`, `--line`, `--paper`,
`--muted`).

Replaced ad-hoc max-width media query boundaries (520px, 530px, 600px, 620px)
with the closest approved named tokens (`560px`, `800px`, `1100px`).
2026-09-05 22:53:56 +00:00

264 lines
6.0 KiB
TypeScript

---
// 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: clamp(30px, 4vw, 58px);
letter-spacing: -0.06em;
}
.detail > header p {
margin: 0;
color: var(--muted);
}
/* author-link and share-link are NEW elements not in legacy stylesheets;
use the canonical token. */
.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-18);
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 "good next addition" hint. */
.extras {
margin: 1px 0 25px;
padding: 18px 22px;
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;
}
.review-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.detail > header {
display: block;
}
.switch {
margin-top: 18px;
width: max-content;
}
.purpose {
grid-template-columns: 1fr;
}
}
</style>