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

@violet-sorrengail/ui

v0.1.3

Published

Company standard React UI framework: application shell, layout and core components.

Readme

@violet-sorrengail/ui

The company's standard React UI framework. It provides the application shell — header, sidebar, navigation, page layout — plus the core components every product needs, so new applications start with the company look and spend their code on business logic.

import { AppShell, Page, Button, DataTable } from '@violet-sorrengail/ui'
import '@violet-sorrengail/ui/styles.css'

function App() {
  return (
    <AppShell brand={{ title: 'Quote Manager' }} nav={navigation} activePath="/quotes">
      <Page title="Quote History" actions={<Button>Create Quote</Button>}>
        <DataTable columns={columns} rows={rows} />
      </Page>
    </AppShell>
  )
}

Repository layout

src/lib/          The published package (@violet-sorrengail/ui)
  components/     One folder per component: .tsx + scoped .css + barrel
  styles/         Design tokens and the scoped reset
  theme/          ThemeProvider and token overrides
  hooks/          Shared behaviour hooks
  types.ts        NavItem / NavSection / LinkComponent / BrandConfig
  index.ts        The package's public surface — nothing else is exported
src/playground/   Demo application, written exactly like a consuming product

The playground is not published. It imports @violet-sorrengail/ui by its real specifier (aliased to source in vite.config.ts), so anything that works there works for a consumer.

Application integration

The published package owns the reusable shell and components. Each product owns its routes, labels, logo assets, permissions and business screens. A product can render the ready-made template immediately:

import { AppShell } from '@violet-sorrengail/ui'
import '@violet-sorrengail/ui/styles.css'
import type { ReactNode } from 'react'

export function ProductShell({ children }: { children: ReactNode }) {
  return <AppShell>{children}</AppShell>
}

This includes the template logo, sidebar navigation, About action, and account dropdown. A product then supplies its own logo, navigation and labels through props, while the header actions stay the template's. A product normally creates one small wrapper around AppShell and uses it in every route:

import { AppShell, type NavItem } from '@violet-sorrengail/ui'
import { useLocation } from 'react-router-dom'
import type { ReactNode } from 'react'
import logo from './assets/product-logo.svg'

const navigation: NavItem[] = [
  { key: 'home', label: 'Home', icon: 'dashboard', href: '/' },
  { key: 'reports', label: 'Reports', icon: 'table', href: '/reports' },
]

export function ProductShell({ children }: { children: ReactNode }) {
  const { pathname } = useLocation()

  return (
    <AppShell
      brand={{ logo: <img src={logo} alt="Product" />, title: 'Product', href: '/' }}
      nav={navigation}
      activePath={pathname}
    >
      {children}
    </AppShell>
  )
}

The framework does not hard-code a product's navigation or branding: replace the navigation data, brand.logo and brand.title per application, and the defaults apply only where a prop is omitted. Supply linkComponent when using a client-side router; otherwise the shell uses normal anchors. Keep product-specific code outside src/lib so the package remains reusable.

The header actions are fixed

The header actions are the one part of the template a product does not own. The About link and the account dropdown are always rendered by the framework, in the same order and the same style, so every company application presents the same affordances. A product supplies their data through headerConfig — never their markup:

<AppShell
  brand={{ title: 'Quote Manager', logo: <img src={logo} alt="Quote Manager" /> }}
  nav={[
    { key: 'dashboard', label: 'Dashboard', icon: 'dashboard', href: '/' },
    { key: 'quotes', label: 'Quotes', icon: 'table', href: '/quotes' },
  ]}
  headerConfig={{
    about: { label: 'Help', href: '/help' },
    account: {
      name: 'Jordan Smith',
      email: '[email protected]',
      items: [
        { key: 'settings', label: 'Settings', href: '/settings' },
        { key: 'signout', label: 'Sign out', tone: 'danger' },
      ],
    },
  }}
>
  {children}
</AppShell>

Every field is optional. Omit headerConfig entirely and the template renders About plus the placeholder account.

| headerConfig field | Effect | | -------------------- | ------ | | updates | Adds the "Latest Updates" action ahead of About. Omitted by default; {} accepts every default (label, /updates, green status dot). | | about | Overrides the About label and href. null removes the action. | | account.name / .email / .avatarSrc | The signed-in user, shown in the avatar and the menu's summary row. | | account.items | Replaces the menu entries below the summary row. The summary row itself is always drawn by the template. | | account | null removes the dropdown — for an unauthenticated shell. | | extraActions | Product-specific affordances, appended after About and before the account menu. |

