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

@socprime/react-ui

v0.0.19

Published

SOC Prime React UI component library and Storybook design system.

Readme

@socprime/react-ui

React component library for SOC Prime products. Ships pre-built ESM modules with TypeScript definitions: generic UI primitives built on Radix UI, CVA and Tailwind CSS, plus a set of SOC Prime domain components behind separate entry points.

Requirements

| Requirement | Notes | | --- | --- | | React 19 | react and react-dom are peer dependencies | | Tailwind CSS v4 | The library ships class names, not compiled utilities — your Tailwind build generates them | | ESM-capable bundler | Vite, webpack 5, Next.js, Rollup, esbuild. The package is ESM-only; require() is not supported | | TypeScript 5 (optional) | moduleResolution must be bundler or node16 | | Node.js 20+ | Only for the build/dev toolchain of your app |

Installation

npm install @socprime/react-ui
# or
yarn add @socprime/react-ui

Install the required peer dependencies alongside it:

npm install react react-dom react-router-dom @tanstack/react-table

Optional peers are only needed for the corresponding subpath — see Peer dependencies.

Quick start

Adopting the library takes three steps. Skipping any of them produces components that render but look broken.

1. Add the theme

The library styles itself entirely through CSS custom properties and Tailwind theme tokens that your app must define. Copy the reference theme into your global stylesheet (for example src/index.css), then import it once at your entry point:

// src/main.tsx
import './index.css';

2. Point Tailwind at the package

Tailwind only generates utilities it can find in scanned sources. Classes like rounded-xs or bg-btn-primary exist only inside the library, so your Tailwind build must scan it:

/* src/index.css */
@import 'tailwindcss';
@source "../node_modules/@socprime/react-ui/**/*.js";

Without this line every component renders unstyled.

3. Set up the app shell

// src/App.tsx
import { BrowserRouter } from 'react-router-dom';
import { Button, Toaster, applyTheme } from '@socprime/react-ui';
import { useEffect } from 'react';

export function App() {
  // Adds the `.dark` / `.light` class and `data-theme` attribute to <html>.
  useEffect(() => applyTheme('dark'), []);

  return (
    <BrowserRouter>
      <Button variant="primary" size="m" onClick={() => console.log('clicked')}>
        Get started
      </Button>

      {/* Render once, anywhere in the tree — required for toast notifications. */}
      <Toaster />
    </BrowserRouter>
  );
}

BrowserRouter (or any React Router provider) is required if you render Aside, PageHeader, or a Button with the to prop — those use React Router internally. Everything else works without a router.

Tooltip provides its own context, so no extra provider is needed for it.

Entry points

The public API is deliberately narrow: seven entry points, no deep imports.

| Import path | Contents | Extra peers | | --- | --- | --- | | @socprime/react-ui | Generic, product-agnostic primitives | — | | @socprime/react-ui/domain | SOC Prime domain components | — | | @socprime/react-ui/editor | Monaco-based code editor | monaco-editor, @monaco-editor/react | | @socprime/react-ui/charts | Charts and statistics cards | recharts | | @socprime/react-ui/attack-flow | MITRE ATT&CK flow graph | @xyflow/react, dagre | | @socprime/react-ui/preset | Storybook config factory | @storybook/react-vite, vite, vite-plugin-svgr | | @socprime/react-ui/decorators | Storybook decorators | @storybook/react-vite |

Heavy modules live behind their own subpaths so that an app which never imports /editor does not pay for Monaco — neither in bundle size nor in installed dependencies.

import { Button, Table, Toaster } from '@socprime/react-ui';
import { Severity, type TSeverity } from '@socprime/react-ui/domain';
import { Editor } from '@socprime/react-ui/editor';
import { AreaChartWithGradient, StatisticCard } from '@socprime/react-ui/charts';
import { AttackFlow } from '@socprime/react-ui/attack-flow';

