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

@shapesos/clay

v0.13.1

Published

Shapes.co design system — tokens and theming

Readme

Clay

Tokens, theming, and composable React components.

npm license bundle


Quick Start

npm install @shapesos/clay
import { colors, typographyTypes } from "@shapesos/clay/tokens";
import { Chat } from "@shapesos/clay/chat";
import { Lottie } from "@shapesos/clay/lottie";

Entry Points

Clay is tree-shakeable with multiple entry points — import only what you need.

| Import | Contents | Peer Dependencies | | ------------------------- | ------------------------------------- | ------------------------ | | @shapesos/clay/tokens | Colors, typography, font families | None (pure JS) | | @shapesos/clay/chat | Chat compound components + types | React, styled-components | | @shapesos/clay/blocks | Typed content blocks (Block, BlockServices, BlockContext) | React, styled-components | | @shapesos/clay/artifacts| Per-type artifact renderers + ArtifactServices map (TABLE, CHART) | React, styled-components, recharts (optional, only for CHART) | | @shapesos/clay/chart | Standalone chart library (<BarChart>, <LineChart>, <PieChart> + composable primitives) | React, recharts | | @shapesos/clay/icon | Icon, IconButton components + types | React, styled-components | | @shapesos/clay/lottie | Lottie animation component + types | React, styled-components | | @shapesos/clay | Everything (convenience re-export) | React, styled-components |


Design Tokens

Pure JavaScript color and typography tokens — no React required.

import { colors, typographyStyles, typographyTypes, typographyMixin } from "@shapesos/clay/tokens";

// Color palette
colors["brown-100"];  // "#171716"
colors["brown-900"];  // "#F7F5F3"

// Typography definitions
typographyStyles[typographyTypes.GEIST_BODY_S_REGULAR];
// => { fontFamily: "Geist", fontSize: 16, fontWeight: 400, lineHeight: 24, letterSpacing: -0.08 }

// CSS mixin for styled-components
typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM);
// => CSS string ready to use in template literals

Chat

Composable compound components for building chat interfaces. Chat.Root provides context — children consume it, giving you full control over layout.

import { Chat } from "@shapesos/clay/chat";
import type { ChatMessage } from "@shapesos/clay/chat";

function MyChat() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  return (
    <Chat.Root
      messages={messages}
      onSendMessage={handleSend}
      isLoading={isLoading}
      onStop={handleStop}
      onCopyMessage={handleCopy}
      onThumbUpClick={handleThumbUp}
      onThumbDownClick={handleThumbDown}
    >
      <Chat.MessageList />
      <Chat.Composer placeholder="Ask anything..." />
    </Chat.Root>
  );
}

Chat.Root

| Prop | Type | Required | Description | | ------------------ | ---------------------------------------------------------- | -------- | ---------------------------------- | | messages | ChatMessage[] | Yes | Array of messages to display | | onSendMessage | (content: string) => void | Yes | Called when the user sends | | isLoading | boolean | No | Show stop button while loading | | onStop | () => void | No | Called when the user clicks stop | | onCopyMessage | (messageId: string) => void | No | Called when a message is copied | | onThumbUpClick | (messageId: string, isHelpful: boolean \| null) => void | No | Thumbs up feedback | | onThumbDownClick | (messageId: string, isHelpful: boolean \| null) => void | No | Thumbs down feedback |

Chat.Composer

| Prop | Type | Required | Default | Description | | ------------- | -------- | -------- | ---------------------- | ----------------- | | placeholder | string | No | "Type a message..." | Input placeholder |

Chat.MessageList

No props — reads messages from context.

Data Model

type MessageRole = "user" | "assistant";

interface ChatMessage {
  id: string;
  role: MessageRole;
  blocks: BlockData[];        // ordered, type-discriminated content blocks (defined by @shapesos/clay/blocks)
  fallbackText: string;       // server-supplied plain-text version of the blocks; used for clipboard/accessibility
  isHelpful?: boolean | null;
}

BlockData, BlockType, and TextBlockData live in @shapesos/clay/blocks. Chat doesn't own block rendering — it composes the Block component from the blocks package and bridges chat-specific concerns (like passing the message's id into TableActions callbacks). This means the same block infrastructure renders chat messages, vibe-view embeds, marketing-site responses, or any other surface that wants typed content blocks — without coupling to chat.


Blocks

Typed content-block infrastructure: a Block dispatcher, a BlockServices registry of concrete renderers (currently TextBlockService), and a BlockContext for renderer-level concerns like table actions. Chat consumes this internally — non-chat surfaces import directly.