Product affordances are added, not substituted:

<AppShell
  headerConfig={{
    account: { name: 'Jordan Smith' },
    extraActions: (
      <HeaderAction icon={<Icon name="table" />} href="/exports">
        Exports
      </HeaderAction>
    ),
  }}
/>

There is no headerActions prop: a product cannot replace the standard actions with its own. A screen that is genuinely not an application shell — a login page, an embedded view — opts out of the header as a whole with header={<MyHeader />}.

The footer

The shell closes the content column with a quiet footer: the copyright line on the left, a short link list on the right. It is on by default with the template's copyright and Privacy / Terms of Use / Support links, and takes its content from footerConfig.

<AppShell
  footerConfig={{
    copyright: '© 2026 IGT. All rights reserved.',
    links: [
      { key: 'privacy', label: 'Privacy', href: '/privacy' },
      { key: 'status', label: 'Status', href: 'https://status.example.com', external: true },
    ],
  }}
>
  {children}
</AppShell>

| footerConfig field | Effect | | -------------------- | ------ | | copyright | The left-hand line. Defaults to the current year and the company name. | | links | The right-hand links. href routes through linkComponent; external opens a new tab and adds the external-link glyph. Pass [] for none. |

The two fields merge over the defaults independently, so naming only copyright keeps the standard links. hideFooter drops the footer for embedded or full-bleed screens, and footer={<MyFooter />} replaces it outright.

Clear the page margin

The framework's reset is scoped to .cui-root so it never fights a host stylesheet, which leaves the page itself carrying the browser default body { margin: 8px }. AppShell is a full-viewport layout with a viewport-fixed header, so that margin shows as a white gutter down the sides and under the footer. Applications without their own reset add:

html,
body {
  margin: 0;
}

#root {
  min-height: 100vh;
}

Scripts

| Command | What it does | | -------------------------- | --------------------------------------------------------- | | npm run dev | Runs the playground with HMR on the framework source | | npm run build | Builds the package: dist/company-ui.js + .css + types | | npm run build:playground | Builds the demo app into dist-playground/ | | npm run typecheck | Type-checks framework and playground | | npm run lint | oxlint |

Consuming the framework

npm install @violet-sorrengail/ui

React 18 or 19 is a peer dependency; the package ships ES modules and one stylesheet.

import '@violet-sorrengail/ui/styles.css' // once, at the application entry point

1. Describe your navigation

Navigation is plain data, so it can come from config, a feature flag, or an API.

import type { NavSection } from '@violet-sorrengail/ui'

const navigation: NavSection[] = [
  {
    key: 'workspace',
    items: [
      { key: 'home', label: 'Dashboard', icon: 'dashboard', href: '/' },
      { key: 'quotes', label: 'Quotes', icon: 'table', href: '/quotes' },
      {
        key: 'labs',
        label: 'Engineering Labs',
        icon: 'flask',
        items: [{ key: 'reports', label: 'Reports', href: '/labs/reports' }],
      },
    ],
  },
  {
    key: 'admin',
    label: 'Administration',
    items: [
      { key: 'roles', label: 'Roles & Permissions', icon: 'userCheck', tone: 'accent', href: '/roles' },
    ],
  },
]

Sections are separated by a rule in the sidebar and may carry a label. Pass a flat NavItem[] instead if you don't need groups. tone: 'accent' renders the icon in the brand accent colour.

2. Mount the shell

import { AppShell } from '@violet-sorrengail/ui'
import { Link, useLocation } from 'react-router-dom'

const RouterLink = ({ href, ...rest }) => <Link to={href} {...rest} />

export function Shell({ children }) {
  const { pathname } = useLocation()

  return (
    <AppShell
      brand={{ logo: <img src={logo} alt="Company" />, title: 'Quote Manager', href: '/' }}
      nav={navigation}
      activePath={pathname}
      linkComponent={RouterLink}
      headerConfig={{
        updates: { href: '/updates' },
        account: { name: 'Jordan Smith', email: '[email protected]' },
      }}
      sidebarFooter={<span>v1.4.0</span>}
    >
      {children}
    </AppShell>
  )
}

