diff --git a/.claude/skills/add-new-component/SKILL.md b/.agents/skills/add-new-component/SKILL.md similarity index 99% rename from .claude/skills/add-new-component/SKILL.md rename to .agents/skills/add-new-component/SKILL.md index 8197fa7c9..324587942 100644 --- a/.claude/skills/add-new-component/SKILL.md +++ b/.agents/skills/add-new-component/SKILL.md @@ -4,6 +4,7 @@ description: End-to-end guide for adding a new component to the Apsara design sy metadata: author: raystack version: "1.0" + internal: true --- # Add New Component to Apsara diff --git a/.agents/skills/apsara/SKILL.md b/.agents/skills/apsara/SKILL.md new file mode 100644 index 000000000..5ba3b52b7 --- /dev/null +++ b/.agents/skills/apsara/SKILL.md @@ -0,0 +1,79 @@ +--- +name: apsara +description: Helps consume the @raystack/apsara React component library correctly in an application. Use when installing or setting up Apsara, building UI with Apsara components (Button, Dialog, Select, Menu, DataTable, Form, Tabs, Sidebar, Toast, etc.), theming (light/dark, accent/gray colors, modern/traditional style), styling with design tokens, or troubleshooting Apsara component behavior. Covers install, the Theme provider, the `--rs-*` token system, compound-component composition, and common pitfalls. +license: ISC +metadata: + author: raystack +--- + +# Apsara + +Apsara (`@raystack/apsara`) is an open-source React component library built on [Base UI](https://base-ui.com/) primitives. It targets enterprise, data-dense interfaces (data tables, navigation shells, forms, overlays) and ships ~70 accessible, typed components. Styling is **vanilla CSS driven by `--rs-*` design tokens** that react to `data-*` attributes set by the theme provider — there is no Tailwind, no runtime CSS-in-JS, and no per-component install step. + +## What this skill is for + +Use this skill to help a **consumer** of the published package: + +- install and wire up Apsara in a new or existing app (Next.js, Vite, etc.) +- pick and correctly compose Apsara components for a UI task +- theme the app (light/dark, accent/gray color, modern/traditional style) +- style and customize components using design tokens and `data-*` attributes +- avoid common composition and SSR pitfalls + +This is **not** the skill for contributing components to the Apsara repo itself — for that, use the `add-new-component` skill. + +## Source of truth + +The library evolves quickly. Per-component exact props/variants are documented on the live docs site, which serves machine-readable Markdown. **Fetch these instead of guessing an API:** + +- Docs index for agents: `https://apsara.raystack.org/llms.txt` +- Full docs (all pages concatenated): `https://apsara.raystack.org/llms-full.txt` +- Any single component: `https://apsara.raystack.org/docs/components/.mdx` (e.g. `.../button.mdx`, `.../select.mdx`, `.../alert-dialog.mdx`) +- Any token category: `https://apsara.raystack.org/tokens/.mdx` (`colors`, `spacing`, `typography`, `effects`, `radius`) + +When you need a prop you are unsure about, fetch the component's `.mdx` page rather than inventing it. + +## Core facts (always true) + +1. **One package, one CSS import.** `npm install @raystack/apsara`, then `import "@raystack/apsara/style.css"` once at the app root. That stylesheet contains every component's styles and all tokens. +2. **Wrap the app in ``.** Theming, dark mode, and the no-flash hydration script all come from the `Theme` provider. Without it, `data-theme`/token resolution will not work. +3. **Components are compound, dot-notation.** Apsara exports a single name per component and hangs sub-parts off it: `Dialog.Content`, `Select.Trigger`, `Menu.Item`, `Tabs.Tab`. It does **not** export flat names like `DialogContent`. (Contrast with shadcn/Radix and coss.) +4. **Style with tokens, never hard-coded values.** Use `--rs-*` custom properties (`var(--rs-color-foreground-base-primary)`, `var(--rs-space-5)`) so styling follows the active theme. +5. **Built on Base UI.** Components expose Base UI `data-*` state attributes (`data-open`, `data-disabled`, `data-starting-style`, …) for state-driven CSS, and trigger-based overlays follow Base UI composition. + +## Principles for output + +1. Use existing Apsara components before inventing custom markup. +2. Verify component props against the live `.mdx` docs; do not fabricate APIs. +3. Use dot-notation sub-components exactly as exported. +4. Use `--rs-*` tokens for all colors, spacing, radius, typography, and effects. +5. Keep accessibility intact: visible/`aria-label` labels, explicit `type` on buttons, icon `aria-hidden` when decorative. +6. Match Apsara's own conventions: CSS Modules + `class-variance-authority` for any custom styled components. + +## Usage workflow + +1. Confirm setup exists (CSS import + `` wrapper). If not, see `references/setup.md`. +2. Identify the component(s) for the task. Use `references/components.md` as the registry index. +3. Fetch the component's `.mdx` page for exact props/variants when unsure. +4. Compose with correct dot-notation sub-parts (`references/composition.md`). +5. Style only via tokens / `className` / `data-*` (`references/styling.md`, `references/tokens.md`). +6. Self-check the output checklist below. + +## References (read on demand) + +- `references/setup.md` — install, CSS import, `` wiring for Next.js App Router & Vite, icons & hooks subpath exports +- `references/theming.md` — full `Theme` / `ThemeProvider` API, `useTheme`, dark mode, accent/gray/style options, scoped (nested) themes, `ThemeSwitcher` +- `references/tokens.md` — **complete `--rs-*` token reference**: semantic colors, color scales, spacing, radius, typography, effects, theme `data-*` attributes +- `references/styling.md` — customizing components via `className`, `style`, `data-*`, CSS Modules, and CVA +- `references/composition.md` — compound dot-notation pattern, Base UI trigger/overlay composition, `render` prop, and per-component composition gotchas +- `references/components.md` — registry index of all exported components grouped by category, with imports and the live docs URL for each + +## Output checklist + +Before returning Apsara code: + +- Component imports come from `@raystack/apsara` (icons from `@raystack/apsara/icons`, hooks from `@raystack/apsara/hooks`). +- Sub-parts use dot-notation exactly as exported (`Dialog.Content`, not `DialogContent`). +- Props/variants are verified against the live `.mdx` docs, not assumed. +- Colors/spacing/radius/typography use `--rs-*` tokens — no hard-coded hex/px for themed values. +- Buttons have explicit `type`; icon-only controls have `aria-label`; decorative icons have `aria-hidden`. diff --git a/.agents/skills/apsara/references/components.md b/.agents/skills/apsara/references/components.md new file mode 100644 index 000000000..80a152127 --- /dev/null +++ b/.agents/skills/apsara/references/components.md @@ -0,0 +1,140 @@ +# Component Registry + +Index of everything exported from `@raystack/apsara`, grouped by purpose. Use it to pick the right component, then fetch its live docs page for exact props/variants: + +`https://apsara.raystack.org/docs/components/.mdx` + +All components import from the root: `import { Button, Dialog } from "@raystack/apsara"`. Sub-parts are dot-notation (`Dialog.Content`) — see `composition.md`. + +## Layout + +| Component | Purpose | slug | +|---|---|---| +| `Box` | Generic layout primitive (display/spacing props) | — | +| `Flex` | Flexbox container (`direction`, `gap`, `align`, `justify`) | `flex` | +| `Grid` | CSS grid container | `grid` | +| `Container` | Width-constrained responsive wrapper | `container` | +| `Separator` | Visual/semantic divider | `separator` | +| `ScrollArea` | Styled custom scroll container | `scroll-area` | + +## Typography + +| Component | Purpose | slug | +|---|---|---| +| `Text` | Body text with `size`/`weight` token mapping | `text` | +| `Headline` | Display/heading text | `headline` | +| `Label` | Accessible form label | `label` | +| `Link` | Styled anchor | `link` | +| `CodeBlock` | Syntax-highlighted code block | `code-block` | + +## Actions + +| Component | Purpose | slug | +|---|---|---| +| `Button` | Primary action trigger (`variant`, `color`, `size`, `loading`, leading/trailing icons) | `button` | +| `IconButton` | Icon-only button (needs `aria-label`) | `icon-button` | +| `CopyButton` | Copy-to-clipboard button | `copy-button` | +| `Toggle` | Two-state pressable button | `toggle` | +| `FloatingActions` | Floating action cluster | `floating-actions` | + +## Forms & inputs + +| Component | Purpose | slug | +|---|---|---| +| `Form` | Form submission/validation wrapper | `form` | +| `Field` | Label + description + error wiring | `field` | +| `Fieldset` | Grouped controls with legend | `fieldset` | +| `Input` | Single-line text input | `input` | +| `TextArea` | Multi-line text input | `textarea` | +| `Select` | Single/multi choice from a list | `select` | +| `Combobox` | Searchable selection | `combobox` | +| `Search` | Search input | `search` | +| `Checkbox` | Boolean checkbox | `checkbox` | +| `Radio` | Mutually exclusive choice group | `radio` | +| `Switch` | On/off toggle | `switch` | +| `Slider` | Numeric range control | `slider` | +| `NumberField` | Numeric input with steppers | `number-field` | +| `OTPField` | Segmented one-time-passcode input | `otp-field` | +| `Calendar` / `DatePicker` / `RangePicker` | Date & range selection | `calendar` | +| `ColorPicker` (`* from color-picker`) | Color selection | `color-picker` | + +## Overlays & popups + +| Component | Purpose | slug | +|---|---|---| +| `Dialog` | Centered modal | `dialog` | +| `AlertDialog` | Confirmation modal (no outside-click close) | `alert-dialog` | +| `Drawer` | Edge drawer with swipe-to-dismiss | `drawer` | +| `SidePanel` | Side panel surface | `sidepanel` | +| `Popover` | Anchored non-modal floating content | `popover` | +| `Tooltip` | Hover/focus hint | `tooltip` | +| `PreviewCard` | Hover-triggered rich preview | `preview-card` | +| `Menu` | Dropdown action menu (groups, submenus) | `menu` | +| `Menubar` | Horizontal app menu bar | `menubar` | +| `ContextMenu` | Right-click context menu | `context-menu` | +| `Command` | Command palette | `command` | + +## Navigation + +| Component | Purpose | slug | +|---|---|---| +| `Sidebar` | App-shell navigation panel | `sidebar` | +| `Navbar` | Top navigation bar | `navbar` | +| `Tabs` | Tabbed panels | `tabs` | +| `Breadcrumb` | Hierarchical trail | `breadcrumb` | +| `Toolbar` | Grouped command strip | `toolbar` | + +## Data display + +| Component | Purpose | slug | +|---|---|---| +| `DataTable` (+ `useDataTable`) | Full-featured data table (sort/filter/virtualize, TanStack-based) | `datatable` | +| `DataView` (+ `useDataView`) | Unified table/list data view | `dataview` | +| `Table` | Primitive table markup | `table` | +| `List` | Structured list | `list` | +| `Avatar` / `AvatarGroup` (+ `getAvatarColor`) | User/entity avatars | `avatar` | +| `Badge` | Status label | `badge` | +| `Chip` | Compact token/tag | `chip` | +| `Amount` | Formatted monetary value | `amount` | +| `Image` | Image with handling | `image` | +| `EmptyState` | Empty-state placeholder | `empty-state` | + +## Feedback & status + +| Component | Purpose | slug | +|---|---|---| +| `Toast` (+ `toastManager`, `useToastManager`) | Transient notifications (imperative `toastManager`) | `toast` | +| `Callout` | Inline attention message | `callout` | +| `Indicator` | Status dot/indicator | `indicator` | +| `Progress` | Determinate/indeterminate progress bar | `progress` | +| `Meter` | Bounded scalar measurement | `meter` | +| `Spinner` | Indeterminate loading spinner | `spinner` | +| `Skeleton` | Loading placeholder | `skeleton` | +| `AnnouncementBar` | Page-level announcement banner | `announcement-bar` | + +## Disclosure + +| Component | Purpose | slug | +|---|---|---| +| `Accordion` | Stacked collapsible sections | `accordion` | +| `Collapsible` | Single expand/collapse region | `collapsible` | + +## Filtering + +| Component | Purpose | slug | +|---|---|---| +| `FilterChip` | Filter token with value/edit affordance | `filter-chip` | + +## Theming (not visual components) + +| Export | Purpose | +|---|---| +| `Theme` (alias `ThemeProvider`, deprecated) | Theme provider — wrap the app. See `theming.md`. | +| `ThemeSwitcher` | Prebuilt light/dark toggle button | +| `useTheme` | Hook to read/set theme (also at `@raystack/apsara/hooks`) | + +## Notes + +- A `—` slug means there's no standalone docs page (e.g. `Box`); use the component's TypeScript types. +- `DataTable` and `DataView` are large, opinionated components — always fetch their `.mdx` (and the `useDataTable`/`useDataView` hooks) before wiring data, sorting, filtering, or virtualization. +- Icons import from `@raystack/apsara/icons`, not the root. diff --git a/.agents/skills/apsara/references/composition.md b/.agents/skills/apsara/references/composition.md new file mode 100644 index 000000000..c6c679642 --- /dev/null +++ b/.agents/skills/apsara/references/composition.md @@ -0,0 +1,160 @@ +# Composition Patterns + +Use this when assembling multi-part Apsara components (overlays, menus, selects, tabs, forms) so the structure and props are correct. Apsara wraps Base UI, so its composition model follows Base UI conventions. + +## The compound dot-notation pattern + +Apsara exports **one name per component** and hangs every sub-part off it as a property. Import the single root; reach sub-parts with dots: + +```tsx +import { Dialog, Button } from "@raystack/apsara"; + + + }>Open + + + Title + + + Body copy + + + Cancel} /> + + + + +``` + +There is **no** `DialogContent`, `SelectTrigger`, etc. as flat exports. If migrating from shadcn/Radix/coss, convert flat names to dot-notation (`DialogContent` → `Dialog.Content`). + +## Composing a custom element into a part: `render` (not `asChild`) + +Base UI uses a `render` prop to merge a part's behavior/props onto your element. Use it on triggers, closes, and any part you want to render as a different element. + +```tsx +// Render the dialog trigger AS an Apsara Button: +}>Open + +// Close button rendered as a Button: +Cancel} /> +``` + +- `render` replaces the old Radix/shadcn `asChild` mental model. Don't pass `asChild`. +- `render` can also take a function for full control: `render={(props) => }`. + +## Trigger + content overlays (Dialog, Popover, Tooltip, Menu, …) + +Common shape: a `Root` holds a `Trigger` and a portaled content part. The trigger must live inside the root. + +```tsx +import { Popover, Button } from "@raystack/apsara"; + + + }>Details + + +``` + +Controlled state (for cross-component flows — e.g. a menu item opening a dialog) uses `open` + `onOpenChange`: + +```tsx +const [open, setOpen] = useState(false); + + + +``` + +Several overlay roots (`Dialog`, `Menu`, `Popover`, `Tooltip`) also expose `createHandle` for advanced imperative open/close from outside the subtree — use only when a trigger can't live in the same tree. + +## Select + +`Select.Value` (with `placeholder`) goes inside `Select.Trigger`; options are `Select.Item` children inside `Select.Content`. Items are children-based — no items array required. + +```tsx +import { Select } from "@raystack/apsara"; + + +``` + +- Put `placeholder` on `Select.Value`, not the trigger. +- `Select.Item` supports `leadingIcon`; `disabled` marks unavailable rows. +- Sub-parts: `Select.Trigger`, `Select.Value`, `Select.Content`, `Select.Item`, `Select.Group`, `Select.Label`, `Select.Separator`, `Select.List`. + +## Menu / ContextMenu + +```tsx +import { Menu, Button } from "@raystack/apsara"; + + + }>Actions + + Edit + + + More + + Archive + + + + +``` + +- Menu actions use `onClick` (not Radix's `onSelect`). +- Sub-parts: `Menu.Trigger`, `Menu.Content`, `Menu.Item`, `Menu.Group`, `Menu.Label`, `Menu.Separator`, `Menu.EmptyState`, `Menu.Submenu`, `Menu.SubmenuTrigger`, `Menu.SubmenuContent`. + +## Tabs + +```tsx +import { Tabs } from "@raystack/apsara"; + + + + Overview + Activity + + + + +``` + +## Forms (`Form` + `Field`) + +Wrap form-bound controls in `Field` so label, description, and error stay wired and accessible. `Form` handles submission/validation. + +```tsx +import { Form, Field, Input, Button } from "@raystack/apsara"; + +
+ + Email + + + + +
+``` + +- Always set `type` on inputs (`type="email"`) and buttons (`submit`/`button`/`reset`). +- Provide a visible label or `aria-label` for every control. +- Fetch `form.mdx` / `field.mdx` from the docs for the exact sub-parts and validation props. + +## Composition rules & anti-patterns + +- **Do** use dot-notation sub-parts exactly as exported; **don't** invent flat names. +- **Do** compose custom elements with `render`; **don't** use `asChild`. +- **Do** keep required structural parts (title/description in dialogs, value in select triggers) for accessibility. +- **Don't** mix one component's API onto another (e.g. Select patterns inside Menu). +- **Don't** assume a shadcn/Radix prop exists — verify against the component's `.mdx` page (`https://apsara.raystack.org/docs/components/.mdx`). diff --git a/.agents/skills/apsara/references/setup.md b/.agents/skills/apsara/references/setup.md new file mode 100644 index 000000000..273cd17d9 --- /dev/null +++ b/.agents/skills/apsara/references/setup.md @@ -0,0 +1,142 @@ +# Setup & Installation + +Use this when bootstrapping Apsara in a consumer app or debugging "styles/tokens aren't applying" or theme-flash issues. + +## Prerequisites + +- **React** 18 or 19 (the package declares a React 19 peer dependency; React 19 is recommended). +- **Node.js** 18+. +- No bundler config, PostCSS, or Tailwind setup is required — Apsara ships a single prebuilt stylesheet. + +## 1. Install + +```bash +npm install @raystack/apsara +# or +pnpm add @raystack/apsara +# or +yarn add @raystack/apsara +``` + +## 2. Import the stylesheet (once, at the app root) + +```ts +import "@raystack/apsara/style.css"; +``` + +This one file contains **all** component styles and **all** `--rs-*` design tokens. Import it before any component renders. Importing it more than once is harmless but unnecessary. + +Optional normalize stylesheet (cross-browser resets) — import it **before** `style.css`: + +```ts +import "@raystack/apsara/normalize.css"; +import "@raystack/apsara/style.css"; +``` + +## 3. Wrap the app in `` + +The `Theme` provider applies `data-theme` / `data-style` / `data-accent-color` / `data-gray-color` to the document and injects a small inline script that sets them **before paint** to avoid a flash of the wrong theme. Tokens only resolve correctly inside it. + +```tsx +import { Theme } from "@raystack/apsara"; + +function App() { + return ( + + + + ); +} +``` + +`defaultTheme` accepts `"light"`, `"dark"`, or `"system"` (follows OS preference). See `theming.md` for the full prop list (accent color, gray color, style variant, storage key, forced theme, etc.). + +## Framework wiring + +### Next.js (App Router) + +Put the CSS import and provider in the root layout. `suppressHydrationWarning` on `` is **required** because the no-flash script mutates `` attributes before React hydrates. + +```tsx +// app/layout.tsx +import { Theme } from "@raystack/apsara"; +import "@raystack/apsara/style.css"; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} +``` + +Notes for the App Router: +- The CSS import and `Theme` can live in a server component (layout); `Theme` itself is a client component (`"use client"`) and handles that boundary internally. +- Interactive Apsara components are client components — render them within client boundaries as usual. + +### Vite / CRA / SPA + +Import CSS and wrap the root in the entry file: + +```tsx +// main.tsx +import React from "react"; +import ReactDOM from "react-dom/client"; +import { Theme } from "@raystack/apsara"; +import "@raystack/apsara/style.css"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); +``` + +## Subpath exports + +| Import | Contents | +|---|---| +| `@raystack/apsara` | All components, the `Theme` provider, `toastManager`/`useToastManager`, type exports | +| `@raystack/apsara/icons` | Icon set (re-exports + Apsara icons) | +| `@raystack/apsara/hooks` | Utility hooks (e.g. `useTheme`) | +| `@raystack/apsara/style.css` | The full stylesheet (required) | +| `@raystack/apsara/normalize.css` | Optional CSS reset | + +```tsx +import { Button } from "@raystack/apsara"; +import { MagnifyingGlassIcon, Cross2Icon } from "@raystack/apsara/icons"; +import { useTheme } from "@raystack/apsara/hooks"; // also re-exported from the root +``` + +> `@raystack/apsara/v1` is an alias of the root entry kept for compatibility; new code should import from `@raystack/apsara`. + +## First component + +```tsx +import { Button, Flex, Text } from "@raystack/apsara"; + +export function Example() { + return ( + + Welcome to Apsara + + + + + + ); +} +``` + +## Setup troubleshooting + +- **Components render unstyled / tokens are blank** → `style.css` isn't imported, or it's imported after a CSS reset that overrides it. Import it once at the root. +- **Colors don't change with light/dark, or `var(--rs-color-*)` resolves to nothing** → the tree isn't wrapped in ``, so `data-theme` is never set on the document. +- **Theme flashes wrong on first paint (Next.js)** → missing `suppressHydrationWarning` on ``, or `Theme` isn't high enough in the tree. +- **Hydration mismatch warnings around theme** → expected without `suppressHydrationWarning`; add it to ``. diff --git a/.agents/skills/apsara/references/styling.md b/.agents/skills/apsara/references/styling.md new file mode 100644 index 000000000..8dd491553 --- /dev/null +++ b/.agents/skills/apsara/references/styling.md @@ -0,0 +1,133 @@ +# Styling & Customization + +Use this when customizing the look of Apsara components or building your own styled elements that live alongside them. Apsara is vanilla CSS + `--rs-*` tokens — no Tailwind, no runtime CSS-in-JS. + +## The three ways to customize a component + +### 1. `className` (preferred for reusable tweaks) + +Every component accepts `className`. Combine with tokens: + +```tsx +import { Button } from "@raystack/apsara"; + + +``` + +```css +.cta { + min-width: 200px; + border-radius: var(--rs-radius-5); +} +``` + +### 2. `style` (one-off inline) + +```tsx + + + +``` + +### 3. `data-*` state attributes (state-driven, no JS) + +Apsara components built on Base UI expose state via `data-*` attributes. Style against them instead of tracking state yourself: + +```css +.my-sidebar[data-open] { width: 240px; } +.my-sidebar[data-closed] { width: 57px; } +.my-nav-item[data-active="true"] { background: var(--rs-color-background-neutral-secondary); } + +/* Enter/exit animation hooks provided by Base UI overlays */ +.my-overlay[data-starting-style], +.my-overlay[data-ending-style] { opacity: 0; } +``` + +Common attributes: `data-open`, `data-closed`, `data-active`, `data-disabled`, `data-state`, `data-starting-style`, `data-ending-style`. Check a component's `.mdx` page for the ones it exposes. + +## Theme-conditional styling + +`` sets `data-theme` / `data-style` / `data-accent-color` / `data-gray-color` on the root (and scope wrappers). Target them for theme-specific overrides: + +```css +[data-theme="dark"] .custom-card { border-color: var(--rs-color-border-base-tertiary); } +[data-style="traditional"] .heading { font-family: var(--rs-font-title); } +``` + +## Overriding tokens (custom palette / sizing) + +To re-skin globally or per-scope, redefine **semantic** tokens under a selector. Prefer semantic tokens over raw scale steps so the override stays theme-correct. + +```css +/* Make the danger emphasis fill a custom red across the app */ +:root, +[data-theme="light"], +[data-theme="dark"] { + --rs-color-background-accent-emphasis: var(--rs-color-viz-iris-9); +} +``` + +Scope an override to a subtree by wrapping it in a nested `` (see `theming.md`) and overriding tokens on that wrapper's class. + +## Building your own styled components + +Match Apsara's internal conventions so custom UI feels native. + +### CSS Modules for scoping + +```tsx +// status-card.tsx +import styles from "./status-card.module.css"; + +export function StatusCard({ status, children }) { + return
{children}
; +} +``` + +```css +/* status-card.module.css */ +.card { + padding: var(--rs-space-4); + border-radius: var(--rs-radius-3); + border: 1px solid var(--rs-color-border-base-primary); +} +.card-success { + border-color: var(--rs-color-border-success-emphasis); + background: var(--rs-color-background-success-primary); +} +.card-danger { + border-color: var(--rs-color-border-danger-emphasis); + background: var(--rs-color-background-danger-primary); +} +``` + +### CVA for variants + +Apsara uses [`class-variance-authority`](https://cva.style/) (a direct dependency) for type-safe variants: + +```tsx +import { cva, type VariantProps } from "class-variance-authority"; +import styles from "./tag.module.css"; + +const tag = cva(styles.tag, { + variants: { + size: { small: styles["tag-small"], medium: styles["tag-medium"] }, + color: { neutral: styles["tag-neutral"], accent: styles["tag-accent"] }, + }, + defaultVariants: { size: "medium", color: "neutral" }, +}); + +type TagProps = VariantProps & { className?: string }; +export function Tag({ size, color, className, children }: TagProps) { + return {children}; +} +``` + +## Best practices + +- **Use semantic tokens, not primitives** (`--rs-color-foreground-base-primary`, not `--rs-neutral-12`). +- **No hard-coded colors** — every color should be a token so light/dark works. +- **Spacing/radius from tokens** — `--rs-space-*`, `--rs-radius-*`. +- **Prefer component props over overrides** — reach for `variant`/`size`/`color` props before custom CSS. +- **Keep specificity low** — single class selectors; avoid `!important` and deep nesting. +- **Co-locate** `.module.css` next to its component. diff --git a/.agents/skills/apsara/references/theming.md b/.agents/skills/apsara/references/theming.md new file mode 100644 index 000000000..35948f9ce --- /dev/null +++ b/.agents/skills/apsara/references/theming.md @@ -0,0 +1,118 @@ +# Theming + +Use this for anything involving the `Theme` provider, light/dark mode, accent/gray colors, the modern/traditional style variant, reading or changing the theme at runtime, or scoping a theme to part of the tree. + +## The `Theme` provider + +`Theme` (aliased as the deprecated `ThemeProvider`) configures the active theme and writes `data-*` attributes that the token CSS keys off. Mount it once near the root (see `setup.md`). It can also be nested to scope a theme to a subtree (see "Scoped themes" below). + +```tsx +import { Theme } from "@raystack/apsara"; + + + + +``` + +### Props + +| Prop | Type | Default | Notes | +|---|---|---|---| +| `defaultTheme` | `string` | `"system"` (or `"light"` if `enableSystem` is false) | Initial theme when nothing is stored. | +| `forcedTheme` | `string` | — | Locks the page to a theme; ignores storage and system. | +| `enableSystem` | `boolean` | `true` | Follow `prefers-color-scheme` when theme is `"system"`. | +| `enableColorScheme` | `boolean` | `true` | Sets `color-scheme` so native UI (scrollbars, inputs) matches. | +| `disableTransitionOnChange` | `boolean` | `false` | Suppress CSS transitions during a theme switch (no flicker). | +| `storageKey` | `string` | `"theme"` | localStorage key for the persisted choice. | +| `themes` | `string[]` | `["light","dark"]` | Allowed theme names. | +| `attribute` | `string \| "class"` | `"data-theme"` | Which HTML attribute reflects the theme. | +| `value` | `Record` | — | Map theme name → attribute value. | +| `nonce` | `string` | — | CSP nonce for the inline no-flash script. | +| `style` | `"modern" \| "traditional"` | `"modern"` | Style variant — controls radius scale and fonts. | +| `accentColor` | `"indigo" \| "orange" \| "mint"` | `"indigo"` | Brand accent ramp. | +| `grayColor` | `"gray" \| "mauve" \| "slate" \| "sage"` | `"gray"` | Neutral ramp. | +| `onThemeChange` | `(theme, resolvedTheme) => void` | — | Fires on change (not on initial mount). `resolvedTheme` is `"light"`/`"dark"` when theme is `"system"`. | + +These attributes land on `` (root provider) and drive the tokens: + +| Attribute | Values | +|---|---| +| `data-theme` | `light`, `dark` | +| `data-style` | `modern`, `traditional` | +| `data-accent-color` | `indigo`, `orange`, `mint` | +| `data-gray-color` | `gray`, `mauve`, `slate`, `sage` | + +## Reading & changing the theme: `useTheme` + +Exported from both `@raystack/apsara` and `@raystack/apsara/hooks`. Must be called under a `Theme` provider. + +```tsx +import { useTheme } from "@raystack/apsara"; + +function ThemeToggle() { + const { theme, resolvedTheme, setTheme } = useTheme(); + return ( + + ); +} +``` + +Returned values: + +- `theme` — the active selection (may be `"system"`). +- `resolvedTheme` — the actually applied theme: `forcedTheme` if set; otherwise `"light"`/`"dark"` (resolving `"system"`); otherwise equals `theme`. **Use this for conditional UI**, not `theme`. +- `systemTheme` — OS preference (`"light"`/`"dark"`) when `enableSystem`. +- `setTheme(name)` — update + persist the nearest scope's theme. At the root this persists the user's choice. Passing `undefined` inside a scope clears that scope's storage and re-inherits from the parent. +- `style`, `accentColor`, `grayColor` — the nearest provider's effective values. +- `forcedTheme`, `themes`. + +`useTheme({ storageKey })` targets a specific provider (the root or a named scope) instead of the nearest one — useful for flipping the page theme from inside a scoped subtree. + +## `ThemeSwitcher` + +A minimal prebuilt sun/moon toggle button (toggles light↔dark via `useTheme`): + +```tsx +import { ThemeSwitcher } from "@raystack/apsara"; + + +``` + +For anything richer (system option, accent pickers), build your own control on top of `useTheme`. + +## Scoped (nested) themes + +Nesting `` scopes overrides to a subtree without affecting the rest of the page. A scope renders a wrapper `
` carrying the layered `data-*` attributes, and `useTheme()` inside it sees the scope's effective values. + +```tsx + + + + {/* This panel is always light + orange accent, regardless of the page theme */} + + + + +``` + +- A scope with a `storageKey` persists and registers itself, so `useTheme({ storageKey })` can address it. +- A stateless scope with no overrides is a transparent pass-through (no wrapper, no extra context). +- `setTheme` from a scope updates only that scope and never propagates outward. + +## Custom accent / palette beyond the presets + +The built-in `accentColor`/`grayColor` presets cover the common cases. To go further, override the `--rs-*` color tokens under a theme selector in your own CSS (see `tokens.md` for the variable names and `styling.md` for override patterns). Prefer overriding **semantic** tokens (`--rs-color-background-accent-emphasis`) over raw scale steps. + +## Theming pitfalls + +- Conditional rendering should branch on `resolvedTheme`, not `theme` (which can be `"system"`). +- `setTheme(undefined)` is a no-op at the root; inside a persistent scope it clears + re-inherits. +- Next.js: missing `suppressHydrationWarning` on `` causes hydration warnings because the no-flash script pre-sets attributes (see `setup.md`). +- Changing `accentColor`/`grayColor`/`style` at runtime just swaps `data-*` attributes — all themed tokens update automatically; you don't need to re-import CSS. diff --git a/.agents/skills/apsara/references/tokens.md b/.agents/skills/apsara/references/tokens.md new file mode 100644 index 000000000..d4b0d0e1f --- /dev/null +++ b/.agents/skills/apsara/references/tokens.md @@ -0,0 +1,235 @@ +# Design Tokens (`--rs-*`) + +Apsara's entire visual system is CSS custom properties prefixed `--rs-`, all shipped in `@raystack/apsara/style.css`. Reference them with `var(--rs-…)` in your CSS / inline styles so your UI inherits theme, accent, gray, and style-variant changes automatically. + +Live, always-current versions of each category: +`https://apsara.raystack.org/tokens/{colors,spacing,typography,effects,radius}.mdx` + +## How the token system is layered + +1. **Primitive ramps** — raw color scales, steps `1`–`12` (Radix-style), e.g. `--rs-neutral-1 … --rs-neutral-12`, `--rs-accent-1 … --rs-accent-12`, plus `-contrast` and alpha (`-a1 … -a12`). These are remapped when `data-accent-color` / `data-gray-color` change. **Don't consume primitives directly** — use semantic tokens. +2. **Semantic tokens** — meaning-based aliases that point at primitives and flip with `data-theme`, e.g. `--rs-color-foreground-base-primary`. **These are what you should use.** +3. **Scalar tokens** — spacing, radius, typography, effects. + +> **Rule of thumb:** prefer `--rs-color-foreground-base-primary` over `--rs-neutral-12`. Semantic tokens carry intent and adapt across themes; primitives do not. + +The active neutral ramp is exposed as `--rs-neutral-*` (driven by `data-gray-color`) and the active brand ramp as `--rs-accent-*` (driven by `data-accent-color`). Status ramps are `--rs-danger-*`, `--rs-success-*`, `--rs-attention-*`. + +--- + +## Color tokens (semantic) + +All semantic color tokens are defined for both `[data-theme="light"]` and `[data-theme="dark"]` and resolve automatically. + +### Foreground (text / icons) + +| Token | Purpose | +|---|---| +| `--rs-color-foreground-base-primary` | Primary text | +| `--rs-color-foreground-base-secondary` | Secondary / muted text | +| `--rs-color-foreground-base-tertiary` | Tertiary / disabled-ish text | +| `--rs-color-foreground-base-emphasis` | Text on emphasis/inverted surfaces | +| `--rs-color-foreground-accent-primary` / `-hover` | Accent-colored text | +| `--rs-color-foreground-accent-emphasis` | Text on an accent-emphasis fill | +| `--rs-color-foreground-danger-primary` / `-hover` / `-emphasis` | Danger text | +| `--rs-color-foreground-success-primary` / `-hover` / `-emphasis` | Success text | +| `--rs-color-foreground-attention-primary` / `-hover` / `-emphasis` | Attention/warning text | + +### Background (surfaces / fills) + +| Token | Purpose | +|---|---| +| `--rs-color-background-base-primary` / `-hover` | App / primary surface | +| `--rs-color-background-base-secondary` | Secondary surface (cards, raised) | +| `--rs-color-background-neutral-primary` | Neutral fill | +| `--rs-color-background-neutral-secondary` / `-hover` | Neutral fill (subtle controls) | +| `--rs-color-background-neutral-tertiary` / `-hover` | Stronger neutral fill | +| `--rs-color-background-neutral-emphasis` | High-contrast neutral fill | +| `--rs-color-background-accent-primary` | Subtle accent surface | +| `--rs-color-background-accent-emphasis` / `-hover` | Solid accent fill (primary buttons) | +| `--rs-color-background-danger-primary` | Subtle danger surface | +| `--rs-color-background-danger-emphasis` / `-hover` | Solid danger fill | +| `--rs-color-background-success-primary` | Subtle success surface | +| `--rs-color-background-success-emphasis` / `-hover` | Solid success fill | +| `--rs-color-background-attention-primary` | Subtle attention surface | +| `--rs-color-background-attention-emphasis` / `-hover` | Solid attention fill | + +### Border + +| Token | Purpose | +|---|---| +| `--rs-color-border-base-primary` | Default border | +| `--rs-color-border-base-secondary` | Subtle border | +| `--rs-color-border-base-tertiary` / `-hover` | Stronger / structural border | +| `--rs-color-border-base-focus` | Focus ring border | +| `--rs-color-border-base-emphasis` | High-contrast border | +| `--rs-color-border-accent-primary` | Accent border | +| `--rs-color-border-accent-emphasis` / `-hover` | Solid accent border | +| `--rs-color-border-danger-primary` / `-emphasis` / `-hover` | Danger borders | +| `--rs-color-border-success-primary` / `-emphasis` / `-hover` | Success borders | +| `--rs-color-border-attention-primary` / `-emphasis` / `-hover` | Attention borders | + +### Overlays + +Translucent layers for scrims, hovers, and elevation. `base` follows the theme; `black`/`white` are fixed. + +- `--rs-color-overlay-base-a1 … -a12` — theme-aware alpha overlay +- `--rs-color-overlay-black-a1 … -a12` — black at increasing opacity (`0.05`→`0.95`) +- `--rs-color-overlay-white-a1 … -a12` — white at increasing opacity (`0.05`→`0.95`) + +### Visualization (charts / categorical) + +14 hues, each with steps `6`, `8`, `9`, `11`: `--rs-color-viz--`. + +Hues: `sky`, `mint`, `lime`, `grass`, `green`, `jade`, `cyan`, `blue`, `iris`, `purple`, `pink`, `crimson`, `orange`, `gold`. Example: `var(--rs-color-viz-blue-9)`. + +### Raw scales (advanced — prefer semantic tokens) + +`--rs-neutral-{1..12}`, `--rs-accent-{1..12}` (+ `--rs-accent-contrast`), `--rs-danger-{1..12}`, `--rs-success-{1..12}`, `--rs-attention-{1..12}` (each with `-contrast`), plus theme-aware alpha `--rs-alpha-a{1..12}`. + +```css +.card { + color: var(--rs-color-foreground-base-primary); + background: var(--rs-color-background-base-secondary); + border: 1px solid var(--rs-color-border-base-primary); +} +``` + +--- + +## Spacing — `--rs-space-*` + +Use for padding, margin, and gap. Values are in `px`. + +| Token | px | | Token | px | +|---|---|---|---|---| +| `--rs-space-1` | 2 | | `--rs-space-10` | 40 | +| `--rs-space-2` | 4 | | `--rs-space-11` | 48 | +| `--rs-space-3` | 8 | | `--rs-space-12` | 56 | +| `--rs-space-4` | 12 | | `--rs-space-13` | 64 | +| `--rs-space-5` | 16 | | `--rs-space-14` | 72 | +| `--rs-space-6` | 20 | | `--rs-space-15` | 80 | +| `--rs-space-7` | 24 | | `--rs-space-16` | 96 | +| `--rs-space-8` | 28 | | `--rs-space-17` | 120 | +| `--rs-space-9` | 32 | | | | + +> Layout components (`Flex`, `Grid`, `Box`) accept numeric `gap`/`padding` props that map to this scale (e.g. `gap={4}` → `--rs-space-4` = 12px). + +--- + +## Radius — `--rs-radius-*` + +Radius depends on `data-style`. `modern` is the default; `traditional` is rounder. + +| Token | `modern` | `traditional` | +|---|---|---| +| `--rs-radius-1` | 2px | 8px | +| `--rs-radius-2` | 4px | 16px | +| `--rs-radius-3` | 6px | 20px | +| `--rs-radius-4` | 8px | 24px | +| `--rs-radius-5` | 12px | 32px | +| `--rs-radius-6` | 16px | 40px | +| `--rs-radius-full` | 800px | 1600px | + +--- + +## Typography — `--rs-font-*` / `--rs-line-height-*` / `--rs-letter-spacing-*` + +### Families & weights + +| Token | Value | +|---|---| +| `--rs-font-body` | Inter (sans) in `modern`; Josefin Sans in `traditional` | +| `--rs-font-title` | Inter in `modern`; Lora (serif) in `traditional` | +| `--rs-font-mono` | Menlo / JetBrains Mono / monospace | +| `--rs-font-weight-regular` | 400 | +| `--rs-font-weight-medium` | 500 | + +### Body text sizes (paired size / line-height / letter-spacing) + +| Scale | size | line-height | letter-spacing | +|---|---|---|---| +| `micro` | 10px | 12px | 0.5px | +| `mini` | 11px | 16px | 0.5px | +| `small` | 12px | 16px | 0.4px | +| `regular` | 14px | 20px | 0.25px | +| `large` | 16px | 24px | 0.5px | + +Tokens: `--rs-font-size-`, `--rs-line-height-`, `--rs-letter-spacing-`. + +### Title sizes + +| Scale | size | line-height | +|---|---|---| +| `t1` | 20px | 24px | +| `t2` | 24px | 32px | +| `t3` | 28px | 36px | +| `t4` | 32px | 40px | + +Tokens: `--rs-font-size-t{1..4}`, `--rs-line-height-t{1..4}`, `--rs-letter-spacing-t{1..4}`. + +### Mono sizes + +`mini` (11/16), `small` (12/16), `regular` (14/20), `large` (16/24) — tokens `--rs-font-size-mono-`, `--rs-line-height-mono-`, `--rs-letter-spacing-mono-`. + +> Prefer the `Text`, `Headline`, and `Label` components, whose `size`/`weight` props map to these tokens, over hand-applying typography tokens. + +--- + +## Effects — shadows, transitions, blurs + +### Shadows (theme-aware: deeper in dark mode) + +| Token | Tier | +|---|---| +| `--rs-shadow-feather` | sm | +| `--rs-shadow-soft` | md | +| `--rs-shadow-lifted` | lg | +| `--rs-shadow-floating` | xl (overlays/popovers) | +| `--rs-shadow-inset` | inset | + +```css +.popover { box-shadow: var(--rs-shadow-floating); } +``` + +### Transitions + +- `--rs-transition-interactive` — standard 0.2s ease transition for `background-color`, `border-color`, `color`, `box-shadow`, `opacity`. Use it on interactive custom elements for consistent motion. + +### Blurs (for `backdrop-filter`) + +| Token | Value | +|---|---| +| `--rs-blur-sm` | blur(0.5px) | +| `--rs-blur-md` | blur(1px) | +| `--rs-blur-lg` | blur(2px) | +| `--rs-blur-xl` | blur(4px) | + +```css +.scrim { backdrop-filter: var(--rs-blur-md); } +``` + +--- + +## Theme `data-*` attributes (for state/theme-conditional CSS) + +Set by `` on the document root (and on scope wrappers): + +| Attribute | Values | +|---|---| +| `data-theme` | `light`, `dark` | +| `data-style` | `modern`, `traditional` | +| `data-accent-color` | `indigo`, `orange`, `mint` | +| `data-gray-color` | `gray`, `mauve`, `slate`, `sage` | + +```css +[data-theme="dark"] .custom-card { border-color: var(--rs-color-border-base-tertiary); } +[data-style="traditional"] .hero { font-family: var(--rs-font-title); } +``` + +## Token usage rules + +- Use semantic color tokens, not raw scales or hex. +- Use `--rs-space-*` for spacing; `--rs-radius-*` for corners; effect tokens for shadows/blurs. +- Don't hard-code colors that should follow the theme. +- To customize a palette, override semantic tokens under a theme selector (see `styling.md`) rather than editing primitives ad hoc. diff --git a/.claude/skills/add-new-component b/.claude/skills/add-new-component new file mode 120000 index 000000000..f78e6d7c0 --- /dev/null +++ b/.claude/skills/add-new-component @@ -0,0 +1 @@ +../../.agents/skills/add-new-component \ No newline at end of file