import { Block, BlockContext, type BlockData } from "@shapesos/clay/blocks";

const blocks: BlockData[] = [{ type: "TEXT", payload: { text: "**hello world**" } }];

function MyContent() {
  return (
    <BlockContext.Provider value={{ TableActions: ({ tableIndex }) => <ExportButton index={tableIndex} /> }}>
      {blocks.map((block, i) => <Block key={i} block={block} />)}
    </BlockContext.Provider>
  );
}

The wire shape ({ type, payload }) matches dreamteam-io-server / shapes-agent 1:1, so blocks fetched from the API can be passed straight to Block with no translation. Unknown block types render as no-ops (forward-compat). Adding a new block type is a registration in BlockServices plus a concrete component — no changes to the dispatcher.

| Export | Kind | Description | | --------------------- | --------- | -------------------------------------------------------------------------------------------- | | Block | Component | Single-block dispatcher. Looks up the concrete renderer in BlockServices by block.type. | | BlockContext | Context | Renderer-level concerns (today: TableActions). Optional — Block works without it. | | useBlockContext | Hook | Reads the ambient BlockContextValue. Returns {} outside a provider. | | BlockServices | Registry | { TEXT: TextBlockService } today; additive over time. | | TextBlockService | Service | Concrete renderer entry for TEXT blocks. | | BlockData | Type | Discriminated union of all block shapes. | | TextBlockData | Type | { type: "TEXT"; payload: { text: string } }. | | BlockType | Type | "TEXT" (literal union; widens as new types are added). | | BlockComponentProps | Type | Props every concrete block component receives — { block }. | | BlockService | Type | Service registry entry contract — { type, Component }. | | BlockContextValue | Type | { TableActions?: ComponentType<{ tableIndex: number }> }. |


Artifacts

Per-type renderers for the artifact union (TABLE, CHART) plus the ArtifactServices registry. The block layer's ARTIFACT_REF block consumes this map to dispatch the inlined artifact to its concrete component; non-block surfaces can render an artifact directly.

import { TableArtifact, ChartArtifact, ArtifactServices } from "@shapesos/clay/artifacts";
import type {
  ArtifactCallbacksMap,
  ArtifactLabelsMap,
  ChartArtifactRecord,
  TableArtifactRecord,
} from "@shapesos/clay/artifacts";

// Direct render — picks the renderer at the call site.
<ChartArtifact
  artifact={chartRecord}
  labels={{ CHART: { download: "Download CSV", /* … */ } }}
  callbacks={{ CHART: { onDownload: (artifact) => analytics.track("chart_downloaded", artifact) } }}
/>

// Registry-driven dispatch — block layer does this.
const { Component } = ArtifactServices[artifact.type];
<Component artifact={artifact} labels={labels} callbacks={callbacks} />

Every artifact reads from a CSV at artifact.protectedAsset.presignedUrl. Loading / error / unavailable / empty states surface as inline status messages with consumer-translated copy from the labels prop (Partial<ArtifactLabelsMap>). Action callbacks (today: onDownload) wire through the callbacks prop (Partial<ArtifactCallbacksMap>) — each callback receives the artifact record so you can derive analytics context. Both props get passed through the block layer via block.payload.labels and block.payload.callbacks.

The CHART artifact requires recharts as a peer dep; bring your own if you import this entry. Adding a new artifact type is one ArtifactService registration plus a concrete component — no changes to the block dispatcher.


Chart

Standalone chart library — usable beyond the artifact path. Three pre-composed high-level components with smart defaults, plus composable primitives for custom recharts trees.

import { BarChart, LineChart, PieChart } from "@shapesos/clay/chart";

<BarChart
  data={[{ month: "Jan", us: 120, uk: 60 }, { month: "Feb", us: 132, uk: 64 }]}
  xKey="month"
  series={[{ key: "us", label: "US" }, { key: "uk", label: "UK" }]}
  stacked
/>

<PieChart
  data={departments}
  categoryKey="department"
  valueKey="headcount"
  // Localise the "Others" bucket detection in non-English UIs:
  othersCategoryLabels={["אחרים"]}
/>

Smart defaults: legend auto-hides for single-series charts; legend at top; x-axis labels truncate by category count; pie's "Others" bucket pins to the end of the sweep in neutral grey. Override any default with the named prop. Bring recharts as a peer dep.


Icon

Render SVG icons with consistent sizing, and icon buttons with selection states.

import { Icon, IconButton } from "@shapesos/clay/icon";
import { IconSearch, IconCopy } from "@tabler/icons-react";