Deep imports such as @socprime/react-ui/dist/ui/Button are blocked by the exports map. This is intentional — it keeps the internal layout of the package free to change without breaking consumers.

Components

Root export

Forms Field, FieldSet, FieldGroup, FieldLabel, FieldTitle, FieldDescription, FieldContent, FieldError, FieldLegend, FieldSeparator, Input, InputNumber, InputPassword, Textarea, Checkbox, RadioGroup, RadioGroupItem, SegmentedControl, Switch, Slider, Select, SelectDefault, SelectTrigger, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectValue, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, MultiSelect, SearchInput, SearchInputFields, Suggestions, Label, LabelInfo, HelperText

Actions and overlays Button, buttonVariants, DeleteButtonConfirm, Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, DialogOverlay, DialogPortal, DialogClose, DefaultDialog, ConfirmDeleteDialog, DropdownMenu (with Trigger, Content, Item, CheckboxItem, RadioGroup, RadioItem, Label, Group, Separator, Shortcut, Sub, SubTrigger, SubContent, Portal), DropdownsCheckbox, DropdownsSelect, Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipParent, Toaster

Data display Table, TableHeader, TableHead, TableBody, TableRow, TableCell, TableFooter, TableCaption, TableWrap, SortableHeader, GroupHeaderRow, ServerGroupedTableBody, SkeletonTable, Pagination, PaginationWrap, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PaginationEllipsis, Badge, badgeVariants, EmptyState, TextTruncate

Layout and navigation Aside, PageHeader, Tabs, Accordion, AccordionItem, AccordionTrigger, AccordionContent, AccordionSection, FolderTreeExpandTrigger, ScrollArea, ScrollBar, Separator, TreeGuide, TreeRow

Feedback and state Spinner, SpinnerCustom, SpinnerSquare, AnimatedDots, Skeleton, Progress, ConditionalContent

Theming ThemeToggle, applyTheme, useDocumentTheme, ThemeName

Every component exports its props type under the <Name>Props convention — ButtonProps, TableWrapProps, and so on.

Subpath exports

| Entry point | Exports | | --- | --- | | /domain | Severity, CorrelationTimer, SyncProcessBar, TSeverity | | /editor | Editor, EditorProps, Theme | | /charts | AreaChartWithGradient, StatisticCard, AreaChartDataPoint, StatisticsGraphData, StatisticsGraphDataPoint | | /attack-flow | AttackFlow, AttackFlowProps, NodeData | | /preset | createStorybookConfig, createStorybookPreview, StorybookHostOptions | | /decorators | withTheme, withRouter, applyTheme, ThemeName |

Theme contract

This is the part that most often goes wrong on first integration. The library never ships colors of its own — it references Tailwind theme tokens and CSS custom properties by name and expects the host application to define them.

You need three things in your global stylesheet:

  1. CSS custom properties for :root, .light and .dark.
  2. A @theme inline block mapping those properties onto Tailwind color tokens.
  3. Keyframes, base styles and utilities used by Accordion, AnimatedDots, SyncProcessBar, Checkbox and TextTruncate.

The .light / .dark classes are toggled on <html> by applyTheme() and by the ThemeToggle component.

@import 'tailwindcss';
@source "../node_modules/@socprime/react-ui/**/*.js";

@custom-variant dark (&:is(.dark *));
@custom-variant light (&:is(.light *));

:root {
  --font-size: 1rem;
  --font-weight-normal: 400;
  --radius-xs: 0.3rem;
  --radius-s: 0.4rem;
  --radius-sm: 0.5rem;

  --white: #ffffff;
  --ring: #95c9b4;
  --darkGray: #6b7280;

  --btn-primary: #4ac18e;
  --btn-primary-hover: #3bac7c;
  --btn-primary-active: #329269;
  --btn-primary-disabled: #333544;

  --btn-secondary: #3b3d4f;
  --btn-secondary-hover: #50536b;
  --btn-secondary-active: #5b5e7a;
  --btn-secondary-disabled: #333544;

  --btn-destructive: #ee3523;
  --btn-destructive-hover: #dc2311;
  --btn-destructive-active: #ba1e0e;
  --btn-destructive-disabled: #333544;

  --border-primary: #95c9b4;
  --border-disabled: #d2d5d8;
  --border-critical: #fd5749;
  --border-critical-disabled: #ffa7a3;

  --bg-critical-light: #fd5749;
  --red-violet: #c32181;
}