The header actions come from the template — see The header actions are fixed. An application names its user and its links; it does not rebuild them.

The shell is router-agnostic. Pass linkComponent and every link the framework renders (sidebar, breadcrumbs, cards, header actions) goes through your router. Without it, links fall back to plain anchors — or supply onNavigate and the shell will intercept clicks and hand them to you.

useAppShell() exposes shell state (collapsed, toggleCollapsed, isMobile, …) anywhere inside the shell.

3. Build screens

<Page
  breadcrumbs={[{ label: 'Home', href: '/' }, { label: 'Quote History' }]}
  title="Quote History"
  subtitle="Every quote raised by your team."
  actions={<Button leftIcon={<Icon name="plus" size={16} />}>Create Quote</Button>}
  toolbar={<Input placeholder="Search" leftIcon={<Icon name="search" size={16} />} />}
>
  <DataTable
    columns={columns}
    rows={rows}
    rowKey={(row) => row.id}
    defaultSort={{ key: 'updated', direction: 'desc' }}
    pageSize={20}
    striped
  />
</Page>

Page also supports width (default | narrow | full), align="center" for hero screens, and bleed to drop the canvas padding.

Components

| Area | Exports | | --------- | -------------------------------------------------------------- | | Shell | AppShell, Header, HeaderAction, HeaderSeparator, Sidebar, Footer, useAppShell | | Layout | Page, PageSection, CardGrid | | Content | Card, DataTable, Badge, Avatar, Icon | | Controls | Button, Input, Dropdown, Spinner | | Theming | ThemeProvider, themeToStyle | | Hooks | useMediaQuery, useControllableState |

Theming

Every visual decision resolves to a CSS custom property (--cui-*) declared in src/lib/styles/tokens.css. Products theme the framework by overriding tokens — never by targeting internal class names, which are not a public API.

<ThemeProvider theme={{ brand: '#004b8d', accent: '#f5a623', sidebarWidth: '280px' }}>
  <App />
</ThemeProvider>

or globally:

:root {
  --cui-color-brand: #004b8d;
  --cui-color-accent: #f5a623;
}

Defaults come from the reference design: brand #0055b9, sidebar #133366, accent #ff7600, canvas #f5f6fa.

Behaviour built in

  • Collapsible sidebar — orange edge toggle, icon-only rail with tooltips, auto-expands when a nested route is active.
  • Responsive — below 900px the sidebar becomes a drawer opened by the header hamburger, and closes on navigation.
  • Accessible — landmark regions, a skip link, aria-current on the active nav item, aria-sort on sortable columns, keyboard-operable rows and menus, and prefers-reduced-motion support.
  • Scoped styles — the reset applies to .cui-root, so the framework can be dropped into an existing application without fighting its stylesheet.

Changing the logo

Brand artwork lives in src/playground/assets/ and is listed in one file, src/playground/brand.ts:

export const BRAND_MARKS: BrandMark[] = [
  { src: igtEveriLogo, alt: 'IGT and Everi', height: 36 },
]

To swap a logo, replace the file in assets/ keeping the same name — no code change at all — or point src at a new file. Add or remove an entry and the header lockup follows, dividers included. height is the rendered height; the width follows the artwork's own aspect ratio.

igt-everi-logo.png is the combined IGT + Everi lockup, so it is a single mark rather than two — it draws its own rule between the companies.

Dark surfaces

The header uses the brand token as its background, and logo artwork sits directly on that surface. The bundled lockup is the reversed (white) artwork drawn for exactly that, which is why it is not legible on a light surface — a login screen or printed view would need a full-colour export.

Applications theming the shell should likewise supply a logo with enough contrast against their own brand token:

<AppShell brand={{ logo: <img src={logoOnBrand} alt="Product" />, title: 'Product' }} />

Adding a component

  1. Create src/lib/components/MyThing/ with MyThing.tsx, myThing.css, and index.ts.
  2. Style only with --cui-* tokens, and prefix class names cui-my-thing__….
  3. Re-export from src/lib/index.ts — if it isn't exported there, it isn't part of the package.

Publishing

npm run build emits dist/company-ui.js, dist/company-ui.css and dist/types/. package.json already declares the exports map, files, and React as a peer dependency; point publishConfig.registry at the internal registry and npm publish when the package name is claimed.