Skip to content

refactor(pagination): one shared Pagination component for all six surfaces#477

Open
rusikv wants to merge 1 commit into
thingsboard:feature/iot-hub-pagefrom
rusikv:feat/iot-hub-pagination
Open

refactor(pagination): one shared Pagination component for all six surfaces#477
rusikv wants to merge 1 commit into
thingsboard:feature/iot-hub-pagefrom
rusikv:feat/iot-hub-pagination

Conversation

@rusikv

@rusikv rusikv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What

Extracts pagination into a single shared component — src/components/Pagination/ — and migrates every surface that paginates to it:

Surface Before After
IoT Hub category / search / creator PaginationLink + PaginationChevron + iot-hub-pagination-update.ts shared component (link mode + dynamic rebuild)
Device Library + Partners hardware shared dumb shell with singleton IDs; windowing JS copy-pasted into both consumers shared component (interactive mode)
Case studies index bespoke innerHTML renderer, cs-* classes shared component (interactive mode)
Blog index numbers-only innerHTML renderer (no prev/next) shared component (interactive mode)

Net: −389 lines, one windowing algorithm instead of three drifted copies, one styling vocabulary instead of four.

Design

  • Two modes, one prop. With basePath, page numbers are real anchors (/iot-hub/widgets/3/, rel=prev/next, crawlable, zero JS). Without it, buttons dispatch bubbling tb-pagination:page-change events and updatePagination() rebuilds the nav. IoT Hub uses both in sequence: static links until search/filters activate the dynamic pipeline.
  • Consumers keep their own behavior. URL state (blog ?page=), scroll handling, sessionStorage restore (case-studies), API refetching (IoT Hub) all stay in consumer code — the component only renders and reports. This boundary is documented in a design-contract comment at the top of the component.
  • IoT-Hub-only extras compose in via a controls slot (PerPageSelector.astro, the items-per-page dropdown). Slotting instead of a prop keeps the dropdown's script/styles out of the other surfaces' bundles — Astro bundles per import graph, not per render.

Improvements over the old implementations

  • Keyboard focus is restored to the equivalent control after every rebuild (all old surfaces dropped focus to <body>; the old case-studies code could even double-fire prev/next).
  • aria-current="page" everywhere (previously IoT Hub only); icon buttons have aria-labels at all widths; prefers-reduced-motion respected.
  • Brand tokens (--color-brand/-contrast) replace hardcoded #b3c7ff/#17181c/--sl-* colors → correct dark theme on all surfaces.
  • Deterministic SSR ids (reproducible builds); compact "Page X of Y" + chevrons below the lg breakpoint on every surface.
  • Multiple instances per page are now safe (the old Device Library shell was a fixed-ID singleton).

Intended visual/behavior deltas (not regressions)

  • Unified look: 40px hit targets, brand-accent current-page pill, chevron prev/next.
  • Blog gains prev/next buttons (it had none).
  • Case studies / Device Library / Partners: text "Previous/Next" → chevrons; full number rows up to 7 pages (was 5).
  • Device Library / Partners: the redundant always-visible "Page X of Y" line is dropped (the compact sub-lg layout shows it).
  • IoT Hub dark mode: current-page text is navy-on-lavender per brand tokens (was near-black).

Verification

  • astro check (0/0/0), ESLint clean on all touched files.
  • Before/after headless-Chrome screenshots of all six surfaces: IoT Hub pixel-identical (incl. page-3 current state); others show only the intended deltas.
  • Deep-linked /blog/?page=2 exercises the client updater + URL restore end-to-end.
  • Repo-wide greps confirm zero references to the deleted components, classes, or old event names.
  • Adversarially reviewed; findings fixed (focus restore, deterministic ids, dead code removal).

lint:linkcheck not yet run (no routes/links changed by this PR — pagination URLs are generated by the same paginate()/getStaticPaths code as before).

Six surfaces each carried their own pagination: IoT Hub's link-based
PaginationLink + runtime updater, a button shell shared by Device
Library and Partners (with the windowing JS copy-pasted into both),
and bespoke innerHTML renderers on the blog and case-studies indexes.
Three drifted copies of the windowing algorithm, four styling
vocabularies, and recurring a11y gaps.

Replace all of it with src/components/Pagination/:

- Pagination.astro — one nav, two modes: basePath renders real
  anchors (SSG, rel=prev/next, crawlable); without it, buttons that
  dispatch bubbling tb-pagination:page-change events. Numbered list
  on desktop, compact 'Page X of Y' row below lg. Brand tokens
  (indigo/lavender current-page pill), aria-current in both modes,
  focus-visible rings.
- pagination-client.ts — updatePagination() rebuilds the lists for
  client-driven surfaces; restores keyboard focus to the equivalent
  control after each rebuild (fixes a focus-loss bug all the old
  implementations shared).
- pagination-shared.ts — the single windowing algorithm + strings.
- PerPageSelector.astro — the IoT-Hub-only items-per-page dropdown,
  composed in via the 'controls' slot from consumer files so its
  script/styles ship only to IoT Hub pages (Astro bundles per import
  graph, not per render).

Consumers keep what is theirs: URL state (?page= on blog), scroll
behavior, sessionStorage restore (case-studies), and the dynamic
search pipeline (IoT Hub) — the component only renders and reports.

Intended deltas: unified look on all surfaces, blog gains prev/next
chevrons, grid surfaces show full number rows up to 7 pages (was 5),
Device Library/Partners drop the redundant always-visible
'Page X of Y' line (lives in the sub-lg compact layout now).
@rusikv rusikv marked this pull request as ready for review June 12, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant