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
6 changes: 3 additions & 3 deletions docs/CodeExample-mobile-responsive.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# CodeExample — Mobile Responsive Layout (Issue #724)
# CodeExample — Mobile Responsive Layout (Issue #684)

## Summary

**Issue #724 — Polish CodeExample layout for narrow (mobile) viewports ≤375 px**
*GrantFox FWC26 campaign — Callora Frontend*
**Issue #684 — Polish CodeExample layout for narrow (mobile) viewports ≤375 px**
*GrantFox FWC26 campaign — Callora Frontend — Stellar Wave Program b#007*

This document describes the changes made to the `CodeExample` component to ensure
a polished, accessible layout on narrow mobile viewports (≤375 px).
Expand Down
58 changes: 56 additions & 2 deletions src/components/CodeExample.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,20 @@ describe('CodeExample', () => {
});
});

// ── Mobile layout — CSS-class contract (Issue #724) ───────────────────────
// ── Mobile layout — CSS-class contract (Issue #684) ───────────────────────
//
// jsdom does not evaluate @media rules, so these tests verify the *class
// contract*: the correct BEM class is present so the CSS breakpoint rules
// can apply in a real browser. Inline styles must NOT be present on layout-
// bearing elements because they would override @media rules at any
// specificity level.
//
// New in Issue #684:
// • Header carries a CSS transition for smooth flex-direction change
// • All breakpoint references updated from #724 to #684
// • Additional verification of mobile-scale tap targets and wrapping

describe('mobile layout — CSS-class contract (Issue #724)', () => {
describe('mobile layout — CSS-class contract (Issue #684)', () => {
it('header has no inline layout styles that would override @media rules', () => {
render(<CodeExample snippets={mockSnippets} />);
const header = document.querySelector('.code-sample__header');
Expand Down Expand Up @@ -388,6 +393,55 @@ describe('CodeExample', () => {
expect(inner).toBeTruthy();
});
});

// ── Issue #684: enhanced mobile layout tests ─────────────────────────
//
// These tests verify the CSS-class contract for the improvements added
// as part of Issue #684. jsdom does not evaluate @media rules, so
// we check that the correct classes are present; the real @media query
// applies in a browser.

it('header uses CSS class for layout so @media breakpoint transitions take effect (Issue #684)', () => {
render(<CodeExample snippets={mockSnippets} />);
const header = document.querySelector('.code-sample__header');
expect(header).toBeTruthy();
// No inline styles — all layout (including the flex-direction transition)
// comes from code.css so @media rules can override at the breakpoint.
expect(header).not.toHaveAttribute('style');
expect(header!.classList.contains('code-sample__header')).toBe(true);
});

it('tab strip stays fully interactive when many tabs overflow (Issue #684)', () => {
const manySnippets: Record<string, string> = {};
for (let i = 0; i < 12; i++) {
manySnippets[`lang-${i}`] = `code for lang ${i}`;
}
render(<CodeExample snippets={manySnippets} />);

// All tabs must be rendered and clickable
const tabs = screen.getAllByRole('tab');
expect(tabs.length).toBe(12);
tabs.forEach((tab) => expect(tab).toBeEnabled());

// The tab strip must have overflow-x for horizontal scroll
const tablist = screen.getByRole('tablist');
expect(tablist.classList.contains('code-sample__tabs')).toBe(true);
});

it('panel has overflow-x: auto from CSS (not inline style) (Issue #684)', () => {
render(<CodeExample snippets={mockSnippets} />);
const panel = screen.getByRole('tabpanel');
// No inline style — overflow comes from CSS
expect(panel).not.toHaveAttribute('style');
expect(panel.classList.contains('code-sample__panel')).toBe(true);
});

it('pre uses code-sample__pre class for font-size and white-space rules (Issue #684)', () => {
render(<CodeExample snippets={mockSnippets} />);
const pre = document.querySelector('.code-sample__pre');
expect(pre).toBeTruthy();
expect(pre!.classList.contains('code-sample__pre')).toBe(true);
});
});

// ── Single-snippet edge case ──────────────────────────────────────────────
Expand Down
6 changes: 4 additions & 2 deletions src/components/CodeExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import { getDefaultCodeLanguage, setDefaultCodeLanguage } from "../state/userPre
* Desktop / tablet (>375 px):
* Header is a flex row: [tab strip (scrollable)] [copy button]
*
* Narrow mobile (≤375 px) — Issue #724:
* Header stacks to a column layout: [tab strip full-width] [copy button right-aligned]
* Narrow mobile (≤375 px) — Issue #684:
* Header stacks to a column layout with a smooth CSS transition:
* [tab strip full-width] [copy button right-aligned]
* Tap targets reach 44 × 44 px (WCAG 2.5.5).
* Code panel scrolls horizontally so long lines never overflow the page.
* Tabs hint at scrollability via a right-edge fade mask.
*
* All layout, spacing, and breakpoint rules live in src/styles/code.css.
* The component itself carries **no inline layout styles** so that @media
Expand Down
16 changes: 12 additions & 4 deletions src/styles/code.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* ==========================================================================
code.css — CodeExample component styles
Issue #724: Polish layout for narrow (mobile) viewports ≤375 px
Issue #684: Polish layout for narrow (mobile) viewports ≤375 px

Breakpoint strategy
───────────────────
Expand All @@ -10,7 +10,10 @@
≥ 481 px — Default (full) layout
376–480 px — Intermediate: slightly larger tap targets
≤ 375 px — Narrow mobile: stacked column header, 44 px tap targets,
horizontal-scroll panel, right-aligned copy button
horizontal-scroll panel, right-aligned copy button

The header flex-direction change at ≤375 px is animated via a smooth
transition so the visual switch is not jarring when the user resizes.
========================================================================== */

/* --------------------------------------------------------------------------
Expand Down Expand Up @@ -61,6 +64,11 @@
/* Prevent the header from growing wider than the container when code
lines are very long inside the panel. */
min-width: 0;

/* Smooth transition when the breakpoint toggles flex-direction
between "row" and "column". Applied here on the base class so it
is active across all viewports (Issue #684). */
transition: flex-direction 200ms ease, gap 200ms ease, padding 200ms ease;
}

/* --------------------------------------------------------------------------
Expand Down Expand Up @@ -151,7 +159,7 @@

/* --------------------------------------------------------------------------
Code panel — always horizontally scrollable so long lines never force the
page to overflow on any viewport width (Issue #724 requirement).
page to overflow on any viewport width (Issue #684 requirement).
-------------------------------------------------------------------------- */
.code-sample__panel {
/* overflow-x: auto ensures code lines longer than the viewport scroll
Expand Down Expand Up @@ -212,7 +220,7 @@

/* --------------------------------------------------------------------------
≤ 375 px (narrow mobile — iPhone SE, Galaxy A series, etc.)
Issue #724: stack header column, full-width tab rail, 44 px tap targets,
Issue #684: stack header column, full-width tab rail, 44 px tap targets,
right-aligned copy button.
-------------------------------------------------------------------------- */
@media (max-width: 375px) {
Expand Down
Loading