diff --git a/apps/www/src/components/mdx/mdx-components.module.css b/apps/www/src/components/mdx/mdx-components.module.css index fcf076049..484c419f4 100644 --- a/apps/www/src/components/mdx/mdx-components.module.css +++ b/apps/www/src/components/mdx/mdx-components.module.css @@ -58,3 +58,52 @@ :global(.prose) ol:not([data-demo] ol) li:first-child { margin-top: var(--rs-space-3); } + +/* Markdown table styles */ +.prose-table { + width: 100%; + border: 0.5px solid var(--rs-color-border-base-primary); + border-radius: var(--rs-radius-2); + overflow: hidden; + margin: var(--rs-space-5) 0; + border-collapse: separate; + border-spacing: 0; +} + +.prose-table thead { + background: var(--rs-color-background-base-secondary); +} + +.prose-table th { + text-align: left; + padding: var(--rs-space-3) var(--rs-space-4); + font-weight: var(--rs-font-weight-medium); + font-size: var(--rs-font-size-small); + line-height: var(--rs-line-height-small); + letter-spacing: var(--rs-letter-spacing-small); + color: var(--rs-color-foreground-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.prose-table td { + padding: var(--rs-space-3) var(--rs-space-4); + font-size: var(--rs-font-size-small); + line-height: var(--rs-line-height-small); + color: var(--rs-color-foreground-base-secondary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.prose-table tbody tr:last-child td { + border-bottom: none; +} + +.prose-table tbody tr:hover { + background: var(--rs-color-background-base-primary-hover); +} + +.prose-table code { + font-family: var(--rs-font-mono); + font-size: var(--rs-font-size-mono-small); + line-height: var(--rs-line-height-mono-small); + color: var(--rs-color-foreground-base-primary); +} diff --git a/apps/www/src/components/mdx/mdx-components.tsx b/apps/www/src/components/mdx/mdx-components.tsx index fb1766f40..8c1b35014 100644 --- a/apps/www/src/components/mdx/mdx-components.tsx +++ b/apps/www/src/components/mdx/mdx-components.tsx @@ -11,6 +11,7 @@ import type { TableHTMLAttributes } from 'react'; import Demo from '../demo'; +import { TokenTable } from '../tokentable'; import { TypeTable } from '../typetable'; import { Code } from './code'; import styles from './mdx-components.module.css'; @@ -33,8 +34,11 @@ function Image( function Table(props: TableHTMLAttributes) { return ( -
- +
+
); } @@ -117,6 +121,9 @@ const mdxComponents = { className={cx(styles['prose-type-table'], props.className)} /> ), + TokenTable: (props: ComponentPropsWithoutRef) => ( + + ), Demo }; diff --git a/apps/www/src/components/tokentable/index.ts b/apps/www/src/components/tokentable/index.ts new file mode 100644 index 000000000..2e2a0cf53 --- /dev/null +++ b/apps/www/src/components/tokentable/index.ts @@ -0,0 +1,6 @@ +export { + type TokenItem, + TokenTable, + type TokenTableProps, + type TokenType +} from './tokentable'; diff --git a/apps/www/src/components/tokentable/tokentable.module.css b/apps/www/src/components/tokentable/tokentable.module.css new file mode 100644 index 000000000..2174162bc --- /dev/null +++ b/apps/www/src/components/tokentable/tokentable.module.css @@ -0,0 +1,237 @@ +.container { + display: flex; + flex-direction: column; + border: 0.5px solid var(--rs-color-border-base-primary); + border-radius: var(--rs-radius-2); + overflow: hidden; + margin: var(--rs-space-5) 0; +} + +.header { + display: flex; + align-items: center; + gap: var(--rs-space-4); + height: 36px; + padding: 0 var(--rs-space-4); + background: var(--rs-color-background-base-secondary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); + font-weight: var(--rs-font-weight-medium); + font-size: var(--rs-font-size-small); + line-height: var(--rs-line-height-small); + letter-spacing: var(--rs-letter-spacing-small); + color: var(--rs-color-foreground-base-primary); +} + +.previewLabel { + width: 40px; + flex-shrink: 0; +} + +.previewLabelWide { + width: 130px; + flex-shrink: 0; +} + +.tokenLabel { + width: 280px; + flex-shrink: 0; +} + +.tokenLabelWide { + width: 360px; + flex-shrink: 0; +} + +.valueLabel { + min-width: 80px; + flex-shrink: 0; +} + +.descriptionLabel { + flex: 1; +} + +.body { + display: flex; + flex-direction: column; +} + +.row { + display: flex; + align-items: center; + gap: var(--rs-space-4); + padding: var(--rs-space-3) var(--rs-space-4); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); + transition: background-color 0.15s ease; +} + +.row:last-child { + border-bottom: none; +} + +.row:hover { + background: var(--rs-color-background-base-primary-hover); +} + +.previewCell { + width: 40px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.previewCellWide { + width: 130px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-start; +} + +.tokenCell { + width: 280px; + flex-shrink: 0; + display: flex; + align-items: center; +} + +.tokenCellWide { + width: 360px; + flex-shrink: 0; + display: flex; + align-items: center; +} + +.valueCell { + min-width: 80px; + flex-shrink: 0; +} + +.descriptionCell { + flex: 1; +} + +.tokenName { + font-family: var(--rs-font-mono); + font-size: var(--rs-font-size-mono-small); + line-height: var(--rs-line-height-mono-small); + color: var(--rs-color-foreground-base-primary); + background: transparent; + padding: 0; +} + +.tokenValue { + font-family: var(--rs-font-mono); + font-size: var(--rs-font-size-mono-small); + line-height: var(--rs-line-height-mono-small); + color: var(--rs-color-foreground-base-secondary); + background: transparent; + padding: 0; +} + +.description { + color: var(--rs-color-foreground-base-secondary); +} + +.copyButton { + opacity: 0; + transition: opacity 0.15s ease; +} + +.row:hover .copyButton, +.row:focus-within .copyButton, +.copyButton:focus-visible { + opacity: 1; +} + +/* Preview styles */ +.colorPreview { + width: 24px; + height: 24px; + border-radius: var(--rs-radius-2); + border: 1px solid var(--rs-color-border-base-secondary); + flex-shrink: 0; +} + +.spacingPreviewContainer { + width: 120px; + height: 24px; + display: flex; + align-items: center; + justify-content: flex-start; + background: var(--rs-color-background-neutral-secondary); + border-radius: var(--rs-radius-1); + overflow: hidden; +} + +.spacingPreview { + height: 100%; + min-width: 2px; + background: var(--rs-color-background-accent-emphasis); + border-radius: var(--rs-radius-1); +} + +.radiusPreview { + width: 24px; + height: 24px; + background: var(--rs-color-background-accent-emphasis); + flex-shrink: 0; +} + +.shadowPreview { + width: 20px; + height: 20px; + background: var(--rs-color-background-base-secondary); + border-radius: var(--rs-radius-2); + flex-shrink: 0; +} + +.blurPreviewContainer { + width: 24px; + height: 24px; + background: linear-gradient( + 135deg, + var(--rs-color-background-accent-emphasis) 0%, + var(--rs-color-background-danger-emphasis) 50%, + var(--rs-color-background-success-emphasis) 100% + ); + border-radius: var(--rs-radius-2); + position: relative; + overflow: hidden; +} + +.blurPreview { + position: absolute; + inset: 0; + background: var(--rs-color-overlay-white-a4); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .header { + display: none; + } + + .row { + flex-direction: column; + align-items: flex-start; + gap: var(--rs-space-2); + padding: var(--rs-space-4); + } + + .previewCell, + .tokenCell, + .valueCell, + .descriptionCell { + width: 100%; + } + + .previewCell { + justify-content: flex-start; + } + + .copyButton { + opacity: 1; + } +} diff --git a/apps/www/src/components/tokentable/tokentable.tsx b/apps/www/src/components/tokentable/tokentable.tsx new file mode 100644 index 000000000..7e4f02bc8 --- /dev/null +++ b/apps/www/src/components/tokentable/tokentable.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { CopyButton, Flex, Text } from '@raystack/apsara'; +import { cx } from 'class-variance-authority'; +import styles from './tokentable.module.css'; + +export type TokenType = + | 'color' + | 'spacing' + | 'radius' + | 'shadow' + | 'blur' + | 'typography' + | 'default'; + +export interface TokenItem { + /** The CSS variable name */ + name: string; + /** The token value (e.g., "4px", "#ffffff") - optional for semantic tokens */ + value?: string; + /** Description of the token's use case */ + description?: string; +} + +export interface TokenTableProps { + /** Array of token items to display */ + tokens: TokenItem[]; + /** Type of tokens for visual preview */ + type?: TokenType; + /** Additional CSS class name */ + className?: string; +} + +function TokenPreview({ token, type }: { token: TokenItem; type: TokenType }) { + switch (type) { + case 'color': + return ( +
+ ); + case 'spacing': + return ( +
+
+
+ ); + case 'radius': + return ( +
+ ); + case 'shadow': + return ( +
+ ); + case 'blur': + return ( +
+
+
+ ); + case 'typography': + return null; + default: + return null; + } +} + +export function TokenTable({ + tokens, + type = 'default', + className +}: TokenTableProps) { + const showPreview = type !== 'default' && type !== 'typography'; + const showValue = tokens.some(token => token.value); + const isSpacing = type === 'spacing'; + + return ( +
+
+ {showPreview && ( + + )} + + Token + + {showValue && Value} + Description +
+
+ {tokens.map(token => ( +
+ {showPreview && ( +
+ +
+ )} +
+ + {token.name} + + +
+ {showValue && ( +
+ {token.value} +
+ )} +
+ + {token.description} + +
+
+ ))} +
+
+ ); +} diff --git a/apps/www/src/content/docs/(overview)/getting-started.mdx b/apps/www/src/content/docs/(overview)/getting-started.mdx index 80ff7570d..e1bd838cb 100644 --- a/apps/www/src/content/docs/(overview)/getting-started.mdx +++ b/apps/www/src/content/docs/(overview)/getting-started.mdx @@ -3,46 +3,163 @@ title: Getting Started description: A quick tutorial to get you up and running with Apsara. --- -Apsara is a pre-styled component library that is designed to work out of the box with minimal configuration. +This guide walks you through installing Apsara and building your first component. -## Installation +## Prerequisites + +Apsara requires: -Getting up and running is quick and easy. +- **Node.js** 18 or later +- **React** 18 or 19 -### 1. Install Apsara +## Installation -Install Apsara from your command line. +Install the package using your preferred package manager: ```package-install @raystack/apsara ``` -### 2. Import the CSS file +## Setup -Import the global CSS file at the root of your application. +### 1. Import styles + +Add the CSS import at the root of your application, before any component renders: ```ts import "@raystack/apsara/style.css"; ``` -#### Using normalize.css +This single stylesheet includes all component styles and CSS custom properties (tokens) for theming. + +### 2. Add ThemeProvider + +Wrap your application with `ThemeProvider` to enable theming support: + +```tsx +import { ThemeProvider } from "@raystack/apsara"; + +function App() { + return ( + + + + ); +} +``` + +The `defaultTheme` prop accepts `"light"`, `"dark"`, or `"system"` (follows OS preference). -Apsara includes an optional `normalize.css` stylesheet to help ensure consistency across different browsers. Unlike a CSS reset, `normalize.css` preserves useful default styles while correcting inconsistencies in how browsers render HTML elements. +### 3. Use components -If you'd like to enable it, simply import the provided stylesheet in your project: +Import and use components directly: + +```tsx +import { Button, Flex, Text } from "@raystack/apsara"; + +function Example() { + return ( + + Welcome to Apsara + + + + + + ); +} +``` + +## Framework Setup + +### Next.js (App Router) + +Add the provider to your root layout: + +```tsx +// app/layout.tsx +import { ThemeProvider } from "@raystack/apsara"; +import "@raystack/apsara/style.css"; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ); +} +``` + +The `suppressHydrationWarning` attribute is required because `ThemeProvider` injects a script to prevent theme flash during hydration. + +### Vite + +Add the provider to your main entry file: + +```tsx +// main.tsx +import React from "react"; +import ReactDOM from "react-dom/client"; +import { ThemeProvider } from "@raystack/apsara"; +import "@raystack/apsara/style.css"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); +``` + +## Optional: Normalize CSS + +Apsara includes an optional normalize stylesheet that ensures consistent rendering across browsers while preserving useful defaults: ```ts import "@raystack/apsara/normalize.css"; +import "@raystack/apsara/style.css"; ``` -### 3. Start building +Import it before the main stylesheet if you choose to use it. + +## Importing Icons -You are now ready to use Apsara components. +Apsara exports a set of icons that can be imported separately: -```jsx +```tsx import { Button } from "@raystack/apsara"; +import { MagnifyingGlassIcon, Cross2Icon } from "@raystack/apsara/icons"; -; + ``` + +## Importing Hooks + +Utility hooks are available from a dedicated export: + +```tsx +import { Button } from "@raystack/apsara"; +import { useTheme } from "@raystack/apsara/hooks"; + +function ThemeToggle() { + const { resolvedTheme, setTheme } = useTheme(); + + return ( + + ); +} +``` + +## Next Steps + +- [Theme Overview](/theme/overview) — Configure colors, spacing, and style variants +- [Button](/components/button) — Start with a common component +- [DataTable](/components/datatable) — Build data-rich interfaces diff --git a/apps/www/src/content/docs/(overview)/index.mdx b/apps/www/src/content/docs/(overview)/index.mdx index 19c9e530b..0ab0d62b8 100644 --- a/apps/www/src/content/docs/(overview)/index.mdx +++ b/apps/www/src/content/docs/(overview)/index.mdx @@ -3,7 +3,68 @@ title: Introduction description: A design system for building accessible and composable user interfaces in React. --- -Apsara is a design system that simplifies React application development. It offers a collection -of carefully crafted components that work seamlessly together, are accessible out of the box, -and help create consistent user interfaces quickly. +Apsara is an open-source React component library that provides enterprise-grade, accessible components for building complex data interfaces. Built on [Radix UI](https://www.radix-ui.com/) primitives and [Base UI](https://base-ui.com/), it combines unstyled accessibility foundations with a carefully designed visual layer powered by CSS custom properties. +## Why Apsara? + +**Accessibility first** — Every component is built on battle-tested primitives that handle ARIA attributes, focus management, and keyboard navigation correctly. You get accessible UI without the effort. + +**Consistent design language** — A unified token system ensures visual consistency across your application. Colors, spacing, typography, and effects all follow predictable patterns that adapt automatically to light and dark modes. + +**Production ready** — Components are designed for real-world applications: data tables with sorting and filtering, command palettes, multi-select comboboxes, and complex form controls that handle edge cases gracefully. + +**Minimal footprint** — No runtime CSS-in-JS. Styles are vanilla CSS with semantic tokens, keeping your bundle lean and your performance predictable. + +## Components + +Apsara provides over 50 components organized by function: + +| Category | Components | +|----------|------------| +| **Layout** | Box, Flex, Grid, Container, Separator | +| **Navigation** | Navbar, Sidebar, Breadcrumb, Tabs, Link | +| **Data Display** | DataTable, Table, List, Avatar, Badge, Chip, Indicator | +| **Forms** | Button, InputField, TextArea, Select, Combobox, Checkbox, Radio, Switch, Slider, ColorPicker, Calendar | +| **Feedback** | Dialog, Sheet, Popover, Tooltip, Toast, Callout, EmptyState, Skeleton, Spinner | +| **Utilities** | Command, Search, CopyButton, CodeBlock, ScrollArea | + +## Theming + +The theming system uses CSS custom properties (tokens) that automatically adapt to the active theme. Wrap your app with `ThemeProvider` to enable light/dark modes, accent colors, and style variants. + +```tsx +import { ThemeProvider } from "@raystack/apsara"; + + + + +``` + +Tokens follow a consistent naming convention: + +```css +.custom-element { + color: var(--rs-color-foreground-base-primary); + background: var(--rs-color-background-base-secondary); + padding: var(--rs-space-4); + border-radius: var(--rs-radius-3); +} +``` + +See the [Theme Overview](/theme/overview) for complete documentation. + +## Technology + +Apsara is built with: + +- **[Radix UI](https://www.radix-ui.com/)** — Unstyled, accessible component primitives +- **[Base UI](https://base-ui.com/)** — Headless UI components from MUI +- **[TanStack Table](https://tanstack.com/table)** — Powerful data table utilities +- **[class-variance-authority](https://cva.style/)** — Type-safe component variants +- **TypeScript** — Full type definitions for all components and props + +## Next Steps + +- [Getting Started](/getting-started) — Install Apsara and build your first component +- [Theme Overview](/theme/overview) — Learn about the theming system +- [Components](/components/button) — Explore the component library diff --git a/apps/www/src/content/docs/components/fliter-chip/demo.ts b/apps/www/src/content/docs/components/filter-chip/demo.ts similarity index 100% rename from apps/www/src/content/docs/components/fliter-chip/demo.ts rename to apps/www/src/content/docs/components/filter-chip/demo.ts diff --git a/apps/www/src/content/docs/components/fliter-chip/index.mdx b/apps/www/src/content/docs/components/filter-chip/index.mdx similarity index 100% rename from apps/www/src/content/docs/components/fliter-chip/index.mdx rename to apps/www/src/content/docs/components/filter-chip/index.mdx diff --git a/apps/www/src/content/docs/components/fliter-chip/props.ts b/apps/www/src/content/docs/components/filter-chip/props.ts similarity index 100% rename from apps/www/src/content/docs/components/fliter-chip/props.ts rename to apps/www/src/content/docs/components/filter-chip/props.ts diff --git a/apps/www/src/content/docs/meta.json b/apps/www/src/content/docs/meta.json index 617294532..8267e5fcb 100644 --- a/apps/www/src/content/docs/meta.json +++ b/apps/www/src/content/docs/meta.json @@ -1,3 +1,3 @@ { - "pages": ["(overview)", "components"] + "pages": ["(overview)", "theme", "components"] } diff --git a/apps/www/src/content/docs/theme/colors/index.mdx b/apps/www/src/content/docs/theme/colors/index.mdx new file mode 100644 index 000000000..554e87f22 --- /dev/null +++ b/apps/www/src/content/docs/theme/colors/index.mdx @@ -0,0 +1,228 @@ +--- +title: Colors +description: Overview of the color system and semantic color tokens in Apsara. +--- + +Apsara uses a semantic color system that provides meaningful, context-aware colors for your interface. Rather than using fixed color values, semantic tokens represent the intended purpose of a color—such as "primary text" or "danger background"—and automatically resolve to the appropriate value based on the active theme and style. This abstraction allows you to build consistent, themeable interfaces by focusing on what a color represents rather than what it looks like. + +## Foreground Colors + +Foreground colors are used for text, icons, and other content elements. + + + +## Background Colors + +Background colors define surfaces and containers in your interface. + + + +## Border Colors + +Border colors define boundaries and separators. + + + +## Overlay Colors + +Overlay colors are used for modals, tooltips, and layered elements. + + + +## Visualization Colors + +A palette of colors designed for data visualization, charts, and graphs. + + + +## Usage + +```css +/* Custom status indicator */ +.status-badge { + padding: var(--rs-space-1) var(--rs-space-3); + border-radius: var(--rs-radius-full); +} + +.status-badge[data-status="active"] { + background-color: var(--rs-color-background-success-primary); + color: var(--rs-color-foreground-success-primary); +} + +.status-badge[data-status="pending"] { + background-color: var(--rs-color-background-attention-primary); + color: var(--rs-color-foreground-attention-primary); +} + +.status-badge[data-status="error"] { + background-color: var(--rs-color-background-danger-primary); + color: var(--rs-color-foreground-danger-primary); +} + +/* Data table with hover states */ +.data-table-row { + border-bottom: 1px solid var(--rs-color-border-base-primary); +} + +.data-table-row:hover { + background-color: var(--rs-color-background-base-primary-hover); +} + +.data-table-cell-muted { + color: var(--rs-color-foreground-base-tertiary); +} + +/* Metric card with visualization */ +.metric-card { + background: var(--rs-color-background-base-secondary); + border: 1px solid var(--rs-color-border-base-primary); +} + +.metric-value { + color: var(--rs-color-foreground-base-primary); +} + +.metric-trend-up { + color: var(--rs-color-foreground-success-primary); +} + +.metric-trend-down { + color: var(--rs-color-foreground-danger-primary); +} + +/* Sidebar navigation */ +.nav-item { + color: var(--rs-color-foreground-base-secondary); +} + +.nav-item:hover { + background-color: var(--rs-color-background-neutral-secondary); + color: var(--rs-color-foreground-base-primary); +} + +.nav-item[data-active="true"] { + background-color: var(--rs-color-background-accent-primary); + color: var(--rs-color-foreground-accent-primary); +} +``` + +## Best Practices + +- **Use semantic tokens** - Prefer semantic color tokens over raw color values for consistency and theming support +- **Match foreground with background** - Use corresponding foreground colors when placing text on semantic backgrounds (e.g., `foreground-accent-emphasis` on `background-accent-emphasis`) +- **Respect color hierarchy** - Use primary colors for main content, secondary for supporting content, and tertiary for subtle elements +- **Test in both themes** - Ensure your color choices work well in both light and dark modes diff --git a/apps/www/src/content/docs/theme/effects/index.mdx b/apps/www/src/content/docs/theme/effects/index.mdx new file mode 100644 index 000000000..bec73353c --- /dev/null +++ b/apps/www/src/content/docs/theme/effects/index.mdx @@ -0,0 +1,107 @@ +--- +title: Effects +description: Overview of shadow and blur effect tokens in Apsara. +--- + +Effects add depth and visual hierarchy to your interface through shadows and blur. These tokens are calibrated to work consistently across light and dark themes—shadows automatically adjust their intensity and color to maintain appropriate contrast in each mode. Using effect tokens ensures your custom surfaces and overlays integrate naturally with the rest of your interface. + +## Shadows + +Shadow tokens create elevation and depth, helping users understand the layering of interface elements. + + + +## Blur Effects + +Blur tokens are used for backdrop effects and creating depth through transparency. + + + +## Usage + +```css +/* Dashboard widget with hover interaction */ +.dashboard-widget { + box-shadow: var(--rs-shadow-feather); + transition: box-shadow 0.2s ease; +} + +.dashboard-widget:hover { + box-shadow: var(--rs-shadow-soft); +} + +/* Floating action panel */ +.action-panel { + position: fixed; + bottom: var(--rs-space-6); + right: var(--rs-space-6); + box-shadow: var(--rs-shadow-floating); +} + +/* Command palette / spotlight search */ +.command-palette { + box-shadow: var(--rs-shadow-floating); + backdrop-filter: var(--rs-blur-xl); + background-color: var(--rs-color-overlay-white-a8); +} + +/* Sticky header with scroll shadow */ +.sticky-header { + position: sticky; + top: 0; + box-shadow: var(--rs-shadow-feather); + backdrop-filter: var(--rs-blur-lg); + background-color: var(--rs-color-overlay-white-a6); +} + +/* Context menu */ +.context-menu { + box-shadow: var(--rs-shadow-lifted); +} + +/* Pressed/active toggle state */ +.toggle[data-pressed="true"] { + box-shadow: var(--rs-shadow-inset); +} + +/* Image lightbox overlay */ +.lightbox-backdrop { + backdrop-filter: var(--rs-blur-xl); + background-color: var(--rs-color-overlay-black-a8); +} + +/* Notification toast */ +.toast { + box-shadow: var(--rs-shadow-lifted); +} + +/* Draggable item while dragging */ +.draggable[data-dragging="true"] { + box-shadow: var(--rs-shadow-floating); +} +``` + +## Best Practices + +- **Use shadows sparingly** - Too many shadows can make interfaces feel cluttered +- **Match elevation to importance** - Higher elevation shadows for more prominent elements +- **Consider theme context** - Shadows are automatically adjusted for dark theme +- **Combine with blur** - Use blur effects with overlays for a modern frosted glass aesthetic +- **Animate transitions** - Smooth shadow transitions on hover create polished interactions diff --git a/apps/www/src/content/docs/theme/meta.json b/apps/www/src/content/docs/theme/meta.json new file mode 100644 index 000000000..0b2d1fb8b --- /dev/null +++ b/apps/www/src/content/docs/theme/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Theme", + "pages": ["overview", "colors", "typography", "spacing", "radius", "effects"] +} diff --git a/apps/www/src/content/docs/theme/overview/index.mdx b/apps/www/src/content/docs/theme/overview/index.mdx new file mode 100644 index 000000000..5d72b418b --- /dev/null +++ b/apps/www/src/content/docs/theme/overview/index.mdx @@ -0,0 +1,134 @@ +--- +title: Overview +description: Understanding the Apsara theming system and ThemeProvider. +--- + +Apsara provides a theming system built on CSS custom properties (tokens). Tokens are semantic variables that automatically resolve to appropriate values based on the active theme—so your UI adapts seamlessly when users switch between light and dark modes or when you change accent colors, without any code changes. + +## Installation + +Wrap your application with the `ThemeProvider` component: + +```tsx +import { ThemeProvider } from "@raystack/apsara"; + +function App() { + return ( + + + + ); +} +``` + +## Customization + +The ThemeProvider accepts props to control the visual identity of your application. Combine `style` variants with `accentColor` and `grayColor` to create distinct aesthetics—from sharp and technical to warm and editorial. The `defaultTheme` prop controls light/dark mode, with `system` respecting the user's OS preference. + +```tsx +// Clean, technical aesthetic + + +// Warm, editorial feel + + +// Vibrant and fresh + +``` + +See [API Reference](#api-reference) for all available props and options. + +## Tokens + +Tokens follow two naming patterns: + +**Semantic tokens** — for context-aware values that adapt to theme: +``` +--rs-{category}-{property}-{variant}-{state} +``` + +**Scale tokens** — for numerical progressions: +``` +--rs-{category}-{step} +``` + +**Examples:** +- `--rs-color-foreground-base-primary` — primary text color +- `--rs-color-background-accent-emphasis` — accent button background +- `--rs-space-5` — 16px spacing +- `--rs-radius-3` — medium border radius +- `--rs-shadow-lifted` — elevated shadow + +**Using tokens in CSS:** + +```css +.custom-card { + background: var(--rs-color-background-base-secondary); + border: 1px solid var(--rs-color-border-base-primary); + border-radius: var(--rs-radius-4); + padding: var(--rs-space-5); + box-shadow: var(--rs-shadow-feather); +} +``` + +**Token Categories:** +- [Colors](/theme/colors) — foreground, background, border, and overlay colors +- [Spacing](/theme/spacing) — consistent scale from 2px to 120px +- [Radius](/theme/radius) — border radius that adapts to style variants +- [Typography](/theme/typography) — font families, sizes, weights, and line heights +- [Effects](/theme/effects) — shadows and blur for depth and elevation + +## API Reference + +### ThemeProvider + +The `ThemeProvider` component wraps your application and manages theme state. It handles persisting the user's preference to localStorage, syncing with system preferences, and injecting the appropriate CSS variables into the document. + + + +### useTheme + +The `useTheme` hook provides access to the current theme state and methods to change it. Use this to build theme toggles, read the resolved theme for conditional rendering, or sync with external systems. + +```tsx +import { useTheme } from "@raystack/apsara"; + +function ThemeToggle() { + const { theme, setTheme, resolvedTheme } = useTheme(); + + return ( + + ); +} +``` + + + +## Framework Integration + +**HTML Attributes** — the ThemeProvider sets data attributes on the document element for CSS targeting: +- `data-theme` — current color scheme (`light` | `dark`) +- `data-style` — active style variant (`modern` | `traditional`) +- `data-accent-color` — active accent color (`indigo` | `orange` | `mint`) +- `data-gray-color` — active gray variant (`gray` | `mauve` | `slate`) + +**SSR & Flash Prevention** — the ThemeProvider includes an inline script that runs before React hydration to prevent flash of incorrect theme. For SSR frameworks, include the provider in your root layout: + +```tsx +// Next.js App Router: app/layout.tsx +import { ThemeProvider } from "@raystack/apsara"; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} +``` + +The `suppressHydrationWarning` is required because the theme script modifies the HTML element before React hydrates. diff --git a/apps/www/src/content/docs/theme/overview/props.ts b/apps/www/src/content/docs/theme/overview/props.ts new file mode 100644 index 000000000..e76751474 --- /dev/null +++ b/apps/www/src/content/docs/theme/overview/props.ts @@ -0,0 +1,93 @@ +export type ThemeProviderProps = { + /** + * Default theme on first load + * @defaultValue "system" + */ + defaultTheme?: string; + + /** + * List of available theme names + * @defaultValue ["light", "dark"] + */ + themes?: string[]; + + /** Force a specific theme, ignoring user preference */ + forcedTheme?: string; + + /** + * Enable system theme detection + * @defaultValue true + */ + enableSystem?: boolean; + + /** + * localStorage key for persisting theme + * @defaultValue "theme" + */ + storageKey?: string; + + /** + * HTML attribute to set on document element + * @defaultValue "data-theme" + */ + attribute?: string | 'class'; + + /** + * Visual style variant + * @defaultValue "modern" + */ + style?: 'modern' | 'traditional'; + + /** + * Primary accent color + * @defaultValue "indigo" + */ + accentColor?: 'indigo' | 'orange' | 'mint'; + + /** + * Neutral gray color variant + * @defaultValue "gray" + */ + grayColor?: 'gray' | 'mauve' | 'slate'; + + /** + * Disable CSS transitions when switching themes + * @defaultValue false + */ + disableTransitionOnChange?: boolean; + + /** + * Set color-scheme CSS property for native elements + * @defaultValue true + */ + enableColorScheme?: boolean; + + /** Nonce string for CSP headers */ + nonce?: string; + + /** Mapping of theme name to HTML attribute value */ + value?: { [themeName: string]: string }; + + /** React children */ + children?: React.ReactNode; +}; + +export type UseThemeProps = { + /** Current theme name ("light", "dark", or "system") */ + theme?: string; + + /** Function to change the theme */ + setTheme: (theme: string) => void; + + /** Resolved theme ("light" or "dark"), useful when theme is "system" */ + resolvedTheme?: string; + + /** System preference, regardless of current theme */ + systemTheme?: 'light' | 'dark'; + + /** List of all available themes */ + themes: string[]; + + /** Forced theme if set, otherwise undefined */ + forcedTheme?: string; +}; diff --git a/apps/www/src/content/docs/theme/radius/index.mdx b/apps/www/src/content/docs/theme/radius/index.mdx new file mode 100644 index 000000000..57d1eaba5 --- /dev/null +++ b/apps/www/src/content/docs/theme/radius/index.mdx @@ -0,0 +1,130 @@ +--- +title: Radius +description: Overview of the border radius scale and style variants in Apsara. +--- + +Border radius tokens control the roundness of corners throughout your interface. These tokens automatically adjust based on the active style variant—modern style uses sharp, minimal radii while traditional style uses softer, more rounded corners. By using radius tokens instead of fixed values, your custom components will seamlessly adapt when the style changes. + +## Radius Scale + +The radius scale provides 7 tokens for different levels of roundness, from subtle to fully circular. + +### Modern Style (Default) + + + +### Traditional Style + +When using `data-style="traditional"`, the radius values increase for a softer, more rounded appearance. + + + +## Usage + +```css +/* Dashboard widget container */ +.widget { + border-radius: var(--rs-radius-4); + overflow: hidden; +} + +.widget-header { + border-radius: var(--rs-radius-4) var(--rs-radius-4) 0 0; +} + +/* Filter tags with remove button */ +.filter-tag { + border-radius: var(--rs-radius-full); + display: inline-flex; + align-items: center; + gap: var(--rs-space-2); +} + +/* Nested card with image */ +.preview-card { + border-radius: var(--rs-radius-4); +} + +.preview-card-image { + border-radius: var(--rs-radius-3); +} + +/* Search results dropdown */ +.search-results { + border-radius: var(--rs-radius-3); +} + +.search-result-item:first-child { + border-radius: var(--rs-radius-3) var(--rs-radius-3) 0 0; +} + +.search-result-item:last-child { + border-radius: 0 0 var(--rs-radius-3) var(--rs-radius-3); +} + +/* User avatar with status indicator */ +.avatar-wrapper { + position: relative; +} + +.avatar-image { + border-radius: var(--rs-radius-full); +} + +.avatar-status { + border-radius: var(--rs-radius-full); + position: absolute; + bottom: 0; + right: 0; +} + +/* Segmented control */ +.segmented-control { + border-radius: var(--rs-radius-3); +} + +.segmented-control-item[data-active="true"] { + border-radius: var(--rs-radius-2); +} +``` + +## Enabling Style Variants + +To switch between modern and traditional styles, set the `data-style` attribute on your root element: + +```html + + + + + +``` + +## Best Practices + +- **Be consistent** - Use the same radius token for similar elements throughout your application +- **Match element size** - Use smaller radius values for smaller elements, larger values for containers +- **Use radius-full for circles** - When you need perfectly circular elements, use `--rs-radius-full` +- **Consider style context** - Choose modern for sharp, contemporary designs; traditional for softer, friendly aesthetics diff --git a/apps/www/src/content/docs/theme/spacing/index.mdx b/apps/www/src/content/docs/theme/spacing/index.mdx new file mode 100644 index 000000000..589c3e2f0 --- /dev/null +++ b/apps/www/src/content/docs/theme/spacing/index.mdx @@ -0,0 +1,97 @@ +--- +title: Spacing +description: Overview of the space scale and spacing tokens in Apsara. +--- + +Consistent spacing creates visual rhythm and hierarchy in your interface. Apsara provides a comprehensive spacing scale through CSS custom properties that maintain proportional relationships across your application. These tokens ensure consistent spacing whether you're building custom layouts, extending components, or creating new UI patterns. + +## Scale + +Spacing values are derived from a 17-step scale, which provides fine-grained control over layout spacing. The scale starts at 2px and progressively increases to 120px, allowing for both tight and spacious layouts. + + + +## Semantic Groupings + +The spacing scale can be organized into semantic groups for easier selection: + + + +## Usage + +```css +/* Custom card grid layout */ +.card-grid { + display: grid; + gap: var(--rs-space-5); + padding: var(--rs-space-7); +} + +/* Sidebar navigation */ +.sidebar-nav { + display: flex; + flex-direction: column; + gap: var(--rs-space-2); + padding: var(--rs-space-4) var(--rs-space-5); +} + +/* Page section with header */ +.page-section { + margin-top: var(--rs-space-13); +} + +.page-section-header { + margin-bottom: var(--rs-space-6); +} + +/* Form field groups */ +.form-group { + display: flex; + flex-direction: column; + gap: var(--rs-space-3); + margin-bottom: var(--rs-space-5); +} + +/* Inline icon with text */ +.icon-text { + display: flex; + align-items: center; + gap: var(--rs-space-2); +} +``` + +## Best Practices + +- **Use smaller values (1-5)** for internal component spacing like padding and gaps between inline elements +- **Use medium values (5-9)** for spacing between related components and within sections +- **Use larger values (10-17)** for spacing between sections and major layout divisions +- **Be consistent** - use the same spacing token for similar contexts throughout your application +- **Avoid mixing** pixel values with spacing tokens to maintain consistency diff --git a/apps/www/src/content/docs/theme/typography/index.mdx b/apps/www/src/content/docs/theme/typography/index.mdx new file mode 100644 index 000000000..f6b680b4d --- /dev/null +++ b/apps/www/src/content/docs/theme/typography/index.mdx @@ -0,0 +1,201 @@ +--- +title: Typography +description: Overview of the typography system and text tokens in Apsara. +--- + +Apsara provides a comprehensive typography system with carefully crafted font families, sizes, weights, and spacing. Typography tokens adapt based on the active style variant—modern style uses Inter for a clean, technical aesthetic while traditional style uses Lora and Josefin Sans for a warmer, editorial feel. Using these tokens ensures your text styling remains consistent and automatically adapts to style changes. + +## Font Families + +Apsara includes three font family categories for different use cases. + + + +### Available Fonts + + + +## Font Weights + + + +## Body Text + +Font sizes, line heights, and letter spacing for body content. + + + +## Title Text + +Font sizes, line heights, and letter spacing for headings and titles. + + + +## Monospace Text + +Font sizes, line heights, and letter spacing for code and technical content. + + + +## Style Variants + +Apsara supports different style variants that change typography to match your design preference. + +### Traditional Style + +When using `data-style="traditional"`, the font families change to: + + + +## Usage + +```css +/* Dashboard metric display */ +.metric-label { + font-family: var(--rs-font-body); + font-size: var(--rs-font-size-small); + font-weight: var(--rs-font-weight-regular); + line-height: var(--rs-line-height-small); + letter-spacing: var(--rs-letter-spacing-small); + text-transform: uppercase; +} + +.metric-value { + font-family: var(--rs-font-title); + font-size: var(--rs-font-size-t3); + font-weight: var(--rs-font-weight-medium); + line-height: var(--rs-line-height-t3); +} + +/* Article or blog post layout */ +.article-title { + font-family: var(--rs-font-title); + font-size: var(--rs-font-size-t4); + line-height: var(--rs-line-height-t4); + font-weight: var(--rs-font-weight-medium); +} + +.article-meta { + font-size: var(--rs-font-size-mini); + line-height: var(--rs-line-height-mini); + letter-spacing: var(--rs-letter-spacing-mini); +} + +.article-body { + font-family: var(--rs-font-body); + font-size: var(--rs-font-size-regular); + line-height: var(--rs-line-height-regular); +} + +/* Terminal or log output */ +.log-output { + font-family: var(--rs-font-mono); + font-size: var(--rs-font-size-mono-small); + line-height: var(--rs-line-height-mono-small); +} + +.log-timestamp { + font-family: var(--rs-font-mono); + font-size: var(--rs-font-size-mono-mini); + line-height: var(--rs-line-height-mono-mini); +} + +/* Data table headers and cells */ +.table-header { + font-size: var(--rs-font-size-small); + font-weight: var(--rs-font-weight-medium); + line-height: var(--rs-line-height-small); + letter-spacing: var(--rs-letter-spacing-small); +} + +.table-cell { + font-size: var(--rs-font-size-regular); + line-height: var(--rs-line-height-regular); +} +``` + +## Best Practices + +- **Use semantic sizing** - Match font sizes to their intended purpose (body, titles, code) +- **Pair sizes with line heights** - Always use the corresponding line height token for each font size +- **Maintain hierarchy** - Use font weights and sizes to establish clear visual hierarchy +- **Consider readability** - Use appropriate letter spacing for different text sizes