.light {
  --btn-secondary: #cfd0d3;
  --tooltip-white: #ffffff;
  --tooltip-black: #000000;

  --bg-primary: #f9f9f9;
  --bg-secondary: #f9f9f9;
  --bg-success: #4ac18e;
  --bg-warning: #ee9d23;
  --bg-purple: #c27aff;
  --bg-light-blue: #51a2ff;
  --bg-blue: #015bbb;
  --bg-gray-chateau: #99a1af;
  --bg-comet: #54556a;
  --bg-critical: #ee3523;
  --bg-hover: #cfd0d3;
  --bg-yellow: #ffe824;

  --foreground: #292c3d;
  --border: #cfd0d3;
  --btn-ghost: #cfd0d3;
  --text-subdued: #292c3d;
  --text-default: #1a1e2f;
  --text-dark: #1a1e2f;
  --text-warning: #ee9d23;
  --text-critical: #ee3523;
  --mirisk: #99a1af;
}

.dark {
  --btn-secondary: #252838;
  --tooltip-white: #000000;
  --tooltip-black: #ffffff;

  --bg-black: #ffffff;
  --bg-primary: #1f2232;
  --bg-secondary: #252838;
  --bg-success: #4ac18e;
  --bg-warning: #ee9d23;
  --bg-purple: #c27aff;
  --bg-light-blue: #51a2ff;
  --bg-blue: #015bbb;
  --bg-gray-chateau: #99a1af;
  --bg-comet: #54556a;
  --bg-critical: #ee3523;
  --bg-hover: #3b3d4f;
  --bg-yellow: #ffe824;

  --foreground: #f9f9f9;
  --border: #3b3d4f;
  --btn-ghost: #3b3d4f;
  --text-subdued: #cfd0d3;
  --text-default: #f9f9f9;
  --text-dark: #1a1e2f;
  --text-warning: #ee9d23;
  --text-critical: #ee3523;
  --silver: #d1d5dc;
}

@theme inline {
  --color-primary: var(--bg-primary);
  --color-secondary: var(--bg-secondary);
  --color-foreground: var(--foreground);
  --color-border: var(--border);
  --color-default: var(--text-default);
  --color-subdued: var(--text-subdued);
  --color-dark: var(--text-dark);
  --color-white: var(--white);
  --color-dark-gray: var(--darkGray);
  --color-silver: var(--silver);
  --color-ring: var(--ring);
  --color-hover: var(--bg-hover);

  --color-btn-primary: var(--btn-primary);
  --color-btn-primary-hover: var(--btn-primary-hover);
  --color-btn-primary-active: var(--btn-primary-active);
  --color-btn-primary-disabled: var(--btn-primary-disabled);
  --color-btn-secondary: var(--btn-secondary);
  --color-btn-secondary-hover: var(--btn-secondary-hover);
  --color-btn-secondary-active: var(--btn-secondary-active);
  --color-btn-secondary-disabled: var(--btn-secondary-disabled);
  --color-btn-destructive: var(--btn-destructive);
  --color-btn-destructive-hover: var(--btn-destructive-hover);
  --color-btn-destructive-active: var(--btn-destructive-active);
  --color-btn-destructive-disabled: var(--btn-destructive-disabled);
  --color-btn-ghost: var(--btn-ghost);

  --color-success: var(--bg-success);
  --color-warning: var(--bg-warning);
  --color-critical: var(--bg-critical);
  --color-critical-light: var(--bg-critical-light);
  --color-purple: var(--bg-purple);
  --color-light-blue: var(--bg-light-blue);
  --color-blue: var(--bg-blue);
  --color-yellow: var(--bg-yellow);
  --color-gray-chateau: var(--bg-gray-chateau);
  --color-comet: var(--bg-comet);
  --color-red-violet: var(--red-violet);
  --color-tooltip-white: var(--tooltip-white);
  --color-tooltip-black: var(--tooltip-black);

  --text-3xs: 0.625rem;
  --text-2xs: 0.75rem;
  --text-xs: 0.875rem;
  --text-sm: 1rem;
  --text-m: 1.125rem;
  --text-l: 1.25rem;
  --text-xl: 1.375rem;
  --text-lg: 1.5rem;

  --animate-accordion-down: accordion-down 0.2s ease-out;
  --animate-accordion-up: accordion-up 0.2s ease-out;
}

