Skip to content

feat(Pagination): add Pagination component aligned with Cyber skin#1555

Open
AlexandraGallipoliRodrigues wants to merge 23 commits into
masterfrom
alex/WEB-2441-create-pagination-component
Open

feat(Pagination): add Pagination component aligned with Cyber skin#1555
AlexandraGallipoliRodrigues wants to merge 23 commits into
masterfrom
alex/WEB-2441-create-pagination-component

Conversation

@AlexandraGallipoliRodrigues

@AlexandraGallipoliRodrigues AlexandraGallipoliRodrigues commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new Pagination component to mistica-web, designed for the Cyber project but built against the standard skin contract so it works across all skins. Implements the Figma anatomy, types, behaviour, animation and "Compact View" specs.

Ticket: WEB-2441

What's included

Component

  • Pagination with controlled and uncontrolled modes (currentPage / defaultPage + onChange).
  • Previous / Next navigation controls that hide automatically on the first / last page. hideNavigationControls and hidePageList allow rendering only the page list or only the navigation arrows.
  • dynamicCount controls the size of the middle visible window; showEllipsis toggles truncation.
  • mode="iconOnly" renders the navigation buttons as chevrons only, even on desktop.
  • Compact View kicks in automatically when the viewport is narrower than 375px: navigation chevrons stack above/below the page list (per Figma).
  • disabled disables all interactive elements.
  • getPaginationItems() helper exported for unit tests / consumers that need the resolved item list.

Styling

  • Page items render the visible 32px circle (::before) inside a 32×48 (mobile) / 32×32 (desktop) interactive container. The Figma "Interaction Area" spec calls for 48×48 on mobile, but a literal 48px width breaks the layout on a 375px screen for the denser 1 … N N+1 N+2 … LAST configurations. The component instead satisfies WCAG 2.2 Target Size (Minimum) via the spacing exception: 32px buttons with a 4px gap leave 36px between centers, enough to inscribe non-overlapping 24px touch circles around every interactive element. A long-form comment in pagination.css.ts documents this trade-off.
  • Hover on regular page items uses neutralLow background at scale(1.06) (32 → ~34px). Pressed uses backgroundContainerPressed. Current page uses brandLow + textActivated text, scale 1.0. Duration 0.2s ease-in-out, matching the Figma "Animation" spec.
  • Previous / Next labels and chevrons use textLink. After the Cyber skin overhaul (master merge) textLink → palette.brand = #0066FF, so the labels render in the blue specified by the Figma without changes to the skin contract.
  • Chevrons use IconChevronLeftRegular / IconChevronRightRegular at size={20} to match the Figma weight and dimensions.
  • Page number labels use medium weight to match the Figma typography.
  • When hidePageList is set, the container gap becomes 16px to honour the "Previous and Next only" Figma layout.

Accessibility

  • <nav aria-label> landmark with a localised default ("Paginación" / "Pagination" / "Paginierung" / "Paginação").
  • aria-current="page" on the active page (rendered as a non-interactive span, not a button).
  • aria-hidden="true" on the ellipsis.
  • All aria-labels are localised via four new tokens in src/text-tokens.tsx:
    • paginationLabel
    • paginationPrevPage
    • paginationNextPage
    • paginationGoToPage (with 1$s placeholder)
  • Consumers can still override via the aria-label, navLeftLabel, navRightLabel props or via the theme's texts overrides, following the same pattern as Carousel.
  • Touch targets pass WCAG 2.2 Target Size (Minimum) via the spacing exception (see Styling above).

Tooling

  • 2 playroom snippets added to playroom/snippets.tsx (Pagination, Pagination iconOnly).
  • Cyber theme exposed in playroom/themes.tsx as Community_Cyber / Community_Cyber_iOS (light + iOS), so they group visually under the Community_ prefix in the flat theme selector.
  • Storybook story at src/__stories__/pagination-story.tsx.
  • 9 unit tests in src/__tests__/pagination-test.tsx (nav landmark, single-page no-render, page click, Next click, disabled, controlled mode + 3 getPaginationItems edge cases).
  • Screenshot tests at src/__screenshot_tests__/pagination-screenshot-test.tsx covering 8 scenarios × 2 viewports (Default, FirstPage, LastPage, WithEllipsis, NavOnlyResponsive, PagesOnly, IconOnlyControls, NextChapterLink).

Screenshots / verification

Run yarn playroom, switch to the Cyber skin in the selector, and load the Pagination snippet. Verify:

  • Hover on a non-current page shows a subtle neutral grey background, not blue.