<Icon icon={IconSearch} size={20} color="#333" aria-label="Search" />

<IconButton icon={IconCopy} size="small" onClick={handleCopy} aria-label="Copy" />

Icon

| Prop | Type | Required | Default | Description | | ------------ | ---------------------------------------- | -------- | ------- | ------------------------ | | icon | ComponentType<SVGProps<SVGSVGElement>> | Yes | | Icon component to render | | size | number | No | 16 | Size in pixels | | color | string | No | | Icon color | | className | string | No | | CSS class | | aria-label | string | No | | Accessible label |

IconButton

| Prop | Type | Required | Default | Description | | ------------ | ---------------------------------------- | -------- | --------- | ------------------------ | | icon | ComponentType<SVGProps<SVGSVGElement>> | Yes | | Icon component to render | | size | "small" \| "medium" | No | "small" | Button size | | isSelected | boolean | No | false | Selected state | | disabled | boolean | No | false | Disabled state | | onClick | () => void | No | | Click handler | | className | string | No | | CSS class | | aria-label | string | No | | Accessible label |


Lottie

Render Lottie animations with declarative props, hover interactions, and imperative playback control.

import { Lottie } from "@shapesos/clay/lottie";
import animationData from "./my-animation.json";

// Basic — loops forever
<Lottie animationData={animationData} />

// Interactive — plays once, then replays on hover
<Lottie
  animationData={animationData}
  loop={false}
  playOnHover
  loopOnHover
  width={80}
  height={80}
/>

Imperative control via ref:

import type { LottieRef } from "@shapesos/clay/lottie";

const ref = useRef<LottieRef>(null);

<Lottie ref={ref} animationData={animationData} autoplay={false} />
<button onClick={() => ref.current?.play()}>Play</button>

Lottie

| Prop | Type | Required | Default | Description | | ------------------- | ---------------------------------------------------------------- | -------- | ------- | ------------------------------ | | animationData | LottieAnimationData | Yes | | Lottie JSON data | | autoplay | boolean | No | true | Play on mount | | loop | boolean \| number | No | true | Loop forever, or N times | | speed | number | No | 1 | Playback speed | | direction | 1 \| -1 | No | 1 | Forward or reverse | | width | string \| number | No | | Container width | | height | string \| number | No | | Container height | | playOnHover | boolean | No | false | Replay on mouse enter | | loopOnHover | boolean | No | false | Loop while hovering | | className | string | No | | CSS class | | aria-label | string | No | | Accessible label | | onAnimationLoaded | (animation: AnimationItem) => void | No | | Fired when animation loads | | onComplete | () => void | No | | Fired on completion | | onLoopComplete | () => void | No | | Fired on each loop | | onEnterFrame | (frame: { currentTime: number; totalTime: number }) => void | No | | Fired on each frame |

LottieRef (imperative handle)

| Method | Description | | ---------------------------------- | ---------------------------------- | | play() | Start playback | | pause() | Pause playback | | stop() | Stop and reset to frame 0 | | goToAndPlay(value, isFrame?) | Jump to value and play | | goToAndStop(value, isFrame?) | Jump to value and stop | | setDirection(direction) | Set direction (1 or -1) | | setSpeed(speed) | Set playback speed | | getAnimationItem() | Access underlying lottie-web instance |


TypeScript

All components export their prop types:

import type { ColorToken, TypographyStyle, TypographyType } from "@shapesos/clay/tokens";
import type { ChatMessage, MessageRole, ChatContextValue } from "@shapesos/clay/chat";
import type { IconProps, IconButtonProps, IconButtonSize } from "@shapesos/clay/icon";
import type { LottieProps, LottieRef, LottieAnimationData } from "@shapesos/clay/lottie";

Development

git clone [email protected]:dreamteamapp/shapes-clay.git
cd shapes-clay
bun install

| Command | Description | | ----------------------- | ------------------------------------------- | | bun run storybook | Component playground (localhost:6006) | | bun run build | Build package (tsup) | | bun run dev | Build in watch mode | | bun run test | Unit tests (Vitest) | | bun run test:coverage | Unit tests with coverage | | bun run test:e2e | E2E tests (Playwright + Storybook) | | bun run check | All quality gates (lint + typecheck + test) |

Releasing

Clay uses changesets for versioning. When making changes:

bunx changeset          # Describe your change and its semver impact
git add .changeset/     # Commit the changeset with your PR

On merge to master, a "Version Packages" PR is created automatically. Merging that PR publishes to npm.


License

MIT