@layer base {
  html,
  body {
    font-size: var(--font-size);
    font-weight: var(--font-weight-normal);
    color: var(--foreground);
    background-color: var(--bg-primary);
    scrollbar-width: thin;
    scrollbar-color: var(--border) var(--bg-primary);
  }

  input[type='checkbox'] {
    appearance: none;
    width: 1rem;
    height: 1rem;
    border-width: 1px;
    border-radius: 0.25rem;
    cursor: pointer;
    position: relative;
    flex-shrink: 0;
  }

  input[type='checkbox']:checked::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0.375rem;
    height: 0.625rem;
    border: solid var(--white);
    border-width: 0 2px 2px 0;
    transform: translate(-50%, -60%) rotate(45deg);
  }

  input[type='checkbox']:focus,
  input[type='checkbox']:focus-visible {
    outline: none;
  }
}

@keyframes accordion-down {
  from { height: 0; opacity: 0; }
  to { height: var(--radix-accordion-content-height); opacity: 1; }
}

@keyframes accordion-up {
  from { height: var(--radix-accordion-content-height); opacity: 1; }
  to { height: 0; opacity: 0; }
}

@keyframes sync-progress {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

@keyframes dot-loading-1 {
  0%, 100% { opacity: 1; }
}

@keyframes dot-loading-2 {
  0%, 33% { opacity: 0; }
  34%, 99% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes dot-loading-3 {
  0%, 66% { opacity: 0; }
  67%, 99% { opacity: 1; }
  100% { opacity: 0; }
}

@layer utilities {
  .text-default { color: var(--text-default); }
  .animate-progress {
    animation: sync-progress 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
    will-change: transform;
  }
  .animate-dot-loading-1 { animation: dot-loading-1 1.2s step-end infinite; }
  .animate-dot-loading-2 { animation: dot-loading-2 1.2s step-end infinite; }
  .animate-dot-loading-3 { animation: dot-loading-3 1.2s step-end infinite; }
  .truncate-1 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 1;
    overflow: hidden;
  }
}

The palette above is the SOC Prime default. Override any custom property to rebrand — the components read tokens only, never hard-coded colors.

Component-level CSS

Button and SpinnerSquare ship a small compiled .css file each. They are imported by the components themselves, so no manual CSS import is needed; the package declares sideEffects so bundlers keep those imports while still tree-shaking unused components.

Peer dependencies

Required for every consumer:

{
  "react": "^19.0.0",
  "react-dom": "^19.0.0",
  "react-router-dom": "^7.0.0",
  "@tanstack/react-table": "^8.21.0"
}

react-router-dom is required rather than optional: Button, PageHeader and Aside use React Router internally, and a second copy of the router in the bundle would carry its own context — links rendered by the library would not see your <BrowserRouter>.

Optional peers, needed only when you import the matching subpath:

| Package | Required for | | --- | --- | | monaco-editor, @monaco-editor/react | @socprime/react-ui/editor | | recharts | @socprime/react-ui/charts | | @xyflow/react, dagre | @socprime/react-ui/attack-flow | | @storybook/react-vite, vite, vite-plugin-svgr | @socprime/react-ui/preset, @socprime/react-ui/decorators |

Everything else (@radix-ui/*, lucide-react, class-variance-authority, clsx, tailwind-merge, motion, sonner) is a regular dependency and installs automatically.

TypeScript

Types ship with the package — there is no separate @types package. Props types are exported as named type exports next to each component:

import { Button, type ButtonProps } from '@socprime/react-ui';

type SubmitButtonProps = Omit<ButtonProps, 'variant'> & { pending: boolean };

Your tsconfig.json must use a resolution mode that understands exports:

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

With the legacy "node" resolution, subpath imports resolve to neither JavaScript nor types.

Bundler notes

Tree shaking

dist/ is built as separate ESM modules rather than one bundle, so importing { Button } from the root export does not pull in Table or Dropdown. All relative imports are fully specified (./Button/index.js), which keeps the output valid for strict resolvers such as Node ESM and webpack 5 with fullySpecified.

Monaco Editor in Vite hosts

@socprime/react-ui/editor registers Monaco workers with new Worker(new URL(specifier, import.meta.url), { type: 'module' }) rather than the Vite-specific ?worker suffix. ?worker is only understood by the full Vite pipeline; during dependency pre-bundling (optimizeDeps, which runs through bare esbuild) such imports fail with errors like Cannot read file: .../ts.worker.js?worker. The new URL(...) form is understood natively by Vite, webpack 5+ and esbuild alike.

Storybook preset

If your team maintains its own Storybook on top of this design system, the package provides a config factory and decorators.

// .storybook/main.ts
import path from 'node:path';
import { createStorybookConfig, type StorybookHostOptions } from '@socprime/react-ui/preset';

const hostRoot = '/absolute/path/to/your/app';

export default createStorybookConfig({
  hostRoot,
  hostSrcDir: path.join(hostRoot, 'src'),
  themeEntry: path.join(hostRoot, 'src/index.scss'),
  packageRoot: path.resolve(import.meta.dirname, '..'),
  appVersion: '1.0.0',
} satisfies StorybookHostOptions);
// .storybook/preview.tsx
import { createStorybookPreview } from '@socprime/react-ui/preset';

import '@storybook-host/theme-entry';

export default createStorybookPreview();

StorybookHostOptions

| Field | Description | | --- | --- | | hostRoot | Absolute path to the host app root. Its public/ is mounted as static files | | hostSrcDir | Absolute path to host/src, used as a Sass loadPaths entry | | themeEntry | Absolute path to the global theme stylesheet, exposed as the @storybook-host/theme-entry alias | | packageRoot? | Root of your package, the one that owns stories/. Required when /preset is consumed as an installed dependency — otherwise createStorybookConfig throws instead of silently finding zero stories | | appVersion? | Injected into the build as __APP_VERSION__ | | extraStories? | Additional story globs, relative to packageRoot |

Decorators are available separately:

import { withTheme, withRouter } from '@socprime/react-ui/decorators';

withTheme applies the .light / .dark class and adds a Theme toolbar toggle; withRouter wraps stories in a MemoryRouter so router-aware components work in isolation.

Troubleshooting

| Symptom | Cause | | --- | --- | | Components render completely unstyled | Missing @source directive, so Tailwind never generated the library's utilities | | Colors are wrong or transparent | The theme custom properties are not defined, or .light / .dark is not set on <html> — call applyTheme() | | useHref() may be used only in the context of a <Router> | Aside, PageHeader or Button to= rendered outside a React Router provider | | Cannot find module '@socprime/react-ui/charts' in TS | moduleResolution is "node"; switch to "bundler" or "node16" | | require() of ES Module | The package is ESM-only; use import or a dynamic import() | | Toasts never appear | <Toaster /> is not rendered anywhere in the tree |

Versioning

See CHANGELOG.md for release history and breaking changes.

License

MIT