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

@divtech/odyssey-ui

v0.7.3

Published

Odyssey design system — React + Tailwind + shadcn components

Readme

@divtech/odyssey-ui

Odyssey design system — React 19 + Tailwind v4 + shadcn-style components, published publicly to npmjs.org.

Quick start

pnpm install
pnpm storybook     # http://localhost:6006

Scripts

| Command | What it does | | ---------------------- | -------------------------------------------------------- | | pnpm storybook | Storybook dev server with a light/dark toolbar toggle | | pnpm build | Tokens → dist/ (ESM + CJS + .d.ts + styles.css) | | pnpm tokens | Regenerate src/styles/tokens.css from tokens/*.json | | pnpm icons | Regenerate src/icons/ from the Foxic API | | pnpm typecheck | tsc --noEmit | | pnpm build-storybook | Static Storybook into storybook-static/ |

Layout

One folder per component. Everything a component owns lives beside it:

src/
├── components/
│   └── button/
│       ├── button.tsx
│       ├── button.stories.tsx
│       └── index.ts
├── icons/          generated — 301 Foxic icons
├── styles/         tokens.css (generated) + theme.css (hand-owned)
├── hooks/          ThemeProvider / useTheme
└── lib/utils.ts    cn()

Figma tokens

tokens/ holds one file per Figma mode, exported straight from Figma's Export variables. Currently four, 169 variables each:

Light Desktop.tokens.json   Light Mobile.tokens.json
Dark Desktop.tokens.json    Dark Mobile.tokens.json

pnpm tokens runs scripts/build-tokens.mjs and regenerates src/styles/tokens.css. Mode names come from $extensions["com.figma.modeName"] and split on two axes — /dark/i → theme, /mobile/i → breakpoint. Light + desktop is the base; every other mode is emitted as a diff, so it is obvious which tokens are actually mode-specific:

:root                     { /* light desktop, all 169 */ }
.dark                     { /* only what dark changes */ }
@media (max-width: 767px) { /* only what mobile changes */ }

Token paths become kebab-case --od-* variables: color/Control/Primary/BgHover--od-color-control-primary-bg-hover.

The script also handles generic DTCG, Tokens Studio, and the Figma REST /variables/local response, in case the export route ever changes.

⚠️ Dark mode is not populated in Figma

The two Dark * exports contain byte-for-byte the same colour values as the light onescolor.Bg.Default is #FFFFFF in the dark mode too. So .dark is currently empty and dark mode renders identically to light. Nothing in the code needs to change when this is fixed: assign real values to the dark mode in Figma, re-export over tokens/, and run pnpm tokens. The generator prints a warning while this is the case.

Three layers

| Layer | File | Owner | | ------------------------ | ----------------------- | --------- | | Figma primitives --od-* | src/styles/tokens.css | generated | | Semantic names | src/styles/theme.css | hand-owned | | Tailwind utilities | @theme inline in theme.css | hand-owned |

Components consume semantic names only (bg-primary, hover:bg-primary-hover, px-200) — never --od-* directly. That indirection is what lets a Figma re-export restyle the library without touching component code.

Figma's space/* and rounding/* scales are exposed as Tailwind utilities: px-200 → 16px, gap-150 → 12px, rounded-lg → 12px, rounded-full → 200px. See the Button → Tokens story for a live swatch of the whole set.

Theming

Dark mode is a .dark class on an ancestor (usually <html>). Either drive it yourself or use the bundled provider:

import { ThemeProvider, useTheme } from "@divtech/odyssey-ui";
import "@divtech/odyssey-ui/styles.css";

<ThemeProvider defaultTheme="system">
  <App />
</ThemeProvider>;

Icons

src/icons/ is generated from the public Foxic project odyssey (301 icons, v0.0.2) by pnpm icons. The output is committed, so builds and CI never depend on the Foxic API being up — re-run only when the icon set changes. Re-runs are cheap: each file records its icon's checksum and unchanged icons are not refetched.

The generator fixes two things about the source SVGs: it injects the viewBox (which only exists in the API metadata, not the markup) and rewrites baked hex fills to currentColor, so icons inherit text colour.

Icons ship as a separate entry point, because tsup bundles each entry into one file and folding 301 components into the main barrel would defeat tree-shaking:

import { CheckOutlineIcon } from "@divtech/odyssey-ui/icons";

<CheckOutlineIcon />              {/* native size: 16, 20 or 24 per icon */}
<CheckOutlineIcon size={32} />
<CheckOutlineIcon className="size-6 text-tone-alert" />

Fonts

The design uses TT Commons Pro (TypeType, commercial). The library declares it in --font-sans but ships no font files — drop your licensed webfonts into src/fonts/ and run pnpm fonts, which generates src/styles/fonts.css.

The generator reads weight and style off the filename (*-DemiBold.woff2 → 600, *-MediumItalic.woff2 → 500 italic) and merges multiple formats of one face into a single @font-face with woff2 first. A *-Variable.woff2 wins outright: the static faces are skipped so the browser never downloads both. See src/fonts/README.md for the full table.

