npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@net-advantage/jo-ui-year-bar

v2.1.0

Published

Publishable year bar for a journey timeline.

Readme

@net-advantage/jo-ui-year-bar

Publishable year bar for a journey timeline.

The bar follows a strict MVVM split. The model owns the window, the pills, and the navigation rules. The view-model owns disabled state, keyboard movement, and scrolling the selected year into view. The view renders that view-model with jo-ui Button, SegmentedControl, and Dropdown.

MVVM

flowchart LR
  Props["YearBarProps"] --> ViewModel["useYearBarViewModel"]
  Model["jo-ui-year-bar.model.ts<br/>window, pills, keys, disabled ends"] --> ViewModel
  ViewModel --> View["YearBar.tsx"]
  View -->|"onSelectYear, onSpanChange"| ViewModel
  ViewModel -->|"onSelectYear, onSpanChange"| Host["Host application"]

| Layer | File | Responsibility | | --- | --- | --- | | Contract | jo-ui-year-bar.types.ts | Range, span, selection, and the view-model | | Model | jo-ui-year-bar.model.ts | Build the window and pills, decide the next year, mark today when the selection is empty, copy root attributes, and normalise the sticky class and chrome labels | | View-model | jo-ui-year-bar.view-model.ts | Framework-free year bar observable; guard clicks and move by keyboard | | View | jo-ui-year-bar.use-view-model.ts | useYearBarViewModel reads that observable and owns scroll and pending focus | | View | YearBar.tsx | Render the bar from the view-model |

Models

