feat: introduced a token-based theming system across the UI
* feat: added themes system * feat: smart sidebar auto-hide for files/diff tabs + lower files sidebar threshold * feat: added Checkbox component and update theming - Add reusable Checkbox component for toggles across UI - Replace several inputs with Checkbox in settings and commands panels - Add DiffIcon and apply surface/border theming to key UI areas * feat: Add convert-vscode-theme.cjs to convert VS Code themes to OpenChamber format * refactor: remove unused permission logic from ChatInput - Remove unused permission rules parsing logic from ChatInput - Memoize renderTheme in DiffWorkerProvider to avoid unnecessary recalculations - Remove forceOpaque helper in vscode theme adapter * fix: guard VSCode theme loading in MarkdownRenderer * feat: add custom user themes loading and reload - Load user themes from ~/.config/openchamber/themes at runtime - Expose /api/config/themes to fetch custom themes - Allow theme reloading from Settings → Theme → Reload themes in the UI
This commit is contained in:
committed by
GitHub
parent
5bb2c56bc0
commit
ddedc02687
@@ -0,0 +1,205 @@
|
||||
---
|
||||
name: theme-system
|
||||
description: Use when creating or modifying UI components, styling, or visual elements in OpenChamber. All UI colors must use theme tokens - never hardcoded values or Tailwind color classes.
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
OpenChamber uses a JSON-based theme system. Themes are defined in `packages/ui/src/lib/theme/themes/`. Users can also add custom themes via `~/.config/openchamber/themes/`.
|
||||
|
||||
**Core principle:** UI colors must use theme tokens - never hardcoded hex colors or Tailwind color classes.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating or modifying UI components
|
||||
- Working with colors, backgrounds, borders, or text
|
||||
|
||||
## Quick Decision Tree
|
||||
|
||||
1. **Code display?** → `syntax.*`
|
||||
2. **Feedback/status?** → `status.*`
|
||||
3. **Primary CTA?** → `primary.*`
|
||||
4. **Interactive/clickable?** → `interactive.*`
|
||||
5. **Background layer?** → `surface.*`
|
||||
6. **Text?** → `surface.foreground` or `surface.mutedForeground`
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- `surface.elevated` = inputs, cards, panels
|
||||
- `interactive.hover` = **ONLY on clickable elements**
|
||||
- `interactive.selection` = active/selected states (not primary!)
|
||||
- Status colors = **ONLY for actual feedback** (errors, warnings, success)
|
||||
- Input footers = `bg-transparent` on elevated background
|
||||
|
||||
### Never Use
|
||||
|
||||
- Hardcoded hex colors (`#FF0000`)
|
||||
- Tailwind colors (`bg-white`, `text-blue-500`, `bg-gray-*`)
|
||||
- Deprecated: `bg-secondary`, `bg-muted`
|
||||
|
||||
## Usage
|
||||
|
||||
### Via Hook
|
||||
```tsx
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
const { currentTheme } = useThemeSystem();
|
||||
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
```
|
||||
|
||||
### Via CSS Variables
|
||||
```tsx
|
||||
<div className="bg-[var(--surface-elevated)] hover:bg-[var(--interactive-hover)]">
|
||||
```
|
||||
|
||||
## Color Tokens
|
||||
|
||||
### Surface Colors
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `surface.background` | Main app background |
|
||||
| `surface.elevated` | Inputs, cards, panels, popovers |
|
||||
| `surface.muted` | Secondary backgrounds, sidebars |
|
||||
| `surface.foreground` | Primary text |
|
||||
| `surface.mutedForeground` | Secondary text, hints |
|
||||
| `surface.subtle` | Subtle dividers |
|
||||
|
||||
### Interactive Colors
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `interactive.border` | Default borders |
|
||||
| `interactive.hover` | Hover on **clickable elements only** |
|
||||
| `interactive.selection` | Active/selected items |
|
||||
| `interactive.selectionForeground` | Text on selection |
|
||||
| `interactive.focusRing` | Focus indicators |
|
||||
|
||||
### Status Colors
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `status.error` | Errors, validation failures |
|
||||
| `status.warning` | Warnings, cautions |
|
||||
| `status.success` | Success messages |
|
||||
| `status.info` | Informational messages |
|
||||
|
||||
Each has variants: `*`, `*Foreground`, `*Background`, `*Border`.
|
||||
|
||||
### Primary Colors
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `primary.base` | Primary CTA buttons |
|
||||
| `primary.hover` | Hover on primary elements |
|
||||
| `primary.foreground` | Text on primary background |
|
||||
|
||||
**Primary vs Selection:** Primary = "click me" (CTA), Selection = "currently active" (state).
|
||||
|
||||
### Syntax Colors
|
||||
|
||||
For code display only. Never use for UI elements.
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `syntax.base.background` | Code block background |
|
||||
| `syntax.base.foreground` | Default code text |
|
||||
| `syntax.base.keyword` | Keywords |
|
||||
| `syntax.base.string` | Strings |
|
||||
| `syntax.highlights.diffAdded` | Added lines |
|
||||
| `syntax.highlights.diffRemoved` | Removed lines |
|
||||
|
||||
## Examples
|
||||
|
||||
### Input Area
|
||||
|
||||
```tsx
|
||||
const { currentTheme } = useThemeSystem();
|
||||
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
<textarea className="bg-transparent" />
|
||||
<div className="bg-transparent">{/* Footer - transparent! */}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Active Tab
|
||||
|
||||
```tsx
|
||||
<button className={isActive
|
||||
? 'bg-interactive-selection text-interactive-selection-foreground'
|
||||
: 'hover:bg-interactive-hover/50'
|
||||
}>
|
||||
```
|
||||
|
||||
### Error Message
|
||||
|
||||
```tsx
|
||||
<div style={{
|
||||
color: currentTheme.colors.status.error,
|
||||
backgroundColor: currentTheme.colors.status.errorBackground
|
||||
}}>
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
<h3 style={{ color: currentTheme.colors.surface.foreground }}>Title</h3>
|
||||
<p style={{ color: currentTheme.colors.surface.mutedForeground }}>Description</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Wrong vs Right
|
||||
|
||||
### Wrong
|
||||
|
||||
```tsx
|
||||
// Hardcoded colors
|
||||
<div style={{ backgroundColor: '#F2F0E5' }}>
|
||||
<button className="bg-blue-500">
|
||||
|
||||
// Primary for active tab
|
||||
<Tab className="bg-primary">Active</Tab>
|
||||
|
||||
// Hover on static element
|
||||
<div className="hover:bg-interactive-hover">Static card</div>
|
||||
|
||||
// Colored footer on input
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
<textarea />
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.muted }}>Footer</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Right
|
||||
|
||||
```tsx
|
||||
// Theme tokens
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
<button style={{ backgroundColor: currentTheme.colors.primary.base }}>
|
||||
|
||||
// Selection for active tab
|
||||
<Tab style={{ backgroundColor: currentTheme.colors.interactive.selection }}>Active</Tab>
|
||||
|
||||
// Hover only on clickable
|
||||
<button className="hover:bg-[var(--interactive-hover)]">Click</button>
|
||||
|
||||
// Transparent footer
|
||||
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
|
||||
<textarea className="bg-transparent" />
|
||||
<div className="bg-transparent">Footer</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **[Adding Themes](references/adding-themes.md)** - Built-in and custom themes
|
||||
|
||||
## Key Files
|
||||
|
||||
- Theme types: `packages/ui/src/types/theme.ts`
|
||||
- Theme hook: `packages/ui/src/contexts/useThemeSystem.ts`
|
||||
- CSS generator: `packages/ui/src/lib/theme/cssGenerator.ts`
|
||||
- Built-in themes: `packages/ui/src/lib/theme/themes/`
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Adding Themes
|
||||
---
|
||||
|
||||
# Adding Themes
|
||||
|
||||
## Custom Themes (User)
|
||||
|
||||
Drop a JSON file into `~/.config/openchamber/themes/`. No rebuild needed.
|
||||
|
||||
1. Create theme file (e.g., `my-theme.json`)
|
||||
2. In app: **Settings → Theme → Reload themes**
|
||||
3. Select from dropdown
|
||||
|
||||
See `docs/CUSTOM_THEMES.md` for full format reference.
|
||||
|
||||
## Built-in Themes (Development)
|
||||
|
||||
### 1. Create JSON Files
|
||||
|
||||
Add to `packages/ui/src/lib/theme/themes/`:
|
||||
- `<id>-light.json`
|
||||
- `<id>-dark.json`
|
||||
|
||||
Use existing themes (e.g., `flexoki-dark.json`) as reference for the full structure.
|
||||
|
||||
### 2. Register in presets.ts
|
||||
|
||||
```typescript
|
||||
import mytheme_light_Raw from './mytheme-light.json';
|
||||
import mytheme_dark_Raw from './mytheme-dark.json';
|
||||
|
||||
export const presetThemes: Theme[] = [
|
||||
// ... existing themes
|
||||
mytheme_light_Raw as Theme,
|
||||
mytheme_dark_Raw as Theme,
|
||||
];
|
||||
```
|
||||
|
||||
### 3. Validate
|
||||
|
||||
```bash
|
||||
bun run type-check && bun run lint && bun run build
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
- Theme types: `packages/ui/src/types/theme.ts`
|
||||
- Presets: `packages/ui/src/lib/theme/themes/presets.ts`
|
||||
- Example: `packages/ui/src/lib/theme/themes/flexoki-dark.json`
|
||||
- Custom themes doc: `docs/CUSTOM_THEMES.md`
|
||||
Reference in New Issue
Block a user