image
  • The current page has a light cyan background (brandLow) with dark-blue text.
  • Previous / Next labels are blue (not purple).
image
  • Chevrons match the Figma weight.
  • On mobile (< 768px), only chevrons are visible and each tap area is 48×48px.
image

Test plan

  • yarn jest src/__tests__/pagination-test.tsx passes (9 tests).
  • yarn tsc --noEmit clean.
  • Visual QA against Figma in playroom for each skin.
  • Switch locale to verify aria-labels translate (es / en / de / pt).

A11y tests

  • IOS:
IOS-a11y-test.MP4
  • Android:
Android-a11y-test.mp4

🤖 Generated with Claude Code

Adds a new Pagination component with desktop and mobile layouts,
following the Mistica design specs.

- Page navigation with Previous/Next controls (hidden at boundaries)
- Configurable visible page window via `dynamicCount`
- Optional ellipsis truncation for large page counts
- Controlled and uncontrolled modes (`currentPage` / `defaultPage`)
- iconOnly mode for compact layouts
- Customisable navigation labels (i18n)
- Hover state uses neutral `backgroundContainerHover` with 1.06 scale
- Current page uses `brandLow` and `textActivated`
- Filled chevrons match the Figma stroke weight
- Numeric labels use medium weight for visual parity with Figma
- Includes playroom snippets and unit tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use textBrand instead of textLink for Previous/Next so the Cyber skin
  (where textLink is purple/accent) renders the blue from secondary
- Expose Cyber theme in playroom/themes.tsx so it can be selected
- Enforce 48x48px touch target on mobile per Figma accessibility spec
  (the visible 32px circle stays centered inside the larger hit area)
- Localise aria-labels via text-tokens (paginationLabel, paginationPrevPage,
  paginationNextPage, paginationGoToPage) following the Carousel pattern;
  navLeftLabel / navRightLabel / aria-label props still override the tokens
- Update tests to assert the localised aria-labels (default locale es-ES)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 1, 2026 07:12
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Size stats

master this branch diff
Total JS 16.2 MB 16.2 MB +11.9 kB
JS without icons 2.07 MB 2.08 MB +11.9 kB
Lib overhead 92.5 kB 92.5 kB 0 B
Lib overhead (gzip) 19.9 kB 19.9 kB 0 B

@AlexandraGallipoliRodrigues AlexandraGallipoliRodrigues added the AI AI Generated label Jun 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Pagination component to mistica-web, aligned with the Cyber skin while remaining compatible with the standard skin contract, including localized accessibility labels, Playroom support, and unit tests.

Changes:

  • Added Pagination component (controlled/uncontrolled) plus getPaginationItems() helper.
  • Introduced new i18n text tokens for pagination aria-labels and actions.
  • Added Playroom themes/snippets and Jest tests covering core pagination behaviors.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/text-tokens.tsx Adds pagination-related localized text tokens.
src/pagination.tsx Implements the Pagination component and getPaginationItems() logic.
src/pagination.css.ts Adds Vanilla Extract styles for pagination layout, states, and touch targets.
src/index.tsx Exposes Pagination from the public entrypoint.
src/__tests__/pagination-test.tsx Adds unit tests for rendering, interactions, disabled/controlled behavior, and helper edge cases.
playroom/themes.tsx Exposes Cyber theme configs for Playroom preview.
playroom/snippets.tsx Adds Playroom snippets for default and iconOnly pagination.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/index.tsx
Comment thread src/pagination.tsx Outdated
Comment on lines +231 to +233
if (totalPages <= 1) {
return null;
}
Comment thread src/pagination.tsx
return (
<li key={`ellipsis-${index}`} className={styles.pageListItem}>
<span className={styles.ellipsis} aria-hidden="true">
<PaginationLabel weight="medium">...</PaginationLabel>
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy preview for mistica-web ready!

Project:mistica-web
Status: ✅  Deploy successful!
Preview URL:https://mistica-qux1oxj9x-flows-projects-65bb050e.vercel.app
Latest Commit:cbc31aa

Deployed with vercel-action

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Accessibility report
✔️ No issues found

ℹ️ You can run this locally by executing yarn audit-accessibility.

The lint rule testing-library/no-node-access flagged the use of `.closest()`
to verify the current page. Switched to asserting that the current page is
absent from the buttons accessible tree (it's rendered as a non-interactive
span) while sibling pages are buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 1, 2026 09:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread src/pagination.tsx Outdated
Comment thread src/pagination.css.ts
Comment on lines +226 to +233
[mq.supportsHover]: {
selectors: {
'&:hover': {
color: skinVars.colors.textBrand,
},
},
},
},
Copilot AI review requested due to automatic review settings June 2, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 26 changed files in this pull request and generated 5 comments.

