Various UI changes - #88
Merged
Merged
Conversation
The useNavigate example sat in a jotai-based routing library but reached for React's own useState for the input's text. Use a plain jotai atom instead so the example is consistent with the rest of the library's model.
…ICENSE.md Copyright still read Downplay Ltd (the pre-transfer org) with a stale 2017-2018 range, in the root README/LICENSE and both packages'. Update to Randomdev Ltd, 2017-2026, and rename each LICENSE to LICENSE.md so the README's mention of it is a real link rather than plain text.
vertical-align: 0.05em over-corrected for input-mono's lower baseline vs alegreya-sans, leaving inline code visibly high. -0.03em confirmed live against the running page.
The tagline now starts under the small-caps 'arl' of the 'Jarl' heading (measured live against the rendered wordmark, 4.72rem) instead of the page margin. 'atomic' cycles per-letter between the brand's red and gold, a 2.4s round trip staggered 0.15s per letter so it reads as a pulse sweeping the word rather than a flicker. Both confirmed live in the shared browser: the indent within 1.4px of the heading, and the animation's computed color sampled mid-cycle at both ends of the range.
Switch, Route and Link are all real jarl-react exports (index.ts re-exports each), and Switch's on prop takes a RouteAtom, not a string - the README's old 'there's no <Switch/> component' line was simply out of date. Separately, the tagline sat ~21px below the heading from the h1's default bottom margin collapsing through the wrapper div. Made both margins explicit so it reads as a subtitle directly under the wordmark instead of a separate paragraph.
…-badges Heading margin-bottom 0.35rem -> 0.15rem; tagline margin-bottom 0 (default) -> 1.5rem. Confirmed live: 2.4px heading-to-strap, 24px strap-to-badges.
marked wasn't generating heading id attributes, so nothing was linkable yet. Added a small GitHub-style slugifier to the heading renderer (dedup handled per-document via a Map cleared before each parse) rather than pulling in an extension package for it - matches the existing custom code renderer's approach. Confirmed #switch/#route/#link resolve and scroll correctly on both API pages, and that headings across the site still get unique ids.
Link's to prop is optional (LinkProps<T>.to?: T); passing an empty object for params-less routes was noise. productRoute already demonstrates the real params-passing case just below.
… jarl-atoms import The opener still pitched against a manually-managed isLoading flag; reframe it around the layout-churn cost of scattered component-level loading states and suspense fallbacks instead, which is the actual pain this guide solves. Kept the Suspense mention accurate - the example just below still uses one, now framed as a single boundary rather than per-component ones. Also collapsed the routes.ts example's two separate jarl-atoms imports (staticRouteAtom/paramRouteAtom, then resolvedAtom) into one.
conventional-commits-parser's note-keyword matching is looser than the Conventional Commits spec's 'footer only' rule: commit 25a5aa3's body contained the plain-English sentence 'alongside the existing breaking-change suppression' mid-paragraph, no colon, not a real footer, and the parser swept it (plus the trailing Co-Authored-By line) into the release notes as a fabricated BREAKING CHANGES entry. What it described was also stale even if it had been real: the very next commit (2db21b1) abandoned that reset-to-2.0.1 plan and continued from 2.5.0 instead, once it turned out npm's dependent-check and dist-tags made the rollback impossible. Nothing about jarl's public API actually broke in 2.6.0 - every other entry in the release is an internal release-process change. Safe to hand-edit: @semantic-release/changelog only ever prepends new releases, it never rewrites past entries.
…px letter-spacing Alegreya Sans reads narrow/condensed at body sizes and no wider cut is served by the Adobe kit; a size + letter-spacing bump on it barely helped. Source Sans 3 was already loaded as a documented Google Fonts fallback, so no new network cost. Compared live in the shared browser before committing. Headings (Alverata/Cinzel) are untouched.
New copy: what a route atom actually is (a link up to rootAtom, matching a URL piece, current-match + reverse-URL), how routing decisions decompose to plain switch/if logic, and navigation via calling the atom setter directly. Links rootAtom to its API reference, matching the Switch/Route/Link convention already in place just below it. Confirmed live against the running page, all five links checked.
The /to/ styling was informal shorthand for nested emphasis, not literal slashes. **bold with _italic_ nested** avoids the same-delimiter ambiguity markdown has with **bold with *italic* nested**. Confirmed live: renders as <strong>how to build a URL <em>to</em> that route</strong>.
… setters rows and activeSort (parsed sort key/direction) are now derived jotai atoms read off the filter route atom, not values recomputed with useMemo inside the component. Navigation drops jarl-react's useNavigate wrapper in favor of jotai's own useSetAtom directly on the route atom - useNavigate is just that call anyway (jarl-react/src/hooks.ts:26-29). filterInput is a separate, un-navigated atom holding only the live input text; typing never touches the URL, and Enter/Search commits it into the filter route atom's setter. A small effect resyncs the input when the URL changes some other way (back/forward, a shared link), so the field doesn't go stale - that isn't live search, just correctness. Verified interactively in the shared browser: typing leaves the URL/rows untouched, Enter commits and filters to one row, sort toggling preserves the filter, and back-navigation resyncs the input and restores all rows.
…eAtom sort
Grid and DataGridApp collapse into one component: both query params are
optional, so the route always matches, and <Route>'s function-as-child was
pure ceremony around atoms the component already had direct access to -
now it just reads them.
activeSort is transformRouteAtom over sort, reshaping the raw query value
into {key, direction} and back. filter chains off activeSort rather than
sort directly, so filter's own values carry the already-parsed sort
alongside the filter text - one atom holding everything the grid needs.
Both writes (sort toggle, filter commit) go through that single atom and
each naturally preserves the other field, since the value it doesn't
change is just read back out of the current match. This is the chain-of-
state-to-the-route's-tip shape jarl is meant to be used in.
Verified interactively: sort survives a filter commit and vice versa
(?sort=price&filter=tools -> toggle -> ?sort=-price&filter=tools, rows
correctly Tools-only and price-descending), zero console errors.
The block comment above createGridRoutes explained the whole down/up flow in one place, disconnected from the code it described - readable once, useless as a reference while reading the function. Split it: each atom gets one or two lines right next to it (why sort is memoised off root, what activeSort's getter/setter each do, why filter chains off activeSort and not sort, why rows needs no useMemo, why filterInput stays local). Only the memoisation note stays above the function, since it's about the factory as a whole rather than any one atom in the chain.
…lue/setter hooks useAtomValue + useSetAtom on the same atom were two separate hook calls (and two subscriptions) for state that's read and written together throughout the component. useAtom(routes.filter) -> [filter, setFilter] is the idiomatic single call; every values? reference becomes filter.values?. Verified live: sort survives a filter commit (?sort=price&filter=iron), rows correctly Iron-only and price-ascending.
…d field lists
Dropped the sortKey/direction destructure - every read is filter.values?.key
/filter.values?.direction now, consistent with how filter.values?.filter was
already accessed. setFilter calls spread {...filter.values} and override
just what changed, instead of repeating all three fields each call; a small
defaults object (parseSort(undefined) plus filter: undefined) keeps the
spread's type whole for the case the route doesn't match, which is real to
the type even though it never actually happens here.
Also clarified the factory comment: the rootAtom-taking factory only exists
because this demo harness mounts at any root - a real app would just
declare these as static atoms.
Verified live: sort toggling still works (?sort=stock, arrow renders),
typecheck/lint/format all clean.
The Table styled-component was the one piece of DataGridApp.tsx that had nothing to do with the demo's routing/state story - pure CSS taking up a third of the file's vertical space. Split it into DataGridTable.tsx. In its place, moved SortKey/SortDirection/sortColumns/parseSort/ stringifySort/filterWares/sortWares in from wares.ts - the logic that actually explains what the atoms are doing. wares.ts is now just the Ware type and the fixed inventory data, the one piece nobody needs to read to understand the demo. Confirmed no other consumer of the moved sort logic exists in the repo. Verified live: sort toggling still works (?sort=category), zero console errors, and the demo's 'View source' panel no longer shows any CSS - just the story.
It doesn't depend on root at all - atom("") is the same regardless of
which URL prefix the demo mounts under - so it doesn't belong inside
createGridRoutes alongside the atoms that genuinely chain off root.
Simplifies the factory's return shape to just {filter, rows}.
Verified live: filter still commits on Enter (?sort=name&filter=wool),
zero console errors.
…ilename heading linked to GitHub 'View source' was a <details> disclosure defaulting closed, but the source is the point of a demo - readers always want it. Replaced with a plain h2 showing the file's own name, linked to its blob on GitHub master. New sourcePath prop on DemoPage (repo-relative, e.g. 'packages/docs/src/demos/DataGridApp.tsx') supplies both the displayed filename and the link target; all four demo pages updated. githubRepoUrl exported from Layout.tsx as the single source of truth for the repo URL rather than duplicating it. Verified live across all four demo pages: correct filename heading, correct GitHub link, source always visible with no accordion, zero console errors.
…reads defaultSort directly Two small follow-ups on the manual tuning pass: toggleSort's flip check still read filter.values?.key/direction directly instead of the currentFilter it uses everywhere else - now consistent, one source of truth. defaultFilter spread parseSort(undefined) to get its key/direction, which just returns defaultSort by construction - now says that directly instead of relying on the two happening to agree. Verified live: sort toggling still correct, zero console errors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interactive-session ticket (TODOS-671). Changes land one commit at a time as they're accepted live; description below grows with the session.
Changes so far
useNavigateexample'ssearchTextnow lives in a jotaiatominstead of React'suseState, matching the rest of the library's model.