Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions docs/ReviewsTab-print.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# ReviewsTab Print Support

Campaign: **GrantFox FWC26**
Ticket type: UI/UX
Status: Shipped

---

## What this change does

When a user prints the API Detail page while viewing the **Reviews** tab, the print output now:

1. **Hides chrome controls** — the "Sort by" dropdown and "Write a Review" button are suppressed with `display: none !important` via the existing `.no-print` mechanism. Neither element is meaningful on paper.
2. **Expands all review cards** — every `.preview-card` inside the reviews section is forced to `display: block`, `max-height: none`, `overflow: visible` so no review body text is clipped.
3. **Expands collapsible regions** — any element using `aria-hidden="true"`, `[hidden]`, or `<details>` that may exist inside the reviews panel is forced open so nothing is silently omitted from the printed page.
4. **Single-column layout** — the `.reviews-list` grid is forced to `grid-template-columns: 1fr` for predictable paper width usage.
5. **Page-break avoidance** — each review card carries `break-inside: avoid` so a card is not split across page boundaries.

The **light-theme override** and general **chrome hiding** (`topbar`, tabs, sidebar, hero CTAs) are handled by the existing `@media print` block already present in `index.css` and are unaffected by this change.

---

## Files changed

| File | Change |
|---|---|
| `src/pages/ApiDetailPage.tsx` | Added `reviews-sort-controls no-print` class to the sort-by row; added `no-print` to the Write a Review button; added `data-reviews-section` attribute to the reviews `<section>`; added `reviews-list` class to the review cards grid |
| `src/index.css` | Extended the existing `@media print` block with ReviewsTab-specific rules under `[data-reviews-section]` |
| `src/pages/ReviewsTab.print.test.ts` | New file — 13 contract tests covering markup and CSS |

No new files were added to `src/styles/`. The existing project contract (`src/print.test.ts`) asserts that `main.tsx` must not import a `print.css` file; all print styles live in `src/index.css`.

---

## How it works

### Markup anchors (ApiDetailPage.tsx)

```tsx
{/* Section anchor — CSS scopes to this */}
<section
id="panel-reviews"
role="tabpanel"
aria-labelledby="tab-reviews"
tabIndex={0}
data-reviews-section {/* ← new */}
>
<div className="api-detail-reviews-header">
<h3>Developer Feedback</h3>
{/* Hidden on print — interaction not possible on paper */}
<button className="secondary-button no-print">Write a Review</button>
</div>

{/* ... rating histogram (visible on print) ... */}

{/* Hidden on print — sort order irrelevant on paper */}
<div className="reviews-sort-controls no-print">
<label htmlFor="review-sort">Sort by</label>
<select id="review-sort">…</select>
</div>

{/* Print-expanded grid */}
<div className="reviews-list">
{sortedReviews.map(…)}
</div>
</section>
```

### CSS rules (index.css, inside `@media print`)

```css
/* ── ReviewsTab: hide chrome, expand collapsibles (FWC26) ──── */

/* Expand every review card */
[data-reviews-section] .preview-card {
display: block !important;
overflow: visible !important;
max-height: none !important;
height: auto !important;
}

/* Expand aria-hidden collapsible regions */
[data-reviews-section] [aria-hidden="true"] {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
max-height: none !important;
height: auto !important;
overflow: visible !important;
}

/* Expand <details> elements */
[data-reviews-section] details { display: block !important; }
[data-reviews-section] details > * { display: block !important; }

/* Expand [hidden] elements */
[data-reviews-section] [hidden] { display: block !important; }

/* Rating histogram — no overflow clip */
[data-reviews-section] .rating-histogram,
[data-reviews-section] .rating-histogram__bar-track,
[data-reviews-section] .rating-histogram__bar-fill {
overflow: visible !important;
max-width: 100% !important;
}

/* Single-column list */
[data-reviews-section] .reviews-list {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 16px !important;
}

/* Avoid splitting a review card across a page break */
[data-reviews-section] .preview-card {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
```

> **Why `[data-reviews-section]` as the scope selector?**
> A data-attribute selector is immune to CSS specificity collisions from class-based resets elsewhere in the `@media print` block. It also serves as clear self-documentation in the markup.

---

## Testing

Run the focused test suite:

```bash
npm test -- --run src/pages/ReviewsTab.print.test.ts
```

All 13 tests should pass. The existing `src/print.test.ts` (10 tests) also continues to pass.

### What the tests cover

| Test group | Tests |
|---|---|
| **Markup: no-print chrome** | Sort controls wrapper has `reviews-sort-controls no-print`; Write a Review button has `no-print`; section has `data-reviews-section`; grid has `reviews-list` |
| **CSS: expansion rules** | Review cards expand (`display: block`, `max-height: none`); `aria-hidden` regions expand; `<details>` expand; `[hidden]` expand; single-column layout; page-break avoidance; rules are inside `@media print` |
| **Import guard** | `main.tsx` does not import a `print.css` file |

---

## Accessibility notes

- The `data-reviews-section` attribute is not exposed in the accessibility tree and has no effect on screen reader behaviour.
- The `no-print` class uses `display: none !important` only inside `@media print`, so all controls remain fully keyboard-accessible in the normal viewing context.
- The single-column print layout maintains left-to-right reading order without requiring any additional ARIA markup.

---

## Design token compliance

All `@media print` rules use absolute values (`#f5f7fa`, `#1a2332`, `1fr`, etc.) because the intent is to override any token-based dark theme and force a legible light appearance. This matches the pattern established by the existing print block above. No tokens are used in print context — this is intentional and consistent with the rest of the file.

---

## Browser notes

`break-inside: avoid` / `page-break-inside: avoid` is supported by all modern browsers. `max-height: none` overrides any inline-style or class-based collapse pattern that sets a pixel height. If a future collapsible inside the reviews section uses a CSS `transition` on `height`, the `height: auto !important` rule will override it since `@media print` transitions are ignored by all major browsers.
90 changes: 90 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6873,6 +6873,96 @@ code,
color: #444;
}

/* ── ReviewsTab: hide chrome, expand collapsibles ────────────────────
FWC26 — When printing the reviews section:
1. .reviews-sort-controls (.no-print) is already hidden by the generic
.no-print rule above; this comment preserves intent for reviewers.
2. Every review card (data-reviews-section .preview-card) is forced
visible and fully expanded so no content is clipped on paper.
3. The RatingHistogram bars use inline `width` — overflow:visible
ensures they are never cut off.
4. The collapsed-content pattern used elsewhere in the app
([aria-hidden="true"], [hidden], details/summary) is expanded so
any future collapsible that lands inside the reviews panel prints
in full without JS intervention.
─────────────────────────────────────────────────────────────────── */

/* Force every review card to be fully visible and expanded */
[data-reviews-section] .preview-card {
display: block !important;
overflow: visible !important;
max-height: none !important;
height: auto !important;
}

/* Expand any aria-hidden collapsible regions inside the reviews panel */
[data-reviews-section] [aria-hidden="true"] {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
max-height: none !important;
height: auto !important;
overflow: visible !important;
}

/* Expand <details> elements that may appear in reviews */
[data-reviews-section] details {
display: block !important;
}

[data-reviews-section] details > * {
display: block !important;
}

/* Expand hidden elements inside the reviews panel */
[data-reviews-section] [hidden] {
display: block !important;
}

/* Rating histogram: allow bars to render at full width without overflow clip */
[data-reviews-section] .rating-histogram,
[data-reviews-section] .rating-histogram__bar-track,
[data-reviews-section] .rating-histogram__bar-fill {
overflow: visible !important;
max-width: 100% !important;
}

/* Review list grid: single column, consistent spacing */
[data-reviews-section] .reviews-list {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 16px !important;
}

/* Review cards: avoid splitting across page breaks */
[data-reviews-section] .preview-card {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
}

/* Share Snapshot Button */
.share-snapshot-button {
display: inline-flex;
align-items: center;
gap: 6px;
margin-left: 8px;
}

/* ─── Category Pills ──────────────────────────────────────────────────────── */
.pill-bar {
display: flex;
gap: 8px;
overflow-x: auto;
padding-bottom: 8px;
margin-bottom: 16px;
scrollbar-width: thin;
}