Comment thread src/pagination.tsx
Comment thread src/pagination.tsx
Comment on lines +250 to +253

const activePage = clamp(isControlled ? currentPage : internalPage, 1, totalPages);

const goToPage = (page: number) => {
Comment thread src/pagination.css.ts
Comment on lines +152 to +156
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.css.ts
Comment on lines +171 to +175
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/index.tsx
Copilot AI review requested due to automatic review settings June 3, 2026 07:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 27 changed files in this pull request and generated 7 comments.

Comment thread src/pagination.tsx Outdated
Comment thread src/pagination.tsx Outdated
}

if (item.current) {
// Rendered as a focuseable button with aria-disabled so VO
Comment thread src/pagination.css.ts
Comment on lines +152 to +156
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.css.ts
Comment on lines +171 to +175
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.css.ts Outdated
Comment on lines +88 to +92
* 48px on mobile to give a comfortable thumb target. WCAG 2.2 Target Size
* (Minimum) is satisfied through the spacing exception: 32px circles with a
* 4px gap between centers (36px apart) easily inscribe non-overlapping 24px
* circles, so the rule passes even though the literal target width is below
* 24×24 only in width.
Comment thread playroom/frame-component.tsx Outdated
Comment thread src/text-tokens.tsx Outdated
Comment on lines +423 to +427
export const paginationLabel: TextToken = {
es: 'Paginación',
en: 'Pagination',
de: 'Paginierung',
pt: 'Paginação',
If both hideNavigationControls and hidePageList are true, the component still renders an empty <nav> landmark (no buttons / list items). This produces a navigation region with no actionable content; returning null in this configuration avoids confusing/empty landmarks.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 3, 2026 08:17
changed import

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 27 changed files in this pull request and generated 6 comments.

Comment thread src/pagination.css.ts
Comment on lines +152 to +156
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.css.ts Outdated
Comment on lines +88 to +92
* 48px on mobile to give a comfortable thumb target. WCAG 2.2 Target Size
* (Minimum) is satisfied through the spacing exception: 32px circles with a
* 4px gap between centers (36px apart) easily inscribe non-overlapping 24px
* circles, so the rule passes even though the literal target width is below
* 24×24 only in width.
Comment thread src/pagination.tsx Outdated
}

if (item.current) {
// Rendered as a focuseable button with aria-disabled so VO
Comment thread src/index.tsx
export {default as HorizontalScroll} from './horizontal-scroll';
export {default as Stepper} from './stepper';
export {ProgressBar, ProgressBarStepped} from './progress-bar';
export {default as Pagination} from './pagination';
Comment thread src/pagination.css.ts
Comment on lines +166 to +170
'&:hover:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1.06)',
backgroundColor: skinVars.colors.neutralLow,
},
Comment thread src/pagination.css.ts
Comment on lines +171 to +175
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
return cases;
};

