mirror of
https://github.com/makeplane/plane.git
synced 2026-02-24 12:11:39 +01:00
chore: added brief agents.md and story for new design system
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -105,10 +105,8 @@ CLAUDE.md
|
||||
|
||||
build/
|
||||
.react-router/
|
||||
AGENTS.md
|
||||
|
||||
build/
|
||||
.react-router/
|
||||
AGENTS.md
|
||||
temp/
|
||||
scripts/
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
|
||||
const meta = {
|
||||
title: "Design System/Philosophy",
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
# Design System Philosophy
|
||||
|
||||
Reusable, composable Storybook stories that demonstrate Canvas, Surface, and Layer concepts.
|
||||
|
||||
Key concepts and rules are preserved, but the implementation is componentized for DRYness and clarity.
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
export type Story = StoryObj<typeof meta>;
|
||||
|
||||
/* -----------------------------
|
||||
Reusable UI building blocks
|
||||
-----------------------------*/
|
||||
|
||||
type ContainerProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const DemoRoot: React.FC<ContainerProps> = ({ children, className = "" }) => (
|
||||
<div className={`p-8 ${className}`}>{children}</div>
|
||||
);
|
||||
|
||||
const Info: React.FC<{ title: string; children?: React.ReactNode; tone?: "info" | "warn" }> = ({
|
||||
title,
|
||||
children,
|
||||
tone = "info",
|
||||
}) => (
|
||||
<div
|
||||
className={`mb-4 rounded-md border ${tone === "warn" ? "border-red-500 bg-red-50 p-4" : "border-subtle bg-layer-1 p-4"}`}
|
||||
>
|
||||
<h3 className={`text-primary mb-2 text-lg font-semibold`}>{title}</h3>
|
||||
<div className="text-secondary text-sm">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Surface: React.FC<ContainerProps> = ({ children, className = "bg-surface-1 rounded-md p-6" }) => (
|
||||
<div className={className}>{children}</div>
|
||||
);
|
||||
|
||||
const Layer: React.FC<ContainerProps & { hover?: boolean }> = ({
|
||||
children,
|
||||
className = "bg-layer-1 hover:bg-layer-1-hover rounded-md p-4",
|
||||
}) => <div className={`${className} transition-colors`}>{children}</div>;
|
||||
|
||||
/* Small helpers to keep stories concise */
|
||||
const TwoColGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<div className="grid grid-cols-2 gap-6">{children}</div>
|
||||
);
|
||||
|
||||
/* -----------------------------
|
||||
Stories (using the building blocks)
|
||||
-----------------------------*/
|
||||
|
||||
export const ApplicationRoot: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Application Root Pattern">
|
||||
This is the <strong>bg-canvas</strong> - the application-level background. It should only appear{" "}
|
||||
<strong>once</strong> in your entire application at the root level.
|
||||
</Info>
|
||||
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-2 font-semibold">Page Content (bg-surface-1)</h4>
|
||||
<p className="text-secondary text-sm">Pages use surfaces, not canvas. This is a typical page layout.</p>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const SurfaceSiblings: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Surface Siblings Pattern">
|
||||
Surfaces are <strong>siblings</strong>, not nested.
|
||||
</Info>
|
||||
|
||||
<TwoColGrid>
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-2 font-semibold">Surface 1</h4>
|
||||
<p className="text-secondary text-sm">This is bg-surface-1 - a primary surface</p>
|
||||
</Surface>
|
||||
|
||||
<Surface className="bg-surface-2 rounded-md p-6">
|
||||
<h4 className="text-primary mb-2 font-semibold">Surface 2</h4>
|
||||
<p className="text-secondary text-sm">This is bg-surface-2 - a secondary surface (sibling to surface-1)</p>
|
||||
</Surface>
|
||||
</TwoColGrid>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const LayerStacking: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Layer Stacking Pattern">Layers stack to create depth: Surface → Layer 1 → Layer 2 → Layer 3</Info>
|
||||
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-3 font-semibold">Surface 1</h4>
|
||||
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover mb-4 rounded-md p-4">
|
||||
<h5 className="text-primary mb-2 font-medium">Layer 1 (First level of depth)</h5>
|
||||
<p className="text-secondary mb-3 text-sm">Hover over me to see the hover state</p>
|
||||
|
||||
<Layer className="bg-layer-2 hover:bg-layer-2-hover rounded-md p-3">
|
||||
<h6 className="text-primary mb-2 text-sm font-medium">Layer 2 (Second level)</h6>
|
||||
<p className="text-secondary mb-2 text-sm">Nested within Layer 1</p>
|
||||
|
||||
<Layer className="bg-layer-3 hover:bg-layer-3-hover rounded-md p-2" hover>
|
||||
<p className="text-primary text-xs font-medium">Layer 3 (Third level)</p>
|
||||
<p className="text-secondary text-xs">Deepest nesting level</p>
|
||||
</Layer>
|
||||
</Layer>
|
||||
</Layer>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const SurfaceLayerAssociation: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Surface-Layer Association">
|
||||
Each surface should use its corresponding layer: surface-1 → layer-1, surface-2 → layer-2
|
||||
</Info>
|
||||
|
||||
<TwoColGrid>
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-3 font-semibold">Surface 1</h4>
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Layer 1</h5>
|
||||
<p className="text-secondary text-sm">Correctly using layer-1 with surface-1</p>
|
||||
</Layer>
|
||||
</Surface>
|
||||
|
||||
<Surface className="bg-surface-2 rounded-md p-6">
|
||||
<h4 className="text-primary mb-3 font-semibold">Surface 2</h4>
|
||||
<Layer className="bg-layer-2 hover:bg-layer-2-hover rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Layer 2</h5>
|
||||
<p className="text-secondary text-sm">Correctly using layer-2 with surface-2</p>
|
||||
</Layer>
|
||||
</Surface>
|
||||
</TwoColGrid>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const ModalException: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Modal Exception Pattern">
|
||||
Modals exist on a <strong>different plane</strong>, so they can use surfaces even when there's a surface
|
||||
below
|
||||
</Info>
|
||||
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-2 font-semibold">Main Page Content</h4>
|
||||
<p className="text-secondary text-sm">This is the main page using bg-surface-1</p>
|
||||
</Surface>
|
||||
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="bg-backdrop absolute inset-0" />
|
||||
<div className="bg-surface-1 relative z-10 max-w-md rounded-lg p-6 shadow-lg">
|
||||
<h4 className="text-primary mb-3 font-semibold">Modal Dialog</h4>
|
||||
<p className="text-secondary mb-4 text-sm">
|
||||
This modal uses bg-surface-1 even though the page below also uses bg-surface-1. This is allowed because
|
||||
they're on different planes.
|
||||
</p>
|
||||
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-3">
|
||||
<p className="text-primary text-sm">Modal content can use layers as normal</p>
|
||||
</Layer>
|
||||
</div>
|
||||
</div>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const CardListPattern: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Card List Pattern">Common pattern: Surface containing multiple layer-1 cards</Info>
|
||||
|
||||
<Surface>
|
||||
<h4 className="text-primary mb-4 font-semibold">Task List</h4>
|
||||
<div className="space-y-3">
|
||||
{[1, 2, 3].map((item) => (
|
||||
<Layer key={item} className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Task {item}</h5>
|
||||
<p className="text-secondary text-sm">This is a task card using bg-layer-1 with hover state</p>
|
||||
</Layer>
|
||||
))}
|
||||
</div>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const SidebarLayoutPattern: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Sidebar Layout Pattern">Sidebar and main content are both part of the same surface</Info>
|
||||
|
||||
<Surface className="bg-surface-1 flex rounded-md">
|
||||
<aside className="border-subtle w-64 border-r p-4">
|
||||
<h4 className="text-primary mb-3 font-semibold">Sidebar</h4>
|
||||
<div className="space-y-2">
|
||||
{["Home", "Projects", "Settings"].map((item) => (
|
||||
<Layer key={item} className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-2">
|
||||
<p className="text-primary text-sm">{item}</p>
|
||||
</Layer>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 p-6">
|
||||
<h4 className="text-primary mb-3 font-semibold">Main Content</h4>
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<p className="text-primary">Content card using layer-1</p>
|
||||
</Layer>
|
||||
</main>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const StateVariants: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ State Variants">Demonstrating hover, active, and selected states</Info>
|
||||
|
||||
<Surface>
|
||||
<div className="space-y-4">
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Hover State</h5>
|
||||
<p className="text-secondary text-sm">Hover over me to see bg-layer-1-hover</p>
|
||||
</Layer>
|
||||
|
||||
<div className="bg-layer-1-active rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Active State</h5>
|
||||
<p className="text-secondary text-sm">Using bg-layer-1-active (pressed/active)</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-layer-1-selected rounded-md p-4">
|
||||
<h5 className="text-primary mb-1 font-medium">Selected State</h5>
|
||||
<p className="text-secondary text-sm">Using bg-layer-1-selected (when selected)</p>
|
||||
</div>
|
||||
</div>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const TextColorHierarchy: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Text Color Hierarchy">Semantic text colors for different importance levels</Info>
|
||||
|
||||
<Surface>
|
||||
<div className="bg-layer-1 rounded-md p-4">
|
||||
<h4 className="text-primary mb-3 text-lg font-semibold">Primary Text</h4>
|
||||
<p className="text-secondary mb-3">Secondary text for descriptions and supporting content</p>
|
||||
<p className="text-tertiary mb-3 text-sm">Tertiary text for labels and metadata</p>
|
||||
<input
|
||||
className="placeholder-placeholder border-subtle rounded border px-3 py-2"
|
||||
placeholder="Placeholder text for inputs"
|
||||
/>
|
||||
</div>
|
||||
</Surface>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const CompleteExample: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="✅ Complete Example">A realistic dashboard layout using all design system concepts</Info>
|
||||
|
||||
<div className="bg-surface-1 mb-6 rounded-md">
|
||||
<div className="border-subtle border-b p-4">
|
||||
<h1 className="text-primary text-xl font-bold">Dashboard</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{[
|
||||
{ label: "Total Users", value: "1,234" },
|
||||
{ label: "Active Projects", value: "42" },
|
||||
{ label: "Completed Tasks", value: "856" },
|
||||
].map((stat, idx) => (
|
||||
<Surface key={idx}>
|
||||
<Layer className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<p className="text-tertiary mb-1 text-sm">{stat.label}</p>
|
||||
<p className="text-primary text-2xl font-bold">{stat.value}</p>
|
||||
</Layer>
|
||||
</Surface>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-6">
|
||||
<Surface>
|
||||
<h3 className="text-primary mb-4 font-semibold">Recent Activity</h3>
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((item) => (
|
||||
<Layer key={item} className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-3">
|
||||
<p className="text-primary mb-1 text-sm font-medium">Activity {item}</p>
|
||||
<p className="text-secondary text-xs">Description of the activity</p>
|
||||
</Layer>
|
||||
))}
|
||||
</div>
|
||||
</Surface>
|
||||
|
||||
<Surface className="bg-surface-2 rounded-md p-6">
|
||||
<h3 className="text-primary mb-4 font-semibold">Quick Actions</h3>
|
||||
<div className="space-y-2">
|
||||
{["Create Project", "Invite Team", "View Reports"].map((action) => (
|
||||
<Layer key={action} className="bg-layer-2 hover:bg-layer-2-hover rounded-md p-3">
|
||||
<p className="text-primary text-sm">{action}</p>
|
||||
</Layer>
|
||||
))}
|
||||
</div>
|
||||
</Surface>
|
||||
</div>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
|
||||
export const CommonMistakes: Story = {
|
||||
render: () => (
|
||||
<DemoRoot>
|
||||
<Info title="❌ Common Mistakes to Avoid" tone="warn">
|
||||
These examples show incorrect usage patterns
|
||||
</Info>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="border-2 border-red-500 rounded-md p-4">
|
||||
<h4 className="text-primary mb-2 font-semibold">❌ Mistake 1: Nested Surfaces (Same Plane)</h4>
|
||||
<Surface>
|
||||
<p className="text-secondary mb-2 text-sm">Surface 1</p>
|
||||
<div className="bg-surface-2 rounded-md p-4">
|
||||
<p className="text-secondary text-sm">Surface 2 nested inside Surface 1 - WRONG!</p>
|
||||
</div>
|
||||
</Surface>
|
||||
<p className="text-tertiary mt-2 text-xs">
|
||||
✅ Fix: Use bg-layer-1 for nested elements, or make them sibling surfaces
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-2 border-red-500 rounded-md p-4">
|
||||
<h4 className="text-primary mb-2 font-semibold">❌ Mistake 2: Wrong Layer-Surface Association</h4>
|
||||
<Surface>
|
||||
<p className="text-secondary mb-2 text-sm">Surface 1</p>
|
||||
<div className="bg-layer-2 rounded-md p-4">
|
||||
<p className="text-secondary text-sm">Using layer-2 with surface-1 - WRONG!</p>
|
||||
</div>
|
||||
</Surface>
|
||||
<p className="text-tertiary mt-2 text-xs">✅ Fix: Use bg-layer-1 with bg-surface-1</p>
|
||||
</div>
|
||||
|
||||
<div className="border-2 border-red-500 rounded-md p-4">
|
||||
<h4 className="text-primary mb-2 font-semibold">❌ Mistake 3: Mismatched Hover State</h4>
|
||||
<Surface>
|
||||
<div className="bg-layer-1 hover:bg-layer-2-hover rounded-md p-4 transition-colors">
|
||||
<p className="text-secondary text-sm">bg-layer-1 with hover:bg-layer-2-hover - WRONG!</p>
|
||||
</div>
|
||||
</Surface>
|
||||
<p className="text-tertiary mt-2 text-xs">✅ Fix: Use bg-layer-1 hover:bg-layer-1-hover</p>
|
||||
</div>
|
||||
|
||||
<div className="border-2 border-red-500 rounded-md p-4">
|
||||
<h4 className="text-primary mb-2 font-semibold">❌ Mistake 4: Canvas for Pages</h4>
|
||||
<div className="bg-canvas rounded-md p-4">
|
||||
<p className="text-secondary text-sm">Using bg-canvas for a page or component - WRONG!</p>
|
||||
</div>
|
||||
<p className="text-tertiary mt-2 text-xs">
|
||||
✅ Fix: Canvas should only be at application root. Use bg-surface-1 for pages
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DemoRoot>
|
||||
),
|
||||
};
|
||||
630
packages/tailwind-config/AGENTS.md
Normal file
630
packages/tailwind-config/AGENTS.md
Normal file
@@ -0,0 +1,630 @@
|
||||
# Design System Philosophy Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide explains the semantic design system philosophy for building consistent, maintainable UIs. The system is built on three core concepts: **Canvas**, **Surface**, and **Layer**.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Canvas (`bg-canvas`)
|
||||
|
||||
**What it is**: The application-level background that serves as the foundation for all content. The canvas is the **entire application background**, not individual pages. There is only **one canvas** in the entire application, used at the root level.
|
||||
|
||||
**When to use**:
|
||||
|
||||
- **Only at the application root** - the single root container that wraps the entire application
|
||||
- The main application background (not page backgrounds)
|
||||
|
||||
**When NOT to use**:
|
||||
|
||||
- ❌ Page-level backgrounds
|
||||
- ❌ Nested containers
|
||||
- ❌ Cards or components
|
||||
- ❌ Modals or dropdowns
|
||||
- ❌ Sidebars or panels
|
||||
- ❌ Anywhere else in the application
|
||||
|
||||
**Critical Rule**: Canvas should only appear **once** in your entire application - at the root level. All pages, routes, and components sit on top of this single canvas.
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Canvas at application root (only place it should be)
|
||||
// App.tsx or root layout
|
||||
<div className="bg-canvas min-h-screen">
|
||||
{/* All application content goes here */}
|
||||
<Routes>
|
||||
<Route path="/" element={<Page />} />
|
||||
</Routes>
|
||||
</div>;
|
||||
|
||||
// ✅ Correct: Pages use surfaces, not canvas
|
||||
function Page() {
|
||||
return <div className="bg-surface-1">{/* Page content */}</div>;
|
||||
}
|
||||
|
||||
// ❌ Wrong: Canvas used for a page
|
||||
function Page() {
|
||||
return <div className="bg-canvas">{/* Don't use canvas here */}</div>;
|
||||
}
|
||||
|
||||
// ❌ Wrong: Canvas used for a card
|
||||
<div className="bg-canvas p-4 rounded-md">{/* Card content */}</div>;
|
||||
```
|
||||
|
||||
### 2. Surface (`bg-surface-1`, `bg-surface-2`, `bg-surface-3`)
|
||||
|
||||
**What it is**: Top-level containers that sit directly on the canvas. Surfaces never overlap each other - they are siblings in the layout hierarchy.
|
||||
|
||||
**When to use**:
|
||||
|
||||
- Main content areas
|
||||
- Sections of a page
|
||||
- Primary containers
|
||||
- Panels that sit side-by-side
|
||||
|
||||
**Surface hierarchy**:
|
||||
|
||||
- `bg-surface-1`: Primary surface (most common)
|
||||
- `bg-surface-2`: Secondary surface (for variation)
|
||||
- `bg-surface-3`: Tertiary surface (rare, for special cases)
|
||||
|
||||
**Rules**:
|
||||
|
||||
- Surfaces are **siblings**, not nested (in the same plane)
|
||||
- Each surface should use its corresponding layer for nested elements
|
||||
- Surfaces provide the base for stacking layers
|
||||
|
||||
**Exception - Different Planes**:
|
||||
|
||||
- Modals, overlays, and popovers exist on a **different plane** (different z-index/stacking context)
|
||||
- In these cases, it's acceptable to use a surface even when there's a surface below
|
||||
- This is because they are visually and functionally separate from the underlying content
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Surfaces as siblings
|
||||
<div className="bg-canvas">
|
||||
<div className="bg-surface-1">
|
||||
{/* Main content area */}
|
||||
</div>
|
||||
<div className="bg-surface-2">
|
||||
{/* Secondary content area - sibling, not nested */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: Page with header and main (same surface)
|
||||
<div className="bg-surface-1">
|
||||
<header className="border-b border-subtle">
|
||||
{/* Header is part of the surface, not a separate surface */}
|
||||
</header>
|
||||
<main>
|
||||
{/* Main is part of the surface, not a separate surface */}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
// ❌ Wrong: Surface nested in surface (same plane)
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-surface-2">
|
||||
{/* This breaks the philosophy */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: Modal on different plane
|
||||
<div className="bg-canvas">
|
||||
{/* Main page content */}
|
||||
<div className="bg-surface-1">
|
||||
Page content
|
||||
</div>
|
||||
|
||||
{/* Modal overlay - different plane */}
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="bg-backdrop fixed inset-0" />
|
||||
<div className="bg-surface-1 rounded-lg shadow-lg p-6">
|
||||
{/* Modal can use surface-1 even though page uses surface-1 */}
|
||||
Modal content
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. Layer (`bg-layer-1`, `bg-layer-2`, `bg-layer-3`)
|
||||
|
||||
**What it is**: Stacking layers that create depth within a surface. Layers stack on top of each other in a specific order.
|
||||
|
||||
**When to use**:
|
||||
|
||||
- Cards within a surface
|
||||
- Group headers
|
||||
- Nested containers
|
||||
- Dropdowns and modals
|
||||
- Sidebars
|
||||
- Any element that needs to appear "on top" of a surface
|
||||
|
||||
**Layer hierarchy**:
|
||||
|
||||
- `bg-layer-1`: First layer (closest to surface)
|
||||
- `bg-layer-2`: Second layer (on top of layer-1)
|
||||
- `bg-layer-3`: Third layer (on top of layer-2)
|
||||
|
||||
**Critical Rule - Layer-to-Surface Association**:
|
||||
|
||||
- `bg-surface-1` → use `bg-layer-1` for nested elements
|
||||
- `bg-surface-2` → use `bg-layer-2` for nested elements
|
||||
- `bg-surface-3` → use `bg-layer-3` for nested elements
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Surface-1 with layer-1
|
||||
<div className="bg-surface-1 p-4">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-3">
|
||||
Card content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: Surface-2 with layer-2
|
||||
<div className="bg-surface-2 p-4">
|
||||
<div className="bg-layer-2 hover:bg-layer-2-hover rounded-md p-3">
|
||||
Card content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ❌ Wrong: Surface-1 with layer-2
|
||||
<div className="bg-surface-1 p-4">
|
||||
<div className="bg-layer-2">
|
||||
{/* Wrong layer for this surface */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Stacking Layers
|
||||
|
||||
### How to Stack Layers
|
||||
|
||||
Layers stack in order: surface → layer-1 → layer-2 → layer-3
|
||||
|
||||
**Pattern**:
|
||||
|
||||
```
|
||||
Canvas
|
||||
└── Surface
|
||||
└── Layer 1 (first level of depth)
|
||||
└── Layer 2 (second level of depth)
|
||||
└── Layer 3 (third level of depth)
|
||||
```
|
||||
|
||||
**Example - Proper Stacking**:
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Proper layer stacking
|
||||
<div className="bg-surface-1 p-4">
|
||||
{/* Layer 1: Card */}
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<h3>Card Title</h3>
|
||||
|
||||
{/* Layer 2: Nested section */}
|
||||
<div className="bg-layer-2 hover:bg-layer-2-hover rounded p-2 mt-2">
|
||||
Nested content
|
||||
{/* Layer 3: Deeply nested */}
|
||||
<div className="bg-layer-3 hover:bg-layer-3-hover rounded p-1 mt-1">Deep content</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**When to use each layer**:
|
||||
|
||||
- **Layer 1**: Most common - cards, headers, primary nested elements
|
||||
- **Layer 2**: Secondary depth - nested cards, sub-sections
|
||||
- **Layer 3**: Deep nesting - rarely needed, for complex hierarchies
|
||||
|
||||
## State Variants
|
||||
|
||||
### Hover States
|
||||
|
||||
**Critical Rule**: Hover must always match the base background layer.
|
||||
|
||||
**Pattern**: `bg-layer-X hover:bg-layer-X-hover`
|
||||
|
||||
**Examples**:
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Matching hover
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover">
|
||||
Hoverable element
|
||||
</div>
|
||||
|
||||
<div className="bg-layer-2 hover:bg-layer-2-hover">
|
||||
Hoverable element
|
||||
</div>
|
||||
|
||||
// ❌ Wrong: Mismatched hover
|
||||
<div className="bg-layer-1 hover:bg-layer-2-hover">
|
||||
{/* Never do this */}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Active States
|
||||
|
||||
Use `-active` variants when an element is in an active/pressed state.
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Active state
|
||||
<button
|
||||
className={cn("bg-layer-1 hover:bg-layer-1-hover", {
|
||||
"bg-layer-1-active": isActive,
|
||||
})}
|
||||
>
|
||||
Button
|
||||
</button>
|
||||
```
|
||||
|
||||
### Selected States
|
||||
|
||||
Use `-selected` variants only when there's actual selection logic.
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Selected state with logic
|
||||
<div className={cn(
|
||||
"bg-layer-1 hover:bg-layer-1-hover",
|
||||
{
|
||||
"bg-layer-1-selected": isSelected
|
||||
}
|
||||
)}>
|
||||
Selectable item
|
||||
</div>
|
||||
|
||||
// ✅ Correct: With data attribute
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover data-[selected]:bg-layer-1-selected">
|
||||
Selectable item
|
||||
</div>
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Application Root Layout
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Application root (only place for canvas)
|
||||
// App.tsx or root layout
|
||||
<div className="bg-canvas min-h-screen">
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/dashboard" element={<DashboardPage />} />
|
||||
</Routes>
|
||||
</div>;
|
||||
|
||||
// ✅ Correct: Individual page structure
|
||||
function HomePage() {
|
||||
return (
|
||||
<div className="bg-surface-1">
|
||||
{/* Header - part of the page surface, uses layer for depth */}
|
||||
<header className="border-b border-subtle">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover px-4 py-2">Header content</div>
|
||||
</header>
|
||||
|
||||
{/* Main content - part of the page surface */}
|
||||
<main className="p-6">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">Content card</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Card with Nested Elements
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Card with proper layering
|
||||
<div className="bg-surface-1 p-4">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">
|
||||
<h3 className="text-primary font-semibold">Card Title</h3>
|
||||
|
||||
{/* Nested section */}
|
||||
<div className="bg-layer-2 hover:bg-layer-2-hover rounded p-3 mt-3">
|
||||
<p className="text-secondary">Nested content</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Pattern 3: Dropdown/Modal
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Modal structure (different plane exception)
|
||||
function PageWithModal() {
|
||||
return (
|
||||
<div className="bg-surface-1">
|
||||
{/* Main page content */}
|
||||
<div>Page content</div>
|
||||
|
||||
{/* Modal - different plane, can use surface even with surface below */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="bg-backdrop fixed inset-0" />
|
||||
<div className="bg-surface-1 rounded-lg shadow-lg p-6">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded p-2">Modal content</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Sidebar Layout
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Sidebar with main content (page level, not app root)
|
||||
// Both sidebar and main are siblings on the same surface
|
||||
function DashboardPage() {
|
||||
return (
|
||||
<div className="bg-surface-1 flex">
|
||||
{/* Sidebar - part of the page surface */}
|
||||
<aside className="border-r border-subtle w-64">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover p-4">Sidebar item</div>
|
||||
</aside>
|
||||
|
||||
{/* Main content - part of the page surface */}
|
||||
<main className="flex-1 p-6">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-4">Main content</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: List with Items
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: List structure
|
||||
<div className="bg-surface-1 p-4">
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md mb-2 p-3">List item 1</div>
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md mb-2 p-3">List item 2</div>
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover rounded-md p-3">List item 3</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Pattern 6: Form with Inputs
|
||||
|
||||
```tsx
|
||||
// ✅ Correct: Form structure
|
||||
<div className="bg-surface-1 p-6">
|
||||
<form className="bg-layer-1 rounded-md p-4 space-y-4">
|
||||
<div>
|
||||
<label className="text-primary font-medium">Name</label>
|
||||
<input className="bg-surface-1 border border-subtle rounded-md px-3 py-2 text-primary" type="text" />
|
||||
</div>
|
||||
|
||||
<button className="bg-layer-2 hover:bg-layer-2-hover rounded-md px-4 py-2 text-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Decision Tree
|
||||
|
||||
### When to use Canvas?
|
||||
|
||||
```
|
||||
Is this the root container of the entire application?
|
||||
(Only one place in the whole app)
|
||||
├─ YES → Use bg-canvas (application root only)
|
||||
└─ NO → Continue to Surface decision
|
||||
```
|
||||
|
||||
### When to use Surface?
|
||||
|
||||
```
|
||||
Is this a top-level container that sits on canvas?
|
||||
AND
|
||||
Is it a sibling to other containers (not nested)?
|
||||
OR
|
||||
Is this a modal/overlay on a different plane (z-index)?
|
||||
├─ YES → Use bg-surface-1 (or surface-2/3 for variation)
|
||||
└─ NO → Continue to Layer decision
|
||||
```
|
||||
|
||||
### When to use Layer?
|
||||
|
||||
```
|
||||
Is this nested within a surface?
|
||||
├─ YES → Use bg-layer-1 (or layer-2/3 for deeper nesting)
|
||||
│ Match layer number to surface number
|
||||
└─ NO → Re-evaluate: Should this be a surface?
|
||||
```
|
||||
|
||||
## Text Colors
|
||||
|
||||
Use semantic text colors that match the hierarchy:
|
||||
|
||||
- `text-primary`: Main text, headings, important content
|
||||
- `text-secondary`: Secondary text, descriptions
|
||||
- `text-tertiary`: Tertiary text, labels, metadata
|
||||
- `text-placeholder`: Placeholder text, hints
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
<div className="bg-layer-1 p-4">
|
||||
<h2 className="text-primary font-semibold">Title</h2>
|
||||
<p className="text-secondary">Description text</p>
|
||||
<span className="text-tertiary text-sm">Metadata</span>
|
||||
<input className="placeholder-placeholder" placeholder="Enter text..." />
|
||||
</div>
|
||||
```
|
||||
|
||||
## Border Colors
|
||||
|
||||
Use semantic border colors:
|
||||
|
||||
- `border-subtle`: Subtle borders, dividers
|
||||
- `border-subtle-1`: Slightly more visible borders
|
||||
- `border-strong`: Strong borders, emphasis
|
||||
- `border-strong-1`: Very strong borders
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
<div className="bg-layer-1 border border-subtle rounded-md p-4">
|
||||
<div className="border-b border-subtle-1 pb-2 mb-2">Section with divider</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
### ❌ Mistake 1: Canvas for Pages or Cards
|
||||
|
||||
```tsx
|
||||
// ❌ Wrong: Canvas used for a page
|
||||
function Page() {
|
||||
return <div className="bg-canvas">Page content</div>;
|
||||
}
|
||||
|
||||
// ❌ Wrong: Canvas used for a card
|
||||
<div className="bg-canvas rounded-md p-4">Card content</div>;
|
||||
|
||||
// ✅ Correct: Pages use surfaces
|
||||
function Page() {
|
||||
return (
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-layer-1 rounded-md p-4">Card content</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Mistake 2: Nested Surfaces (Same Plane)
|
||||
|
||||
```tsx
|
||||
// ❌ Wrong: Nested surfaces in same plane
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-surface-2">
|
||||
Nested surface
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: Use layer instead
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-layer-1">
|
||||
Nested layer
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: Exception - Modal on different plane
|
||||
<div className="bg-canvas">
|
||||
<div className="bg-surface-1">Page content</div>
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="bg-surface-1">Modal (different plane)</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### ❌ Mistake 3: Wrong Layer for Surface
|
||||
|
||||
```tsx
|
||||
// ❌ Wrong: surface-1 with layer-2
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-layer-2">
|
||||
Content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ✅ Correct: surface-1 with layer-1
|
||||
<div className="bg-surface-1">
|
||||
<div className="bg-layer-1">
|
||||
Content
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### ❌ Mistake 4: Mismatched Hover
|
||||
|
||||
```tsx
|
||||
// ❌ Wrong
|
||||
<div className="bg-layer-1 hover:bg-layer-2-hover">
|
||||
Content
|
||||
</div>
|
||||
|
||||
// ✅ Correct
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover">
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
### ❌ Mistake 5: Missing Hover Prefix
|
||||
|
||||
```tsx
|
||||
// ❌ Wrong
|
||||
<div className="bg-layer-1-hover">
|
||||
Content
|
||||
</div>
|
||||
|
||||
// ✅ Correct
|
||||
<div className="bg-layer-1 hover:bg-layer-1-hover">
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Canvas is Application Root Only**: Canvas should only appear once in your entire application - at the root level (App.tsx or root layout). All pages use surfaces, not canvas.
|
||||
|
||||
2. **Use Surfaces for Pages and Top-Level Containers**: Surfaces are siblings, not nested (except modals/overlays on different planes)
|
||||
|
||||
3. **Match Layers to Surfaces**: surface-1 → layer-1, surface-2 → layer-2, etc.
|
||||
|
||||
4. **Stack Layers Properly**: Use layer-1 first, then layer-2, then layer-3 as needed
|
||||
|
||||
5. **Always Match Hover States**: If base is `bg-layer-X`, hover must be `hover:bg-layer-X-hover`
|
||||
|
||||
6. **Use Semantic Text Colors**: Match text color to importance (primary, secondary, tertiary, placeholder)
|
||||
|
||||
7. **Keep It Simple**: Don't over-nest. Most components only need layer-1
|
||||
|
||||
8. **Test Visual Hierarchy**: Ensure the stacking creates clear visual depth
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Hierarchy Structure
|
||||
|
||||
```
|
||||
Canvas (one per page)
|
||||
└── Surface 1 (top-level container)
|
||||
└── Layer 1 (first level of depth)
|
||||
└── Layer 2 (second level)
|
||||
└── Layer 3 (third level)
|
||||
```
|
||||
|
||||
### Common Combinations
|
||||
|
||||
- Application Root: `bg-canvas` (only one place in entire app)
|
||||
- Single Surface Page: `bg-surface-1` → `bg-layer-1`
|
||||
- Multiple Surfaces Page: Grid of `bg-surface-1` (or `bg-surface-2`) as siblings
|
||||
- Card: `bg-surface-1` → `bg-layer-1 hover:bg-layer-1-hover`
|
||||
- Nested Card: `bg-surface-1` → `bg-layer-1` → `bg-layer-2`
|
||||
- Sidebar Layout: `bg-surface-1` (sidebar) + `bg-surface-1` (main) - both siblings
|
||||
|
||||
### State Variants
|
||||
|
||||
- Hover: `bg-layer-X hover:bg-layer-X-hover`
|
||||
- Active: `bg-layer-X-active` (when pressed/active)
|
||||
- Selected: `bg-layer-X-selected` (when selected)
|
||||
|
||||
## Alignment Checklist
|
||||
|
||||
When reviewing components, ensure:
|
||||
|
||||
- [ ] Canvas is only used at the application root (one place in entire app)
|
||||
- [ ] Pages use surfaces, not canvas
|
||||
- [ ] Surfaces are siblings, not nested (except modals/overlays on different planes)
|
||||
- [ ] Layers match their surface (surface-1 → layer-1)
|
||||
- [ ] Hover states match base layers
|
||||
- [ ] Layers stack properly (1 → 2 → 3)
|
||||
- [ ] Text colors are semantic (primary, secondary, tertiary, placeholder)
|
||||
- [ ] Borders use semantic colors (subtle, strong)
|
||||
- [ ] No unnecessary nesting
|
||||
- [ ] Visual hierarchy is clear
|
||||
- [ ] State variants are used correctly
|
||||
- [ ] Modals/overlays use surfaces (exception to nesting rule)
|
||||
Reference in New Issue
Block a user