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-shadow

v2.1.0

Published

Publishable skeleton ("shadow") loader. Renders grey placeholder blocks shaped like the content that is loading, so layouts stay stable while data is fetched in the background.

Readme

@net-advantage/jo-ui-shadow

Publishable skeleton ("shadow") loader. Renders grey placeholder blocks shaped like the content that is loading, so layouts stay stable while data is fetched in the background.

{loading ? <Shadow variant="cards" count={6} /> : <CardList items={items} ... />}
{loading ? <JourneyDetailsShadow count={4} /> : <JourneyDetails ... />}
  • Variants: cards (matches jo-ui-card-list), grid (header plus rows), form (label/input fields plus actions). New variants are added in the model without changing the view.
  • Journey details: JourneyDetailsShadow holds a plain Journey Details shape: a row of moment cards above a thin year bar, then empty panel rectangles. Below 720px the panels stack. From 720px note and story sit together and the side column spans below. From 1024px there are three columns (note, story, calendar). From 1500px the route map is a fourth column. It does not draw the shell header.
  • Animation: shimmer (default) uses one fixed gradient so a single light sweep crosses every block together; pulse fades blocks with a staggered wave; none is static. Groups fade in with a staggered entrance. All motion stops under prefers-reduced-motion.
  • Accessibility: the root is a polite status region with aria-busy and a visually hidden label (Loading on Shadow, Loading journey on JourneyDetailsShadow). The placeholder layout is aria-hidden.
  • Theming: colors are CSS variables with fallbacks, and .theme-dark overrides. The built stylesheet stays scoped to jo-ui-shadow classes. It does not emit global utilities such as .flex or .block.
  • Dependencies: journey details reads year-bar, moment-card, color, and radius tokens from @net-advantage/jo-ui-theme. The dependency is workspace:*, so the published package depends on the workspace theme (1.4.5), which is the version check:dist checks. The peer stays ^1.4.4. Moment-card widths repeat 150px and 196px as fallbacks, so a 1.4.4 stylesheet still lays the rail out. The gutter is the local --jo-ui-shadow-journey-gutter (0.75rem); the theme has no spacing token for it. The 16px side inset is local for the same reason. This package does not import other components' JavaScript.

MVVM

flowchart LR
  Props["ShadowProps"] --> ViewModel["useShadowViewModel"]
  Model["jo-ui-shadow.model.ts"] --> ViewModel
  ViewModel --> View["Shadow.tsx"]
  JourneyProps["JourneyDetailsShadowProps"] --> JourneyViewModel["useJourneyDetailsShadowViewModel"]
  JourneyModel["journey-details-shadow.model.ts"] --> JourneyViewModel
  JourneyViewModel --> JourneyView["JourneyDetailsShadow.tsx"]
  View --> Host["Host application"]
  JourneyView --> Host

| Layer | File | Responsibility | | --- | --- | --- | | Contract | jo-ui-shadow.types.ts | Props, variants, animations, node tree, view-model | | Contract | journey-details-shadow.types.ts | Journey details props and view-model | | Model | jo-ui-shadow.model.ts | Count defaults, class names, CSS variables, deterministic widths, per-variant node trees | | Model | journey-details-shadow.model.ts | Moment count, sticky year bar, rail label, and plain panels | | View-model | jo-ui-shadow.view-model.ts | Apply defaults and memoize the node tree | | View-model | journey-details-shadow.view-model.ts | Apply journey defaults and memoize that tree | | View | Shadow.tsx | Render the status region and the node tree | | View | JourneyDetailsShadow.tsx | Render the journey details status region |

Models

flowchart TD
  Props["ShadowProps"] --> Variant["ShadowVariant"]
  Props --> Animation["ShadowAnimation"]
  Variant --> Nodes["ShadowNode[]"]
  Nodes --> ViewModel["ShadowViewModel"]
  Animation --> ViewModel
  JourneyProps["JourneyDetailsShadowProps"] --> MomentCount["moment count"]
  JourneyProps --> Sticky["sticky year bar"]
  JourneyProps --> AddLabel["addMomentLabel"]
  MomentCount --> JourneyNodes["rail, year bar, body"]
  Sticky --> JourneyNodes
  AddLabel --> JourneyNodes
  JourneyNodes --> JourneyViewModel["JourneyDetailsShadowViewModel"]
  • count means cards, grid rows, or form fields, depending on the variant. Invalid values fall back to the variant default (4, 5, 4).
  • columns applies to grid (default 4). minCardWidth applies to cards (default 16rem). showActions={false} drops the form buttons row, for example inside a dialog that already has a footer.
  • Widths vary deterministically by position, so the skeleton looks organic but never jumps between renders.
  • JourneyDetailsShadow count is the number of moment cards. 0 is an empty rail. Any other invalid count falls back to 4. The rail is a row of empty card blocks and always ends with the Add New Moment card. addMomentLabel is that card's accessible name and defaults to Add New Moment. It is not drawn inside the block.
  • There is no shell header, title, breadcrumb, or action row.
  • The rail sits above the year bar. The year bar is one thin bar, the same height as YearBar, with nothing inside it. The rail, the year bar, and the panels share one gutter, 0.75rem (--jo-ui-shadow-journey-gutter), and a 16px side inset.
  • sticky defaults to true, matching YearBar. sticky={false} leaves the year bar in normal flow. The pinned bar uses top: var(--jo-year-bar-sticky-top, 0px) and z-index: var(--jo-year-bar-z-index, 2).
  • Moment cards use --jo-moment-card-min-width (150px) and --jo-moment-card-max-width (196px). Below 720px the panels stack: note, story, then the side column. From 720px note and story are side by side and the side column spans underneath. From 1024px the columns are note 240px, story, and calendar 272px. From 1500px they are note 280px, story, route map, and calendar 300px. Each panel is an empty rectangle.

Structure

  • src: runtime component, model, view-model, types, and companion tests.
  • stories: Storybook stories for documentation and visual testing.

Publish footprint

This package publishes only:

  • dist
  • README.md

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