test.each(getCases())('Pagination %s - %s', async (_name, id, device) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a table?

Comment on lines +35 to +36
// Compact view only renders below 375px viewport. We use MOBILE_IOS_SMALL (320px)
// to drive the vertical layout and the current ± 1 page reduction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a lot of comments that we probably don't need, right? We should probably update AGENTS.md so it doesn't generate that much comments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could take advantage of a feedback loop by using a skill that "auto documents" (better said: it helps updating/creating) things that the AI does not the way we want. each time we say "no", the skill /create-doc (or something similar) can update the thing to fine-tune the thing for the next round.
I've tried it and it works quite well! it saved me time and it improved my agentic code experience. take look :)

https://github.com/Telefonica/webapp/blob/a8839f60de1ce350613289f459abb69222edb495/.agents/skills/create-doc/SKILL.md

p.d.
I've found it very effective the fact of giving a good and bad example. most of the time it does help the AI doing the thing you expect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have added controls instead of creating a lot of examples like the rest of the stories. Perhaps this is something we should add to AGENTS.md so it is the default behaviour

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agreed. we can improve this by exploiting controls. that way it will be improving the DX!

Comment thread src/pagination.tsx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are very annoying layout shifts when ellipsis appear and when buttons disappear, I think we should address that @aweell @yceballost

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated the specs to fix it:
Navigation controls:

  • Previous control is disabled when user is in first page, next control is disabled when user reaches last page.
  • By default previous and nexts will be always visible unless are configured to be hidden.

You can check it here.

Comment thread src/pagination.tsx Outdated
const orderedPages = Array.from(pages).sort((a, b) => a - b);
const items: Array<PaginationItem> = [];

orderedPages.forEach((page, index) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably .map ?

Comment thread src/pagination.tsx Outdated
onChange?.(nextPage);
};

const items = isCompact

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should render compact view depending on css only to avoid layout shifts. window size won't be available until js is executed in the browser

@aweell aweell requested review from AnaMontes11 and removed request for yceballost June 8, 2026 08:29
@brtbrt brtbrt self-requested a review June 8, 2026 16:34

// Compact view only renders below 375px viewport. We use MOBILE_IOS_SMALL (320px)
// to drive the vertical layout and the current ± 1 page reduction.
test('Pagination CompactView - MOBILE_IOS_SMALL', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure we want this? by fiddling with the component, I see that even smaller viewports could take advantage of a single line, maybe.

http://localhost:9000/#?code=N4Igxg9gJgpiBcIA8AlGBnADhAduglgG4wAyAhgJ4QCuALgHwA6OABC0gApkDm%2BOZtfLha0ItMgBsu3DAF5gAJgDMAXxZhqAJ00wctaTHkBGNS1gAzMtQn6ehxWqgV%2BAW3xgAwjT0sA9ExwkXzQsXAJicio6ehAVIA

should we talk to design maybe?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/pagination.tsx

dynamicCount?: number;

navLeftLabel?: string;

Copy link
Copy Markdown
Contributor

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[O2_SKIN]: 'en-GB',
[O2_NEW_SKIN]: 'en-GB',
[ESIMFLAG_SKIN]: 'es-ES',
[CYBER_SKIN]: 'es-ES',

@aweell aweell Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm idk it does work for me...

Grabacion.de.pantalla.2026-06-09.a.las.16.05.01.mov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is the PreviewTools component that is broken

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Marcosld correct, we figured it out yesterday in pair programming. the skin is missing/not supported yet. I'd open a ticket to address it in a different branch. it's a quick and easy fix :)

Copilot AI review requested due to automatic review settings June 10, 2026 16:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 27 changed files in this pull request and generated 6 comments.

Comment thread src/index.tsx
export {default as HorizontalScroll} from './horizontal-scroll';
export {default as Stepper} from './stepper';
export {ProgressBar, ProgressBarStepped} from './progress-bar';
export {default as Pagination} from './pagination';
Comment thread src/pagination.css.ts
Comment on lines +133 to +137
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.css.ts
Comment on lines +152 to +156
'&:active:before': {
opacity: 1,
transform: 'translate(-50%, -50%) scale(1)',
backgroundColor: skinVars.colors.brandLow,
},
Comment thread src/pagination.tsx
type="button"
className={styles.currentPage}
aria-current="page"
aria-disabled="true"
Comment thread src/pagination.tsx
})}
{...getPrefixedDataAttributes(dataAttributes, 'Pagination')}
>
{!hideNavigationControls && (
Comment thread src/text-tokens.tsx
Comment on lines +50 to +56
paginationSection: string;
paginationPrevPage: string;
paginationNextPage: string;
paginationPrevPageAriaLabel: string;
paginationNextPageAriaLabel: string;
paginationGoToPage: string;
paginationCurrentPage: string;
Copilot AI review requested due to automatic review settings June 10, 2026 16:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 27 changed files in this pull request and generated 4 comments.

Comment thread src/pagination.tsx
String(clamp(isControlled ? currentPage : internalPage, 1, totalPages)),
String(totalPages)
);
const resolvedAriaLabel = ariaLabel ? `${sectionLabel}, ${ariaLabel}` : sectionLabel;
Comment thread src/index.tsx
export {default as HorizontalScroll} from './horizontal-scroll';
export {default as Stepper} from './stepper';
export {ProgressBar, ProgressBarStepped} from './progress-bar';
export {default as Pagination} from './pagination';
Comment thread src/pagination.tsx
Comment on lines +173 to +177
type="button"
className={styles.currentPage}
aria-current="page"
aria-disabled="true"
aria-label={currentPageLabel(item.page)}
Comment thread src/pagination.tsx
Comment on lines +267 to +271
const isPrevDisabled = activePage <= 1;
const isNextDisabled = activePage >= totalPages;

return (
<nav
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI AI Generated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants