npm package: @shapesos/clay
Description: Shapes.co design system — tokens and theming
Latest version: 0.9.0

<div align="center">

# Clay

**Tokens, theming, and composable React components.**

[![npm](https://img.shields.io/npm/v/@shapesos/clay?style=flat-square&color=C4A882&label=npm)](https://www.npmjs.com/package/@shapesos/clay)
[![license](https://img.shields.io/npm/l/@shapesos/clay?style=flat-square&color=C4A882)](LICENSE)
[![bundle](https://img.shields.io/bundlephobia/minzip/@shapesos/clay?style=flat-square&color=C4A882&label=gzip)](https://bundlephobia.com/package/@shapesos/clay)

</div>

---

## Quick Start

```bash
npm install @shapesos/clay
```

```tsx
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/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.

```typescript
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.

```tsx
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>
  );
}
```

<details>
<summary><strong>Props Reference</strong></summary>

#### 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

```typescript
type MessageRole = "user" | "assistant";

interface ChatMessage {
  id: string;
  role: MessageRole;
  content: string;            // Markdown supported
  isHelpful?: boolean | null;
}
```

</details>

---

## Icon

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

```tsx
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" />
```

<details>
<summary><strong>Props Reference</strong></summary>

#### 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         |

</details>

---

## Lottie

Render [Lottie](https://airbnb.io/lottie/) animations with declarative props, hover interactions, and imperative playback control.

```tsx
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:

```tsx
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>
```

<details>
<summary><strong>Props Reference</strong></summary>

#### 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 |

</details>

---

## TypeScript

All components export their prop types:

```typescript
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

```bash
git clone git@github.com: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](https://github.com/changesets/changesets) for versioning. When making changes:

```bash
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](LICENSE)