Back to Cherry Studio

@cherrystudio/ui

packages/ui/README.md

2.0.013.9 KB
Original Source

@cherrystudio/ui

Cherry Studio UI component library for React applications.

โœจ Features

  • ๐ŸŽจ Design System: Cherry Studio primitive palettes, product semantics, and Shadcn-compatible theme mappings
  • ๐ŸŒ“ Dark Mode: Built-in light and dark theme support
  • ๐Ÿš€ Tailwind v4: Built on top of the latest Tailwind CSS v4
  • ๐Ÿ“ฆ Flexible Imports: Two style integration modes for different adoption paths
  • ๐Ÿ”ท TypeScript: Complete type definitions and editor support
  • ๐ŸŽฏ Low Collision: CSS variable isolation without taking over app runtime state by default

๐Ÿš€ Quick Start

Install

bash
npm install @cherrystudio/ui
# peer dependencies
npm install motion react react-dom tailwindcss

The recommended integration style in this repository is to use the package export entry points: @cherrystudio/ui @cherrystudio/ui/components @cherrystudio/ui/icons @cherrystudio/ui/utils @cherrystudio/ui/styles/tokens.css @cherrystudio/ui/styles/theme.css

Two Integration Modes

Mode 1: Full Theme Contract โœจ

Use the full Cherry Studio design system so Tailwind theme tokens resolve to Cherry Studio values.

css
/* app.css */
@import '@cherrystudio/ui/styles/theme.css';

Characteristics:

  • โœ… Use standard Tailwind utility names directly (bg-primary, bg-red-500, p-4, rounded-lg)
  • โœ… Colors resolve to Cherry Studio design values
  • โœ… Uses Tailwind's standard numeric spacing scale
  • โœ… Includes Shadcn-derived radii through rounded-4xl plus rounded-full; smaller Cherry aliases remain available for compatibility
  • โš ๏ธ Overrides the default Tailwind theme contract for the imported app bundle

Example:

tsx
<Button className="bg-primary text-red-500 p-4 rounded-lg">
</Button>

<div className="rounded-4xs">Tiny radius (0.03125rem)</div>
<div className="rounded-xs">Small radius (0.125rem)</div>
<div className="rounded-md">Medium radius (0.5rem)</div>
<div className="rounded-xl">Large radius (0.875rem)</div>
<div className="rounded-full">Full radius (9999px)</div>

Mode 2: Selective Foundation Consumption ๐ŸŽฏ

Import only primitives and existing foundation providers, then decide which values your design system exposes.

css
/* app.css */
@import 'tailwindcss';
@import '@cherrystudio/ui/styles/tokens.css';

/* Re-export only the parts you need */
@theme inline {
  --color-primary: var(--cs-brand-500); /* Adopt a Cherry Studio foundation value */
  --color-red-500: oklch(...); /* Keep your own red scale */
  --radius-lg: 1rem; /* Keep your own radius */
}

Characteristics:

  • โœ… Does not override the full Tailwind theme
  • โœ… Gives access to Cherry Studio foundation values (var(--cs-brand-500), var(--cs-red-500))
  • โœ… Lets you choose what to adopt and what to keep
  • โœ… Works when you already own the semantic contract and only need selected Cherry Studio foundations
  • โš ๏ธ Does not expose the complete Shadcn or Cherry Studio product contract

Component consumption after defining the adapter:

tsx
<button className="bg-primary text-primary-foreground">Use the adopted primary color</button>
<div className="bg-red-500">
  Use the default Tailwind red
</div>
<div className="rounded-lg bg-primary text-primary-foreground" />

src/styles/contract.css is an internal composition layer used by the generated theme.css entry to preserve the foundation โ†’ runtime input โ†’ Shadcn โ†’ product import order. It is not a public package export or a supported consumer entry point.

CSS Variable Rules

The normative v2 architecture, Shadcn contract, and migration boundary are defined in Design Token System. Official Shadcn variables remain unprefixed; approved Cherry Studio product variables extend the same unprefixed public namespace. Use the Variable Catalog to select a stable role and distinguish runtime API from internal providers and tooling-only historical names.

To avoid mixing value sources, semantic variables, theme mappings, and runtime overrides, use these rules:

  1. --background, --primary, --muted-foreground, and the other variables in shadcn.css are the official Shadcn contract
  2. Approved Cherry Studio product semantics are also unprefixed, such as --success and --background-subtle
  3. Historical migration names are tooling-only and must not be recreated as runtime product variables
  4. Shared --cs-* variables are internal value providers; a selective-foundation consumer may reference primitive providers only while defining its own adapter, not from ordinary component styles. --cs-theme-* is the reserved host-written input subset
  5. --color-*, --radius-*, and --font-* are Tailwind adapter output, not another semantic input layer. Only the adapter owner declares --color-* inside @theme; component CSS, page CSS, and renderer TypeScript/TSX-authored styles must neither declare nor consume --color-*. Components normally consume generated radius and typography mappings through Tailwind utilities.
  6. --cs-theme-* is a controlled host-written input, not a component-facing semantic role or Tailwind utility
  7. Component-, page-, and Electron-shell variables stay in their owning stylesheet and are not added to the shared contract merely because they are CSS custom properties

Default consumption rules:

  1. Regular application packages should depend on @cherrystudio/ui/styles/theme.css by default
  2. Components should prefer semantic utilities such as bg-background, text-muted-foreground, and bg-success; custom CSS may use the matching official or product variable
  3. Only design-system-adjacent packages that explicitly need foundation-level access should depend on @cherrystudio/ui/styles/tokens.css
  4. Runtime theme logic should write shared theme values only through registered --cs-theme-* inputs, not directly to official semantics or derived --color-* variables; renderer-only runtime values stay owner-local under --app-*

Shadcn CLI Ownership

Use the Shadcn CLI to scaffold or update component source and dependency metadata only. Cherry Studio's authored theme layers and generator own the shared CSS contract, even though components.json points the CLI at the generated src/styles/theme.css entry.

  • Do not retain direct CLI edits to src/styles/theme.css; pnpm theme:build is its only writer.
  • Review any CSS proposed by shadcn add and place it according to ownership: official semantics in shadcn.css, Cherry Studio product semantics in product.css and theme-contract.ts, and component-local styles with the component.
  • Add Tailwind mappings through the theme generator rather than by hand-editing its output.
  • Run pnpm theme:build followed by pnpm theme:check after accepting a component that changes theme requirements.

Usage

Basic Components

tsx
import { Button, Input } from '@cherrystudio/ui'

function App() {
  return (
    <div>
      <Button variant="default" size="default">Click me</Button>
      <Input
        type="text"
        placeholder="Type here"
        onChange={(event) => console.log(event.currentTarget.value)}
      />
    </div>
  )
}

Modular Imports

tsx
// Components only
import { Button } from '@cherrystudio/ui/components'

// Utilities only
import { DIALOG_CLOSE_DURATION_MS, DIALOG_UNMOUNT_DELAY_MS, toUndefinedIfNull } from '@cherrystudio/ui/utils'

Development

bash
# Install dependencies
pnpm install

# Development mode
pnpm dev

# Build
pnpm build

# Type check
pnpm type:check

# Validate the variable graph, generated adapter, registry, and renderer authored-CSS boundary
pnpm theme:check

# Run tests
pnpm test

Icon Generation

Use the package command for all icon generation so ESLint fixes and the repository formatter run after the generated files are updated.

In this command, --type=icons means general UI icons that are not Provider or Model logos.

bash
# Generate general icons, Providers, and Models
pnpm icons:generate

# General icons
pnpm icons:generate --type=icons

# Provider icons, Avatars, barrels, and catalogs
pnpm icons:generate --type=providers

# Model icons, Avatars, barrels, and catalogs
pnpm icons:generate --type=models
TypeSVG sourceGenerated output
iconsicons/general/*.svgGeneral React icon components and their barrel
providersicons/providers/{light,dark}/*.svgProvider light/dark components, metadata, Avatars, barrels, and catalogs
modelsicons/models/{light,dark}/*.svgModel light/dark components, metadata, Avatars, barrels, and catalogs

Generation uses a hash cache and skips unchanged SVG files. Use the optional arguments when a narrower or clean regeneration is needed:

bash
# Regenerate one provider and its Avatar/catalog entries
pnpm icons:generate --type=providers --only=opencode

# Regenerate multiple models
pnpm icons:generate --type=models --only=claude,gemini

# Ignore the hash cache and regenerate every provider
pnpm icons:generate --type=providers --force
  • Omitting --type generates all three groups in order: icons, providers, then models.
  • --type=icons|providers|models limits generation to one source and output group.
  • --only=<name[,name]> limits Provider or Model component and Avatar generation to the listed names.
  • --force bypasses the SVG hash cache.

Provider and Model generation runs the SVG component stage first and the Avatar/catalog stage second. The posticons:generate lifecycle script fixes the generated icon files with ESLint, then runs the repository formatter once after both stages complete. Internal scripts under scripts/ are still available for pipeline development, but normal usage should go through pnpm icons:generate.

Package Surface

The packages/ui workspace contains both runtime code and development-only assets.

  • Runtime surface:
    • src/
    • dist/ build output
    • package export entry points declared in package.json
  • Development assets:
    • stories/ and .storybook/
    • scripts/ used for icon and theme generation
    • icons/ source assets used by the generation pipeline
    • docs/ for migration and reference material

Only the runtime surface should be treated as consumable package API.

Structural Markers

packages/ui keeps Shadcn-compatible data-slot attributes for component-internal styling and its standalone build. When Cherry Studio consumes the package source, the app's UI-contract generator treats those markers as structural semantics and emits the corresponding public data-ui part:* tokens without removing the original attributes. Existing renderer code, application tests, and custom themes that use data-slot continue to work; new selectors can use the generated semantic layer. The application-level token grammar, stability tiers, maintained anchors, and selector rules are defined by the UI Semantic Contract. Explicit roles and maintained part:* tokens are public selectors; inferred roles are best-effort discovery coordinates.

Directory Structure

text
docs/                    # Migration plans and reference docs
src/
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ primitives/     # Primitive components
โ”‚   โ”œโ”€โ”€ composites/     # Composite components
โ”‚   โ”œโ”€โ”€ icons/          # Icon runtime exports and catalogs
โ”‚   โ””โ”€โ”€ index.ts
โ”œโ”€โ”€ hooks/              # React Hooks
โ”œโ”€โ”€ lib/                # Internal utilities
โ”œโ”€โ”€ styles/             # Tokens and theme entry files
โ”œโ”€โ”€ utils/              # Utility functions
โ””โ”€โ”€ index.ts            # Main runtime entry point
scripts/                # Theme and icon generation tooling
stories/                # Storybook stories and sandbox usage
icons/                  # Raw icon assets for code generation

Naming Conventions

All file and directory names under packages/ui/ follow kebab-case (per shadcn CLI convention and project-wide rule ยง4.5 in ../../docs/references/naming-conventions.md). This covers primitives/, composites/, icons/, hooks/, and stories/ alike. Exported identifiers inside files remain PascalCase for components and camelCase for utilities and hooks.

Examples:

  • button.tsx exports Button
  • data-table.tsx exports DataTable
  • error-boundary/index.tsx exports ErrorBoundary
  • use-dnd-reorder.ts exports useDndReorder

Components

Button

A button component with multiple variants and sizes.

Props:

  • variant: default | destructive | outline | secondary | emphasis | ghost | link
  • size: default | sm | lg | icon | icon-sm | icon-lg | icon-navbar
  • loading, loadingIcon, loadingIconClassName: loading-state controls
  • asChild: render through Radix Slot
  • all standard React button props

Input

The Shadcn-compatible native input primitive.

Props:

  • accepts standard React input props, including native type, value, and event-based onChange
  • use aria-invalid for invalid-state styling
  • use className for supported layout composition

Hooks

useDndReorder

Keeps drag reordering correct when the rendered list is a filtered subset of the source list.

useDndState

Reads the active and hovered identifiers from the current dnd-kit context.

Utilities

toUndefinedIfNull(value)

Converts null to undefined at API boundaries.

toNullIfUndefined(value)

Converts undefined to null at API boundaries.

DIALOG_CLOSE_DURATION_MS

Duration of the Dialog CSS close animation: 200 ms.

DIALOG_UNMOUNT_DELAY_MS

Delay for imperative Dialog hosts before unmounting: 200 ms.

License

MIT