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

@vectramindnpm/unified-design-system

v0.2.3

Published

Standalone, publishable UI component library + design tokens extracted from Firstpass-Neo (src/designSystem + src/components/ui), decoupled from app-specific i18n/Redux/session wiring.

Downloads

463

Readme

unified-design-system

Standalone, publishable UI component library extracted from Firstpass-Neo's src/designSystem (design tokens) and src/components/ui (29 components), with the app-specific couplings removed so it can be installed as a plain npm dependency in any React app — and, later, browsed in its own Storybook.

What's here

src/
  tokens/            ← designTokens.js → semanticTokens.js → componentTokens.js (+ index.js barrel)
  lib/utils.ts        ← cn() (clsx + tailwind-merge)
  types/view-mode.ts   ← local EViewMode enum (stand-in for the app's models/utilitiesModel)
  components/ui/       ← all 29 components — implementation only (.tsx/.ts/.css), no stories
  stories/               ← one *.stories.tsx per component, kept separate from components/ui/
  styles/
    tokens.css          ← design-token CSS custom properties (from the app's variables.css)
    shadcn-base.css      ← the shadcn-style base HSL vars a few components use
    theme.css            ← Tailwind v4 `@theme inline` bridge (auto-generated, see below)
    components.css        ← custom classNames the components render beyond tokens/Tailwind
    icon-font.css           ← the `fp-neo` icon font (icon-neo-*) — see below
    fonts.css                ← Inter variable-font @font-face
    fonts/                     ← the actual font files (fp-neo.*, Inter .woff2)
    index.css                 ← styles.css entry: imports all of the above
  index.ts             ← the package's public barrel export
tailwind-tokens.cjs   ← flat Tailwind v3 color/radius/spacing map (from the app's tailwind.tokens.cjs)
tailwind-preset.js     ← Tailwind v3 preset for consumers still on Tailwind v3

The Rsbuild app in the rest of this repo (src/App.tsx, .storybook/) is a local dev harness / smoke test for the same src/ — it is not what gets published.

What changed vs. the original app code

Per docs/design-system-npm-storybook-guide.md's Phase 1 decoupling, plus a few couplings that guide's scan didn't catch:

  • semanticTokens/designTokens/componentTokens — every ui/* file that imported these through ../../helpers/helpersIndex now imports directly from ../../tokens.
  • i18n removed entirely (per explicit request, not just made optional) — icon.tsx, buttonIcon.tsx, delete-action.tsx no longer import react-i18next or getTranslationKey. Tooltip/aria labels fall back to a static string (the icon name, or a hardcoded label like "Delete"/"Refresh") instead of a translation lookup. If you need i18n later, wrap the exported components in your own thin wrapper that passes a translated tooltipText prop — every component that shows a label already accepts one.
  • view-toggle.tsx — dropped getSessionStateKey/getViewType (app's internaljscontrols.ts) and useSelector (Redux). ViewToggle is now a controlled component (viewMode + onViewModeChange props); useViewMode() is a plain useState + localStorage hook with no Redux/app dependency.
  • truncated-text.tsx — same Redux/session coupling removed; takes a viewMode prop instead of reading it from the app's store.
  • toast.tsx — the Toast component itself was already prop-driven. Its useToast() hook (which dispatched into the app's own Redux alert store) was app-specific state wiring, not a reusable UI primitive, so it's not included — drive <Toast onClose={...} /> from your own state instead.
  • loader.tsx — dropped the useSelector read of the app's Layout.loading Redux state; Loader now just uses its own isLoading prop. The loading GIF is bundled into the package (inlined as a data URI at build time) instead of importing from the app's src/icons/.
  • time-picker.scsstime-picker.css — the original Sass file was nested under the app's #fpneo Tailwind-important selector (would never match elsewhere) and used Sass-only nesting. Flattened to plain, framework-agnostic CSS with its @apply utilities inlined as literal styles, so it needs no Sass or Tailwind build step at all.
  • models/utilitiesModel.EViewMode → copied locally as types/view-mode.ts (it was a two-value enum with no other app dependency).
  • A couple of components (dropdown-menu.tsx, popover.tsx, tooltip.tsx, expandable-search.tsx, table.tsx) needed small, behavior-preserving TypeScript fixes (a stricter useRef/RefObject generic, a type-only import, JSX.ElementReact.JSX.Element) to type-check as an isolated package — same runtime behavior, just satisfies a standalone tsup build.

Untouched: all component markup, class names, and visual variants — this was a decoupling pass, not a redesign.

Component-level styles (beyond tokens)

Several components render classNames that are neither Tailwind utilities nor covered by tokens.css/shadcn-base.css — plain custom classes like .app-card, .switch-pill, .view-toggle__btn, .dropdownHead, .item-table__head, .fp-loader-overlay, and the icon-neo-* icon font every Icon/ButtonIcon/DeleteAction/Toast/TimePicker glyph depends on. These lived in the source app's src/styles/common.scss, mainstyles.css, and font-icons.css — SCSS nested under that app's #fpneo Tailwind-important wrapper, with @apply directives, so they'd neither match nor build here as-is.

  • src/styles/components.css — every one of those classes, flattened to plain CSS (@apply resolved to literal declarations, #fpneo/SCSS nesting stripped). Page- and business-specific rules were left out on purpose: Table's per-page *-cols grid-column layouts (.flows-cols, .appt-cols, ...) are data-shape-specific, not reusable design system material — bring your own to match your own columns. (Two of table.tsx's own variant keys, users.users-cols and devices.devices-cols, turned out to have no corresponding CSS anywhere in the source app either — likely dead/mismatched keys there already, not something introduced here.)
  • src/styles/icon-font.css + src/styles/fonts.css — the fp-neo icon font (icon-neo-*) and Inter variable font @font-face rules, copied verbatim (already plain CSS) with only their url() paths adjusted to this package's own src/styles/fonts/ copy of the four fp-neo.* files and the Inter .woff2.
  • All of the above are wired into src/styles/index.css (and so into styles.css), alongside tokens.css/shadcn-base.css/theme.css — importing that one file is enough. Each is also independently exports-mapped (./components.css, ./icon-font.css, ./fonts.css) if you'd rather cherry-pick.

Verified via npm run build-storybook and npm run build (the demo app): Rsbuild/Rspack's asset pipeline picks up and correctly hashes all five font files from the url() references in this CSS — proof the paths resolve. (No headless browser was available in this environment to capture an actual rendered screenshot; that's the one thing not independently confirmed visually.)

Button variant redesign

Button originally had 19 variant values conflating four unrelated things: color/ emphasis, shape (pill vs. rounded), fixed footer sizing, and one-off page-specific skins. Redesigned into independent, composable axes instead:

variant: "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link"   // 6 semantic colors
        | "borderPrimary" | "deptedit" | "submenu"                               // page-context presets
        | "cancel" | "save" | "close"
        | "formButtonSubmit" | "formButtonCancel" | "formButtonBack"
size:    "default" | "sm" | "lg" | "icon" | "iconLg"                              // unchanged
shape?:  "default" | "pill"                                                       // NEW
selected?: boolean                                                                // unchanged

Originally shipped (v0.1.x) with only the 6 semantic colors — the 9 "preset" variants below were deliberately left out, on the theory that fixed page-context styling (specific sizing + a one-off color skin) belonged in a consumer's own className, not the shared component. That held up fine in isolation, but once a real consumer (Firstpass-Neo) migrated ~90 call sites, the same className string ended up duplicated across dozens of files with no single place to update it. Re-added in v0.2.0 as named variants for exactly that reason — each is a self-contained preset (see PRESET_BASE/PRESET_OVERRIDE in button.tsx) that doesn't interact with shape/selected at all, so it's safe to combine with them if you ever need to (you generally won't - just use the variant alone).

Mapping from the original 19 to the current API:

| Old variant | Current equivalent | Note | |---|---|---| | default (purple) and primary (blue) | variant="primary" | Consolidated to one color — the token-driven interactive-brand (purple), not the hardcoded blue. The two hex values are ~identical anyway (#5D62B5 vs #5c62bc), so this is not a visible change. | | secondary, destructive, outline, ghost | same name, unchanged | No visual change. | | lang | variant="ghost" | One-off page skin, not worth a dedicated preset for its single call site — the only difference from ghost was a missing hover effect, not worth preserving. | | pill | shape="pill" (with any variant) | Combine, e.g. variant="outline" shape="pill". | | borderPrimary, deptedit, submenu, cancel, save, close, formButtonSubmit, formButtonCancel, formButtonBack | same name, now a real variant again | Just variant="cancel" etc. — no shape/className needed, each preset is fully self-contained. deptedit no longer includes the legacy dept-edit-btn className-sniffing hack (pass the variant explicitly). | | refreshIcon | removed — use <ButtonIcon variant="refresh" /> | That's already ButtonIcon's job; Button doesn't need to duplicate it. | | selectedIcon | removed — use <ButtonIcon variant="filled" /> or <Button variant="primary" size="icon"> | Same reasoning. |

link is genuinely new (no old equivalent) — a text-only, underline-on-hover variant with no padding/height, the one common pattern the original 19 never actually had.

Using it in this repo right now

import { Button, Badge, Card } from './index'; // see src/App.tsx for a working example

Building the package

npm run build:lib     # tsup → dist/{index.cjs,index.mjs,index.d.ts} + dist/*.css

This has been verified end-to-end: npm run build:lib succeeds, npm pack produces a clean ~390 KB tarball, and installing that tarball into a scratch project and require()-ing it resolves all exports correctly (with react, react-dom, and formik installed as peers).

Installing it in another app

npm install /path/to/unified-design-system-0.1.0.tgz   # or from your registry once published
import { Button, Icon, Table } from 'unified-design-system';

Peer dependencies you must have installed: react, react-dom, and formik (4 of the 29 components — FormField, Input with as="field", Textarea with as="field", ScrollToFieldError — call real Formik APIs at runtime, so it's a required peer, not optional).

Styling — pick one based on your Tailwind version:

  • Tailwind v4: @import "unified-design-system/styles.css"; in your global CSS, alongside your own @import "tailwindcss";. This one import already includes the icon font, component classes, and tokens.
  • Tailwind v3: add presets: [require("unified-design-system/tailwind-preset")] to your tailwind.config.js, and @import "unified-design-system/tokens.css"; + @import "unified-design-system/shadcn-base.css"; + @import "unified-design-system/components.css";
    • @import "unified-design-system/icon-font.css"; + @import "unified-design-system/fonts.css"; in your global CSS.
  • No Tailwind: same four @imports as above (skip tokens.css/shadcn-base.css only if you don't need the CSS-variable-driven components) — most components read CSS custom properties (var(--color-...)) rather than Tailwind utility classes.

Any icon (<Icon name="icon-neo-trash" />, etc.) needs icon-font.css loaded — without it the glyph classes resolve to nothing and icons render blank.

Publishing (once you're ready)

  1. npm run build:lib
  2. npm version patch|minor|major
  3. npm publish (add --registry for a private/internal registry)
  4. Tag the release in git.

This package has no consumers yet, so no versioning discipline is enforced — once something depends on it, breaking changes need a major bump.

Storybook

Set up via the official installer (npm create storybook@latest, framework auto-detected as react-rsbuild), which added @storybook/addon-a11y and @storybook/addon-docs to .storybook/main.ts and to devDependencies.

The generic Storybook onboarding tutorial (the original src/stories/ — its demo Button/Header/Page components, their .css, Configure.mdx, and the screenshot/icon images under assets/) was removed entirely; it wasn't part of this design system. That same src/stories/ path has since been repurposed to hold this design system's own stories instead (see below) — a fresh, unrelated folder with the same name.

Every one of the 29 components has its own *.stories.tsx in src/stories/ — kept in a separate folder from src/components/ui/ (which holds only .tsx/.ts/.css implementation files, no stories), and importing from there (import { Button } from "../components/ui/button"). .storybook/main.ts's ../src/**/*.stories.tsx glob picks them up regardless of where under src/ they live, so this split needed no config change. Each story has a Default plus variant/state stories and, for the compound components (Dialog, DropdownMenu, Popover, Tooltip, Table, AppList, TimePicker, Toast, ViewToggle, ...), a composed working example; components with a real cva variant/size enum (Button, Badge, Icon, ButtonIcon, Checkbox, RadioButton, Tab) also get an AllVariants/AllColors/AllSizes-style story enumerating every value from their actual source, cross-checked against it rather than sampled. Every meta also carries tags: ["autodocs"], a one-line parameters.docs.description.component explaining what the component is/isn't (e.g. Icon vs. ButtonIcon, Popover/Tooltip/DropdownMenu being hand-built rather than Radix-based), and a layout parameter ("centered" for atomic controls, "padded" for larger/compound ones) so each renders sensibly on its own Docs page.

One correctness fix worth knowing about: .storybook/preview.tsx originally imported src/styles/index.css directly, which never triggers Tailwind itself — same as src/App.css needing its own @import 'tailwindcss'; alongside the design-system styles. Without it, Storybook's build silently produced zero Tailwind-generated utility classes (bg-interactive-brand, rounded-md, inline-flex, ...), so every utility-class-driven style rendered as if unstyled even though the underlying CSS variables were present. Fixed via .storybook/preview.css (@import 'tailwindcss';

  • the design-system styles) — verified by diffing Storybook's compiled CSS against the demo app's byte-for-byte, not just a clean build.

Verified working: npm run build-storybook produces a static build with exactly 29 autodocs pages (one per component) alongside 106 individual story variants — confirmed by inspecting the build's index.json, not just a successful exit code. npm run storybook boots and serves at http://localhost:6006/. npm run lint reports zero errors across the story files.

npm run storybook        # dev server
npm run build-storybook   # static build, e.g. for Chromatic/GitHub Pages