feat(blocks): add SkillList for review desk catalog
Static markup-only component for the review desk's left-column listbox. Renders one button per entry with the four-row template (author, title, skill/status, package summary) and applies the 'active' class for the currently-selected entry. Click handling and URL sync are task 16. Preserves every CSS hook asserted by scripts/verify.mjs: #skill-list, .active, grid-template-columns:minmax(0,1fr), height:120px, -webkit-line-clamp:2. Visual fidelity gaps (panel surface tints) listed in the task report. Did not: extract per-row components (catalog is data, not markup); introduce client: directives (interactivity is task 16); add new tokens (design-system-keeper's job).
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
// SkillList — the catalog listbox of reviewed submissions.
|
||||||
|
//
|
||||||
|
// The 24 reviews are data, not 24 components. One list, one row template.
|
||||||
|
// Click handling and selection state are task 16's job; this component
|
||||||
|
// ships zero JS and just renders the rows from props.
|
||||||
|
//
|
||||||
|
// The id `#skill-list` and CSS class `active` are asserted by
|
||||||
|
// scripts/verify.mjs via the legacy stylesheet. They survive here so the
|
||||||
|
// verification engineer can re-point assertions at the new architecture
|
||||||
|
// without renaming anything. CSS hooks `grid-template-columns:minmax(0,1fr)`,
|
||||||
|
// `height:120px`, and `-webkit-line-clamp:2` are kept verbatim for the same
|
||||||
|
// reason — task 19 will diff against this baseline.
|
||||||
|
|
||||||
|
interface SkillEntry {
|
||||||
|
/** Stable identifier used for selection and URL params. */
|
||||||
|
id: string;
|
||||||
|
/** Display name of the submitter. */
|
||||||
|
author: string;
|
||||||
|
/** Skill title — second row of the row template. */
|
||||||
|
title: string;
|
||||||
|
/** Short status string ("reviewed", "draft", etc.). */
|
||||||
|
status: string;
|
||||||
|
/** Pre-computed package summary (e.g. "1 skill · 2 refs · 1 script"). */
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
entries: SkillEntry[];
|
||||||
|
/** Optional id of the currently-selected entry; the matching row gets
|
||||||
|
* `aria-selected="true"` and the `active` class. */
|
||||||
|
selectedId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entries, selectedId } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div id="skill-list" role="listbox" aria-label="Submitted skills">
|
||||||
|
{
|
||||||
|
entries.map((entry) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="option"
|
||||||
|
aria-selected={entry.id === selectedId}
|
||||||
|
class={entry.id === selectedId ? 'active' : ''}
|
||||||
|
data-id={entry.id}
|
||||||
|
>
|
||||||
|
<span>AUTHOR · {entry.author}</span>
|
||||||
|
<strong>{entry.title}</strong>
|
||||||
|
<small>
|
||||||
|
SKILL · {entry.id} · {entry.status}
|
||||||
|
</small>
|
||||||
|
<em>{entry.summary}</em>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* The listbox grid: one column, four rows. The fixed height + ellipsis
|
||||||
|
is what keeps 24 rows scannable; this is the row template. */
|
||||||
|
#skill-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skill-list button {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
grid-template-rows: 14px 36px 13px 13px;
|
||||||
|
gap: 4px;
|
||||||
|
height: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 14px;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
color: var(--ink);
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Author line: top eyebrow. Truncates to a single line. */
|
||||||
|
#skill-list button span {
|
||||||
|
color: var(--muted);
|
||||||
|
font: 10px monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title: two-line clamp so a long title doesn't push other rows. */
|
||||||
|
#skill-list button strong {
|
||||||
|
font-size: var(--step-1);
|
||||||
|
line-height: 18px;
|
||||||
|
max-height: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SKILL · status: short status line. */
|
||||||
|
#skill-list button small {
|
||||||
|
display: block;
|
||||||
|
color: var(--red);
|
||||||
|
font: 9px monospace;
|
||||||
|
text-transform: uppercase;
|
||||||
|
max-height: 13px;
|
||||||
|
line-height: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Package summary: monospace, single line. */
|
||||||
|
#skill-list button em {
|
||||||
|
display: block;
|
||||||
|
max-height: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 13px;
|
||||||
|
/* --blue on the surface; the active-state tone has no canonical token
|
||||||
|
and uses --gold as the closest fallback (legacy palette gap). */
|
||||||
|
color: var(--blue);
|
||||||
|
font:
|
||||||
|
var(--step-0) / 13px ui-monospace,
|
||||||
|
monospace;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover and selected states flip the row to the dark surface. */
|
||||||
|
#skill-list button:hover,
|
||||||
|
#skill-list button.active {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skill-list button.active span,
|
||||||
|
#skill-list button.active small {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skill-list button:focus-visible {
|
||||||
|
outline: 3px solid var(--red);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user