.pill-bar::-webkit-scrollbar {
height: 6px;
}

/* Expand EndpointSummary collapsibles when printing */
.endpoint-summary-content--collapsed {
display: block !important;
Expand Down
18 changes: 16 additions & 2 deletions src/pages/ApiDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,11 +548,11 @@
// ── Render ────────────────────────────────────────────────────────────────

return (
<div className="api-detail-page">

Check failure on line 551 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

JSX element 'div' has no corresponding closing tag.
<div className="sr-only" aria-live="polite" aria-atomic="true">
{isLoading ? "Loading API details…" : `Loaded ${api.name}`}
</div>
<div className="api-detail-container">

Check failure on line 555 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

JSX element 'div' has no corresponding closing tag.
<Breadcrumb
items={[
{ label: "Marketplace", href: "/marketplace" },
Expand All @@ -560,7 +560,7 @@
]}
/>

<div className="api-detail-shell">

Check failure on line 563 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

JSX element 'div' has no corresponding closing tag.
{/* ── Hero ──────────────────────────────────────────────────────── */}
<div className="api-detail-hero">
<div className="api-detail-heading">
Expand Down Expand Up @@ -916,10 +916,17 @@
* any runtime JS. See issue #580 and src/styles/print.css.
*/}
{tab === "reviews" && (
<section id="panel-reviews" role="tabpanel" aria-labelledby="tab-reviews" tabIndex={0}>
<section
id="panel-reviews"
role="tabpanel"
aria-labelledby="tab-reviews"
tabIndex={0}
data-reviews-section
>
<div className="api-detail-reviews-header">
<h3 style={{ margin: 0 }}>Developer Feedback</h3>
<button className="secondary-button">Write a Review</button>
{/* Write a Review button: not meaningful on paper */}
<button className="secondary-button no-print">Write a Review</button>
</div>

{rawReviews.length === 0 ? (
Expand All @@ -934,6 +941,12 @@
<RatingHistogram rating={averageRating} distribution={ratingDistribution} />
</div>

{/* Sort controls: hidden when printing — sort order is irrelevant on paper */}
<div
className="reviews-sort-controls no-print"
style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, flexWrap: "wrap" }}
>
<label htmlFor="review-sort" style={{ fontSize: 13, color: "var(--muted)", whiteSpace: "nowrap" }}>
<div style={{ display: "flex", alignItems: "center", gap: "var(--mkt-space-lg)", marginBottom: "var(--mkt-space-xl)", flexWrap: "wrap" }}>
<label htmlFor="review-sort" style={{ fontSize: "var(--mkt-font-size-sm)", color: "var(--muted)", whiteSpace: "nowrap" }}>
Sort by
Expand Down Expand Up @@ -963,6 +976,7 @@
</select>
</div>

<div className="reviews-list" style={{ display: "grid", gap: 16 }}>
<div style={{ display: "grid", gap: "var(--mkt-space-xl)" }}>
{sortedReviews.map((review) => (
<div key={review.id} className="preview-card" style={{ padding: "var(--mkt-space-2xl)" }}>
Expand Down Expand Up @@ -1018,10 +1032,10 @@
</div>
))}
</div>
</>

Check failure on line 1035 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Identifier expected.
)}

Check failure on line 1036 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
</section>

Check failure on line 1037 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Expected corresponding JSX closing tag for 'label'.
)}

Check failure on line 1038 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

{/* ── EMBED ───────────────────────────────────────────────── */}
{tab === "embed" && (
Expand All @@ -1045,7 +1059,7 @@
)}
</div>
{/* /tab-content */}
</div>

Check failure on line 1062 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Unexpected token. Did you mean `{'>'}` or `&gt;`?

Check failure on line 1062 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Expected corresponding closing tag for JSX fragment.
{/* /content-left */}

{/* ── Sidebar ─────────────────────────────────────────────────── */}
Expand Down Expand Up @@ -1108,7 +1122,7 @@
/>
</div>
</aside>
</div>

Check failure on line 1125 in src/pages/ApiDetailPage.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Expected corresponding JSX closing tag for 'section'.
{/* /api-detail-content-grid */}
</div>
{/* /api-detail-shell */}
Expand Down
Loading
Loading