Without the files everything still builds — fonts.css comes out empty and --font-sans falls through to the system UI stack. Layout stays correct; only the typeface is wrong.

The published package carries no fonts — by design

src/fonts/* is gitignored and src/styles/index.css does not import the generated fonts.css, so neither the repo nor the npm tarball ever contains a woff2. On a public registry that is not optional: redistributing TT Commons Pro would breach its licence. Consumers self-host the face and declare it themselves — see Consuming → Fonts.

Storybook loads src/styles/fonts.css directly, so local dev is unaffected.

Still open: font-display: swap will flash the fallback before the webfont lands. Fixing it properly needs a size-adjust/ascent-override fallback face computed from the real font metrics — worth doing once the files are in place.

Adding a component

  1. src/components/<name>/<name>.tsxcva for variants, cn for class merging, matching button.tsx.
  2. src/components/<name>/<name>.stories.tsx — include a Variants story so light/dark is easy to eyeball.
  3. src/components/<name>/index.ts — re-export the public surface.
  4. Export it from src/index.ts.

Pulling one from upstream shadcn works too: copy the source in, then swap @radix-ui/react-* imports for the unified radix-ui package this repo uses.

Two gotchas

  • Never hardcode a hex or a raw --od-*. Use a semantic name from theme.css. If the role you need doesn't exist, add it there rather than reaching past it.
  • New scales must be registered with tailwind-merge in src/lib/utils.ts. It cannot tell text-body (a font size) from text-tone-white (a colour) on its own — it groups both as text-<unknown> and lets the last one win, which silently deletes the colour.

Publishing

Published publicly to npmjs.org as @divtech/odyssey-ui. The repo carries no .npmrc — it is gitignored, so a stray token can never be committed; the publish credential is supplied per machine or by CI.

One-time setup on npmjs.org:

  1. Create the divtech organization (npmjs.com → Add organization → Free). The free plan allows unlimited public packages; it is what makes the @divtech scope yours. The plain odyssey-ui name was not available — an unrelated package has held it since v0.0.5 — which is why the package is scoped.
  2. Generate a granular access token (Access Tokens → Generate → Granular) with read/write on the @divtech scope, and tick Bypass two-factor authentication. That checkbox defaults to off, and without it the publish dies on E403 … two-factor authentication or granular access token with bypass 2fa enabled is required — the token authenticates fine (npm whoami succeeds), it just cannot publish. The setting cannot be added later, so a token created without it has to be replaced. Classic Automation tokens used to work here; npm removed classic tokens in November 2025, so granular is the only option now.
  3. Store it in GitLab as the CI/CD variable NPM_TOKEN, masked. Leave Protected unchecked unless Settings → Repository → Protected tags actually carries a v* pattern — otherwise the variable never reaches the tag pipeline and the job fails on the test -n "$NPM_TOKEN" guard instead.

publishConfig.access is public — without it npm defaults a scoped package to restricted and refuses to publish on a free account.

Releases run off tags:

npm version patch && git push --follow-tags

The publish job fires on any v*.*.* tag and pushes to npmjs.org.

To publish by hand instead, npm login once, then pnpm publish.

Consuming

Public package on the default registry — nothing to configure:

pnpm add @divtech/odyssey-ui

Only if a project routes installs through a private proxy (Nexus, Verdaccio) does it need one line in its .npmrc, so the scope skips the proxy:

@divtech:registry=https://registry.npmjs.org/

Then in the app root:

import "@divtech/odyssey-ui/styles.css";
import { Button } from "@divtech/odyssey-ui";
import { IconChevronDown } from "@divtech/odyssey-ui/icons";

styles.css is prebuilt by Tailwind here, so consumers do not add an @source for node_modules/@divtech/odyssey-ui. Peer deps are just react and react-dom (18 or 19).

Fonts

The package ships no font files. TT Commons Pro is a commercial TypeType face and putting the woff2 files in a public tarball would be redistribution, which its licence does not permit. dist/styles.css therefore carries no @font-face — until the host app supplies one, components render in the fallback stack.

Apps with a licence declare the two families themselves, under exactly these names:

@font-face {
  font-family: "TT Commons Pro";          /* weights 450, 500, 600 */
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/TT_Commons_Pro_Medium.woff2") format("woff2");
}
@font-face {
  font-family: "TT Commons Pro Expanded"; /* weight 600 — Avatar initials only */
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/TT_Commons_Pro_Expanded_DemiBold.woff2") format("woff2");
}

The names are what --font-sans and --font-initials ask for in theme.css; getting them wrong is a silent fallback, not an error. Inside this repo the faces still load — Storybook imports src/styles/fonts.css directly, which index.css no longer pulls in.

License

MIT — see LICENSE.

That covers the code in this repository only. TT Commons Pro is not ours to license: it is a commercial TypeType face, no part of it ships here, and using it in your app needs a licence bought from TypeType. The MIT grant on this package confers no right to the typeface.