flowchart TD
  Range["YearRange"] --> Bounds["range window"]
  Selected["selectedYear or today"] --> Anchor["anchor"]
  Bounds --> Anchor
  Span["5Y, 10Y, or All"] --> Window["visible window"]
  Anchor --> Window
  Window --> Pills["pills minus omitted years"]
  Moments["momentYears"] --> Pills
  Pills --> Move["previous, next, today, keys"]
  Pills --> Mark["today mark when empty"]
  • yearSpanLength is { 5Y: 5, 10Y: 10 }. yearsAfterAnchor is 1.
  • yearBarWindow({ range, selectedYear, span, todayYear }) returns { from, to }, the inclusive years the bar lays out. The bar calls this function. 5Y and 10Y run from the anchor minus the span through the year after the anchor. The anchor is the selected year, or today clamped into the range. The window may pass the journey dates.
  • All runs from min(range.startYear, todayYear + 1) through todayYear + 1. Omitted years stay inside yearBarWindow; they are removed only from the pills.
  • An ongoing range extends through today when endYear is earlier.
  • Omitted years are not pills. A pill hasMoments when its year is in momentYears. Empty years use --jo-color-year-bar-muted. Years with moments use --jo-color-year-bar-text.
  • Block padding is 6px below 1500px and 8px from 1500px, from --jo-year-bar-padding-block and --jo-year-bar-padding-block-wide. That is a 38px bar, then a 42px bar.
  • A year pill's keyboard focus ring is 2px solid --jo-color-focus with outline-offset: 2px. The years strip keeps 4px of block padding for that ring and cancels it with margin-block: -4px, so overflow-x: auto does not clip the ring and the bar stays 38px, then 42px.
  • Previous and next move to the neighbouring pill. When the selection is not a pill they move to the last or first pill. They are disabled on the first or last pill, and when there are no pills.
  • Today selects today clamped into the range, then the nearest pill. It is disabled only when there are no pills. A tie keeps the earlier year.
  • A span change does not change the selection. With clearable, clicking the selected pill calls onSelectYear(null).
  • With highlightTodayWhenEmpty and selectedYear null, today's pill uses the active fill plus an underline and aria-current="date". aria-pressed stays false, so the mark is not the selection. Clicking it selects that year. If today is omitted or outside the window, no pill is marked. A real selection still uses aria-current="true" and is not given the today mark.
  • Arrow keys, Home, and End move the roving tab stop. The selected pill scrolls into view. With the today mark and no selection, that marked pill scrolls into view. The roving tab stop stays on the first pill when nothing is selected.
  • A missing size is xs. size stays xs only. That face is 38px below 1500px, 42px from 1500px, and the 44px compact row when the slot is at or under --jo-year-bar-compact-max. No larger size ships until a design defines its metrics. A missing ariaLabel is Year. A missing clearable stays off. A missing highlightTodayWhenEmpty stays off. A missing sticky stays on.
  • sticky={false} leaves the bar in normal flow, for a bar that sits at the bottom of a page. The default pins it with position: sticky, top: var(--jo-year-bar-sticky-top, 0px), and z-index: var(--jo-year-bar-z-index, 2). The class is jo-ui-year-bar--sticky. Both tokens are 0px and 2, the same in light and dark. A host can override them. The fallbacks are those same values when the theme tokens are absent. The bar sticks inside its scroll parent, so that parent has to extend past the bar. A wrapper that is only as tall as the bar scrolls away with it.
  • labels replaces the chrome strings. previous defaults to "Previous year", next to "Next year", today to "Today", years (the years group) to "Years", and span (the span control) to "Year span". A blank or whitespace value keeps that default. The arrow glyphs and the span option text (5Y, 10Y, All years) stay fixed.
  • data-* and aria-* props are copied onto the root section. className is appended to jo-ui-year-bar. The accessible name stays ariaLabel. There is no style prop.
  • The root width is auto with box-sizing: border-box, so it fills its slot and a horizontal margin or padding on the root stays inside that slot. Put the margin on a host class passed through className. Wrap the bar when the slot itself should be inset.
  • Previous and next are secondary xs icon-only Buttons. Today is a secondary xs Button. Above the compact slot, 5Y / 10Y / All is an xs SegmentedControl. Unselected span labels use --jo-color-year-bar-span-text. All reads as "All years".
  • Colour, type size, padding, gaps, and radius come from the year-bar theme tokens. Navy and white are --jo-color-year-bar-active and --jo-color-year-bar-active-text. The font is --jo-font-family through --jo-ui-control-font-family.
  • The shipped stylesheet is scoped to .jo-ui-year-bar. It does not emit Tailwind utility selectors such as .flex, .hidden, or .block.
  • A slot at or under --jo-year-bar-compact-max (768px) uses the compact layout. The container query is on the bar, so the slot decides, not the viewport. The 960px desktop bar stays the single-row layout.
  • The compact bar stays one row. Its layout height is --jo-year-bar-hit-min (44px), with the track padding removed so nothing stacks below the row. Previous and next are hidden. Today stays, at the xs face (--jo-ui-button-xs-height), with a ::before hit area of --jo-year-bar-hit-min. The span group is a jo-ui Dropdown. Its accessible name comes from labels.span, which defaults to "Year span". The options are 5Y, 10Y, and All years. The select box is --jo-year-bar-hit-min tall.
  • The years strip fills the remaining width. It scrolls on the horizontal axis (overflow-x: auto, overflow-y: hidden, scroll snap x proximity). The scrollbar is hidden (scrollbar-width: none) and the strip stays scrollable. The desktop ring padding is cleared here (padding-block: 0, margin-block: 0) because the row is already --jo-year-bar-hit-min tall. The focus ring stays a 2px outline in --jo-color-focus, drawn 2px outside the painted face and inside --jo-year-bar-pill-hit-border, so the 44px row does not clip it. Pills use the Sarah mock's mobile strip: min height --jo-year-bar-hit-min (44px), min width --jo-year-bar-pill-min-width-compact (58px), type --jo-year-bar-pill-font-size-compact (12.5px), a --jo-year-bar-pill-hit-border (6px) transparent border with background-clip: padding-box, and radius --jo-year-bar-pill-radius-compact (999px). The year gap is --jo-year-bar-year-gap-compact (0px). The strip padding is --jo-year-bar-year-padding-inline-compact (8px), with an --jo-year-bar-year-fade (18px) edge mask. The selected year is centred in the strip. Arrow keys, Home, and End still move the roving tab stop.

Structure

  • src: runtime component, model, view-model, types, and companion tests.
  • stories: Storybook stories for 5Y, 10Y, All, clearable, today marked when nothing is selected, host data and aria attributes, an unpinned bar, host labels, no pills, omitted years, dark, mobile (375px), and tablet (768px), each of the last two in light and dark. The today mark has light, dark, and 375px stories. Focus stories check the selected year's ring at desktop and at 375px.

Publish footprint

This package publishes only:

  • dist
  • README.md

Stories and tests are excluded from the published artifact by design.

The ESM bundle imports react and react/jsx-runtime from the host. It does not bundle React's JSX runtime or call require.