github-repos-react — GitHub Repositories (React)

A Vite + React port of github-repos-js — feature-identical (GitHub Search REST API, Link: rel="next" continuation-URL pagination, custom DataManager, toolbar, detail panel, 8 themes, EN/IT i18n, resizable container, Excel export, multi-selection), all TypeScript. The github-repos-react ↔ github-repos-js relationship is the same as wikipedia-pages-vue ↔ wikipedia-pages-js: a framework port of a proven plain-JS app, demonstrating that the zero-dependency components drop into a framework app consumed as-is via classic script tags.

Built with React 19 (function components + hooks, no state library) on the same Vite setup as wikipedia-pages-vue.

What it shows

How the components are loaded

index.html loads the three components with classic script tags from /vendor/ — they are globals that resolve their own feature scripts/CSS relative to their script URL, so they are not ES imports. src/main.tsx awaits window.VanillaGridReady before createRoot(...).render(), so every vn-* element is upgraded and window.DataManager exists before the first component renders.

/vendor/ is not a real folder in this app: the vendorComponentsPlugin in vite.config.ts (same design as wikipedia-pages-vue's) streams the sibling component folders from the repo root in dev, and copies them into dist/vendor/ on build so the built app stays self-contained.

The base stylesheet (public/styles.css) and the theme stylesheet are linked directly in index.html — in that order — so the cascade matches github-repos-js. They are deliberately not imported in main.tsx: Vite would inject imported CSS at the end of <head>, which would put the base sheet after the theme link and clobber theme overrides.

Resizing the grid

The grid sits in a src/vanilla-resize-box/ (aria-label="Resizable repositories grid"). Drag the corner handle to resize it — resizing is pointer-driven, with no keyboard path.

The handle carries role="separator" and takes its accessible name from the host's aria-label — here "Resizable repositories grid — resize handle" — but is not focusable, so it adds no tab stop to the page. This app's markup needs nothing beyond the aria-label it already carries.

Endpoint

GET https://api.github.com/search/repositories
    ?q=language:javascript
    &sort=stars
    &order=desc
    &per_page=100

per_page is capped at 100. The GitHub Search API silently clamps any larger value to 100. The data manager enforces this cap itself so the actual page size always matches what the server returns.

The grid's infinite-scroll (skip, pageSize) hint is ignored — the next page is defined entirely by the Link: rel="next" URL captured from the previous response. GitHub returns the real total_count (millions) so the toolbar status shows "Loaded N/total", but the Search API only allows pagination through the first 1,000 results — past that rel="next" stops being returned and pagination naturally stops.

Sorting

Server-side sorting is wired for the three columns the GitHub Search API supports:

Grid column API sort= value
Stars stars
Forks forks
Updated updated

Every other column is sortable="false" in the markup. open_issues_count is intentionally non-sortable: the closest GitHub option (help-wanted-issues) counts only issues labelled "help wanted", which is not the same as total open issues.

Rate limits

GitHub allows 60 unauthenticated requests/hour per IP. To raise that to 5000/h, set a personal access token in the DevTools console:

localStorage.setItem('github-repos-react:githubToken', 'ghp_yourReadOnlyToken');
location.reload();

The token requires no scopes for public-repo search.

Run

npm install       # first time only
npm run dev       # serves on http://localhost:5173/

Manual verification checklist:

  1. Grid loads the first GitHub page (100 rows), infinite-scrolls via Link continuation, stops cleanly at the 1,000-result API ceiling.
  2. Server-side sort round-trips on Stars/Forks/Updated; other headers inert.
  3. Toolbar: status counts, auto-fit, reset, clear-filters (enabled only with filter/sort), reload, export (both scopes, theme-styled file), view (single selection only), clear selection.
  4. Row double-click opens the detail panel; theme switch is live across all 8 themes (app chrome + grid + overlay); EN/IT toggle reloads localized.
  5. github-repos-react:githubToken in localStorage raises the rate limit.
  6. npm run build output works when served from a subdirectory (relative base), and node build.js (repo root) integrates it into dist/ with shared component paths.

Build

npm run build       # typecheck (tsc --noEmit, both tsconfigs) + vite build → dist/
npm run typecheck   # typecheck only
npm run preview     # serve the built dist/ locally

npm run build type-checks the whole app before bundling — the github-repos-react analogue of wikipedia-pages-vue's npm run typecheck. The repo-level node build.js delegates to this build, copies the output to dist/samples/github-repos-react/, rewrites the vendor/ script paths to the shared ../../vanilla-components/latest/ folder, minifies the public CSS, and drops the copied vendor/ folder — the same post-process contract wikipedia-pages-vue gets.

Project layout

github-repos-react/
├── index.html                      # favicon ("GRR" badge), overlay boot script, vendor script tags
├── package.json                    # react + react-dom only — no other runtime deps
├── vite.config.ts                  # base "./"; vendorComponentsPlugin (dev middleware + dist copy)
├── tsconfig.json                   # app typecheck (src/, TSX)
├── tsconfig.node.json              # vite.config.ts typecheck
├── public/
│   ├── styles.css                  # github-repos-js's stylesheet, verbatim
│   └── themes/app-*.css            # the 8 app themes + TEMPLATE, verbatim from github-repos-js
└── src/
    ├── main.tsx                    # await VanillaGridReady → createRoot (no StrictMode — see header)
    ├── App.tsx                     # shell: header, theme/language selectors, tab, overlay handoff
    ├── i18n.ts                     # module port of github-repos-js's modules/i18n.js
    ├── theme.ts                    # live theme swap: app <link href> swap, resolved name returned
    ├── vanilla/
    │   ├── VnGrid.tsx              # ref handle + CustomEvent bridges + setDataManager routing
    │   └── VnGridToolbar.tsx       # HTML-string state templates → real <template> children
    ├── components/
    │   ├── ReposTab.tsx            # toolbar + resize-box + grid + columns + wiring
    │   ├── RepoDetailPanel.tsx
    │   └── ThemeSwitchOverlay.tsx
    ├── data/
    │   └── github-repos-data-manager.ts  # lazy class factory, port of github-repos-js's manager
    └── types/
        ├── global.d.ts             # triple-slash refs to the 3 component .d.ts files
        ├── jsx.d.ts                # JSX.IntrinsicElements for the vn-* elements
        └── repo.ts                 # GitHubRepo row interface

Behavior notes (ported 1:1 from github-repos-js)