mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +08:00
feat: add queue management and terminal dashboard documentation in Chinese
- Introduced comprehensive documentation for the queue management feature, detailing its pain points, core functionalities, and component structure. - Added terminal dashboard documentation, highlighting its layout, core features, and usage examples. - Created an index page in Chinese for Claude Code Workflow, summarizing its purpose and core features, along with quick links to installation and guides.
This commit is contained in:
1
ccw/frontend/src/components/.gitignore
vendored
Normal file
1
ccw/frontend/src/components/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.ace-tool/
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { withMermaid } from 'vitepress-plugin-mermaid'
|
||||
import { transformDemoBlocks } from './theme/markdownTransform'
|
||||
import path from 'path'
|
||||
|
||||
const repoName = process.env.GITHUB_REPOSITORY?.split('/')[1]
|
||||
const isUserOrOrgSite = Boolean(repoName && repoName.endsWith('.github.io'))
|
||||
@@ -11,7 +13,7 @@ const base =
|
||||
export default withMermaid(defineConfig({
|
||||
title: 'Claude Code Workflow Documentation',
|
||||
description: 'Claude Code Workspace - Advanced AI-Powered Development Environment',
|
||||
lang: 'zh-CN',
|
||||
lang: 'en-US',
|
||||
base,
|
||||
|
||||
// Ignore dead links for incomplete docs
|
||||
@@ -44,12 +46,22 @@ export default withMermaid(defineConfig({
|
||||
|
||||
// Vite build/dev optimizations
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '../../ccw/frontend/src'),
|
||||
'@/components': path.resolve(__dirname, '../../ccw/frontend/src/components'),
|
||||
'@/lib': path.resolve(__dirname, '../../ccw/frontend/src/lib')
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['flexsearch']
|
||||
include: ['flexsearch', 'react', 'react-dom']
|
||||
},
|
||||
build: {
|
||||
target: 'es2019',
|
||||
cssCodeSplit: true
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ['react', 'react-dom', 'class-variance-authority', 'clsx', 'tailwind-merge']
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,13 +80,7 @@ export default withMermaid(defineConfig({
|
||||
{ text: 'Guide', link: '/guide/ch01-what-is-claude-dms3' },
|
||||
{ text: 'Commands', link: '/commands/claude/' },
|
||||
{ text: 'Skills', link: '/skills/' },
|
||||
{ text: 'Features', link: '/features/spec' },
|
||||
{
|
||||
text: 'Languages',
|
||||
items: [
|
||||
{ text: '简体中文', link: '/zh/guide/ch01-what-is-claude-dms3' }
|
||||
]
|
||||
}
|
||||
{ text: 'Features', link: '/features/spec' }
|
||||
],
|
||||
|
||||
// Sidebar - 优化导航结构,增加二级标题和归类
|
||||
@@ -181,6 +187,17 @@ export default withMermaid(defineConfig({
|
||||
]
|
||||
}
|
||||
],
|
||||
'/components/': [
|
||||
{
|
||||
text: 'UI Components',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Button', link: '/components/ui/button' },
|
||||
{ text: 'Card', link: '/components/ui/card' },
|
||||
{ text: 'Input', link: '/components/ui/input' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/mcp/': [
|
||||
{
|
||||
text: '🔗 MCP Tools',
|
||||
@@ -276,6 +293,14 @@ export default withMermaid(defineConfig({
|
||||
],
|
||||
config: (md) => {
|
||||
// Add markdown-it plugins if needed
|
||||
// Custom demo block transform is handled by markdownTransform.ts
|
||||
md.core.ruler.before('block', 'demo-blocks', (state) => {
|
||||
const src = state.src
|
||||
const transformed = transformDemoBlocks(src, { path: '' })
|
||||
if (transformed !== src) {
|
||||
state.src = transformed
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -299,13 +324,7 @@ export default withMermaid(defineConfig({
|
||||
{ text: '指南', link: '/zh/guide/ch01-what-is-claude-dms3' },
|
||||
{ text: '命令', link: '/zh/commands/claude/' },
|
||||
{ text: '技能', link: '/zh/skills/claude-index' },
|
||||
{ text: '功能', link: '/zh/features/spec' },
|
||||
{
|
||||
text: '语言',
|
||||
items: [
|
||||
{ text: 'English', link: '/guide/ch01-what-is-claude-dms3' }
|
||||
]
|
||||
}
|
||||
{ text: '功能', link: '/zh/features/spec' }
|
||||
],
|
||||
sidebar: {
|
||||
'/zh/guide/': [
|
||||
@@ -424,6 +443,74 @@ export default withMermaid(defineConfig({
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
label: '简体中文',
|
||||
lang: 'zh-CN',
|
||||
title: 'Claude Code Workflow 文档',
|
||||
description: 'Claude Code Workspace - 高级 AI 驱动开发环境',
|
||||
themeConfig: {
|
||||
outline: {
|
||||
level: [2, 3],
|
||||
label: '本页目录'
|
||||
},
|
||||
nav: [
|
||||
{ text: '指南', link: '/zh-CN/guide/ch01-what-is-claude-dms3' },
|
||||
{ text: '命令', link: '/zh-CN/commands/claude/' },
|
||||
{ text: '技能', link: '/zh-CN/skills/claude-index' },
|
||||
{ text: '功能', link: '/zh-CN/features/spec' },
|
||||
{ text: '组件', link: '/zh-CN/components/' }
|
||||
],
|
||||
sidebar: {
|
||||
'/zh-CN/guide/': [
|
||||
{
|
||||
text: '📖 指南',
|
||||
collapsible: false,
|
||||
items: [
|
||||
{ text: 'Claude Code Workflow 是什么', link: '/zh-CN/guide/ch01-what-is-claude-dms3' },
|
||||
{ text: '快速开始', link: '/zh-CN/guide/ch02-getting-started' },
|
||||
{ text: '核心概念', link: '/zh-CN/guide/ch03-core-concepts' },
|
||||
{ text: '工作流基础', link: '/zh-CN/guide/ch04-workflow-basics' },
|
||||
{ text: '高级技巧', link: '/zh-CN/guide/ch05-advanced-tips' },
|
||||
{ text: '最佳实践', link: '/zh-CN/guide/ch06-best-practices' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '🚀 快速入口',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '安装', link: '/zh-CN/guide/installation' },
|
||||
{ text: '第一个工作流', link: '/zh-CN/guide/first-workflow' },
|
||||
{ text: 'CLI 工具', link: '/zh-CN/guide/cli-tools' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/zh-CN/features/': [
|
||||
{
|
||||
text: '⚙️ 核心功能',
|
||||
collapsible: false,
|
||||
items: [
|
||||
{ text: 'Spec 规范系统', link: '/zh-CN/features/spec' },
|
||||
{ text: 'Memory 记忆系统', link: '/zh-CN/features/memory' },
|
||||
{ text: 'CLI 调用', link: '/zh-CN/features/cli' },
|
||||
{ text: 'Dashboard 面板', link: '/zh-CN/features/dashboard' },
|
||||
{ text: 'CodexLens', link: '/zh-CN/features/codexlens' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/zh-CN/components/': [
|
||||
{
|
||||
text: 'UI 组件',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Button 按钮', link: '/zh-CN/components/ui/button' },
|
||||
{ text: 'Card 卡片', link: '/zh-CN/components/ui/card' },
|
||||
{ text: 'Input 输入框', link: '/zh-CN/components/ui/input' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
64
docs/.vitepress/demos/README.md
Normal file
64
docs/.vitepress/demos/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Demo Components
|
||||
|
||||
This directory contains React components that are embedded in the documentation as interactive demos.
|
||||
|
||||
## Creating a New Demo
|
||||
|
||||
1. Create a new `.tsx` file in this directory (e.g., `my-demo.tsx`)
|
||||
2. Export a default React component
|
||||
3. Use it in markdown with `:::demo my-demo :::`
|
||||
|
||||
## Demo Template
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Brief description of what this demo shows
|
||||
*/
|
||||
export default function MyDemo() {
|
||||
return (
|
||||
<div style={{ padding: '16px' }}>
|
||||
{/* Your demo content */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Demo Guidelines
|
||||
|
||||
- **Keep it simple**: Demos should be focused and easy to understand
|
||||
- **Use inline styles**: Avoid external dependencies for portability
|
||||
- **Add comments**: Explain what the demo is showing
|
||||
- **Test interactions**: Ensure buttons, inputs, etc. work correctly
|
||||
- **Handle state**: Use React hooks (`useState`, `useEffect`) for interactive demos
|
||||
|
||||
## Demo File Naming
|
||||
|
||||
- Use kebab-case: `button-variants.tsx`, `card-basic.tsx`
|
||||
- Group by category:
|
||||
- `ui/` - UI component demos
|
||||
- `shared/` - Shared component demos
|
||||
- `pages/` - Page-level demos
|
||||
|
||||
## Using Props
|
||||
|
||||
If you need to pass custom props to a demo, use the extended markdown syntax:
|
||||
|
||||
```markdown
|
||||
:::demo my-demo
|
||||
title: Custom Title
|
||||
height: 300px
|
||||
expandable: false
|
||||
:::
|
||||
```
|
||||
|
||||
## Available Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| name | string | - | Demo component name (required) |
|
||||
| title | string | name | Custom demo title |
|
||||
| height | string | 'auto' | Container height |
|
||||
| expandable | boolean | true | Allow expand/collapse |
|
||||
| showCode | boolean | true | Show code tab |
|
||||
54
docs/.vitepress/demos/button-variants.tsx
Normal file
54
docs/.vitepress/demos/button-variants.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
|
||||
/**
|
||||
* Button Variants Demo
|
||||
* Shows all 8 visual variants and 4 sizes of the Button component
|
||||
*
|
||||
* Variants include:
|
||||
* - default: Primary brand color button
|
||||
* - destructive: Red danger button
|
||||
* - outline: Bordered light button
|
||||
* - secondary: Gray secondary button
|
||||
* - ghost: Transparent hover button
|
||||
* - link: Link-style button
|
||||
* - gradient: Brand gradient button
|
||||
* - gradientPrimary: Primary gradient button
|
||||
*
|
||||
* Sizes include:
|
||||
* - default: Standard size (h-10)
|
||||
* - sm: Small size (h-9)
|
||||
* - lg: Large size (h-11)
|
||||
* - icon: Square icon size (h-10 w-10)
|
||||
*/
|
||||
export default function ButtonVariants() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-4">
|
||||
{/* Variants Section */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-3 text-muted-foreground">Variants</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button variant="default">Default</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="gradient">Gradient</Button>
|
||||
<Button variant="gradientPrimary">Gradient Primary</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sizes Section */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-3 text-muted-foreground">Sizes</h3>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="default">Default</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="icon">🔍</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
147
docs/.vitepress/theme/components/CodeViewer.vue
Normal file
147
docs/.vitepress/theme/components/CodeViewer.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
code: string
|
||||
lang?: string
|
||||
showCopy?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
lang: 'tsx',
|
||||
showCopy: true
|
||||
})
|
||||
|
||||
const copyStatus = ref<'idle' | 'copying' | 'copied'>('idle')
|
||||
const copyTimeout = ref<number>()
|
||||
|
||||
const lineCount = computed(() => props.code.split('\n').length)
|
||||
const copyButtonText = computed(() => {
|
||||
switch (copyStatus.value) {
|
||||
case 'copying': return '复制中...'
|
||||
case 'copied': return '已复制'
|
||||
default: return '复制'
|
||||
}
|
||||
})
|
||||
|
||||
const copyCode = async () => {
|
||||
if (copyStatus.value === 'copying') return
|
||||
|
||||
copyStatus.value = 'copying'
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.code)
|
||||
copyStatus.value = 'copied'
|
||||
|
||||
if (copyTimeout.value) {
|
||||
clearTimeout(copyTimeout.value)
|
||||
}
|
||||
copyTimeout.value = window.setTimeout(() => {
|
||||
copyStatus.value = 'idle'
|
||||
}, 2000)
|
||||
} catch {
|
||||
copyStatus.value = 'idle'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code-viewer">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">{{ lang }}</span>
|
||||
<button
|
||||
v-if="showCopy"
|
||||
class="copy-button"
|
||||
:class="copyStatus"
|
||||
:disabled="copyStatus === 'copying'"
|
||||
@click="copyCode"
|
||||
>
|
||||
<span class="copy-icon">📋</span>
|
||||
<span class="copy-text">{{ copyButtonText }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<pre class="code-content" :class="`language-${lang}`"><code>{{ code }}</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.code-viewer {
|
||||
background: var(--vp-code-bg);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: var(--vp-code-block-bg);
|
||||
border-bottom: 1px solid var(--vp-c-border);
|
||||
}
|
||||
|
||||
.code-lang {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.copy-button:hover:not(:disabled) {
|
||||
background: var(--vp-c-bg-mute);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.copy-button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.copy-button.copied {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.copy-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.copy-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-content code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--vp-code-color);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.code-content {
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
270
docs/.vitepress/theme/components/DemoContainer.vue
Normal file
270
docs/.vitepress/theme/components/DemoContainer.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import type { Root } from 'react-dom/client'
|
||||
import CodeViewer from './CodeViewer.vue'
|
||||
|
||||
interface Props {
|
||||
name: string // Demo component name
|
||||
file?: string // Optional: explicit source file
|
||||
height?: string // Demo container height
|
||||
expandable?: boolean // Allow expand/collapse
|
||||
showCode?: boolean // Show code tab
|
||||
title?: string // Custom demo title
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
height: 'auto',
|
||||
expandable: true,
|
||||
showCode: true,
|
||||
title: ''
|
||||
})
|
||||
|
||||
const demoRoot = ref<HTMLElement>()
|
||||
const reactRoot = ref<Root>()
|
||||
const sourceCode = ref('')
|
||||
const isExpanded = ref(false)
|
||||
const activeTab = ref<'preview' | 'code'>('preview')
|
||||
const isLoading = ref(true)
|
||||
const loadError = ref('')
|
||||
|
||||
// Derive demo title
|
||||
const demoTitle = computed(() => props.title || props.name)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Dynamically import demo component
|
||||
const demoModule = await import(`../demos/${props.name}.tsx`)
|
||||
const DemoComponent = demoModule.default || demoModule[props.name]
|
||||
|
||||
if (!DemoComponent) {
|
||||
throw new Error(`Demo component "${props.name}" not found`)
|
||||
}
|
||||
|
||||
// Mount React component
|
||||
if (demoRoot.value) {
|
||||
reactRoot.value = createRoot(demoRoot.value)
|
||||
reactRoot.value.render(DemoComponent)
|
||||
|
||||
// Extract source code
|
||||
try {
|
||||
const rawModule = await import(`../demos/${props.name}.tsx?raw`)
|
||||
sourceCode.value = rawModule.default || rawModule
|
||||
} catch {
|
||||
sourceCode.value = '// Source code not available'
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
loadError.value = err instanceof Error ? err.message : 'Failed to load demo'
|
||||
console.error('DemoContainer load error:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
reactRoot.value?.unmount()
|
||||
})
|
||||
|
||||
const toggleExpanded = () => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
}
|
||||
|
||||
const switchTab = (tab: 'preview' | 'code') => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="demo-container" :class="{ expanded: isExpanded }">
|
||||
<!-- Demo Header -->
|
||||
<div class="demo-header">
|
||||
<span class="demo-title">{{ demoTitle }}</span>
|
||||
<div class="demo-actions">
|
||||
<button
|
||||
v-if="expandable"
|
||||
class="demo-toggle"
|
||||
:aria-expanded="isExpanded"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
{{ isExpanded ? '收起' : '展开' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="showCode"
|
||||
class="tab-button"
|
||||
:class="{ active: activeTab === 'preview' }"
|
||||
:aria-selected="activeTab === 'preview'"
|
||||
@click="switchTab('preview')"
|
||||
>
|
||||
预览
|
||||
</button>
|
||||
<button
|
||||
v-if="showCode"
|
||||
class="tab-button"
|
||||
:class="{ active: activeTab === 'code' }"
|
||||
:aria-selected="activeTab === 'code'"
|
||||
@click="switchTab('code')"
|
||||
>
|
||||
代码
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Demo Content -->
|
||||
<div
|
||||
class="demo-content"
|
||||
:style="{ height: isExpanded ? 'auto' : height }"
|
||||
>
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="demo-loading">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="loadError" class="demo-error">
|
||||
<span class="error-icon">⚠️</span>
|
||||
<span class="error-message">{{ loadError }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Preview Content -->
|
||||
<div
|
||||
v-else-if="activeTab === 'preview'"
|
||||
ref="demoRoot"
|
||||
class="demo-preview"
|
||||
/>
|
||||
|
||||
<!-- Code Content -->
|
||||
<CodeViewer
|
||||
v-else-if="showCode && activeTab === 'code'"
|
||||
:code="sourceCode"
|
||||
lang="tsx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 8px;
|
||||
margin: 16px 0;
|
||||
overflow: hidden;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-bottom: 1px solid var(--vp-c-border);
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-toggle,
|
||||
.tab-button {
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.demo-toggle:hover,
|
||||
.tab-button:hover {
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
background: var(--vp-c-bg);
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.demo-preview {
|
||||
padding: 24px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.demo-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 40px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--vp-c-border);
|
||||
border-top-color: var(--vp-c-brand);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.demo-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 40px;
|
||||
color: var(--vp-c-danger-1);
|
||||
background: var(--vp-c-danger-soft);
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.demo-container.expanded .demo-content {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.demo-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.demo-preview {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
186
docs/.vitepress/theme/components/LanguageSwitcher.vue
Normal file
186
docs/.vitepress/theme/components/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
|
||||
const { site, lang, page } = useData()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const switcherRef = ref<HTMLElement>()
|
||||
|
||||
// Get available locales from VitePress config
|
||||
const locales = computed(() => {
|
||||
const localeConfig = site.value.locales || {}
|
||||
return Object.entries(localeConfig).map(([code, config]) => ({
|
||||
code,
|
||||
label: (config as any).label || code,
|
||||
link: (config as any).link || `/${code === 'root' ? '' : code}/`
|
||||
}))
|
||||
})
|
||||
|
||||
// Current locale
|
||||
const currentLocale = computed(() => {
|
||||
const current = locales.value.find(l => l.code === lang.value)
|
||||
return current || locales.value[0]
|
||||
})
|
||||
|
||||
// Get alternate language link for current page
|
||||
const getAltLink = (localeCode: string) => {
|
||||
if (localeCode === 'root') localeCode = ''
|
||||
|
||||
// Get current page path without locale prefix
|
||||
const currentPath = page.value.relativePath
|
||||
const altPath = localeCode ? `/${localeCode}/${currentPath}` : `/${currentPath}`
|
||||
|
||||
return altPath
|
||||
}
|
||||
|
||||
const switchLanguage = (localeCode: string) => {
|
||||
const altLink = getAltLink(localeCode)
|
||||
window.location.href = altLink
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
onMounted(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (switcherRef.value && !switcherRef.value.contains(e.target as Node)) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="switcherRef" class="language-switcher">
|
||||
<button
|
||||
class="switcher-button"
|
||||
:aria-expanded="isOpen"
|
||||
aria-label="Switch language"
|
||||
@click="isOpen = !isOpen"
|
||||
>
|
||||
<span class="current-locale">{{ currentLocale?.label }}</span>
|
||||
<span class="dropdown-icon" :class="{ open: isOpen }">▼</span>
|
||||
</button>
|
||||
|
||||
<Transition name="fade">
|
||||
<ul v-if="isOpen" class="locale-list">
|
||||
<li v-for="locale in locales" :key="locale.code">
|
||||
<button
|
||||
class="locale-button"
|
||||
:class="{ active: locale.code === lang }"
|
||||
@click="switchLanguage(locale.code)"
|
||||
>
|
||||
<span class="locale-label">{{ locale.label }}</span>
|
||||
<span v-if="locale.code === lang" class="check-icon">✓</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.language-switcher {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.switcher-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.switcher-button:hover {
|
||||
background: var(--vp-c-bg-mute);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.current-locale {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 10px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-icon.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.locale-list {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 150px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.locale-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.locale-button:hover {
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
|
||||
.locale-button.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.locale-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.locale-list {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
docs/.vitepress/theme/components/PropsTable.vue
Normal file
165
docs/.vitepress/theme/components/PropsTable.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
interface Prop {
|
||||
name: string
|
||||
type: string
|
||||
required?: boolean
|
||||
default?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
props: Prop[]
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="props-table-container">
|
||||
<table class="props-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Prop</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(prop, index) in props" :key="index" class="prop-row">
|
||||
<td class="prop-name">
|
||||
<code>{{ prop.name }}</code>
|
||||
</td>
|
||||
<td class="prop-type">
|
||||
<code>{{ prop.type }}</code>
|
||||
</td>
|
||||
<td class="prop-required">
|
||||
<span v-if="prop.required" class="badge required">Yes</span>
|
||||
<span v-else class="badge optional">No</span>
|
||||
</td>
|
||||
<td class="prop-default">
|
||||
<code v-if="prop.default !== undefined">{{ prop.default }}</code>
|
||||
<span v-else class="empty">-</span>
|
||||
</td>
|
||||
<td class="prop-description">
|
||||
{{ prop.description }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.props-table-container {
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.props-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.props-table thead {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-bottom: 1px solid var(--vp-c-border);
|
||||
}
|
||||
|
||||
.props-table th {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.props-table td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.props-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.props-table tr:hover {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.prop-name code,
|
||||
.prop-type code,
|
||||
.prop-default code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
padding: 2px 6px;
|
||||
background: var(--vp-code-bg);
|
||||
border-radius: 4px;
|
||||
color: var(--vp-code-color);
|
||||
}
|
||||
|
||||
.prop-name {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.prop-type {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.prop-required {
|
||||
white-space: nowrap;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge.required {
|
||||
background: var(--vp-c-danger-soft);
|
||||
color: var(--vp-c-danger-1);
|
||||
}
|
||||
|
||||
.badge.optional {
|
||||
background: var(--vp-c-default-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.prop-default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.prop-default .empty {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.prop-description {
|
||||
color: var(--vp-c-text-2);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.props-table {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.props-table th,
|
||||
.props-table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.prop-description {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,9 +7,16 @@ import Breadcrumb from './components/Breadcrumb.vue'
|
||||
import PageToc from './components/PageToc.vue'
|
||||
import ProfessionalHome from './components/ProfessionalHome.vue'
|
||||
import Layout from './layouts/Layout.vue'
|
||||
// Demo system components
|
||||
import DemoContainer from './components/DemoContainer.vue'
|
||||
import CodeViewer from './components/CodeViewer.vue'
|
||||
import PropsTable from './components/PropsTable.vue'
|
||||
// Language switcher component
|
||||
import LanguageSwitcher from './components/LanguageSwitcher.vue'
|
||||
import './styles/variables.css'
|
||||
import './styles/custom.css'
|
||||
import './styles/mobile.css'
|
||||
import './styles/demo.css'
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
@@ -23,5 +30,11 @@ export default {
|
||||
app.component('Breadcrumb', Breadcrumb)
|
||||
app.component('PageToc', PageToc)
|
||||
app.component('ProfessionalHome', ProfessionalHome)
|
||||
// Register demo system components
|
||||
app.component('DemoContainer', DemoContainer)
|
||||
app.component('CodeViewer', CodeViewer)
|
||||
app.component('PropsTable', PropsTable)
|
||||
// Register language switcher component
|
||||
app.component('LanguageSwitcher', LanguageSwitcher)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DefaultTheme from 'vitepress/theme'
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
import { useDynamicIcon } from '../composables/useDynamicIcon'
|
||||
import ThemeLogo from '../components/ThemeLogo.vue'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
|
||||
let mediaQuery: MediaQueryList | null = null
|
||||
let systemThemeChangeHandler: (() => void) | null = null
|
||||
@@ -84,6 +85,7 @@ onBeforeUnmount(() => {
|
||||
<DocSearch />
|
||||
<DarkModeToggle />
|
||||
<ThemeSwitcher />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</template>
|
||||
</DefaultTheme.Layout>
|
||||
|
||||
66
docs/.vitepress/theme/markdownTransform.ts
Normal file
66
docs/.vitepress/theme/markdownTransform.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { MarkdownTransformContext } from 'vitepress'
|
||||
|
||||
const demoBlockRE = /:::\s*demo\s+(.+?)\s*:::/g
|
||||
|
||||
export interface DemoBlockMeta {
|
||||
name: string
|
||||
file?: string
|
||||
height?: string
|
||||
expandable?: boolean
|
||||
showCode?: boolean
|
||||
title?: string
|
||||
}
|
||||
|
||||
export function transformDemoBlocks(
|
||||
code: string,
|
||||
ctx: MarkdownTransformContext
|
||||
): string {
|
||||
return code.replace(demoBlockRE, (match, content) => {
|
||||
const meta = parseDemoBlock(content)
|
||||
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${Math.random().toString(36).slice(2, 8)}`
|
||||
|
||||
const props = [
|
||||
`id="${demoId}"`,
|
||||
`name="${meta.name}"`,
|
||||
meta.file ? `file="${meta.file}"` : '',
|
||||
meta.height ? `height="${meta.height}"` : '',
|
||||
meta.expandable === false ? ':expandable="false"' : ':expandable="true"',
|
||||
meta.showCode === false ? ':show-code="false"' : ':show-code="true"',
|
||||
meta.title ? `title="${meta.title}"` : ''
|
||||
].filter(Boolean).join(' ')
|
||||
|
||||
return `<DemoContainer ${props} />`
|
||||
})
|
||||
}
|
||||
|
||||
function parseDemoBlock(content: string): DemoBlockMeta {
|
||||
const lines = content.trim().split('\n')
|
||||
const firstLine = lines[0] || ''
|
||||
|
||||
// Parse: name or # file
|
||||
const [name, ...rest] = firstLine.split(/\s+/)
|
||||
const file = rest.find(l => l.startsWith('#'))?.slice(1)
|
||||
|
||||
return {
|
||||
name: name.trim(),
|
||||
file,
|
||||
height: extractProp(lines, 'height'),
|
||||
expandable: extractProp(lines, 'expandable') !== 'false',
|
||||
showCode: extractProp(lines, 'showCode') !== 'false',
|
||||
title: extractProp(lines, 'title')
|
||||
}
|
||||
}
|
||||
|
||||
function extractProp(lines: string[], prop: string): string | undefined {
|
||||
const line = lines.find(l => l.trim().startsWith(`${prop}:`))
|
||||
return line?.split(':', 2)[1]?.trim()
|
||||
}
|
||||
|
||||
// VitePress markdown configuration hook
|
||||
export function markdownTransformSetup() {
|
||||
return {
|
||||
transform: (code: string, ctx: MarkdownTransformContext) => {
|
||||
return transformDemoBlocks(code, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
104
docs/.vitepress/theme/styles/demo.css
Normal file
104
docs/.vitepress/theme/styles/demo.css
Normal file
@@ -0,0 +1,104 @@
|
||||
/* Demo system styles for VitePress */
|
||||
|
||||
/* Demo container theme-aware styling */
|
||||
.demo-container {
|
||||
/* Theme-aware borders */
|
||||
border: 1px solid var(--vp-c-border);
|
||||
|
||||
/* Rounded corners */
|
||||
border-radius: 8px;
|
||||
|
||||
/* Subtle shadow */
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* Dark mode support */
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.demo-preview {
|
||||
/* Consistent padding */
|
||||
padding: 24px;
|
||||
|
||||
/* Min height for visibility */
|
||||
min-height: 100px;
|
||||
|
||||
/* Center content when small */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Dark mode overrides */
|
||||
.dark .demo-container {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.demo-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-preview {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
/* Demo code viewer styles */
|
||||
.code-viewer {
|
||||
background: var(--vp-code-bg);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: var(--vp-code-block-bg);
|
||||
border-bottom: 1px solid var(--vp-c-border);
|
||||
}
|
||||
|
||||
/* Props table styles */
|
||||
.props-table-container {
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.props-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.props-table thead {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-bottom: 1px solid var(--vp-c-border);
|
||||
}
|
||||
|
||||
/* Interactive demo feedback */
|
||||
.demo-container:has(.demo-preview:hover) {
|
||||
border-color: var(--vp-c-brand);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
/* Code syntax highlighting in demos */
|
||||
.demo-preview code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
padding: 2px 6px;
|
||||
background: var(--vp-code-bg);
|
||||
border-radius: 4px;
|
||||
color: var(--vp-code-color);
|
||||
}
|
||||
600
docs/components/index.md
Normal file
600
docs/components/index.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# Component Library
|
||||
|
||||
## One-Liner
|
||||
**A comprehensive collection of reusable UI components built with Radix UI primitives and Tailwind CSS, following shadcn/ui patterns for consistent, accessible, and customizable interfaces.**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/components/ui/`
|
||||
|
||||
**Purpose**: Provides a consistent set of UI components for building the CCW frontend application.
|
||||
|
||||
**Technology Stack**:
|
||||
- **Radix UI**: Unstyled, accessible component primitives
|
||||
- **Tailwind CSS**: Utility-first styling with custom theme
|
||||
- **class-variance-authority (CVA)**: Type-safe variant prop management
|
||||
- **Lucide React**: Consistent iconography
|
||||
|
||||
---
|
||||
|
||||
## Live Demo: Component Gallery
|
||||
|
||||
:::demo ComponentGallery
|
||||
# component-gallery.tsx
|
||||
/**
|
||||
* Component Gallery Demo
|
||||
* Interactive showcase of all UI components
|
||||
*/
|
||||
export function ComponentGallery() {
|
||||
const [selectedCategory, setSelectedCategory] = React.useState('all')
|
||||
const [buttonVariant, setButtonVariant] = React.useState('default')
|
||||
const [switchState, setSwitchState] = React.useState(false)
|
||||
const [checkboxState, setCheckboxState] = React.useState(false)
|
||||
const [selectedTab, setSelectedTab] = React.useState('variants')
|
||||
|
||||
const categories = [
|
||||
{ id: 'all', label: 'All Components' },
|
||||
{ id: 'buttons', label: 'Buttons' },
|
||||
{ id: 'forms', label: 'Forms' },
|
||||
{ id: 'feedback', label: 'Feedback' },
|
||||
{ id: 'navigation', label: 'Navigation' },
|
||||
{ id: 'overlays', label: 'Overlays' },
|
||||
]
|
||||
|
||||
const components = {
|
||||
buttons: [
|
||||
{ name: 'Button', variants: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link', 'gradient'] },
|
||||
],
|
||||
forms: [
|
||||
{ name: 'Input', type: 'text' },
|
||||
{ name: 'Textarea', type: 'textarea' },
|
||||
{ name: 'Select', type: 'select' },
|
||||
{ name: 'Checkbox', type: 'checkbox' },
|
||||
{ name: 'Switch', type: 'switch' },
|
||||
],
|
||||
feedback: [
|
||||
{ name: 'Badge', variants: ['default', 'secondary', 'success', 'warning', 'destructive'] },
|
||||
{ name: 'Progress', type: 'progress' },
|
||||
{ name: 'Alert', type: 'alert' },
|
||||
],
|
||||
navigation: [
|
||||
{ name: 'Tabs', type: 'tabs' },
|
||||
{ name: 'Breadcrumb', type: 'breadcrumb' },
|
||||
],
|
||||
overlays: [
|
||||
{ name: 'Dialog', type: 'dialog' },
|
||||
{ name: 'Drawer', type: 'drawer' },
|
||||
{ name: 'Dropdown', type: 'dropdown' },
|
||||
],
|
||||
}
|
||||
|
||||
const buttonVariants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">UI Component Library</h1>
|
||||
<p className="text-muted-foreground">Interactive showcase of all available UI components</p>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
className={`px-4 py-2 rounded-md text-sm transition-colors ${
|
||||
selectedCategory === cat.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Buttons Section */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'buttons') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Buttons</h2>
|
||||
<div className="space-y-6">
|
||||
{/* Variant Selector */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Variant</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{buttonVariants.map((variant) => (
|
||||
<button
|
||||
key={variant}
|
||||
onClick={() => setButtonVariant(variant)}
|
||||
className={`px-4 py-2 rounded-md text-sm capitalize transition-colors ${
|
||||
buttonVariant === variant
|
||||
? 'bg-primary text-primary-foreground ring-2 ring-ring'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{variant}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button Sizes */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Sizes</label>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<button className={`h-8 rounded-md px-3 text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
Small
|
||||
</button>
|
||||
<button className={`h-10 px-4 py-2 rounded-md text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
Default
|
||||
</button>
|
||||
<button className={`h-11 rounded-md px-8 text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
Large
|
||||
</button>
|
||||
<button className={`h-10 w-10 rounded-md flex items-center justify-center ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
⚙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* All Button Variants */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">All Variants</label>
|
||||
<div className="flex flex-wrap gap-3 p-4 border rounded-lg bg-muted/20">
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:opacity-90">Default</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-destructive text-destructive-foreground hover:opacity-90">Destructive</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm border bg-background hover:bg-accent">Outline</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-secondary text-secondary-foreground hover:opacity-80">Secondary</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm hover:bg-accent">Ghost</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm text-primary underline-offset-4 hover:underline">Link</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white hover:opacity-90">Gradient</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Forms Section */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'forms') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Form Components</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Input */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Input</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter text..."
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Error state"
|
||||
className="flex h-10 w-full rounded-md border border-destructive bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-destructive"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Textarea */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Textarea</label>
|
||||
<textarea
|
||||
placeholder="Enter multi-line text..."
|
||||
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Checkbox */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Checkbox</label>
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border border-primary" checked={checkboxState} onChange={(e) => setCheckboxState(e.target.checked)} />
|
||||
<span>Accept terms and conditions</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer opacity-50">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border border-primary" />
|
||||
<span>Subscribe to newsletter</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Switch */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Switch</label>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<div className="relative">
|
||||
<input type="checkbox" className="sr-only peer" checked={switchState} onChange={(e) => setSwitchState(e.target.checked)} />
|
||||
<div className="w-9 h-5 bg-input rounded-full peer peer-focus:ring-2 peer-focus:ring-ring peer-checked:bg-primary after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-background after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full" />
|
||||
</div>
|
||||
<span className="text-sm">Enable notifications {switchState ? '(on)' : '(off)'}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Select */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Select</label>
|
||||
<select className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
<option value="">Choose an option</option>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Form Actions */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Form Actions</label>
|
||||
<div className="flex gap-2">
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:opacity-90">Submit</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm border hover:bg-accent">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Feedback Section */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'feedback') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Feedback Components</h2>
|
||||
|
||||
{/* Badges */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Badges</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary text-primary-foreground">Default</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-secondary text-secondary-foreground">Secondary</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-destructive text-destructive-foreground">Destructive</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-success text-white">Success</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-warning text-white">Warning</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-info text-white">Info</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold text-foreground">Outline</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Progress Bars</label>
|
||||
<div className="space-y-3 max-w-md">
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span>Processing...</span>
|
||||
<span>65%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-primary rounded-full transition-all" style={{ width: '65%' }}/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span>Uploading...</span>
|
||||
<span>30%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full transition-all" style={{ width: '30%' }}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alerts */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Alerts</label>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3 p-4 border rounded-lg bg-destructive/10 border-destructive/20 text-destructive">
|
||||
<span className="text-lg">⚠</span>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">Error occurred</div>
|
||||
<div className="text-xs mt-1 opacity-80">Something went wrong. Please try again.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 border rounded-lg bg-success/10 border-success/20 text-success">
|
||||
<span className="text-lg">✓</span>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">Success!</div>
|
||||
<div className="text-xs mt-1 opacity-80">Your changes have been saved.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Navigation Section */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'navigation') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Navigation Components</h2>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Tabs</label>
|
||||
<div className="border-b">
|
||||
<div className="flex gap-4">
|
||||
{['Overview', 'Documentation', 'API Reference', 'Examples'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setSelectedTab(tab.toLowerCase().replace(' ', '-'))}
|
||||
className={`pb-3 px-1 text-sm border-b-2 transition-colors ${
|
||||
selectedTab === tab.toLowerCase().replace(' ', '-')
|
||||
? 'border-primary text-primary'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Breadcrumb</label>
|
||||
<nav className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<a href="#" className="hover:text-foreground">Home</a>
|
||||
<span>/</span>
|
||||
<a href="#" className="hover:text-foreground">Components</a>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">Library</span>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Overlays Section */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'overlays') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Overlay Components</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-4 text-sm">
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">Dialog</h3>
|
||||
<p className="text-muted-foreground text-xs">Modal dialogs for focused user interactions.</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded">Open Dialog</button>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">Drawer</h3>
|
||||
<p className="text-muted-foreground text-xs">Side panels that slide in from screen edges.</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs border rounded hover:bg-accent">Open Drawer</button>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">Dropdown Menu</h3>
|
||||
<p className="text-muted-foreground text-xs">Context menus and action lists.</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs border rounded hover:bg-accent">▼ Open Menu</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Available Components
|
||||
|
||||
### Form Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Button](/components/ui/button) | Clickable action buttons with variants and sizes | `variant`, `size`, `asChild` |
|
||||
| [Input](/components/ui/input) | Text input field | `error` |
|
||||
| [Textarea](/components/ui/textarea) | Multi-line text input | `error` |
|
||||
| [Select](/components/ui/select) | Dropdown selection (Radix) | Select components |
|
||||
| [Checkbox](/components/ui/checkbox) | Boolean checkbox (Radix) | `checked`, `onCheckedChange` |
|
||||
| [Switch](/components/ui/switch) | Toggle switch | `checked`, `onCheckedChange` |
|
||||
|
||||
### Layout Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Card](/components/ui/card) | Content container with header/footer | Nested components |
|
||||
| [Separator](/components/ui/separator) | Visual divider | `orientation` |
|
||||
| [ScrollArea](/components/ui/scroll-area) | Custom scrollbar container | - |
|
||||
|
||||
### Feedback Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Badge](/components/ui/badge) | Status indicator label | `variant` |
|
||||
| [Progress](/components/ui/progress) | Progress bar | `value` |
|
||||
| [Alert](/components/ui/alert) | Notification message | `variant` |
|
||||
| [Toast](/components/ui/toast) | Temporary notification (Radix) | Toast components |
|
||||
|
||||
### Navigation Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Tabs](/components/ui/tabs) | Tab navigation (Radix) | Tabs components |
|
||||
| [TabsNavigation](/components/ui/tabs-navigation) | Custom tab bar | `tabs`, `value`, `onValueChange` |
|
||||
| [Breadcrumb](/components/ui/breadcrumb) | Navigation breadcrumb | Breadcrumb components |
|
||||
|
||||
### Overlay Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Dialog](/components/ui/dialog) | Modal dialog (Radix) | `open`, `onOpenChange` |
|
||||
| [Drawer](/components/ui/drawer) | Side panel (Radix) | `open`, `onOpenChange` |
|
||||
| [Dropdown Menu](/components/ui/dropdown) | Context menu (Radix) | Dropdown components |
|
||||
| [Popover](/components/ui/popover) | Floating content (Radix) | `open`, `onOpenChange` |
|
||||
| [Tooltip](/components/ui/tooltip) | Hover tooltip (Radix) | `content` |
|
||||
| [AlertDialog](/components/ui/alert-dialog) | Confirmation dialog (Radix) | Dialog components |
|
||||
|
||||
### Disclosure Components
|
||||
|
||||
| Component | Description | Props |
|
||||
|-----------|-------------|-------|
|
||||
| [Collapsible](/components/ui/collapsible) | Expand/collapse content (Radix) | `open`, `onOpenChange` |
|
||||
| [Accordion](/components/ui/accordion) | Collapsible sections (Radix) | Accordion components |
|
||||
|
||||
---
|
||||
|
||||
## Button Variants
|
||||
|
||||
The Button component supports 8 variants via CVA (class-variance-authority):
|
||||
|
||||
| Variant | Use Case | Preview |
|
||||
|---------|----------|--------|
|
||||
| `default` | Primary action | <span className="inline-block px-3 py-1 rounded bg-primary text-primary-foreground text-xs">Default</span> |
|
||||
| `destructive` | Dangerous actions | <span className="inline-block px-3 py-1 rounded bg-destructive text-destructive-foreground text-xs">Destructive</span> |
|
||||
| `outline` | Secondary action | <span className="inline-block px-3 py-1 rounded border text-xs">Outline</span> |
|
||||
| `secondary` | Less emphasis | <span className="inline-block px-3 py-1 rounded bg-secondary text-secondary-foreground text-xs">Secondary</span> |
|
||||
| `ghost` | Subtle action | <span className="inline-block px-3 py-1 rounded text-xs">Ghost</span> |
|
||||
| `link` | Text link | <span className="inline-block px-3 py-1 underline text-primary text-xs">Link</span> |
|
||||
| `gradient` | Featured action | <span className="inline-block px-3 py-1 rounded bg-gradient-to-r from-blue-500 to-purple-500 text-white text-xs">Gradient</span> |
|
||||
| `gradientPrimary` | Primary gradient | <span className="inline-block px-3 py-1 rounded bg-gradient-to-r from-purple-500 to-pink-500 text-white text-xs">Primary</span> |
|
||||
|
||||
### Button Sizes
|
||||
|
||||
| Size | Height | Padding |
|
||||
|------|--------|---------|
|
||||
| `sm` | 36px | 12px horizontal |
|
||||
| `default` | 40px | 16px horizontal |
|
||||
| `lg` | 44px | 32px horizontal |
|
||||
| `icon` | 40px | Square (icon only) |
|
||||
|
||||
---
|
||||
|
||||
## Badge Variants
|
||||
|
||||
The Badge component supports 9 variants for different status types:
|
||||
|
||||
| Variant | Use Case | Color |
|
||||
|---------|----------|-------|
|
||||
| `default` | General info | Primary theme |
|
||||
| `secondary` | Less emphasis | Secondary theme |
|
||||
| `destructive` | Error/Danger | Destructive theme |
|
||||
| `outline` | Subtle | Text color only |
|
||||
| `success` | Success state | Green |
|
||||
| `warning` | Warning state | Amber |
|
||||
| `info` | Information | Blue |
|
||||
| `review` | Review status | Purple |
|
||||
| `gradient` | Featured | Brand gradient |
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Button
|
||||
|
||||
```tsx
|
||||
import { Button } from '@/components/ui/Button'
|
||||
|
||||
<Button variant="default" onClick={handleClick}>
|
||||
Click me
|
||||
</Button>
|
||||
|
||||
<Button variant="destructive" size="sm">
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="icon">
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Input with Error State
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
error={hasError}
|
||||
placeholder="Enter your name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
|
||||
```tsx
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
|
||||
<Checkbox
|
||||
checked={accepted}
|
||||
onCheckedChange={setAccepted}
|
||||
/>
|
||||
<label>Accept terms</label>
|
||||
```
|
||||
|
||||
### Switch
|
||||
|
||||
```tsx
|
||||
import { Switch } from '@/components/ui/Switch'
|
||||
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onCheckedChange={setEnabled}
|
||||
/>
|
||||
<span>Enable feature</span>
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/Card'
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
<CardDescription>Brief description here</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Main content goes here.</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Action</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Badge
|
||||
|
||||
```tsx
|
||||
import { Badge, badgeVariants } from '@/components/ui/Badge'
|
||||
|
||||
<Badge variant="success">Completed</Badge>
|
||||
<Badge variant="warning">Pending</Badge>
|
||||
<Badge variant="destructive">Failed</Badge>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
All components follow Radix UI's accessibility standards:
|
||||
|
||||
- **Keyboard Navigation**: All interactive components are fully keyboard accessible
|
||||
- **ARIA Attributes**: Proper roles, states, and properties
|
||||
- **Screen Reader Support**: Semantic HTML and ARIA labels
|
||||
- **Focus Management**: Visible focus indicators and logical tab order
|
||||
- **Color Contrast**: WCAG AA compliant color combinations
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
| Component | Keys |
|
||||
|-----------|-------|
|
||||
| Button | <kbd>Enter</kbd>, <kbd>Space</kbd> |
|
||||
| Checkbox/Switch | <kbd>Space</kbd> to toggle |
|
||||
| Select | <kbd>Arrow</kbd> keys, <kbd>Enter</kbd> to select, <kbd>Esc</kbd> to close |
|
||||
| Dialog | <kbd>Esc</kbd> to close |
|
||||
| Tabs | <kbd>Arrow</kbd> keys to navigate |
|
||||
| Dropdown | <kbd>Arrow</kbd> keys, <kbd>Enter</kbd> to select |
|
||||
|
||||
---
|
||||
|
||||
## Related Links
|
||||
|
||||
- [Radix UI Primitives](https://www.radix-ui.com/) - Headless UI component library
|
||||
- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - Component patterns reference
|
||||
- [CVA Documentation](https://cva.style/) - Class Variance Authority
|
||||
119
docs/components/ui/badge.md
Normal file
119
docs/components/ui/badge.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: Badge
|
||||
description: Small status or label component for visual categorization
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Badge
|
||||
|
||||
## Overview
|
||||
|
||||
The Badge component is used to display status, categories, or labels in a compact form. It's commonly used for tags, status indicators, and counts.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo badge-variants
|
||||
Shows all available badge variants including default, secondary, destructive, outline, success, warning, info, review, and gradient
|
||||
:::
|
||||
|
||||
## Props
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'variant', type: '\'default\' | \'secondary\' | \'destructive\' | \'outline\' | \'success\' | \'warning\' | \'info\' | \'review\' | \'gradient\'', required: false, default: '\'default\'', description: 'Visual style variant' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: 'Custom CSS class name' },
|
||||
{ name: 'children', type: 'ReactNode', required: true, default: '-', description: 'Badge content' }
|
||||
]" />
|
||||
|
||||
## Variants
|
||||
|
||||
### Default
|
||||
|
||||
Primary badge with theme color background. Used for primary labels and categories.
|
||||
|
||||
### Secondary
|
||||
|
||||
Muted badge for secondary information.
|
||||
|
||||
### Destructive
|
||||
|
||||
Red badge for errors, danger states, or negative status.
|
||||
|
||||
### Outline
|
||||
|
||||
Badge with only text and border, no background. Used for subtle labels.
|
||||
|
||||
### Success
|
||||
|
||||
Green badge for success states, completed actions, or positive status.
|
||||
|
||||
### Warning
|
||||
|
||||
Yellow/amber badge for warnings, pending states, or caution indicators.
|
||||
|
||||
### Info
|
||||
|
||||
Blue badge for informational content or neutral status.
|
||||
|
||||
### Review
|
||||
|
||||
Purple badge for review status, pending review, or feedback indicators.
|
||||
|
||||
### Gradient
|
||||
|
||||
Badge with brand gradient background for featured or highlighted items.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Badge
|
||||
|
||||
```vue
|
||||
<Badge>Default</Badge>
|
||||
```
|
||||
|
||||
### Status Indicators
|
||||
|
||||
```vue
|
||||
<Badge variant="success">Active</Badge>
|
||||
<Badge variant="warning">Pending</Badge>
|
||||
<Badge variant="destructive">Failed</Badge>
|
||||
<Badge variant="info">Draft</Badge>
|
||||
```
|
||||
|
||||
### Count Badge
|
||||
|
||||
```vue
|
||||
<div class="relative">
|
||||
<Bell />
|
||||
<Badge variant="destructive" class="absolute -top-2 -right-2 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs">
|
||||
3
|
||||
</Badge>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Category Tags
|
||||
|
||||
```vue
|
||||
<div class="flex gap-2">
|
||||
<Badge variant="outline">React</Badge>
|
||||
<Badge variant="outline">TypeScript</Badge>
|
||||
<Badge variant="outline">Tailwind</Badge>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Review Status
|
||||
|
||||
```vue
|
||||
<Badge variant="review">In Review</Badge>
|
||||
```
|
||||
|
||||
### Gradient Badge
|
||||
|
||||
```vue
|
||||
<Badge variant="gradient">Featured</Badge>
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Card](/components/ui/card)
|
||||
- [Button](/components/ui/button)
|
||||
- [Avatar](/components/ui/avatar)
|
||||
80
docs/components/ui/button.md
Normal file
80
docs/components/ui/button.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Button
|
||||
description: Button component for triggering actions or submitting forms
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Button
|
||||
|
||||
## Overview
|
||||
|
||||
The Button component is one of the most commonly used interactive elements, used to trigger actions, submit forms, or navigate to other pages.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo button-variants
|
||||
Shows all visual variants of the button component
|
||||
:::
|
||||
|
||||
## Props
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'variant', type: '\'default\' | \'destructive\' | \'outline\' | \'secondary\' | \'ghost\' | \'link\' | \'gradient\' | \'gradientPrimary\'', required: false, default: '\'default\'', description: 'Visual style variant' },
|
||||
{ name: 'size', type: '\'default\' | \'sm\' | \'lg\' | \'icon\'', required: false, default: '\'default\'', description: 'Button size' },
|
||||
{ name: 'asChild', type: 'boolean', required: false, default: 'false', description: 'Whether to merge props with child element (for Radix UI composition)' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Whether the button is disabled' },
|
||||
{ name: 'onClick', type: '() => void', required: false, default: '-', description: 'Click event callback' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: 'Custom CSS class name' },
|
||||
{ name: 'children', type: 'ReactNode', required: true, default: '-', description: 'Button content' }
|
||||
]" />
|
||||
|
||||
## Variants
|
||||
|
||||
### Default
|
||||
|
||||
Default buttons are used for primary actions.
|
||||
|
||||
### Destructive
|
||||
|
||||
Destructive buttons are used for irreversible actions like delete or remove.
|
||||
|
||||
### Outline
|
||||
|
||||
Outline buttons are used for secondary actions with a lighter visual weight.
|
||||
|
||||
### Secondary
|
||||
|
||||
Secondary buttons are used for auxiliary actions.
|
||||
|
||||
### Ghost
|
||||
|
||||
Ghost buttons have no border and the lightest visual weight.
|
||||
|
||||
### Link
|
||||
|
||||
Link buttons look like links but have button interaction behavior.
|
||||
|
||||
### Gradient
|
||||
|
||||
Gradient buttons use the brand gradient with a glow effect on hover.
|
||||
|
||||
### Gradient Primary
|
||||
|
||||
Gradient Primary buttons use the primary theme gradient with an enhanced glow effect on hover.
|
||||
|
||||
## Usage Scenarios
|
||||
|
||||
| Scenario | Recommended Variant |
|
||||
|----------|---------------------|
|
||||
| Primary actions (submit, save) | default, gradientPrimary |
|
||||
| Dangerous actions (delete, remove) | destructive |
|
||||
| Secondary actions | outline, secondary |
|
||||
| Cancel actions | ghost, outline |
|
||||
| Navigation links | link |
|
||||
| Promotional/Featured CTAs | gradient |
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Input](/components/ui/input)
|
||||
- [Select](/components/ui/select)
|
||||
- [Dialog](/components/ui/dialog)
|
||||
107
docs/components/ui/card.md
Normal file
107
docs/components/ui/card.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: Card
|
||||
description: Container component for grouping related content
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Card
|
||||
|
||||
## Overview
|
||||
|
||||
The Card component is a versatile container used to group related content and actions. It consists of several sub-components that work together to create a cohesive card layout.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo card-variants
|
||||
Shows different card layouts including header, content, footer, and gradient border variants
|
||||
:::
|
||||
|
||||
## Components
|
||||
|
||||
The Card component includes the following sub-components:
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| `Card` | Main container with border and background |
|
||||
| `CardHeader` | Header section with padding |
|
||||
| `CardTitle` | Title heading element |
|
||||
| `CardDescription` | Descriptive text with muted color |
|
||||
| `CardContent` | Content area with top padding |
|
||||
| `CardFooter` | Footer section for actions |
|
||||
| `CardGradientBorder` | Card with gradient border |
|
||||
|
||||
## Props
|
||||
|
||||
All Card components accept standard HTML div attributes:
|
||||
|
||||
| Component | Props |
|
||||
|-----------|-------|
|
||||
| `Card` | `className?: string` |
|
||||
| `CardHeader` | `className?: string` |
|
||||
| `CardTitle` | `className?: string`, `children?: ReactNode` |
|
||||
| `CardDescription` | `className?: string`, `children?: ReactNode` |
|
||||
| `CardContent` | `className?: string` |
|
||||
| `CardFooter` | `className?: string` |
|
||||
| `CardGradientBorder` | `className?: string` |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Card
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardContent>
|
||||
<p>This is a basic card with content.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Card with Header
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
<CardDescription>Card description goes here</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Card content goes here.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Complete Card with Footer
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Settings</CardTitle>
|
||||
<CardDescription>Manage your project configuration</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Configure your project settings and preferences.</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Save Changes</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Card with Gradient Border
|
||||
|
||||
```vue
|
||||
<CardGradientBorder>
|
||||
<CardHeader>
|
||||
<CardTitle>Featured Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>This card has a gradient border effect.</p>
|
||||
</CardContent>
|
||||
</CardGradientBorder>
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Button](/components/ui/button)
|
||||
- [Badge](/components/ui/badge)
|
||||
- [Separator](/components/ui/separator)
|
||||
120
docs/components/ui/checkbox.md
Normal file
120
docs/components/ui/checkbox.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Checkbox
|
||||
description: Checkbox component for binary choice selection
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Checkbox
|
||||
|
||||
## Overview
|
||||
|
||||
The Checkbox component allows users to select one or more options from a set. Built on Radix UI Checkbox Primitive, it provides full accessibility support including keyboard navigation.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo checkbox-variants
|
||||
Shows different checkbox states including checked, unchecked, indeterminate, and disabled
|
||||
:::
|
||||
|
||||
## Props
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'checked', type: 'boolean | \'indeterminate\'', required: false, default: 'false', description: 'Whether the checkbox is checked' },
|
||||
{ name: 'defaultChecked', type: 'boolean', required: false, default: 'false', description: 'Initial checked state (uncontrolled)' },
|
||||
{ name: 'onCheckedChange', type: '(checked: boolean) => void', required: false, default: '-', description: 'Callback when checked state changes' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Whether the checkbox is disabled' },
|
||||
{ name: 'required', type: 'boolean', required: false, default: 'false', description: 'Whether the checkbox is required' },
|
||||
{ name: 'name', type: 'string', required: false, default: '-', description: 'Form input name' },
|
||||
{ name: 'value', type: 'string', required: false, default: '-', description: 'Form input value' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: 'Custom CSS class name' }
|
||||
]" />
|
||||
|
||||
## States
|
||||
|
||||
### Unchecked
|
||||
|
||||
The default state when the checkbox is not selected.
|
||||
|
||||
### Checked
|
||||
|
||||
The checkbox shows a checkmark icon when selected.
|
||||
|
||||
### Indeterminate
|
||||
|
||||
A mixed state (partial selection) typically used for parent checkboxes when some but not all children are selected.
|
||||
|
||||
### Disabled
|
||||
|
||||
Disabled checkboxes are non-interactive and displayed with reduced opacity.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Checkbox
|
||||
|
||||
```vue
|
||||
<Checkbox />
|
||||
```
|
||||
|
||||
### With Label
|
||||
|
||||
```vue
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label for="terms">I agree to the terms and conditions</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Controlled Checkbox
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const checked = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox v-model:checked="checked" />
|
||||
<span>{{ checked ? 'Checked' : 'Unchecked' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Indeterminate State
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const state = ref('indeterminate')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Checkbox :checked="state" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### Form Integration
|
||||
|
||||
```vue
|
||||
<form @submit="handleSubmit">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="newsletter" name="newsletter" value="yes" />
|
||||
<label for="newsletter">Subscribe to newsletter</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="updates" name="updates" value="yes" />
|
||||
<label for="updates">Receive product updates</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" class="mt-4">Submit</Button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Input](/components/ui/input)
|
||||
- [Select](/components/ui/select)
|
||||
- [Radio Group](/components/ui/radio-group)
|
||||
118
docs/components/ui/input.md
Normal file
118
docs/components/ui/input.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Input
|
||||
description: Text input component for forms and user input
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Input
|
||||
|
||||
## Overview
|
||||
|
||||
The Input component provides a styled text input field that extends the native HTML input element with consistent styling and error state support.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo input-variants
|
||||
Shows all input states including default, error, and disabled
|
||||
:::
|
||||
|
||||
## Props
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'type', type: 'string', required: false, default: '\'text\'', description: 'HTML input type (text, password, email, number, etc.)' },
|
||||
{ name: 'error', type: 'boolean', required: false, default: 'false', description: 'Whether the input has an error (shows destructive border)' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Whether the input is disabled' },
|
||||
{ name: 'placeholder', type: 'string', required: false, default: '-', description: 'Placeholder text shown when input is empty' },
|
||||
{ name: 'value', type: 'string | number', required: false, default: '-', description: 'Controlled input value' },
|
||||
{ name: 'defaultValue', type: 'string | number', required: false, default: '-', description: 'Uncontrolled input default value' },
|
||||
{ name: 'onChange', type: '(event: ChangeEvent) => void', required: false, default: '-', description: 'Change event callback' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: 'Custom CSS class name' }
|
||||
]" />
|
||||
|
||||
## States
|
||||
|
||||
### Default
|
||||
|
||||
Standard input field with border and focus ring.
|
||||
|
||||
### Error
|
||||
|
||||
Error state with destructive border color. Set the `error` prop to `true`.
|
||||
|
||||
### Disabled
|
||||
|
||||
Disabled state with reduced opacity. Set the `disabled` prop to `true`.
|
||||
|
||||
### Focus
|
||||
|
||||
Focused state with ring outline.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Input
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return <input type="text" placeholder="Enter text..." />
|
||||
}
|
||||
```
|
||||
|
||||
### Controlled Input
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter text..."
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Input with Error State
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
error
|
||||
placeholder="Invalid input..."
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Password Input
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return <Input type="password" placeholder="Enter password..." />
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- **Keyboard Navigation**: Full native keyboard support
|
||||
- **ARIA Attributes**: Supports all standard input ARIA attributes
|
||||
- **Focus Visible**: Clear focus indicator for keyboard navigation
|
||||
- **Error State**: Visual indication for error state (use with `aria-invalid` and `aria-describedby`)
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Button](/components/ui/button)
|
||||
- [Select](/components/ui/select)
|
||||
- [Checkbox](/components/ui/checkbox)
|
||||
127
docs/components/ui/select.md
Normal file
127
docs/components/ui/select.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Select
|
||||
description: Dropdown select component for choosing from a list of options
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Select
|
||||
|
||||
## Overview
|
||||
|
||||
The Select component allows users to choose a single value from a list of options. Built on Radix UI Select Primitive, it provides accessibility and keyboard navigation out of the box.
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo select-variants
|
||||
Shows different select configurations including basic usage, with labels, and with separators
|
||||
:::
|
||||
|
||||
## Components
|
||||
|
||||
The Select component includes the following sub-components:
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| `Select` | Root component that manages state |
|
||||
| `SelectTrigger` | Button that opens the dropdown |
|
||||
| `SelectValue` | Displays the selected value |
|
||||
| `SelectContent` | Dropdown content container |
|
||||
| `SelectItem` | Individual selectable option |
|
||||
| `SelectLabel` | Non-interactive label for grouping |
|
||||
| `SelectGroup` | Groups items together |
|
||||
| `SelectSeparator` | Visual separator between items |
|
||||
| `SelectScrollUpButton` | Scroll button for long lists |
|
||||
| `SelectScrollDownButton` | Scroll button for long lists |
|
||||
|
||||
## Props
|
||||
|
||||
### Select Root
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `value` | `string` | - | Currently selected value (controlled) |
|
||||
| `defaultValue` | `string` | - | Default selected value |
|
||||
| `onValueChange` | `(value: string) => void` | - | Callback when value changes |
|
||||
| `disabled` | `boolean` | `false` | Whether the select is disabled |
|
||||
| `required` | `boolean` | `false` | Whether a value is required |
|
||||
| `name` | `string` | - | Form input name |
|
||||
|
||||
### SelectTrigger
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `className` | `string` | - | Custom CSS class name |
|
||||
|
||||
### SelectItem
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `value` | `string` | - | Item value |
|
||||
| `disabled` | `boolean` | `false` | Whether the item is disabled |
|
||||
| `className` | `string` | - | Custom CSS class name |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Select
|
||||
|
||||
```vue
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an option" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="option1">Option 1</SelectItem>
|
||||
<SelectItem value="option2">Option 2</SelectItem>
|
||||
<SelectItem value="option3">Option 3</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
### With Labels and Groups
|
||||
|
||||
```vue
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectLabel>Fruits</SelectLabel>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
<SelectItem value="orange">Orange</SelectItem>
|
||||
<SelectSeparator />
|
||||
<SelectLabel>Vegetables</SelectLabel>
|
||||
<SelectItem value="carrot">Carrot</SelectItem>
|
||||
<SelectItem value="broccoli">Broccoli</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
### Controlled Select
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedValue = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select v-model="selectedValue">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="a">Option A</SelectItem>
|
||||
<SelectItem value="b">Option B</SelectItem>
|
||||
<SelectItem value="c">Option C</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- [Input](/components/ui/input)
|
||||
- [Checkbox](/components/ui/checkbox)
|
||||
- [Button](/components/ui/button)
|
||||
@@ -1,7 +1,6 @@
|
||||
# Dashboard
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Dashboard provides an at-a-glance overview of your project's workflow status, statistics, and recent activity through an intuitive widget-based interface.**
|
||||
|
||||
---
|
||||
@@ -18,16 +17,15 @@
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/HomePage.tsx`
|
||||
|
||||
**Purpose**: Dashboard home page providing project overview, statistics, workflow status, and recent activity monitoring.
|
||||
|
||||
**Access**: Navigation → Dashboard (default home page)
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Dashboard (default home page at `/`)
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Dashboard Header (title + refresh) |
|
||||
@@ -55,6 +53,149 @@
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo DashboardOverview
|
||||
# dashboard-overview.tsx
|
||||
/**
|
||||
* Dashboard Overview Demo
|
||||
* Shows the main dashboard layout with widgets
|
||||
*/
|
||||
export function DashboardOverview() {
|
||||
return (
|
||||
<div className="space-y-6 p-6 bg-background min-h-[600px]">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Dashboard</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Project overview and activity monitoring
|
||||
</p>
|
||||
</div>
|
||||
<button className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Workflow Stats Widget */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="p-4 border-b bg-muted/30">
|
||||
<h2 className="font-semibold">Project Overview & Statistics</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Statistics</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
{ label: 'Active Sessions', value: '12', color: 'text-blue-500' },
|
||||
{ label: 'Total Tasks', value: '48', color: 'text-green-500' },
|
||||
{ label: 'Completed', value: '35', color: 'text-emerald-500' },
|
||||
{ label: 'Pending', value: '8', color: 'text-amber-500' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="p-2 bg-muted/50 rounded">
|
||||
<div className={`text-lg font-bold ${stat.color}`}>{stat.value}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Workflow Status</div>
|
||||
<div className="flex items-center justify-center h-24">
|
||||
<div className="relative w-20 h-20">
|
||||
<svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-muted opacity-20"/>
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-blue-500" strokeDasharray="70 100"/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-xs font-bold">70%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-center space-y-1">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>Completed: 70%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Recent Session</div>
|
||||
<div className="p-3 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">Feature: Auth Flow</span>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-600">Running</span>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-green-500"/>
|
||||
<span>Implement login</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-amber-500"/>
|
||||
<span>Add OAuth</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-muted"/>
|
||||
<span className="text-muted-foreground">Test flow</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Sessions Widget */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="border-b bg-muted/30">
|
||||
<div className="flex gap-1 p-2">
|
||||
{['All Tasks', 'Workflow', 'Lite Tasks'].map((tab, i) => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`px-3 py-1.5 text-xs rounded-md transition-colors ${
|
||||
i === 0 ? 'bg-background text-foreground' : 'text-muted-foreground hover:bg-foreground/5'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ name: 'Refactor UI Components', status: 'In Progress', progress: 65 },
|
||||
{ name: 'Fix Login Bug', status: 'Pending', progress: 0 },
|
||||
{ name: 'Add Dark Mode', status: 'Completed', progress: 100 },
|
||||
].map((task, i) => (
|
||||
<div key={i} className="p-3 bg-muted/30 rounded border cursor-pointer hover:border-primary/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium line-clamp-1">{task.name}</span>
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
task.status === 'Completed' ? 'bg-green-500/20 text-green-600' :
|
||||
task.status === 'In Progress' ? 'bg-blue-500/20 text-blue-600' :
|
||||
'bg-gray-500/20 text-gray-600'
|
||||
}`}>{task.status}</span>
|
||||
</div>
|
||||
{task.progress > 0 && task.progress < 100 && (
|
||||
<div className="w-full h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full" style={{ width: `${task.progress}%` }}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
@@ -68,85 +209,399 @@
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **View Project Overview**: Check the project info banner for tech stack and development index
|
||||
2. **Monitor Statistics**: Review mini stat cards for current project metrics and trends
|
||||
3. **Track Workflow Status**: View pie chart for session status distribution
|
||||
4. **Browse Active Sessions**: Use session carousel to see task details and progress
|
||||
5. **Access Recent Work**: Switch between All Tasks/Workflow/Lite Tasks tabs to find specific sessions
|
||||
|
||||
### Key Interactions
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Expand Project Details** | Click the chevron button in the project banner to show architecture, components, patterns |
|
||||
| **Navigate Sessions** | Click arrow buttons or wait for auto-rotation (5s interval) in the carousel |
|
||||
| **View Session Details** | Click on any session card to navigate to session detail page |
|
||||
| **Filter Recent Tasks** | Click tab buttons to filter by task type (All/Workflow/Lite) |
|
||||
| **Refresh Dashboard** | Click the refresh button in the header to reload all data |
|
||||
|
||||
### Index Status Indicator
|
||||
|
||||
| Status | Icon | Meaning |
|
||||
|--------|------|---------|
|
||||
| **Building** | Pulsing blue dot | Code index is being built/updated |
|
||||
| **Completed** | Green dot | Index is up-to-date |
|
||||
| **Idle** | Gray dot | Index status is unknown/idle |
|
||||
| **Failed** | Red dot | Index build failed |
|
||||
```
|
||||
HomePage
|
||||
├── DashboardHeader
|
||||
│ ├── Title
|
||||
│ └── Refresh Action Button
|
||||
├── WorkflowTaskWidget
|
||||
│ ├── ProjectInfoBanner (expandable)
|
||||
│ │ ├── Project Name & Description
|
||||
│ │ ├── Tech Stack Badges
|
||||
│ │ ├── Quick Stats Cards
|
||||
│ │ ├── Index Status Indicator
|
||||
│ │ ├── Architecture Section
|
||||
│ │ ├── Key Components
|
||||
│ │ └── Design Patterns
|
||||
│ ├── Stats Section
|
||||
│ │ └── MiniStatCard (6 cards with Sparkline)
|
||||
│ ├── WorkflowStatusChart
|
||||
│ │ └── Pie Chart with Legend
|
||||
│ └── SessionCarousel
|
||||
│ ├── Navigation Arrows
|
||||
│ └── Session Cards (Task List)
|
||||
└── RecentSessionsWidget
|
||||
├── Tab Navigation (All | Workflow | Lite)
|
||||
├── Task Grid
|
||||
│ └── TaskItemCard
|
||||
└── Loading/Empty States
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Reference
|
||||
## Props API
|
||||
|
||||
### Main Components
|
||||
### HomePage Component
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `DashboardHeader` | `@/components/dashboard/DashboardHeader.tsx` | Page header with title and refresh action |
|
||||
| `WorkflowTaskWidget` | `@/components/dashboard/widgets/WorkflowTaskWidget.tsx` | Combined widget with project info, stats, workflow status, and session carousel |
|
||||
| `RecentSessionsWidget` | `@/components/dashboard/widgets/RecentSessionsWidget.tsx` | Recent sessions across workflow and lite tasks |
|
||||
| `MiniStatCard` | (internal to WorkflowTaskWidget) | Individual stat card with optional sparkline |
|
||||
| `HomeEmptyState` | (internal to WorkflowTaskWidget) | Empty state display when no sessions exist |
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This page component accepts no props (data fetched via hooks) |
|
||||
|
||||
### State Management
|
||||
### WorkflowTaskWidget
|
||||
|
||||
- **Local state**:
|
||||
- `hasError` - Error tracking for critical failures
|
||||
- `projectExpanded` - Project info banner expansion state
|
||||
- `currentSessionIndex` - Active session in carousel
|
||||
- `activeTab` - Recent sessions widget filter tab
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `className` | `string` | `undefined` | Additional CSS classes for styling |
|
||||
|
||||
- **Custom hooks**:
|
||||
- `useWorkflowStatusCounts` - Session status distribution data
|
||||
- `useDashboardStats` - Statistics with auto-refresh (60s)
|
||||
- `useProjectOverview` - Project information and tech stack
|
||||
- `useIndexStatus` - Real-time index status (30s refresh)
|
||||
- `useSessions` - Active sessions data
|
||||
- `useLiteTasks` - Lite tasks data for recent widget
|
||||
### RecentSessionsWidget
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `className` | `string` | `undefined` | Additional CSS classes |
|
||||
| `maxItems` | `number` | `6` | Maximum number of items to display |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
## Usage Examples
|
||||
|
||||
No configuration required. The dashboard automatically fetches data from the backend.
|
||||
### Basic Dashboard
|
||||
|
||||
### Auto-Refresh Intervals
|
||||
```tsx
|
||||
import { HomePage } from '@/pages/HomePage'
|
||||
|
||||
| Data Type | Interval |
|
||||
|-----------|----------|
|
||||
| Dashboard stats | 60 seconds |
|
||||
| Index status | 30 seconds |
|
||||
| Discovery sessions | 3 seconds (on discovery page) |
|
||||
// The dashboard is automatically rendered at the root route (/)
|
||||
// No props needed - data is fetched via hooks
|
||||
```
|
||||
|
||||
### Embedding WorkflowTaskWidget
|
||||
|
||||
```tsx
|
||||
import { WorkflowTaskWidget } from '@/components/dashboard/widgets/WorkflowTaskWidget'
|
||||
|
||||
function CustomDashboard() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<WorkflowTaskWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Recent Sessions Widget
|
||||
|
||||
```tsx
|
||||
import { RecentSessionsWidget } from '@/components/dashboard/widgets/RecentSessionsWidget'
|
||||
|
||||
function ActivityFeed() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<RecentSessionsWidget maxItems={10} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State
|
||||
|
||||
| State | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `hasError` | `boolean` | Error tracking for critical failures |
|
||||
| `projectExpanded` | `boolean` | Project info banner expansion state |
|
||||
| `currentSessionIndex` | `number` | Active session index in carousel |
|
||||
| `activeTab` | `'all' \| 'workflow' \| 'lite'` | Recent sessions widget filter tab |
|
||||
|
||||
### Store Selectors (Zustand)
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `appStore` | `selectIsImmersiveMode` | Check if immersive mode is active |
|
||||
|
||||
### Custom Hooks (Data Fetching)
|
||||
|
||||
| Hook | Description | Refetch Interval |
|
||||
|------|-------------|------------------|
|
||||
| `useWorkflowStatusCounts` | Session status distribution data | - |
|
||||
| `useDashboardStats` | Statistics with sparkline data | 60 seconds |
|
||||
| `useProjectOverview` | Project information and tech stack | - |
|
||||
| `useIndexStatus` | Real-time index status | 30 seconds |
|
||||
| `useSessions` | Active sessions data | - |
|
||||
| `useLiteTasks` | Lite tasks data for recent widget | - |
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Statistics Cards Demo
|
||||
|
||||
:::demo MiniStatCards
|
||||
# mini-stat-cards.tsx
|
||||
/**
|
||||
* Mini Stat Cards Demo
|
||||
* Individual statistics cards with sparkline trends
|
||||
*/
|
||||
export function MiniStatCards() {
|
||||
const stats = [
|
||||
{ label: 'Active Sessions', value: 12, trend: [8, 10, 9, 11, 10, 12, 12], color: 'blue' },
|
||||
{ label: 'Total Tasks', value: 48, trend: [40, 42, 45, 44, 46, 47, 48], color: 'green' },
|
||||
{ label: 'Completed', value: 35, trend: [25, 28, 30, 32, 33, 34, 35], color: 'emerald' },
|
||||
{ label: 'Pending', value: 8, trend: [12, 10, 11, 9, 8, 7, 8], color: 'amber' },
|
||||
{ label: 'Failed', value: 5, trend: [3, 4, 3, 5, 4, 5, 5], color: 'red' },
|
||||
{ label: 'Today Activity', value: 23, trend: [5, 10, 15, 18, 20, 22, 23], color: 'purple' },
|
||||
]
|
||||
|
||||
const colorMap = {
|
||||
blue: 'text-blue-500 bg-blue-500/10',
|
||||
green: 'text-green-500 bg-green-500/10',
|
||||
emerald: 'text-emerald-500 bg-emerald-500/10',
|
||||
amber: 'text-amber-500 bg-amber-500/10',
|
||||
red: 'text-red-500 bg-red-500/10',
|
||||
purple: 'text-purple-500 bg-purple-500/10',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Statistics with Sparklines</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{stats.map((stat, i) => (
|
||||
<div key={i} className="p-4 border rounded-lg bg-card">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs text-muted-foreground">{stat.label}</span>
|
||||
<div className={`w-2 h-2 rounded-full ${colorMap[stat.color].split(' ')[1]}`}/>
|
||||
</div>
|
||||
<div className={`text-2xl font-bold ${colorMap[stat.color].split(' ')[0]}`}>{stat.value}</div>
|
||||
<div className="mt-2 h-8 flex items-end gap-0.5">
|
||||
{stat.trend.map((v, j) => (
|
||||
<div
|
||||
key={j}
|
||||
className="flex-1 rounded-t"
|
||||
style={{
|
||||
height: `${(v / Math.max(...stat.trend)) * 100}%`,
|
||||
backgroundColor: v === stat.value ? 'currentColor' : 'rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Project Info Banner Demo
|
||||
|
||||
:::demo ProjectInfoBanner
|
||||
# project-info-banner.tsx
|
||||
/**
|
||||
* Project Info Banner Demo
|
||||
* Expandable project information with tech stack
|
||||
*/
|
||||
export function ProjectInfoBanner() {
|
||||
const [expanded, setExpanded] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Project Info Banner</h3>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
{/* Banner Header */}
|
||||
<div className="p-4 bg-muted/30 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold">My Awesome Project</h4>
|
||||
<p className="text-sm text-muted-foreground">A modern web application built with React</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="p-2 rounded-md hover:bg-accent"
|
||||
>
|
||||
<svg className={`w-5 h-5 transition-transform ${expanded ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tech Stack Badges */}
|
||||
<div className="px-4 pb-3 flex flex-wrap gap-2">
|
||||
{['TypeScript', 'React', 'Vite', 'Tailwind CSS', 'Zustand'].map((tech) => (
|
||||
<span key={tech} className="px-2 py-1 text-xs rounded-full bg-primary/10 text-primary">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<div className="p-4 border-t bg-muted/20 space-y-4">
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">Architecture</h5>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>• Component-based UI architecture</div>
|
||||
<div>• Centralized state management</div>
|
||||
<div>• RESTful API integration</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">Key Components</h5>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
{['Session Manager', 'Dashboard', 'Task Scheduler', 'Analytics'].map((comp) => (
|
||||
<div key={comp} className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-primary"/>
|
||||
{comp}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Session Carousel Demo
|
||||
|
||||
:::demo SessionCarousel
|
||||
# session-carousel.tsx
|
||||
/**
|
||||
* Session Carousel Demo
|
||||
* Auto-rotating session cards with navigation
|
||||
*/
|
||||
export function SessionCarousel() {
|
||||
const [currentIndex, setCurrentIndex] = React.useState(0)
|
||||
const sessions = [
|
||||
{
|
||||
name: 'Feature: User Authentication',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: 'Implement login form', status: 'completed' },
|
||||
{ name: 'Add OAuth provider', status: 'in-progress' },
|
||||
{ name: 'Create session management', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Bug Fix: Memory Leak',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: 'Identify leak source', status: 'completed' },
|
||||
{ name: 'Fix cleanup handlers', status: 'in-progress' },
|
||||
{ name: 'Add unit tests', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Refactor: API Layer',
|
||||
status: 'planning',
|
||||
tasks: [
|
||||
{ name: 'Design new interface', status: 'pending' },
|
||||
{ name: 'Migrate existing endpoints', status: 'pending' },
|
||||
{ name: 'Update documentation', status: 'pending' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const statusColors = {
|
||||
completed: 'bg-green-500',
|
||||
'in-progress': 'bg-amber-500',
|
||||
pending: 'bg-muted',
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentIndex((i) => (i + 1) % sessions.length)
|
||||
}, 5000)
|
||||
return () => clearInterval(timer)
|
||||
}, [sessions.length])
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Session Carousel (auto-rotates every 5s)</h3>
|
||||
<div className="border rounded-lg p-4 bg-card">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-medium">Session {currentIndex + 1} of {sessions.length}</span>
|
||||
<div className="flex gap-1">
|
||||
{sessions.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentIndex(i)}
|
||||
className={`w-2 h-2 rounded-full transition-colors ${
|
||||
i === currentIndex ? 'bg-primary' : 'bg-muted-foreground/30'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="font-medium">{sessions[currentIndex].name}</span>
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
sessions[currentIndex].status === 'running' ? 'bg-green-500/20 text-green-600' : 'bg-blue-500/20 text-blue-600'
|
||||
}`}>
|
||||
{sessions[currentIndex].status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{sessions[currentIndex].tasks.map((task, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm">
|
||||
<div className={`w-3 h-3 rounded ${statusColors[task.status]}`}/>
|
||||
<span className={task.status === 'pending' ? 'text-muted-foreground' : ''}>{task.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-3">
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i - 1 + sessions.length) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i + 1) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through interactive elements
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate buttons and cards
|
||||
- <kbd>Arrow</kbd> keys - Navigate carousel sessions
|
||||
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on navigation buttons
|
||||
- `aria-expanded` on expandable sections
|
||||
- `aria-live` regions for real-time updates
|
||||
|
||||
- **Screen Reader Support**:
|
||||
- All charts have text descriptions
|
||||
- Status indicators include text labels
|
||||
- Navigation is announced properly
|
||||
|
||||
---
|
||||
|
||||
## Related Links
|
||||
|
||||
- [Sessions](/features/sessions) - View and manage all sessions
|
||||
- [Terminal Dashboard](/features/terminal) - Terminal-first monitoring interface
|
||||
- [Queue](/features/queue) - Issue execution queue management
|
||||
- [Discovery](/features/discovery) - Discovery session tracking
|
||||
- [Memory](/features/memory) - Persistent memory management
|
||||
- [System Settings](/features/system-settings) - Global application settings
|
||||
- [Settings](/features/settings) - Global application settings
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Queue
|
||||
# Queue Management
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Queue page manages issue execution queues, displaying grouped tasks and solutions with conflict detection and merge capabilities.**
|
||||
**The Queue Management page provides centralized control over issue execution queues with scheduler controls, status monitoring, and session pool management.**
|
||||
|
||||
---
|
||||
|
||||
@@ -11,153 +10,627 @@
|
||||
| Pain Point | Current State | Queue Solution |
|
||||
|------------|---------------|----------------|
|
||||
| **Disorganized execution** | No unified task queue | Centralized queue with grouped items |
|
||||
| **Unknown queue status** | Can't tell if queue is ready | Status indicator with conflicts warning |
|
||||
| **Manual queue management** | No way to control execution | Activate/deactivate/delete with actions |
|
||||
| **Duplicate handling** | Confusing duplicate items | Merge queues functionality |
|
||||
| **No visibility** | Don't know what's queued | Stats cards with items/groups/tasks/solutions counts |
|
||||
| **Unknown scheduler status** | Can't tell if scheduler is running | Real-time status indicator (idle/running/paused) |
|
||||
| **No execution control** | Can't start/stop queue processing | Start/Pause/Stop controls with confirmation |
|
||||
| **Concurrency limits** | Too many simultaneous sessions | Configurable max concurrent sessions |
|
||||
| **No visibility** | Don't know what's queued | Stats cards + item list with status tracking |
|
||||
| **Resource waste** | Idle sessions consuming resources | Session pool overview with timeout config |
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/QueuePage.tsx`
|
||||
**Location**: `ccw/frontend/src/pages/QueuePage.tsx` (legacy), `ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx` (current)
|
||||
|
||||
**Purpose**: View and manage issue execution queues with stats, conflict detection, and queue operations.
|
||||
**Purpose**: View and manage issue execution queues with scheduler controls, progress tracking, and session pool management.
|
||||
|
||||
**Access**: Navigation → Issues → Queue tab OR directly via `/queue` route
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Issues → Queue tab OR Terminal Dashboard → Queue floating panel
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Queue Title [Refresh] |
|
||||
| Queue Panel Header |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Stats Cards |
|
||||
| +-------------+ +-------------+ +-------------+ +-------------+ |
|
||||
| | Total Items | | Groups | | Tasks | | Solutions | |
|
||||
| +-------------+ +-------------+ +-------------+ +-------------+ |
|
||||
| Scheduler Status Bar |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
| | Status Badge | | Progress | | Concurrency (2/2) | |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Conflicts Warning (when conflicts exist) |
|
||||
| Scheduler Controls |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
| | Start | | Pause | | Stop | | Config | |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Queue Cards (1-2 columns) |
|
||||
| Queue Items List |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | QueueCard | |
|
||||
| | - Queue info | |
|
||||
| | - Grouped items preview | |
|
||||
| | - Action buttons (Activate/Deactivate/Delete/Merge) | |
|
||||
| | QueueItemRow (status, issue_id, session_key, actions) | |
|
||||
| | - Status icon (pending/executing/completed/blocked/failed) | |
|
||||
| | - Issue ID / Item ID display | |
|
||||
| | - Session binding info | |
|
||||
| | - Progress indicator (for executing items) | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | [More queue items...] | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Status Footer (Ready/Pending indicator) |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Session Pool Overview (optional) |
|
||||
| +--------------------------------------------------------------------------+
|
||||
| | Active Sessions | Idle Sessions | Total Sessions |
|
||||
| +--------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo QueueManagementDemo
|
||||
# queue-management-demo.tsx
|
||||
/**
|
||||
* Queue Management Demo
|
||||
* Shows scheduler controls and queue items list
|
||||
*/
|
||||
export function QueueManagementDemo() {
|
||||
const [schedulerStatus, setSchedulerStatus] = React.useState('idle')
|
||||
const [progress, setProgress] = React.useState(0)
|
||||
|
||||
const queueItems = [
|
||||
{ id: '1', status: 'completed', issueId: 'ISSUE-1', sessionKey: 'session-1' },
|
||||
{ id: '2', status: 'executing', issueId: 'ISSUE-2', sessionKey: 'session-2' },
|
||||
{ id: '3', status: 'pending', issueId: 'ISSUE-3', sessionKey: null },
|
||||
{ id: '4', status: 'pending', issueId: 'ISSUE-4', sessionKey: null },
|
||||
{ id: '5', status: 'blocked', issueId: 'ISSUE-5', sessionKey: null },
|
||||
]
|
||||
|
||||
const statusConfig = {
|
||||
idle: { label: 'Idle', color: 'bg-gray-500/20 text-gray-600 border-gray-500' },
|
||||
running: { label: 'Running', color: 'bg-green-500/20 text-green-600 border-green-500' },
|
||||
paused: { label: 'Paused', color: 'bg-amber-500/20 text-amber-600 border-amber-500' },
|
||||
}
|
||||
|
||||
const itemStatusConfig = {
|
||||
completed: { icon: '✓', color: 'text-green-500', label: 'Completed' },
|
||||
executing: { icon: '▶', color: 'text-blue-500', label: 'Executing' },
|
||||
pending: { icon: '○', color: 'text-gray-400', label: 'Pending' },
|
||||
blocked: { icon: '✕', color: 'text-red-500', label: 'Blocked' },
|
||||
failed: { icon: '!', color: 'text-red-500', label: 'Failed' },
|
||||
}
|
||||
|
||||
// Simulate progress when running
|
||||
React.useEffect(() => {
|
||||
if (schedulerStatus === 'running') {
|
||||
const interval = setInterval(() => {
|
||||
setProgress((p) => (p >= 100 ? 0 : p + 10))
|
||||
}, 500)
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [schedulerStatus])
|
||||
|
||||
const handleStart = () => {
|
||||
if (schedulerStatus === 'idle' || schedulerStatus === 'paused') {
|
||||
setSchedulerStatus('running')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePause = () => {
|
||||
if (schedulerStatus === 'running') {
|
||||
setSchedulerStatus('paused')
|
||||
}
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
setSchedulerStatus('idle')
|
||||
setProgress(0)
|
||||
}
|
||||
|
||||
const currentConfig = statusConfig[schedulerStatus]
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Queue Management</h3>
|
||||
<p className="text-sm text-muted-foreground">Manage issue execution queue</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scheduler Status Bar */}
|
||||
<div className="border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={`px-3 py-1 rounded text-xs font-medium border ${currentConfig.color}`}>
|
||||
{currentConfig.label}
|
||||
</span>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span>{queueItems.filter((i) => i.status === 'completed').length}/{queueItems.length} items</span>
|
||||
<span>2/2 concurrent</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
{schedulerStatus === 'running' && (
|
||||
<div className="space-y-1">
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{progress}% complete</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Scheduler Controls */}
|
||||
<div className="flex items-center gap-2">
|
||||
{(schedulerStatus === 'idle' || schedulerStatus === 'paused') && (
|
||||
<button
|
||||
onClick={handleStart}
|
||||
className="px-4 py-2 text-sm bg-green-500 text-white rounded hover:bg-green-600 flex items-center gap-2"
|
||||
>
|
||||
<span>▶</span> Start
|
||||
</button>
|
||||
)}
|
||||
{schedulerStatus === 'running' && (
|
||||
<button
|
||||
onClick={handlePause}
|
||||
className="px-4 py-2 text-sm bg-amber-500 text-white rounded hover:bg-amber-600 flex items-center gap-2"
|
||||
>
|
||||
<span>⏸</span> Pause
|
||||
</button>
|
||||
)}
|
||||
{schedulerStatus !== 'idle' && (
|
||||
<button
|
||||
onClick={handleStop}
|
||||
className="px-4 py-2 text-sm bg-red-500 text-white rounded hover:bg-red-600 flex items-center gap-2"
|
||||
>
|
||||
<span>⬛</span> Stop
|
||||
</button>
|
||||
)}
|
||||
<button className="px-4 py-2 text-sm border rounded hover:bg-accent">
|
||||
Config
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Queue Items List */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="px-4 py-3 border-b bg-muted/30">
|
||||
<h4 className="text-sm font-semibold">Queue Items</h4>
|
||||
</div>
|
||||
<div className="divide-y max-h-80 overflow-auto">
|
||||
{queueItems.map((item) => {
|
||||
const config = itemStatusConfig[item.status]
|
||||
return (
|
||||
<div key={item.id} className="px-4 py-3 flex items-center gap-4 hover:bg-accent/50">
|
||||
<span className={`text-lg ${config.color}`}>{config.icon}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{item.issueId}</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${config.color} bg-opacity-10`}>
|
||||
{config.label}
|
||||
</span>
|
||||
</div>
|
||||
{item.sessionKey && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Session: {item.sessionKey}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{item.status === 'executing' && (
|
||||
<div className="w-20 h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 animate-pulse" style={{ width: '60%' }}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Stats Cards** | Four metric cards showing total items, groups, tasks, and solutions counts |
|
||||
| **Conflicts Warning** | Banner alert when conflicts exist, showing count and description |
|
||||
| **Queue Card** | Displays queue information with grouped items preview and action buttons |
|
||||
| **Activate/Deactivate** | Toggle queue active state for execution control |
|
||||
| **Delete Queue** | Remove queue with confirmation dialog |
|
||||
| **Merge Queues** | Combine multiple queues (if multiple exist) |
|
||||
| **Status Footer** | Visual indicator showing if queue is ready (active) or pending (inactive/conflicts) |
|
||||
| **Loading State** | Skeleton placeholders during data fetch |
|
||||
| **Empty State** | Friendly message when no queue exists |
|
||||
| **Scheduler Status** | Real-time status indicator (idle/running/paused) with visual badge |
|
||||
| **Progress Tracking** | Progress bar showing overall queue completion percentage |
|
||||
| **Start/Pause/Stop Controls** | Control queue execution with confirmation dialog for stop action |
|
||||
| **Concurrency Display** | Shows current active sessions vs max concurrent sessions |
|
||||
| **Queue Items List** | Scrollable list of all queue items with status, issue ID, and session binding |
|
||||
| **Status Icons** | Visual indicators for item status (pending/executing/completed/blocked/failed) |
|
||||
| **Session Pool** | Overview of active, idle, and total sessions in the pool |
|
||||
| **Config Panel** | Adjust max concurrent sessions and timeout settings |
|
||||
| **Empty State** | Friendly message when queue is empty with instructions to add items |
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **Check Queue Status**: Review stats cards and status footer to understand queue state
|
||||
2. **Review Conflicts**: If conflicts warning is shown, resolve conflicts before activation
|
||||
3. **Activate Queue**: Click "Activate" to enable queue for execution (only if no conflicts)
|
||||
4. **Deactivate Queue**: Click "Deactivate" to pause execution
|
||||
5. **Delete Queue**: Click "Delete" to remove the queue (requires confirmation)
|
||||
6. **Merge Queues**: Use merge action to combine multiple queues when applicable
|
||||
|
||||
### Key Interactions
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Refresh queue** | Click the refresh button to reload queue data |
|
||||
| **Activate queue** | Click the Activate button on the queue card |
|
||||
| **Deactivate queue** | Click the Deactivate button to pause the queue |
|
||||
| **Delete queue** | Click the Delete button, confirm in the dialog |
|
||||
| **Merge queues** | Select source and target queues, click merge |
|
||||
| **View status** | Check the status footer for ready/pending indication |
|
||||
|
||||
### Queue Status
|
||||
|
||||
| Status | Condition | Appearance |
|
||||
|--------|-----------|------------|
|
||||
| **Ready/Active** | Total items > 0 AND no conflicts | Green badge with checkmark |
|
||||
| **Pending/Inactive** | No items OR conflicts exist | Gray/amber badge with clock icon |
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
When conflicts are detected:
|
||||
1. A warning banner appears showing conflict count
|
||||
2. Queue cannot be activated until conflicts are resolved
|
||||
3. Status footer shows "Pending" state
|
||||
4. Resolve conflicts through the Issues panel or related workflows
|
||||
```
|
||||
QueuePage (legacy) / QueuePanel (current)
|
||||
├── QueuePanelHeader
|
||||
│ ├── Title
|
||||
│ └── Tab Switcher (Queue | Orchestrator)
|
||||
├── SchedulerBar (inline in QueueListColumn)
|
||||
│ ├── Status Badge
|
||||
│ ├── Progress + Concurrency
|
||||
│ └── Control Buttons (Play/Pause/Stop)
|
||||
├── QueueItemsList
|
||||
│ └── QueueItemRow (repeating)
|
||||
│ ├── Status Icon
|
||||
│ ├── Issue ID / Item ID
|
||||
│ ├── Session Binding
|
||||
│ └── Progress (for executing items)
|
||||
└── SchedulerPanel (standalone)
|
||||
├── Status Section
|
||||
├── Progress Bar
|
||||
├── Control Buttons
|
||||
├── Config Section
|
||||
│ ├── Max Concurrent Sessions
|
||||
│ ├── Session Idle Timeout
|
||||
│ └── Resume Key Binding Timeout
|
||||
└── Session Pool Overview
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Reference
|
||||
## Props API
|
||||
|
||||
### Main Components
|
||||
### QueuePanel
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `QueuePage` | `@/pages/QueuePage.tsx` | Main queue management page |
|
||||
| `QueueCard` | `@/components/issue/queue/QueueCard.tsx` | Queue display with actions |
|
||||
| `QueuePageSkeleton` | (internal to QueuePage) | Loading placeholder |
|
||||
| `QueueEmptyState` | (internal to QueuePage) | Empty state display |
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `embedded` | `boolean` | `false` | Whether panel is embedded in another component |
|
||||
|
||||
### State Management
|
||||
### SchedulerPanel
|
||||
|
||||
- **Local state**:
|
||||
- None (all data from hooks)
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This component accepts no props (all data from Zustand store) |
|
||||
|
||||
- **Custom hooks**:
|
||||
- `useIssueQueue` - Queue data fetching
|
||||
- `useQueueMutations` - Queue operations (activate, deactivate, delete, merge)
|
||||
### QueueListColumn
|
||||
|
||||
### Mutation States
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This component accepts no props (all data from Zustand store) |
|
||||
|
||||
| State | Loading Indicator |
|
||||
|-------|------------------|
|
||||
| `isActivating` | Disable activate button during activation |
|
||||
| `isDeactivating` | Disable deactivate button during deactivation |
|
||||
| `isDeleting` | Disable delete button during deletion |
|
||||
| `isMerging` | Disable merge button during merge operation |
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `queueSchedulerStore` | `selectQueueSchedulerStatus` | Current scheduler status (idle/running/paused) |
|
||||
| `queueSchedulerStore` | `selectSchedulerProgress` | Overall queue completion percentage |
|
||||
| `queueSchedulerStore` | `selectQueueItems` | List of all queue items |
|
||||
| `queueSchedulerStore` | `selectCurrentConcurrency` | Active sessions count |
|
||||
| `queueSchedulerStore` | `selectSchedulerConfig` | Scheduler configuration |
|
||||
| `queueSchedulerStore` | `selectSessionPool` | Session pool overview |
|
||||
| `queueSchedulerStore` | `selectSchedulerError` | Error message if any |
|
||||
| `issueQueueIntegrationStore` | `selectAssociationChain` | Current association chain for highlighting |
|
||||
| `queueExecutionStore` | `selectByQueueItem` | Execution data for queue item |
|
||||
|
||||
### Queue Item Status
|
||||
|
||||
```typescript
|
||||
type QueueItemStatus =
|
||||
| 'pending' // Waiting to be executed
|
||||
| 'executing' // Currently being processed
|
||||
| 'completed' // Finished successfully
|
||||
| 'blocked' // Blocked by dependency
|
||||
| 'failed'; // Failed with error
|
||||
```
|
||||
|
||||
### Scheduler Status
|
||||
|
||||
```typescript
|
||||
type QueueSchedulerStatus =
|
||||
| 'idle' // No items or stopped
|
||||
| 'running' // Actively processing items
|
||||
| 'paused'; // Temporarily paused
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Queue Panel
|
||||
|
||||
```tsx
|
||||
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel'
|
||||
|
||||
function QueueSection() {
|
||||
return <QueuePanel />
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Scheduler Panel
|
||||
|
||||
```tsx
|
||||
import { SchedulerPanel } from '@/components/terminal-dashboard/SchedulerPanel'
|
||||
|
||||
function SchedulerControls() {
|
||||
return <SchedulerPanel />
|
||||
}
|
||||
```
|
||||
|
||||
### Queue List Column (Embedded)
|
||||
|
||||
```tsx
|
||||
import { QueueListColumn } from '@/components/terminal-dashboard/QueueListColumn'
|
||||
|
||||
function EmbeddedQueue() {
|
||||
return <QueueListColumn />
|
||||
}
|
||||
```
|
||||
|
||||
### Queue Store Actions
|
||||
|
||||
```tsx
|
||||
import { useQueueSchedulerStore } from '@/stores/queueSchedulerStore'
|
||||
|
||||
function QueueActions() {
|
||||
const startQueue = useQueueSchedulerStore((s) => s.startQueue)
|
||||
const pauseQueue = useQueueSchedulerStore((s) => s.pauseQueue)
|
||||
const stopQueue = useQueueSchedulerStore((s) => s.stopQueue)
|
||||
const updateConfig = useQueueSchedulerStore((s) => s.updateConfig)
|
||||
|
||||
const handleStart = () => startQueue()
|
||||
const handlePause = () => pauseQueue()
|
||||
const handleStop = () => stopQueue()
|
||||
const handleConfig = (config) => updateConfig(config)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleStart}>Start</button>
|
||||
<button onClick={handlePause}>Pause</button>
|
||||
<button onClick={handleStop}>Stop</button>
|
||||
<button onClick={() => handleConfig({ maxConcurrentSessions: 4 })}>
|
||||
Set Max 4
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Queue Item Status Demo
|
||||
|
||||
:::demo QueueItemStatusDemo
|
||||
# queue-item-status-demo.tsx
|
||||
/**
|
||||
* Queue Item Status Demo
|
||||
* Shows all possible queue item states
|
||||
*/
|
||||
export function QueueItemStatusDemo() {
|
||||
const itemStates = [
|
||||
{ status: 'pending', issueId: 'ISSUE-101', sessionKey: null },
|
||||
{ status: 'executing', issueId: 'ISSUE-102', sessionKey: 'cli-session-abc' },
|
||||
{ status: 'completed', issueId: 'ISSUE-103', sessionKey: 'cli-session-def' },
|
||||
{ status: 'blocked', issueId: 'ISSUE-104', sessionKey: null },
|
||||
{ status: 'failed', issueId: 'ISSUE-105', sessionKey: 'cli-session-ghi' },
|
||||
]
|
||||
|
||||
const statusConfig = {
|
||||
pending: { icon: '○', color: 'text-gray-400', bg: 'bg-gray-500/10', label: 'Pending' },
|
||||
executing: { icon: '▶', color: 'text-blue-500', bg: 'bg-blue-500/10', label: 'Executing' },
|
||||
completed: { icon: '✓', color: 'text-green-500', bg: 'bg-green-500/10', label: 'Completed' },
|
||||
blocked: { icon: '✕', color: 'text-red-500', bg: 'bg-red-500/10', label: 'Blocked' },
|
||||
failed: { icon: '!', color: 'text-red-500', bg: 'bg-red-500/10', label: 'Failed' },
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-4">
|
||||
<h3 className="text-sm font-semibold">Queue Item Status States</h3>
|
||||
<div className="space-y-2">
|
||||
{itemStates.map((item) => {
|
||||
const config = statusConfig[item.status]
|
||||
return (
|
||||
<div key={item.status} className="border rounded-lg p-4 flex items-center gap-4">
|
||||
<span className={`text-2xl ${config.color}`}>{config.icon}</span>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{item.issueId}</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${config.bg} ${config.color}`}>
|
||||
{config.label}
|
||||
</span>
|
||||
</div>
|
||||
{item.sessionKey && (
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
Bound session: <code className="text-xs bg-muted px-1 rounded">{item.sessionKey}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{item.status === 'executing' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-24 h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 animate-pulse" style={{ width: '60%' }}/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">60%</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Scheduler Config Demo
|
||||
|
||||
:::demo SchedulerConfigDemo
|
||||
# scheduler-config-demo.tsx
|
||||
/**
|
||||
* Scheduler Config Demo
|
||||
* Interactive configuration panel
|
||||
*/
|
||||
export function SchedulerConfigDemo() {
|
||||
const [config, setConfig] = React.useState({
|
||||
maxConcurrentSessions: 2,
|
||||
sessionIdleTimeoutMs: 60000,
|
||||
resumeKeySessionBindingTimeoutMs: 300000,
|
||||
})
|
||||
|
||||
const formatMs = (ms) => {
|
||||
if (ms >= 60000) return `${ms / 60000}m`
|
||||
if (ms >= 1000) return `${ms / 1000}s`
|
||||
return `${ms}ms`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6 max-w-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Scheduler Configuration</h3>
|
||||
<button className="px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded hover:opacity-90">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Max Concurrent Sessions */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Max Concurrent Sessions</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="8"
|
||||
value={config.maxConcurrentSessions}
|
||||
onChange={(e) => setConfig({ ...config, maxConcurrentSessions: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-8 text-center">{config.maxConcurrentSessions}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Maximum number of sessions to run simultaneously
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Session Idle Timeout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Session Idle Timeout</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="10000"
|
||||
max="300000"
|
||||
step="10000"
|
||||
value={config.sessionIdleTimeoutMs}
|
||||
onChange={(e) => setConfig({ ...config, sessionIdleTimeoutMs: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-12 text-right">{formatMs(config.sessionIdleTimeoutMs)}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Time before idle session is terminated
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Resume Key Binding Timeout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Resume Key Binding Timeout</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="60000"
|
||||
max="600000"
|
||||
step="60000"
|
||||
value={config.resumeKeySessionBindingTimeoutMs}
|
||||
onChange={(e) => setConfig({ ...config, resumeKeySessionBindingTimeoutMs: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-12 text-right">{formatMs(config.resumeKeySessionBindingTimeoutMs)}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Time to preserve resume key session binding
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Config Display */}
|
||||
<div className="border rounded-lg p-4 bg-muted/30">
|
||||
<h4 className="text-xs font-semibold mb-3">Current Configuration</h4>
|
||||
<dl className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Max Concurrent</dt>
|
||||
<dd className="font-medium">{config.maxConcurrentSessions} sessions</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Idle Timeout</dt>
|
||||
<dd className="font-medium">{formatMs(config.sessionIdleTimeoutMs)}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Binding Timeout</dt>
|
||||
<dd className="font-medium">{formatMs(config.resumeKeySessionBindingTimeoutMs)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
No configuration required. Queue data is automatically fetched from the backend.
|
||||
### Scheduler Config
|
||||
|
||||
### Queue Data Structure
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `maxConcurrentSessions` | `number` | `2` | Maximum sessions running simultaneously |
|
||||
| `sessionIdleTimeoutMs` | `number` | `60000` | Idle session timeout in milliseconds |
|
||||
| `resumeKeySessionBindingTimeoutMs` | `number` | `300000` | Resume key binding timeout in milliseconds |
|
||||
|
||||
### Queue Item Structure
|
||||
|
||||
```typescript
|
||||
interface QueueData {
|
||||
tasks: Task[];
|
||||
solutions: Solution[];
|
||||
conflicts: Conflict[];
|
||||
grouped_items: Record<string, GroupedItem>;
|
||||
interface QueueItem {
|
||||
item_id: string;
|
||||
issue_id?: string;
|
||||
sessionKey?: string;
|
||||
status: QueueItemStatus;
|
||||
execution_order: number;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through queue items and controls
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate buttons
|
||||
- <kbd>Escape</kbd> - Close dialogs
|
||||
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on control buttons
|
||||
- `aria-live` regions for status updates
|
||||
- `aria-current` for active queue item
|
||||
- `role="list"` on queue items list
|
||||
|
||||
- **Screen Reader Support**:
|
||||
- Status changes announced
|
||||
- Progress updates spoken
|
||||
- Error messages announced
|
||||
|
||||
---
|
||||
|
||||
## Related Links
|
||||
|
||||
- [Issue Hub](/features/issue-hub) - Unified issues, queue, and discovery management
|
||||
- [Terminal Dashboard](/features/terminal) - Terminal-first workspace with integrated queue panel
|
||||
- [Discovery](/features/discovery) - Discovery session tracking
|
||||
- [Issues Panel](/features/issue-hub) - Issue list and GitHub sync
|
||||
- [Sessions](/features/sessions) - Session management and details
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Terminal Dashboard
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Terminal Dashboard provides a terminal-first workspace with resizable panes, floating panels, and integrated tools for session monitoring and orchestration.**
|
||||
|
||||
---
|
||||
@@ -18,16 +17,15 @@
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/TerminalDashboardPage.tsx`
|
||||
|
||||
**Purpose**: Terminal-first layout for multi-terminal session management with integrated tools and resizable panels.
|
||||
|
||||
**Access**: Navigation → Terminal Dashboard
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Terminal Dashboard (`/terminal-dashboard`)
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Dashboard Toolbar (panel toggles, layout presets, fullscreen) |
|
||||
@@ -48,6 +46,134 @@
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo TerminalDashboardOverview
|
||||
# terminal-dashboard-overview.tsx
|
||||
/**
|
||||
* Terminal Dashboard Overview Demo
|
||||
* Shows the three-column layout with resizable panes and toolbar
|
||||
*/
|
||||
export function TerminalDashboardOverview() {
|
||||
const [fileSidebarOpen, setFileSidebarOpen] = React.useState(true)
|
||||
const [sessionSidebarOpen, setSessionSidebarOpen] = React.useState(true)
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
return (
|
||||
<div className="h-[600px] flex flex-col bg-background">
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-muted/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">Terminal Dashboard</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{['Sessions', 'Files', 'Issues', 'Queue', 'Inspector', 'Scheduler'].map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
onClick={() => {
|
||||
if (item === 'Sessions') setSessionSidebarOpen(!sessionSidebarOpen)
|
||||
else if (item === 'Files') setFileSidebarOpen(!fileSidebarOpen)
|
||||
else setActivePanel(activePanel === item.toLowerCase() ? null : item.toLowerCase())
|
||||
}}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors ${
|
||||
(item === 'Sessions' && sessionSidebarOpen) ||
|
||||
(item === 'Files' && fileSidebarOpen) ||
|
||||
activePanel === item.toLowerCase()
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Layout */}
|
||||
<div className="flex-1 flex min-h-0">
|
||||
{/* Session Sidebar */}
|
||||
{sessionSidebarOpen && (
|
||||
<div className="w-60 border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 space-y-1 text-sm overflow-auto">
|
||||
{['Active Sessions', 'Completed', 'Archived'].map((group) => (
|
||||
<div key={group}>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded hover:bg-accent cursor-pointer">
|
||||
<span className="text-xs">▼</span>
|
||||
<span>{group}</span>
|
||||
</div>
|
||||
<div className="ml-4 space-y-0.5">
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 1
|
||||
</div>
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terminal Grid */}
|
||||
<div className="flex-1 bg-muted/20 p-2">
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-2 h-full">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="bg-background border rounded p-3 font-mono text-xs">
|
||||
<div className="text-green-500 mb-2">$ Terminal {i}</div>
|
||||
<div className="text-muted-foreground">
|
||||
<div>Working directory: /project</div>
|
||||
<div>Type a command to begin...</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Sidebar */}
|
||||
{fileSidebarOpen && (
|
||||
<div className="w-64 border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm overflow-auto">
|
||||
<div className="space-y-1">
|
||||
{['src', 'docs', 'tests', 'package.json', 'README.md'].map((item) => (
|
||||
<div key={item} className="px-2 py-1 rounded hover:bg-accent cursor-pointer flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">📁</span>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Floating Panel */}
|
||||
{activePanel && (
|
||||
<div className="absolute top-12 right-4 w-80 bg-background border rounded-lg shadow-lg">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium capitalize">{activePanel} Panel</span>
|
||||
<button onClick={() => setActivePanel(null)} className="text-xs hover:bg-accent px-2 py-1 rounded">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
{activePanel} content placeholder
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
@@ -64,38 +190,396 @@
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
```
|
||||
TerminalDashboardPage
|
||||
├── AssociationHighlightProvider (context)
|
||||
├── DashboardToolbar
|
||||
│ ├── Layout Preset Buttons (Single | Split-H | Split-V | Grid-2x2)
|
||||
│ ├── Panel Toggles (Sessions | Files | Issues | Queue | Inspector | Execution | Scheduler)
|
||||
│ ├── Fullscreen Toggle
|
||||
│ └── Launch CLI Button
|
||||
├── Allotment (Three-Column Layout)
|
||||
│ ├── SessionGroupTree
|
||||
│ │ └── Session Group Items (collapsible)
|
||||
│ ├── TerminalGrid
|
||||
│ │ ├── GridGroupRenderer (recursive)
|
||||
│ │ └── TerminalPane
|
||||
│ └── FileSidebarPanel
|
||||
│ └── File Tree View
|
||||
└── FloatingPanel (multiple, mutually exclusive)
|
||||
├── Issues+Queue (split panel)
|
||||
│ ├── IssuePanel
|
||||
│ └── QueueListColumn
|
||||
├── QueuePanel (feature flag)
|
||||
├── InspectorContent (feature flag)
|
||||
├── ExecutionMonitorPanel (feature flag)
|
||||
└── SchedulerPanel
|
||||
```
|
||||
|
||||
1. **Launch CLI Session**: Click "Launch CLI" button, configure options (tool, model, shell, working directory)
|
||||
2. **Arrange Terminals**: Use layout presets or manually split panes
|
||||
3. **Navigate Sessions**: Browse session groups in the left tree
|
||||
4. **Toggle Panels**: Click toolbar buttons to show/hide floating panels
|
||||
5. **Inspect Issues**: Open Issues panel to view associated issues
|
||||
6. **Monitor Execution**: Use Execution Monitor panel for real-time tracking
|
||||
---
|
||||
|
||||
### Key Interactions
|
||||
## Props API
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Launch CLI** | Click "Launch CLI" button, configure session in modal |
|
||||
| **Toggle sidebar** | Click Sessions/Files button in toolbar |
|
||||
| **Open floating panel** | Click Issues/Queue/Inspector/Execution/Scheduler button |
|
||||
| **Close floating panel** | Click X on panel or toggle button again |
|
||||
| **Resize panes** | Drag the divider between panes |
|
||||
| **Change layout** | Click layout preset buttons (single/split/grid) |
|
||||
| **Fullscreen mode** | Click fullscreen button to hide app chrome |
|
||||
### TerminalDashboardPage
|
||||
|
||||
### Panel Types
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This page component accepts no props (state managed via hooks and Zustand stores) |
|
||||
|
||||
| Panel | Content | Position |
|
||||
|-------|---------|----------|
|
||||
| **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) |
|
||||
| **Queue** | Full queue management panel | Right (overlay, feature flag) |
|
||||
| **Inspector** | Association chain inspector | Right (overlay, feature flag) |
|
||||
| **Execution Monitor** | Real-time execution tracking | Right (overlay, feature flag) |
|
||||
| **Scheduler** | Queue scheduler controls | Right (overlay) |
|
||||
### DashboardToolbar
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `activePanel` | `PanelId \| null` | `null` | Currently active floating panel |
|
||||
| `onTogglePanel` | `(panelId: PanelId) => void` | - | Callback to toggle panel visibility |
|
||||
| `isFileSidebarOpen` | `boolean` | `true` | File sidebar visibility state |
|
||||
| `onToggleFileSidebar` | `() => void` | - | Toggle file sidebar callback |
|
||||
| `isSessionSidebarOpen` | `boolean` | `true` | Session sidebar visibility state |
|
||||
| `onToggleSessionSidebar` | `() => void` | - | Toggle session sidebar callback |
|
||||
| `isFullscreen` | `boolean` | `false` | Fullscreen mode state |
|
||||
| `onToggleFullscreen` | `() => void` | - | Toggle fullscreen callback |
|
||||
|
||||
### FloatingPanel
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `isOpen` | `boolean` | `false` | Panel open state |
|
||||
| `onClose` | `() => void` | - | Close callback |
|
||||
| `title` | `string` | - | Panel title |
|
||||
| `side` | `'left' \| 'right'` | `'left'` | Panel side |
|
||||
| `width` | `number` | `400` | Panel width in pixels |
|
||||
| `children` | `ReactNode` | - | Panel content |
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State
|
||||
|
||||
| State | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `activePanel` | `PanelId \| null` | Currently active floating panel (mutually exclusive) |
|
||||
| `isFileSidebarOpen` | `boolean` | File sidebar visibility |
|
||||
| `isSessionSidebarOpen` | `boolean` | Session sidebar visibility |
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `workflowStore` | `selectProjectPath` | Current project path for file sidebar |
|
||||
| `appStore` | `selectIsImmersiveMode` | Fullscreen mode state |
|
||||
| `configStore` | `featureFlags` | Feature flag configuration |
|
||||
| `terminalGridStore` | Grid layout and focused pane state |
|
||||
| `executionMonitorStore` | Active execution count |
|
||||
| `queueSchedulerStore` | Scheduler status and settings |
|
||||
|
||||
### Panel ID Type
|
||||
|
||||
```typescript
|
||||
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Terminal Dashboard
|
||||
|
||||
```tsx
|
||||
import { TerminalDashboardPage } from '@/pages/TerminalDashboardPage'
|
||||
|
||||
// The terminal dashboard is automatically rendered at /terminal-dashboard
|
||||
// No props needed - layout state managed internally
|
||||
```
|
||||
|
||||
### Using FloatingPanel Component
|
||||
|
||||
```tsx
|
||||
import { FloatingPanel } from '@/components/terminal-dashboard/FloatingPanel'
|
||||
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel'
|
||||
|
||||
function CustomLayout() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<FloatingPanel
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title="Issues"
|
||||
side="left"
|
||||
width={700}
|
||||
>
|
||||
<IssuePanel />
|
||||
</FloatingPanel>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Panel Toggle Pattern
|
||||
|
||||
```tsx
|
||||
import { useState, useCallback } from 'react'
|
||||
|
||||
function usePanelToggle() {
|
||||
const [activePanel, setActivePanel] = useState<string | null>(null)
|
||||
|
||||
const togglePanel = useCallback((panelId: string) => {
|
||||
setActivePanel((prev) => (prev === panelId ? null : panelId))
|
||||
}, [])
|
||||
|
||||
const closePanel = useCallback(() => {
|
||||
setActivePanel(null)
|
||||
}, [])
|
||||
|
||||
return { activePanel, togglePanel, closePanel }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Layout Presets Demo
|
||||
|
||||
:::demo TerminalLayoutPresets
|
||||
# terminal-layout-presets.tsx
|
||||
/**
|
||||
* Terminal Layout Presets Demo
|
||||
* Interactive layout preset buttons
|
||||
*/
|
||||
export function TerminalLayoutPresets() {
|
||||
const [layout, setLayout] = React.useState('grid-2x2')
|
||||
|
||||
const layouts = {
|
||||
single: 'grid-cols-1 grid-rows-1',
|
||||
'split-h': 'grid-cols-2 grid-rows-1',
|
||||
'split-v': 'grid-cols-1 grid-rows-2',
|
||||
'grid-2x2': 'grid-cols-2 grid-rows-2',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Terminal Layout Presets</h3>
|
||||
<div className="flex gap-2">
|
||||
{Object.keys(layouts).map((preset) => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => setLayout(preset)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
layout === preset
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{preset.replace('-', ' ').toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-2 h-64 ${layouts[layout]}`}>
|
||||
{Array.from({ length: layout === 'single' ? 1 : layout.includes('2x') ? 4 : 2 }).map((_, i) => (
|
||||
<div key={i} className="bg-muted/20 border rounded p-4 font-mono text-xs">
|
||||
<div className="text-green-500">$ Terminal {i + 1}</div>
|
||||
<div className="text-muted-foreground mt-1">Ready for input...</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Floating Panels Demo
|
||||
|
||||
:::demo FloatingPanelsDemo
|
||||
# floating-panels-demo.tsx
|
||||
/**
|
||||
* Floating Panels Demo
|
||||
* Mutually exclusive overlay panels
|
||||
*/
|
||||
export function FloatingPanelsDemo() {
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
const panels = [
|
||||
{ id: 'issues', title: 'Issues + Queue', side: 'left', width: 700 },
|
||||
{ id: 'queue', title: 'Queue', side: 'right', width: 400 },
|
||||
{ id: 'inspector', title: 'Inspector', side: 'right', width: 360 },
|
||||
{ id: 'execution', title: 'Execution Monitor', side: 'right', width: 380 },
|
||||
{ id: 'scheduler', title: 'Scheduler', side: 'right', width: 340 },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="relative h-[500px] p-6 bg-background border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold">Floating Panels</h3>
|
||||
<div className="flex gap-2">
|
||||
{panels.map((panel) => (
|
||||
<button
|
||||
key={panel.id}
|
||||
onClick={() => setActivePanel(activePanel === panel.id ? null : panel.id)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
activePanel === panel.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{panel.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[380px] bg-muted/20 border rounded flex items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{activePanel ? `"${panels.find((p) => p.id === activePanel)?.title}" panel is open` : 'Click a button to open a floating panel'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Floating Panel Overlay */}
|
||||
{activePanel && (
|
||||
<div
|
||||
className={`absolute top-16 border rounded-lg shadow-lg bg-background ${
|
||||
panels.find((p) => p.id === activePanel)?.side === 'left' ? 'left-6' : 'right-6'
|
||||
}`}
|
||||
style={{ width: panels.find((p) => p.id === activePanel)?.width }}
|
||||
>
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium">{panels.find((p) => p.id === activePanel)?.title}</span>
|
||||
<button
|
||||
onClick={() => setActivePanel(null)}
|
||||
className="text-xs hover:bg-accent px-2 py-1 rounded"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>Item 1</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500"/>
|
||||
<span>Item 2</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-amber-500"/>
|
||||
<span>Item 3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Resizable Panes Demo
|
||||
|
||||
:::demo ResizablePanesDemo
|
||||
# resizable-panes-demo.tsx
|
||||
/**
|
||||
* Resizable Panes Demo
|
||||
* Simulates the Allotment resizable split behavior
|
||||
*/
|
||||
export function ResizablePanesDemo() {
|
||||
const [leftWidth, setLeftWidth] = React.useState(240)
|
||||
const [rightWidth, setRightWidth] = React.useState(280)
|
||||
const [isDragging, setIsDragging] = React.useState(null)
|
||||
|
||||
const handleDragStart = (side) => (e) => {
|
||||
setIsDragging(side)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleMouseMove = (e) => {
|
||||
if (isDragging === 'left') {
|
||||
setLeftWidth(Math.max(180, Math.min(320, e.clientX)))
|
||||
} else if (isDragging === 'right') {
|
||||
setRightWidth(Math.max(200, Math.min(400, window.innerWidth - e.clientX)))
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseUp = () => setIsDragging(null)
|
||||
|
||||
if (isDragging) {
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
return (
|
||||
<div className="h-[400px] flex bg-background border rounded-lg overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
<div style={{ width: leftWidth }} className="border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['Active Sessions', 'Completed'].map((g) => (
|
||||
<div key={g} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{g}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Left Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('left')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'left' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 bg-muted/20 flex items-center justify-center">
|
||||
<span className="text-sm text-muted-foreground">Terminal Grid Area</span>
|
||||
</div>
|
||||
|
||||
{/* Right Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('right')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'right' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div style={{ width: rightWidth }} className="border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['src/', 'docs/', 'tests/'].map((f) => (
|
||||
<div key={f} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{f}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Feature Flags
|
||||
|
||||
| Flag | Controls |
|
||||
|------|----------|
|
||||
| `dashboardQueuePanelEnabled` | Queue panel visibility |
|
||||
| `dashboardInspectorEnabled` | Inspector panel visibility |
|
||||
| `dashboardExecutionMonitorEnabled` | Execution Monitor panel visibility |
|
||||
|
||||
### Layout Presets
|
||||
|
||||
@@ -106,59 +590,36 @@
|
||||
| **Split-V** | Two panes stacked vertically |
|
||||
| **Grid-2x2** | Four panes in 2x2 grid |
|
||||
|
||||
---
|
||||
### Panel Types
|
||||
|
||||
## Components Reference
|
||||
|
||||
### Main Components
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `TerminalDashboardPage` | `@/pages/TerminalDashboardPage.tsx` | Main terminal dashboard page |
|
||||
| `DashboardToolbar` | `@/components/terminal-dashboard/DashboardToolbar.tsx` | Top toolbar with panel toggles |
|
||||
| `SessionGroupTree` | `@/components/terminal-dashboard/SessionGroupTree.tsx` | Session tree navigation |
|
||||
| `TerminalGrid` | `@/components/terminal-dashboard/TerminalGrid.tsx` | Tmux-style terminal panes |
|
||||
| `FileSidebarPanel` | `@/components/terminal-dashboard/FileSidebarPanel.tsx` | File explorer sidebar |
|
||||
| `FloatingPanel` | `@/components/terminal-dashboard/FloatingPanel.tsx` | Overlay panel wrapper |
|
||||
| `IssuePanel` | `@/components/terminal-dashboard/IssuePanel.tsx` | Issues list panel |
|
||||
| `QueuePanel` | `@/components/terminal-dashboard/QueuePanel.tsx` | Queue management panel |
|
||||
| `QueueListColumn` | `@/components/terminal-dashboard/QueueListColumn.tsx` | Queue list (compact) |
|
||||
| `SchedulerPanel` | `@/components/terminal-dashboard/SchedulerPanel.tsx` | Queue scheduler controls |
|
||||
| `InspectorContent` | `@/components/terminal-dashboard/BottomInspector.tsx` | Association inspector |
|
||||
| `ExecutionMonitorPanel` | `@/components/terminal-dashboard/ExecutionMonitorPanel.tsx` | Execution tracking |
|
||||
|
||||
### State Management
|
||||
|
||||
- **Local state** (TerminalDashboardPage):
|
||||
- `activePanel`: PanelId | null
|
||||
- `isFileSidebarOpen`: boolean
|
||||
- `isSessionSidebarOpen`: boolean
|
||||
|
||||
- **Zustand stores**:
|
||||
- `useWorkflowStore` - Project path
|
||||
- `useAppStore` - Immersive mode state
|
||||
- `useConfigStore` - Feature flags
|
||||
- `useTerminalGridStore` - Terminal grid layout and focused pane
|
||||
- `useExecutionMonitorStore` - Active execution count
|
||||
- `useQueueSchedulerStore` - Scheduler status
|
||||
| Panel | Content | Position | Feature Flag |
|
||||
|-------|---------|----------|--------------|
|
||||
| **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) | - |
|
||||
| **Queue** | Full queue management panel | Right (overlay) | `dashboardQueuePanelEnabled` |
|
||||
| **Inspector** | Association chain inspector | Right (overlay) | `dashboardInspectorEnabled` |
|
||||
| **Execution Monitor** | Real-time execution tracking | Right (overlay) | `dashboardExecutionMonitorEnabled` |
|
||||
| **Scheduler** | Queue scheduler controls | Right (overlay) | - |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
## Accessibility
|
||||
|
||||
### Panel IDs
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through toolbar buttons
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate toolbar buttons
|
||||
- <kbd>Escape</kbd> - Close floating panels
|
||||
- <kbd>F11</kbd> - Toggle fullscreen mode
|
||||
|
||||
```typescript
|
||||
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
```
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on toolbar buttons
|
||||
- `aria-expanded` on sidebar toggles
|
||||
- `aria-hidden` on inactive floating panels
|
||||
- `role="dialog"` on floating panels
|
||||
|
||||
### Feature Flags
|
||||
|
||||
| Flag | Controls |
|
||||
|------|----------|
|
||||
| `dashboardQueuePanelEnabled` | Queue panel visibility |
|
||||
| `dashboardInspectorEnabled` | Inspector panel visibility |
|
||||
| `dashboardExecutionMonitorEnabled` | Execution Monitor panel visibility |
|
||||
- **Screen Reader Support**:
|
||||
- Panel state announced when toggled
|
||||
- Layout changes announced
|
||||
- Focus management when panels open/close
|
||||
|
||||
---
|
||||
|
||||
@@ -168,3 +629,4 @@ type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
- [Sessions](/features/sessions) - Session management
|
||||
- [Issue Hub](/features/issue-hub) - Issues, queue, discovery
|
||||
- [Explorer](/features/explorer) - File explorer
|
||||
- [Queue](/features/queue) - Queue management standalone page
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-i18n": "^10.0.0"
|
||||
"vue-i18n": "^10.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"tailwind-merge": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
600
docs/zh-CN/components/index.md
Normal file
600
docs/zh-CN/components/index.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# 组件库
|
||||
|
||||
## 一句话概述
|
||||
**基于 Radix UI 原语和 Tailwind CSS 构建的可复用 UI 组件综合集合,遵循 shadcn/ui 模式,提供一致、可访问和可定制的界面。**
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
**位置**: `ccw/frontend/src/components/ui/`
|
||||
|
||||
**用途**: 为构建 CCW 前端应用程序提供一致的 UI 组件集合。
|
||||
|
||||
**技术栈**:
|
||||
- **Radix UI**: 无样式、可访问的组件原语
|
||||
- **Tailwind CSS**: 带自定义主题的实用优先样式
|
||||
- **class-variance-authority (CVA)**: 类型安全的变体 props 管理
|
||||
- **Lucide React**: 一致的图标系统
|
||||
|
||||
---
|
||||
|
||||
## 实时演示:组件库展示
|
||||
|
||||
:::demo ComponentGallery
|
||||
# component-gallery.tsx
|
||||
/**
|
||||
* 组件库展示演示
|
||||
* 所有 UI 组件的交互式展示
|
||||
*/
|
||||
export function ComponentGallery() {
|
||||
const [selectedCategory, setSelectedCategory] = React.useState('all')
|
||||
const [buttonVariant, setButtonVariant] = React.useState('default')
|
||||
const [switchState, setSwitchState] = React.useState(false)
|
||||
const [checkboxState, setCheckboxState] = React.useState(false)
|
||||
const [selectedTab, setSelectedTab] = React.useState('variants')
|
||||
|
||||
const categories = [
|
||||
{ id: 'all', label: '全部组件' },
|
||||
{ id: 'buttons', label: '按钮' },
|
||||
{ id: 'forms', label: '表单' },
|
||||
{ id: 'feedback', label: '反馈' },
|
||||
{ id: 'navigation', label: '导航' },
|
||||
{ id: 'overlays', label: '叠加层' },
|
||||
]
|
||||
|
||||
const components = {
|
||||
buttons: [
|
||||
{ name: 'Button', variants: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link', 'gradient'] },
|
||||
],
|
||||
forms: [
|
||||
{ name: 'Input', type: 'text' },
|
||||
{ name: 'Textarea', type: 'textarea' },
|
||||
{ name: 'Select', type: 'select' },
|
||||
{ name: 'Checkbox', type: 'checkbox' },
|
||||
{ name: 'Switch', type: 'switch' },
|
||||
],
|
||||
feedback: [
|
||||
{ name: 'Badge', variants: ['default', 'secondary', 'success', 'warning', 'destructive'] },
|
||||
{ name: 'Progress', type: 'progress' },
|
||||
{ name: 'Alert', type: 'alert' },
|
||||
],
|
||||
navigation: [
|
||||
{ name: 'Tabs', type: 'tabs' },
|
||||
{ name: 'Breadcrumb', type: 'breadcrumb' },
|
||||
],
|
||||
overlays: [
|
||||
{ name: 'Dialog', type: 'dialog' },
|
||||
{ name: 'Drawer', type: 'drawer' },
|
||||
{ name: 'Dropdown', type: 'dropdown' },
|
||||
],
|
||||
}
|
||||
|
||||
const buttonVariants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-8">
|
||||
{/* 标题 */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">UI 组件库</h1>
|
||||
<p className="text-muted-foreground">所有可用 UI 组件的交互式展示</p>
|
||||
</div>
|
||||
|
||||
{/* 分类过滤 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
className={`px-4 py-2 rounded-md text-sm transition-colors ${
|
||||
selectedCategory === cat.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 按钮部分 */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'buttons') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">按钮</h2>
|
||||
<div className="space-y-6">
|
||||
{/* 变体选择器 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">变体</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{buttonVariants.map((variant) => (
|
||||
<button
|
||||
key={variant}
|
||||
onClick={() => setButtonVariant(variant)}
|
||||
className={`px-4 py-2 rounded-md text-sm capitalize transition-colors ${
|
||||
buttonVariant === variant
|
||||
? 'bg-primary text-primary-foreground ring-2 ring-ring'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{variant}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 按钮尺寸 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">尺寸</label>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<button className={`h-8 rounded-md px-3 text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
小
|
||||
</button>
|
||||
<button className={`h-10 px-4 py-2 rounded-md text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
默认
|
||||
</button>
|
||||
<button className={`h-11 rounded-md px-8 text-sm ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
大
|
||||
</button>
|
||||
<button className={`h-10 w-10 rounded-md flex items-center justify-center ${buttonVariant === 'default' ? 'bg-primary text-primary-foreground' : 'border'}`}>
|
||||
⚙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 所有按钮变体 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">所有变体</label>
|
||||
<div className="flex flex-wrap gap-3 p-4 border rounded-lg bg-muted/20">
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:opacity-90">默认</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-destructive text-destructive-foreground hover:opacity-90">危险</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm border bg-background hover:bg-accent">轮廓</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-secondary text-secondary-foreground hover:opacity-80">次要</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm hover:bg-accent">幽灵</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm text-primary underline-offset-4 hover:underline">链接</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white hover:opacity-90">渐变</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 表单部分 */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'forms') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">表单组件</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* 输入框 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">输入框</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="输入文本..."
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="错误状态"
|
||||
className="flex h-10 w-full rounded-md border border-destructive bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-destructive"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 文本区域 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">文本区域</label>
|
||||
<textarea
|
||||
placeholder="输入多行文本..."
|
||||
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 复选框 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">复选框</label>
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border border-primary" checked={checkboxState} onChange={(e) => setCheckboxState(e.target.checked)} />
|
||||
<span>接受条款和条件</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer opacity-50">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border border-primary" />
|
||||
<span>订阅新闻通讯</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 开关 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">开关</label>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<div className="relative">
|
||||
<input type="checkbox" className="sr-only peer" checked={switchState} onChange={(e) => setSwitchState(e.target.checked)} />
|
||||
<div className="w-9 h-5 bg-input rounded-full peer peer-focus:ring-2 peer-focus:ring-ring peer-checked:bg-primary after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-background after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full" />
|
||||
</div>
|
||||
<span className="text-sm">启用通知 {switchState ? '(开)' : '(关)'}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 下拉选择 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">下拉选择</label>
|
||||
<select className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
<option value="">选择一个选项</option>
|
||||
<option value="1">选项 1</option>
|
||||
<option value="2">选项 2</option>
|
||||
<option value="3">选项 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 表单操作 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">表单操作</label>
|
||||
<div className="flex gap-2">
|
||||
<button className="px-4 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:opacity-90">提交</button>
|
||||
<button className="px-4 py-2 rounded-md text-sm border hover:bg-accent">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 反馈部分 */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'feedback') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">反馈组件</h2>
|
||||
|
||||
{/* 徽章 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">徽章</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary text-primary-foreground">默认</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-secondary text-secondary-foreground">次要</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-destructive text-destructive-foreground">危险</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-success text-white">成功</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-warning text-white">警告</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-info text-white">信息</span>
|
||||
<span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold text-foreground">轮廓</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">进度条</label>
|
||||
<div className="space-y-3 max-w-md">
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span>处理中...</span>
|
||||
<span>65%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-primary rounded-full transition-all" style={{ width: '65%' }}/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span>上传中...</span>
|
||||
<span>30%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full transition-all" style={{ width: '30%' }}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 警告 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">警告提示</label>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3 p-4 border rounded-lg bg-destructive/10 border-destructive/20 text-destructive">
|
||||
<span className="text-lg">⚠</span>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">发生错误</div>
|
||||
<div className="text-xs mt-1 opacity-80">出现了一些问题,请重试。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 border rounded-lg bg-success/10 border-success/20 text-success">
|
||||
<span className="text-lg">✓</span>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">成功!</div>
|
||||
<div className="text-xs mt-1 opacity-80">您的更改已保存。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 导航部分 */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'navigation') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">导航组件</h2>
|
||||
|
||||
{/* 标签页 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">标签页</label>
|
||||
<div className="border-b">
|
||||
<div className="flex gap-4">
|
||||
{['概览', '文档', 'API 参考', '示例'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setSelectedTab(tab.toLowerCase().replace(' ', '-'))}
|
||||
className={`pb-3 px-1 text-sm border-b-2 transition-colors ${
|
||||
selectedTab === tab.toLowerCase().replace(' ', '-')
|
||||
? 'border-primary text-primary'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 面包屑 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">面包屑</label>
|
||||
<nav className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<a href="#" className="hover:text-foreground">首页</a>
|
||||
<span>/</span>
|
||||
<a href="#" className="hover:text-foreground">组件</a>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">库</span>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 叠加层部分 */}
|
||||
{(selectedCategory === 'all' || selectedCategory === 'overlays') && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">叠加层组件</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-4 text-sm">
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">对话框</h3>
|
||||
<p className="text-muted-foreground text-xs">用于焦点用户交互的模态对话框。</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded">打开对话框</button>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">抽屉</h3>
|
||||
<p className="text-muted-foreground text-xs">从屏幕边缘滑入的侧边面板。</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs border rounded hover:bg-accent">打开抽屉</button>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-medium mb-2">下拉菜单</h3>
|
||||
<p className="text-muted-foreground text-xs">上下文菜单和操作列表。</p>
|
||||
<button className="mt-3 px-3 py-1.5 text-xs border rounded hover:bg-accent">▼ 打开菜单</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 可用组件
|
||||
|
||||
### 表单组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Button](/components/ui/button) | 可点击的操作按钮,带变体和尺寸 | `variant`, `size`, `asChild` |
|
||||
| [Input](/components/ui/input) | 文本输入字段 | `error` |
|
||||
| [Textarea](/components/ui/textarea) | 多行文本输入 | `error` |
|
||||
| [Select](/components/ui/select) | 下拉选择(Radix) | Select 组件 |
|
||||
| [Checkbox](/components/ui/checkbox) | 布尔复选框(Radix) | `checked`, `onCheckedChange` |
|
||||
| [Switch](/components/ui/switch) | 切换开关 | `checked`, `onCheckedChange` |
|
||||
|
||||
### 布局组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Card](/components/ui/card) | 带标题/脚注的内容容器 | 嵌套组件 |
|
||||
| [Separator](/components/ui/separator) | 视觉分隔符 | `orientation` |
|
||||
| [ScrollArea](/components/ui/scroll-area) | 自定义滚动条容器 | - |
|
||||
|
||||
### 反馈组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Badge](/components/ui/badge) | 状态指示器标签 | `variant` |
|
||||
| [Progress](/components/ui/progress) | 进度条 | `value` |
|
||||
| [Alert](/components/ui/alert) | 通知消息 | `variant` |
|
||||
| [Toast](/components/ui/toast) | 临时通知(Radix) | Toast 组件 |
|
||||
|
||||
### 导航组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Tabs](/components/ui/tabs) | 标签页导航(Radix) | Tabs 组件 |
|
||||
| [TabsNavigation](/components/ui/tabs-navigation) | 自定义标签栏 | `tabs`, `value`, `onValueChange` |
|
||||
| [Breadcrumb](/components/ui/breadcrumb) | 导航面包屑 | Breadcrumb 组件 |
|
||||
|
||||
### 叠加层组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Dialog](/components/ui/dialog) | 模态对话框(Radix) | `open`, `onOpenChange` |
|
||||
| [Drawer](/components/ui/drawer) | 侧边面板(Radix) | `open`, `onOpenChange` |
|
||||
| [Dropdown Menu](/components/ui/dropdown) | 上下文菜单(Radix) | Dropdown 组件 |
|
||||
| [Popover](/components/ui/popover) | 浮动内容(Radix) | `open`, `onOpenChange` |
|
||||
| [Tooltip](/components/ui/tooltip) | 悬停工具提示(Radix) | `content` |
|
||||
| [AlertDialog](/components/ui/alert-dialog) | 确认对话框(Radix) | Dialog 组件 |
|
||||
|
||||
### 展开组件
|
||||
|
||||
| 组件 | 描述 | Props |
|
||||
|------|------|------|
|
||||
| [Collapsible](/components/ui/collapsible) | 展开/折叠内容(Radix) | `open`, `onOpenChange` |
|
||||
| [Accordion](/components/ui/accordion) | 可折叠部分(Radix) | Accordion 组件 |
|
||||
|
||||
---
|
||||
|
||||
## 按钮变体
|
||||
|
||||
Button 组件通过 CVA(class-variance-authority)支持 8 种变体:
|
||||
|
||||
| 变体 | 用途 | 预览 |
|
||||
|---------|----------|--------|
|
||||
| `default` | 主要操作 | <span className="inline-block px-3 py-1 rounded bg-primary text-primary-foreground text-xs">默认</span> |
|
||||
| `destructive` | 危险操作 | <span className="inline-block px-3 py-1 rounded bg-destructive text-destructive-foreground text-xs">危险</span> |
|
||||
| `outline` | 次要操作 | <span className="inline-block px-3 py-1 rounded border text-xs">轮廓</span> |
|
||||
| `secondary` | 较少强调 | <span className="inline-block px-3 py-1 rounded bg-secondary text-secondary-foreground text-xs">次要</span> |
|
||||
| `ghost` | 微妙操作 | <span className="inline-block px-3 py-1 rounded text-xs">幽灵</span> |
|
||||
| `link` | 文本链接 | <span className="inline-block px-3 py-1 underline text-primary text-xs">链接</span> |
|
||||
| `gradient` | 特色操作 | <span className="inline-block px-3 py-1 rounded bg-gradient-to-r from-blue-500 to-purple-500 text-white text-xs">渐变</span> |
|
||||
| `gradientPrimary` | 主要渐变 | <span className="inline-block px-3 py-1 rounded bg-gradient-to-r from-purple-500 to-pink-500 text-white text-xs">主要</span> |
|
||||
|
||||
### 按钮尺寸
|
||||
|
||||
| 尺寸 | 高度 | 内边距 |
|
||||
|------|--------|---------|
|
||||
| `sm` | 36px | 水平 12px |
|
||||
| `default` | 40px | 水平 16px |
|
||||
| `lg` | 44px | 水平 32px |
|
||||
| `icon` | 40px | 正方形(仅图标) |
|
||||
|
||||
---
|
||||
|
||||
## 徽章变体
|
||||
|
||||
Badge 组件支持 9 种变体,用于不同的状态类型:
|
||||
|
||||
| 变体 | 用途 | 颜色 |
|
||||
|---------|----------|-------|
|
||||
| `default` | 一般信息 | 主要主题 |
|
||||
| `secondary` | 较少强调 | 次要主题 |
|
||||
| `destructive` | 错误/危险 | 危险主题 |
|
||||
| `outline` | 微妙 | 仅文本颜色 |
|
||||
| `success` | 成功状态 | 绿色 |
|
||||
| `warning` | 警告状态 | 琥珀色 |
|
||||
| `info` | 信息 | 蓝色 |
|
||||
| `review` | 审查状态 | 紫色 |
|
||||
| `gradient` | 特色 | 品牌渐变 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### Button
|
||||
|
||||
```tsx
|
||||
import { Button } from '@/components/ui/Button'
|
||||
|
||||
<Button variant="default" onClick={handleClick}>
|
||||
点击我
|
||||
</Button>
|
||||
|
||||
<Button variant="destructive" size="sm">
|
||||
删除
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="icon">
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Input with Error State
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
error={hasError}
|
||||
placeholder="输入您的名称"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
|
||||
```tsx
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
|
||||
<Checkbox
|
||||
checked={accepted}
|
||||
onCheckedChange={setAccepted}
|
||||
/>
|
||||
<label>接受条款</label>
|
||||
```
|
||||
|
||||
### Switch
|
||||
|
||||
```tsx
|
||||
import { Switch } from '@/components/ui/Switch'
|
||||
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onCheckedChange={setEnabled}
|
||||
/>
|
||||
<span>启用功能</span>
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/Card'
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>卡片标题</CardTitle>
|
||||
<CardDescription>简短描述在这里</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>主要内容放在这里。</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>操作</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Badge
|
||||
|
||||
```tsx
|
||||
import { Badge, badgeVariants } from '@/components/ui/Badge'
|
||||
|
||||
<Badge variant="success">已完成</Badge>
|
||||
<Badge variant="warning">待处理</Badge>
|
||||
<Badge variant="destructive">失败</Badge>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可访问性
|
||||
|
||||
所有组件遵循 Radix UI 的可访问性标准:
|
||||
|
||||
- **键盘导航**:所有交互组件完全可键盘访问
|
||||
- **ARIA 属性**:正确的角色、状态和属性
|
||||
- **屏幕阅读器支持**:语义化 HTML 和 ARIA 标签
|
||||
- **焦点管理**:可见的焦点指示器和逻辑 Tab 顺序
|
||||
- **颜色对比度**:符合 WCAG AA 标准的颜色组合
|
||||
|
||||
### 键盘快捷键
|
||||
|
||||
| 组件 | 按键 |
|
||||
|-----------|-------|
|
||||
| Button | <kbd>Enter</kbd>, <kbd>Space</kbd> |
|
||||
| Checkbox/Switch | <kbd>Space</kbd> 切换 |
|
||||
| Select | <kbd>Arrow</kbd> 键,<kbd>Enter</kbd> 选择,<kbd>Esc</kbd> 关闭 |
|
||||
| Dialog | <kbd>Esc</kbd> 关闭 |
|
||||
| Tabs | <kbd>Arrow</kbd> 键导航 |
|
||||
| Dropdown | <kbd>Arrow</kbd> 键,<kbd>Enter</kbd> 选择 |
|
||||
|
||||
---
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [Radix UI Primitives](https://www.radix-ui.com/) - 无头 UI 组件库
|
||||
- [Tailwind CSS](https://tailwindcss.com/) - 实用优先 CSS 框架
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - 组件模式参考
|
||||
- [CVA Documentation](https://cva.style/) - Class Variance Authority
|
||||
119
docs/zh-CN/components/ui/badge.md
Normal file
119
docs/zh-CN/components/ui/badge.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: Badge 徽章
|
||||
description: 用于视觉分类的小型状态或标签组件
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Badge 徽章
|
||||
|
||||
## 概述
|
||||
|
||||
Badge 徽章组件用于以紧凑形式显示状态、类别或标签。它通常用于标签、状态指示器和计数。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo badge-variants
|
||||
展示所有可用的徽章变体,包括默认、次要、破坏性、轮廓、成功、警告、信息、审查和渐变样式
|
||||
:::
|
||||
|
||||
## 属性
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'variant', type: '\'default\' | \'secondary\' | \'destructive\' | \'outline\' | \'success\' | \'warning\' | \'info\' | \'review\' | \'gradient\'', required: false, default: '\'default\'', description: '视觉样式变体' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: '自定义 CSS 类名' },
|
||||
{ name: 'children', type: 'ReactNode', required: true, default: '-', description: '徽章内容' }
|
||||
]" />
|
||||
|
||||
## 变体说明
|
||||
|
||||
### Default(默认)
|
||||
|
||||
主题色背景的主要徽章。用于主要标签和类别。
|
||||
|
||||
### Secondary(次要)
|
||||
|
||||
次要信息的灰色徽章。
|
||||
|
||||
### Destructive(破坏性)
|
||||
|
||||
红色徽章,用于错误、危险状态或负面状态指示。
|
||||
|
||||
### Outline(轮廓)
|
||||
|
||||
只有文本和边框、无背景的徽章。用于微妙的标签。
|
||||
|
||||
### Success(成功)
|
||||
|
||||
绿色徽章,用于成功状态、完成的操作或正面状态指示。
|
||||
|
||||
### Warning(警告)
|
||||
|
||||
黄色/琥珀色徽章,用于警告、待处理状态或注意事项。
|
||||
|
||||
### Info(信息)
|
||||
|
||||
蓝色徽章,用于信息内容或中性状态。
|
||||
|
||||
### Review(审查)
|
||||
|
||||
紫色徽章,用于审查状态、待审查或反馈指示器。
|
||||
|
||||
### Gradient(渐变)
|
||||
|
||||
带品牌渐变背景的徽章,用于特色或突出显示的项目。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础徽章
|
||||
|
||||
```vue
|
||||
<Badge>默认</Badge>
|
||||
```
|
||||
|
||||
### 状态指示器
|
||||
|
||||
```vue
|
||||
<Badge variant="success">活跃</Badge>
|
||||
<Badge variant="warning">待处理</Badge>
|
||||
<Badge variant="destructive">失败</Badge>
|
||||
<Badge variant="info">草稿</Badge>
|
||||
```
|
||||
|
||||
### 计数徽章
|
||||
|
||||
```vue
|
||||
<div class="relative">
|
||||
<Bell />
|
||||
<Badge variant="destructive" class="absolute -top-2 -right-2 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs">
|
||||
3
|
||||
</Badge>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 分类标签
|
||||
|
||||
```vue
|
||||
<div class="flex gap-2">
|
||||
<Badge variant="outline">React</Badge>
|
||||
<Badge variant="outline">TypeScript</Badge>
|
||||
<Badge variant="outline">Tailwind</Badge>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 审查状态
|
||||
|
||||
```vue
|
||||
<Badge variant="review">审查中</Badge>
|
||||
```
|
||||
|
||||
### 渐变徽章
|
||||
|
||||
```vue
|
||||
<Badge variant="gradient">特色</Badge>
|
||||
```
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Card 卡片](/zh-CN/components/ui/card)
|
||||
- [Button 按钮](/zh-CN/components/ui/button)
|
||||
- [Avatar 头像](/zh-CN/components/ui/avatar)
|
||||
80
docs/zh-CN/components/ui/button.md
Normal file
80
docs/zh-CN/components/ui/button.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Button 按钮
|
||||
description: 按钮组件用于触发操作或提交表单
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Button 按钮
|
||||
|
||||
## 概述
|
||||
|
||||
按钮是最常用的交互元素之一,用于触发操作、提交表单或导航到其他页面。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo button-variants
|
||||
展示所有视觉变体的按钮组件
|
||||
:::
|
||||
|
||||
## 属性
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'variant', type: '\'default\' | \'destructive\' | \'outline\' | \'secondary\' | \'ghost\' | \'link\' | \'gradient\' | \'gradientPrimary\'', required: false, default: '\'default\'', description: '按钮的视觉变体' },
|
||||
{ name: 'size', type: '\'default\' | \'sm\' | \'lg\' | \'icon\'', required: false, default: '\'default\'', description: '按钮的大小' },
|
||||
{ name: 'asChild', type: 'boolean', required: false, default: 'false', description: '是否将 props 与子元素合并(用于 Radix UI 组合)' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: '是否禁用按钮' },
|
||||
{ name: 'onClick', type: '() => void', required: false, default: '-', description: '点击事件回调函数' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: '自定义 CSS 类名' },
|
||||
{ name: 'children', type: 'ReactNode', required: true, default: '-', description: '按钮内容' }
|
||||
]" />
|
||||
|
||||
## 变体说明
|
||||
|
||||
### Default(默认)
|
||||
|
||||
默认按钮用于主要的操作场景。
|
||||
|
||||
### Destructive(破坏性)
|
||||
|
||||
破坏性按钮用于删除、移除等不可逆的操作。
|
||||
|
||||
### Outline(轮廓)
|
||||
|
||||
轮廓按钮用于次要操作,视觉上更轻量。
|
||||
|
||||
### Secondary(次要)
|
||||
|
||||
次要按钮用于辅助操作。
|
||||
|
||||
### Ghost(幽灵)
|
||||
|
||||
幽灵按钮没有边框,视觉上最轻量。
|
||||
|
||||
### Link(链接)
|
||||
|
||||
链接按钮看起来像链接,但具有按钮的交互行为。
|
||||
|
||||
### Gradient(渐变)
|
||||
|
||||
渐变按钮使用品牌渐变色,悬停时带有发光效果。
|
||||
|
||||
### Gradient Primary(主色渐变)
|
||||
|
||||
主色渐变按钮使用主题主色渐变,悬停时带有增强的发光效果。
|
||||
|
||||
## 使用场景
|
||||
|
||||
| 场景 | 推荐变体 |
|
||||
|------|----------|
|
||||
| 主要操作(提交、保存) | default, gradientPrimary |
|
||||
| 危险操作(删除、移除) | destructive |
|
||||
| 次要操作 | outline, secondary |
|
||||
| 取消操作 | ghost, outline |
|
||||
| 导航链接 | link |
|
||||
| 促销/特色 CTA | gradient |
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Input 输入框](/zh-CN/components/ui/input)
|
||||
- [Select 选择器](/zh-CN/components/ui/select)
|
||||
- [Dialog 对话框](/zh-CN/components/ui/dialog)
|
||||
107
docs/zh-CN/components/ui/card.md
Normal file
107
docs/zh-CN/components/ui/card.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: Card 卡片
|
||||
description: 用于分组相关内容的容器组件
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Card 卡片
|
||||
|
||||
## 概述
|
||||
|
||||
卡片组件是一个通用的容器,用于分组相关的内容和操作。它由多个子组件组成,协同工作以创建连贯的卡片布局。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo card-variants
|
||||
展示不同的卡片布局,包括页眉、内容、页脚和渐变边框变体
|
||||
:::
|
||||
|
||||
## 组件
|
||||
|
||||
卡片组件包含以下子组件:
|
||||
|
||||
| 组件 | 用途 |
|
||||
|------|------|
|
||||
| `Card` | 带有边框和背景的主容器 |
|
||||
| `CardHeader` | 带有内边距的页眉部分 |
|
||||
| `CardTitle` | 标题元素 |
|
||||
| `CardDescription` | 带有次要颜色的描述文本 |
|
||||
| `CardContent` | 带有顶部内边距的内容区域 |
|
||||
| `CardFooter` | 用于操作的页脚部分 |
|
||||
| `CardGradientBorder` | 带有渐变边框的卡片 |
|
||||
|
||||
## 属性
|
||||
|
||||
所有 Card 组件接受标准 HTML div 属性:
|
||||
|
||||
| 组件 | 属性 |
|
||||
|------|------|
|
||||
| `Card` | `className?: string` |
|
||||
| `CardHeader` | `className?: string` |
|
||||
| `CardTitle` | `className?: string`, `children?: ReactNode` |
|
||||
| `CardDescription` | `className?: string`, `children?: ReactNode` |
|
||||
| `CardContent` | `className?: string` |
|
||||
| `CardFooter` | `className?: string` |
|
||||
| `CardGradientBorder` | `className?: string` |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础卡片
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardContent>
|
||||
<p>这是一个带有内容的基础卡片。</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### 带页眉的卡片
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>卡片标题</CardTitle>
|
||||
<CardDescription>卡片描述放在这里</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>卡片内容放在这里。</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### 带页脚的完整卡片
|
||||
|
||||
```vue
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>项目设置</CardTitle>
|
||||
<CardDescription>管理您的项目配置</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>配置您的项目设置和偏好。</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>保存更改</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### 带渐变边框的卡片
|
||||
|
||||
```vue
|
||||
<CardGradientBorder>
|
||||
<CardHeader>
|
||||
<CardTitle>特色卡片</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>此卡片具有渐变边框效果。</p>
|
||||
</CardContent>
|
||||
</CardGradientBorder>
|
||||
```
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Button 按钮](/zh-CN/components/ui/button)
|
||||
- [Badge 徽章](/zh-CN/components/ui/badge)
|
||||
- [Separator 分隔线](/zh-CN/components/ui/separator)
|
||||
120
docs/zh-CN/components/ui/checkbox.md
Normal file
120
docs/zh-CN/components/ui/checkbox.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Checkbox 复选框
|
||||
description: 用于二元选择的复选框组件
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Checkbox 复选框
|
||||
|
||||
## 概述
|
||||
|
||||
Checkbox 复选框组件允许用户从一组选项中选择一个或多个选项。基于 Radix UI Checkbox Primitive 构建,提供完整的无障碍支持,包括键盘导航。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo checkbox-variants
|
||||
展示不同的复选框状态,包括选中、未选中、半选和禁用状态
|
||||
:::
|
||||
|
||||
## 属性
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'checked', type: 'boolean | \'indeterminate\'', required: false, default: 'false', description: '是否选中复选框' },
|
||||
{ name: 'defaultChecked', type: 'boolean', required: false, default: 'false', description: '初始选中状态(非受控)' },
|
||||
{ name: 'onCheckedChange', type: '(checked: boolean) => void', required: false, default: '-', description: '选中状态变化时的回调函数' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: '是否禁用复选框' },
|
||||
{ name: 'required', type: 'boolean', required: false, default: 'false', description: '是否为必填项' },
|
||||
{ name: 'name', type: 'string', required: false, default: '-', description: '表单输入名称' },
|
||||
{ name: 'value', type: 'string', required: false, default: '-', description: '表单输入值' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: '自定义 CSS 类名' }
|
||||
]" />
|
||||
|
||||
## 状态说明
|
||||
|
||||
### Unchecked(未选中)
|
||||
|
||||
复选框未被选择时的默认状态。
|
||||
|
||||
### Checked(已选中)
|
||||
|
||||
复选框被选择时显示勾选图标。
|
||||
|
||||
### Indeterminate(半选)
|
||||
|
||||
混合状态(部分选择),通常用于父级复选框,当部分但不是全部子项被选中时使用。
|
||||
|
||||
### Disabled(禁用)
|
||||
|
||||
禁用的复选框不可交互,并以降低的透明度显示。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础复选框
|
||||
|
||||
```vue
|
||||
<Checkbox />
|
||||
```
|
||||
|
||||
### 带标签的复选框
|
||||
|
||||
```vue
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label for="terms">我同意条款和条件</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 受控复选框
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const checked = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox v-model:checked="checked" />
|
||||
<span>{{ checked ? '已选中' : '未选中' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 半选状态
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const state = ref('indeterminate')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Checkbox :checked="state" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 表单集成
|
||||
|
||||
```vue
|
||||
<form @submit="handleSubmit">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="newsletter" name="newsletter" value="yes" />
|
||||
<label for="newsletter">订阅新闻通讯</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="updates" name="updates" value="yes" />
|
||||
<label for="updates">接收产品更新</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" class="mt-4">提交</Button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Input 输入框](/zh-CN/components/ui/input)
|
||||
- [Select 选择器](/zh-CN/components/ui/select)
|
||||
- [Radio Group 单选框组](/zh-CN/components/ui/radio-group)
|
||||
118
docs/zh-CN/components/ui/input.md
Normal file
118
docs/zh-CN/components/ui/input.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Input 输入框
|
||||
description: 用于表单和用户输入的文本输入组件
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Input 输入框
|
||||
|
||||
## 概述
|
||||
|
||||
输入框组件提供了一个样式化的文本输入字段,扩展了原生 HTML input 元素,具有一致的样式和错误状态支持。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo input-variants
|
||||
展示所有输入框状态,包括默认、错误和禁用
|
||||
:::
|
||||
|
||||
## 属性
|
||||
|
||||
<PropsTable :props="[
|
||||
{ name: 'type', type: 'string', required: false, default: '\'text\'', description: 'HTML input 类型(text、password、email、number 等)' },
|
||||
{ name: 'error', type: 'boolean', required: false, default: 'false', description: '输入框是否处于错误状态(显示破坏性边框)' },
|
||||
{ name: 'disabled', type: 'boolean', required: false, default: 'false', description: '是否禁用输入框' },
|
||||
{ name: 'placeholder', type: 'string', required: false, default: '-', description: '输入框为空时显示的占位符文本' },
|
||||
{ name: 'value', type: 'string | number', required: false, default: '-', description: '受控输入框的值' },
|
||||
{ name: 'defaultValue', type: 'string | number', required: false, default: '-', description: '非受控输入框的默认值' },
|
||||
{ name: 'onChange', type: '(event: ChangeEvent) => void', required: false, default: '-', description: '变更事件回调函数' },
|
||||
{ name: 'className', type: 'string', required: false, default: '-', description: '自定义 CSS 类名' }
|
||||
]" />
|
||||
|
||||
## 状态
|
||||
|
||||
### Default(默认)
|
||||
|
||||
带有边框和焦点环的标准输入框。
|
||||
|
||||
### Error(错误)
|
||||
|
||||
错误状态,显示破坏性边框颜色。将 `error` 属性设置为 `true`。
|
||||
|
||||
### Disabled(禁用)
|
||||
|
||||
禁用状态,透明度降低。将 `disabled` 属性设置为 `true`。
|
||||
|
||||
### Focus(聚焦)
|
||||
|
||||
聚焦状态,带有环形轮廓。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础输入框
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return <input type="text" placeholder="输入文本..." />
|
||||
}
|
||||
```
|
||||
|
||||
### 受控输入框
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="输入文本..."
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 错误状态输入框
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
error
|
||||
placeholder="无效输入..."
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 密码输入框
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input'
|
||||
|
||||
function Example() {
|
||||
return <Input type="password" placeholder="输入密码..." />
|
||||
}
|
||||
```
|
||||
|
||||
## 无障碍访问
|
||||
|
||||
- **键盘导航**:完全支持原生键盘操作
|
||||
- **ARIA 属性**:支持所有标准输入框 ARIA 属性
|
||||
- **焦点可见**:为键盘导航提供清晰的焦点指示器
|
||||
- **错误状态**:错误状态的视觉指示(与 `aria-invalid` 和 `aria-describedby` 配合使用)
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Button 按钮](/zh-CN/components/ui/button)
|
||||
- [Select 选择器](/zh-CN/components/ui/select)
|
||||
- [Checkbox 复选框](/zh-CN/components/ui/checkbox)
|
||||
127
docs/zh-CN/components/ui/select.md
Normal file
127
docs/zh-CN/components/ui/select.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Select 选择器
|
||||
description: 用于从选项列表中选择值的下拉选择组件
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Select 选择器
|
||||
|
||||
## 概述
|
||||
|
||||
Select 选择器组件允许用户从选项列表中选择单个值。基于 Radix UI Select Primitive 构建,提供开箱即用的无障碍支持和键盘导航。
|
||||
|
||||
## 语法演示
|
||||
|
||||
:::demo select-variants
|
||||
展示不同的选择器配置,包括基础用法、带标签和带分隔线的示例
|
||||
:::
|
||||
|
||||
## 子组件
|
||||
|
||||
Select 选择器组件包含以下子组件:
|
||||
|
||||
| 组件 | 用途 |
|
||||
|------|------|
|
||||
| `Select` | 管理状态的根组件 |
|
||||
| `SelectTrigger` | 打开下拉菜单的按钮 |
|
||||
| `SelectValue` | 显示选中的值 |
|
||||
| `SelectContent` | 下拉菜单内容容器 |
|
||||
| `SelectItem` | 单个可选项 |
|
||||
| `SelectLabel` | 用于分组的不可交互标签 |
|
||||
| `SelectGroup` | 将项目分组 |
|
||||
| `SelectSeparator` | 项目之间的视觉分隔线 |
|
||||
| `SelectScrollUpButton` | 长列表的向上滚动按钮 |
|
||||
| `SelectScrollDownButton` | 长列表的向下滚动按钮 |
|
||||
|
||||
## 属性
|
||||
|
||||
### Select 根组件
|
||||
|
||||
| 属性 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `value` | `string` | - | 当前选中的值(受控) |
|
||||
| `defaultValue` | `string` | - | 默认选中的值 |
|
||||
| `onValueChange` | `(value: string) => void` | - | 值变化时的回调函数 |
|
||||
| `disabled` | `boolean` | `false` | 是否禁用选择器 |
|
||||
| `required` | `boolean` | `false` | 是否必填 |
|
||||
| `name` | `string` | - | 表单输入名称 |
|
||||
|
||||
### SelectTrigger
|
||||
|
||||
| 属性 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `className` | `string` | - | 自定义 CSS 类名 |
|
||||
|
||||
### SelectItem
|
||||
|
||||
| 属性 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `value` | `string` | - | 选项值 |
|
||||
| `disabled` | `boolean` | `false` | 是否禁用该选项 |
|
||||
| `className` | `string` | - | 自定义 CSS 类名 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础选择器
|
||||
|
||||
```vue
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择一个选项" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="option1">选项 1</SelectItem>
|
||||
<SelectItem value="option2">选项 2</SelectItem>
|
||||
<SelectItem value="option3">选项 3</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
### 带标签和分组
|
||||
|
||||
```vue
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择水果" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectLabel>水果</SelectLabel>
|
||||
<SelectItem value="apple">苹果</SelectItem>
|
||||
<SelectItem value="banana">香蕉</SelectItem>
|
||||
<SelectItem value="orange">橙子</SelectItem>
|
||||
<SelectSeparator />
|
||||
<SelectLabel>蔬菜</SelectLabel>
|
||||
<SelectItem value="carrot">胡萝卜</SelectItem>
|
||||
<SelectItem value="broccoli">西兰花</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
### 受控选择器
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedValue = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select v-model="selectedValue">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="a">选项 A</SelectItem>
|
||||
<SelectItem value="b">选项 B</SelectItem>
|
||||
<SelectItem value="c">选项 C</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 相关组件
|
||||
|
||||
- [Input 输入框](/zh-CN/components/ui/input)
|
||||
- [Checkbox 复选框](/zh-CN/components/ui/checkbox)
|
||||
- [Button 按钮](/zh-CN/components/ui/button)
|
||||
607
docs/zh-CN/features/dashboard.md
Normal file
607
docs/zh-CN/features/dashboard.md
Normal file
@@ -0,0 +1,607 @@
|
||||
# 仪表板
|
||||
|
||||
## 一句话概述
|
||||
**仪表板通过直观的基于小部件的界面,提供项目工作流状态、统计信息和最近活动的概览。**
|
||||
|
||||
---
|
||||
|
||||
## 解决的痛点
|
||||
|
||||
| 痛点 | 当前状态 | 仪表板解决方案 |
|
||||
|------|----------|----------------|
|
||||
| **项目可见性不足** | 无法查看整体项目健康状况 | 带有技术栈和开发索引的项目信息横幅 |
|
||||
| **指标分散** | 统计信息分布在多个位置 | 集中式统计数据,带有迷你趋势图 |
|
||||
| **工作流状态未知** | 难以跟踪会话进度 | 带有状态细分的饼图 |
|
||||
| **最近工作丢失** | 无法快速访问活动会话 | 带有任务详情的会话轮播 |
|
||||
| **索引状态不明确** | 不知道代码是否已索引 | 实时索引状态指示器 |
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
**位置**: `ccw/frontend/src/pages/HomePage.tsx`
|
||||
|
||||
**用途**: 仪表板主页,提供项目概览、统计信息、工作流状态和最近活动监控。
|
||||
|
||||
**访问**: 导航 → 仪表板(默认首页,路径为 `/`)
|
||||
|
||||
**布局**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| 仪表板头部(标题 + 刷新) |
|
||||
+--------------------------------------------------------------------------+
|
||||
| WorkflowTaskWidget(组合卡片) |
|
||||
| +--------------------------------------------------------------------+ |
|
||||
| | 项目信息横幅(可展开) | |
|
||||
| | - 项目名称、描述、技术栈徽章 | |
|
||||
| | - 快速统计(功能、bug修复、增强) | |
|
||||
| | - 索引状态指示器 | |
|
||||
| +----------------------------------+---------------------------------+ |
|
||||
| | 统计部分 | 工作流状态 | 任务详情(轮播) | |
|
||||
| | - 6 个迷你卡片 | - 饼图 | - 会话导航 | |
|
||||
| | - 迷你趋势图 | - 图例 | - 任务列表(2 列) | |
|
||||
| +----------------+-----------------+-------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| RecentSessionsWidget |
|
||||
| +--------------------------------------------------------------------+ |
|
||||
| | 标签页:所有任务 | 工作流 | 轻量任务 | |
|
||||
| | +---------------+---------------+-------------------------------+ | |
|
||||
| | | 带有状态、进度、标签、时间的任务卡片 | | |
|
||||
| | +---------------------------------------------------------------+ | |
|
||||
+--------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实时演示
|
||||
|
||||
:::demo DashboardOverview
|
||||
# dashboard-overview.tsx
|
||||
/**
|
||||
* 仪表板概览演示
|
||||
* 显示带有小部件的主仪表板布局
|
||||
*/
|
||||
export function DashboardOverview() {
|
||||
return (
|
||||
<div className="space-y-6 p-6 bg-background min-h-[600px]">
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">仪表板</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
项目概览和活动监控
|
||||
</p>
|
||||
</div>
|
||||
<button className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent">
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 工作流统计小部件 */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="p-4 border-b bg-muted/30">
|
||||
<h2 className="font-semibold">项目概览与统计</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">统计数据</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
{ label: '活动会话', value: '12', color: 'text-blue-500' },
|
||||
{ label: '总任务', value: '48', color: 'text-green-500' },
|
||||
{ label: '已完成', value: '35', color: 'text-emerald-500' },
|
||||
{ label: '待处理', value: '8', color: 'text-amber-500' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="p-2 bg-muted/50 rounded">
|
||||
<div className={`text-lg font-bold ${stat.color}`}>{stat.value}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">工作流状态</div>
|
||||
<div className="flex items-center justify-center h-24">
|
||||
<div className="relative w-20 h-20">
|
||||
<svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-muted opacity-20"/>
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-blue-500" strokeDasharray="70 100"/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-xs font-bold">70%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-center space-y-1">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>已完成:70%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">最近会话</div>
|
||||
<div className="p-3 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">功能:身份验证流程</span>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-600">运行中</span>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-green-500"/>
|
||||
<span>实现登录表单</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-amber-500"/>
|
||||
<span>添加 OAuth 提供商</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-muted"/>
|
||||
<span className="text-muted-foreground">测试流程</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最近会话小部件 */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="border-b bg-muted/30">
|
||||
<div className="flex gap-1 p-2">
|
||||
{['所有任务', '工作流', '轻量任务'].map((tab, i) => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`px-3 py-1.5 text-xs rounded-md transition-colors ${
|
||||
i === 0 ? 'bg-background text-foreground' : 'text-muted-foreground hover:bg-foreground/5'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ name: '重构 UI 组件', status: '进行中', progress: 65 },
|
||||
{ name: '修复登录 Bug', status: '待处理', progress: 0 },
|
||||
{ name: '添加深色模式', status: '已完成', progress: 100 },
|
||||
].map((task, i) => (
|
||||
<div key={i} className="p-3 bg-muted/30 rounded border cursor-pointer hover:border-primary/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium line-clamp-1">{task.name}</span>
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
task.status === '已完成' ? 'bg-green-500/20 text-green-600' :
|
||||
task.status === '进行中' ? 'bg-blue-500/20 text-blue-600' :
|
||||
'bg-gray-500/20 text-gray-600'
|
||||
}`}>{task.status}</span>
|
||||
</div>
|
||||
{task.progress > 0 && task.progress < 100 && (
|
||||
<div className="w-full h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full" style={{ width: `${task.progress}%` }}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| **项目信息横幅** | 可展开的横幅,显示项目名称、描述、技术栈(语言、框架、架构)、开发索引(功能/bug修复/增强)和实时索引状态 |
|
||||
| **统计部分** | 6 个迷你统计卡片(活动会话、总任务、已完成任务、待处理任务、失败任务、今日活动),带有 7 天迷你趋势图 |
|
||||
| **工作流状态饼图** | 环形图显示会话状态细分(已完成、进行中、计划中、已暂停、已归档),附带百分比 |
|
||||
| **会话轮播** | 自动轮播(5秒间隔)的会话卡片,带有任务列表、进度条和手动导航箭头 |
|
||||
| **最近会话小部件** | 所有任务类型的标签页视图,带有筛选、状态徽章和进度指示器 |
|
||||
| **实时更新** | 统计数据每 60 秒自动刷新,索引状态每 30 秒刷新 |
|
||||
|
||||
---
|
||||
|
||||
## 组件层次结构
|
||||
|
||||
```
|
||||
HomePage
|
||||
├── DashboardHeader
|
||||
│ ├── 标题
|
||||
│ └── 刷新操作按钮
|
||||
├── WorkflowTaskWidget
|
||||
│ ├── ProjectInfoBanner(可展开)
|
||||
│ │ ├── 项目名称和描述
|
||||
│ │ ├── 技术栈徽章
|
||||
│ │ ├── 快速统计卡片
|
||||
│ │ ├── 索引状态指示器
|
||||
│ │ ├── 架构部分
|
||||
│ │ ├── 关键组件
|
||||
│ │ └── 设计模式
|
||||
│ ├── 统计部分
|
||||
│ │ └── MiniStatCard(6 个卡片,带迷你趋势图)
|
||||
│ ├── WorkflowStatusChart
|
||||
│ │ └── 饼图与图例
|
||||
│ └── SessionCarousel
|
||||
│ ├── 导航箭头
|
||||
│ └── 会话卡片(任务列表)
|
||||
└── RecentSessionsWidget
|
||||
├── 标签导航(全部 | 工作流 | 轻量任务)
|
||||
├── 任务网格
|
||||
│ └── TaskItemCard
|
||||
└── 加载/空状态
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Props API
|
||||
|
||||
### HomePage 组件
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| - | - | - | 此页面组件不接受任何 props(数据通过 hooks 获取) |
|
||||
|
||||
### WorkflowTaskWidget
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `className` | `string` | `undefined` | 用于样式的额外 CSS 类 |
|
||||
|
||||
### RecentSessionsWidget
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `className` | `string` | `undefined` | 用于样式的额外 CSS 类 |
|
||||
| `maxItems` | `number` | `6` | 要显示的最大项目数量 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础仪表板
|
||||
|
||||
```tsx
|
||||
import { HomePage } from '@/pages/HomePage'
|
||||
|
||||
// 仪表板在根路由 (/) 自动渲染
|
||||
// 不需要 props - 数据通过 hooks 获取
|
||||
```
|
||||
|
||||
### 嵌入 WorkflowTaskWidget
|
||||
|
||||
```tsx
|
||||
import { WorkflowTaskWidget } from '@/components/dashboard/widgets/WorkflowTaskWidget'
|
||||
|
||||
function CustomDashboard() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<WorkflowTaskWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义最近会话小部件
|
||||
|
||||
```tsx
|
||||
import { RecentSessionsWidget } from '@/components/dashboard/widgets/RecentSessionsWidget'
|
||||
|
||||
function ActivityFeed() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<RecentSessionsWidget maxItems={10} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 状态管理
|
||||
|
||||
### 本地状态
|
||||
|
||||
| 状态 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `hasError` | `boolean` | 关键错误的错误跟踪 |
|
||||
| `projectExpanded` | `boolean` | 项目信息横幅展开状态 |
|
||||
| `currentSessionIndex` | `number` | 轮播中活动会话的索引 |
|
||||
| `activeTab` | `'all' \| 'workflow' \| 'lite'` | 最近会话小部件筛选标签页 |
|
||||
|
||||
### Store 选择器(Zustand)
|
||||
|
||||
| Store | 选择器 | 用途 |
|
||||
|-------|--------|------|
|
||||
| `appStore` | `selectIsImmersiveMode` | 检查沉浸模式是否激活 |
|
||||
|
||||
### 自定义 Hooks(数据获取)
|
||||
|
||||
| Hook | 描述 | 重新获取间隔 |
|
||||
|------|-------------|--------------|
|
||||
| `useWorkflowStatusCounts` | 会话状态分布数据 | - |
|
||||
| `useDashboardStats` | 带迷你趋势图的统计数据 | 60 秒 |
|
||||
| `useProjectOverview` | 项目信息和技术栈 | - |
|
||||
| `useIndexStatus` | 实时索引状态 | 30 秒 |
|
||||
| `useSessions` | 活动会话数据 | - |
|
||||
| `useLiteTasks` | 最近小部件的轻量任务数据 | - |
|
||||
|
||||
---
|
||||
|
||||
## 交互演示
|
||||
|
||||
### 统计卡片演示
|
||||
|
||||
:::demo MiniStatCards
|
||||
# mini-stat-cards.tsx
|
||||
/**
|
||||
* 迷你统计卡片演示
|
||||
* 带有迷你趋势图的独立统计卡片
|
||||
*/
|
||||
export function MiniStatCards() {
|
||||
const stats = [
|
||||
{ label: '活动会话', value: 12, trend: [8, 10, 9, 11, 10, 12, 12], color: 'blue' },
|
||||
{ label: '总任务', value: 48, trend: [40, 42, 45, 44, 46, 47, 48], color: 'green' },
|
||||
{ label: '已完成', value: 35, trend: [25, 28, 30, 32, 33, 34, 35], color: 'emerald' },
|
||||
{ label: '待处理', value: 8, trend: [12, 10, 11, 9, 8, 7, 8], color: 'amber' },
|
||||
{ label: '失败', value: 5, trend: [3, 4, 3, 5, 4, 5, 5], color: 'red' },
|
||||
{ label: '今日活动', value: 23, trend: [5, 10, 15, 18, 20, 22, 23], color: 'purple' },
|
||||
]
|
||||
|
||||
const colorMap = {
|
||||
blue: 'text-blue-500 bg-blue-500/10',
|
||||
green: 'text-green-500 bg-green-500/10',
|
||||
emerald: 'text-emerald-500 bg-emerald-500/10',
|
||||
amber: 'text-amber-500 bg-amber-500/10',
|
||||
red: 'text-red-500 bg-red-500/10',
|
||||
purple: 'text-purple-500 bg-purple-500/10',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">带迷你趋势图的统计</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{stats.map((stat, i) => (
|
||||
<div key={i} className="p-4 border rounded-lg bg-card">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs text-muted-foreground">{stat.label}</span>
|
||||
<div className={`w-2 h-2 rounded-full ${colorMap[stat.color].split(' ')[1]}`}/>
|
||||
</div>
|
||||
<div className={`text-2xl font-bold ${colorMap[stat.color].split(' ')[0]}`}>{stat.value}</div>
|
||||
<div className="mt-2 h-8 flex items-end gap-0.5">
|
||||
{stat.trend.map((v, j) => (
|
||||
<div
|
||||
key={j}
|
||||
className="flex-1 rounded-t"
|
||||
style={{
|
||||
height: `${(v / Math.max(...stat.trend)) * 100}%`,
|
||||
backgroundColor: v === stat.value ? 'currentColor' : 'rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### 项目信息横幅演示
|
||||
|
||||
:::demo ProjectInfoBanner
|
||||
# project-info-banner.tsx
|
||||
/**
|
||||
* 项目信息横幅演示
|
||||
* 带有技术栈的可展开项目信息
|
||||
*/
|
||||
export function ProjectInfoBanner() {
|
||||
const [expanded, setExpanded] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">项目信息横幅</h3>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
{/* 横幅头部 */}
|
||||
<div className="p-4 bg-muted/30 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold">我的项目</h4>
|
||||
<p className="text-sm text-muted-foreground">使用 React 构建的现代化 Web 应用</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="p-2 rounded-md hover:bg-accent"
|
||||
>
|
||||
<svg className={`w-5 h-5 transition-transform ${expanded ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 技术栈徽章 */}
|
||||
<div className="px-4 pb-3 flex flex-wrap gap-2">
|
||||
{['TypeScript', 'React', 'Vite', 'Tailwind CSS', 'Zustand'].map((tech) => (
|
||||
<span key={tech} className="px-2 py-1 text-xs rounded-full bg-primary/10 text-primary">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 展开内容 */}
|
||||
{expanded && (
|
||||
<div className="p-4 border-t bg-muted/20 space-y-4">
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">架构</h5>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>• 基于组件的 UI 架构</div>
|
||||
<div>• 集中式状态管理</div>
|
||||
<div>• RESTful API 集成</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">关键组件</h5>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
{['会话管理器', '仪表板', '任务调度器', '分析'].map((comp) => (
|
||||
<div key={comp} className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-primary"/>
|
||||
{comp}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### 会话轮播演示
|
||||
|
||||
:::demo SessionCarousel
|
||||
# session-carousel.tsx
|
||||
/**
|
||||
* 会话轮播演示
|
||||
* 带有导航的自动轮播会话卡片
|
||||
*/
|
||||
export function SessionCarousel() {
|
||||
const [currentIndex, setCurrentIndex] = React.useState(0)
|
||||
const sessions = [
|
||||
{
|
||||
name: '功能:用户身份验证',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: '实现登录表单', status: 'completed' },
|
||||
{ name: '添加 OAuth 提供商', status: 'in-progress' },
|
||||
{ name: '创建会话管理', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Bug 修复:内存泄漏',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: '识别泄漏源', status: 'completed' },
|
||||
{ name: '修复清理处理器', status: 'in-progress' },
|
||||
{ name: '添加单元测试', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '重构:API 层',
|
||||
status: 'planning',
|
||||
tasks: [
|
||||
{ name: '设计新接口', status: 'pending' },
|
||||
{ name: '迁移现有端点', status: 'pending' },
|
||||
{ name: '更新文档', status: 'pending' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const statusColors = {
|
||||
completed: 'bg-green-500',
|
||||
'in-progress': 'bg-amber-500',
|
||||
pending: 'bg-muted',
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentIndex((i) => (i + 1) % sessions.length)
|
||||
}, 5000)
|
||||
return () => clearInterval(timer)
|
||||
}, [sessions.length])
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">会话轮播(每 5 秒自动轮播)</h3>
|
||||
<div className="border rounded-lg p-4 bg-card">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-medium">会话 {currentIndex + 1} / {sessions.length}</span>
|
||||
<div className="flex gap-1">
|
||||
{sessions.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentIndex(i)}
|
||||
className={`w-2 h-2 rounded-full transition-colors ${
|
||||
i === currentIndex ? 'bg-primary' : 'bg-muted-foreground/30'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="font-medium">{sessions[currentIndex].name}</span>
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
sessions[currentIndex].status === 'running' ? 'bg-green-500/20 text-green-600' : 'bg-blue-500/20 text-blue-600'
|
||||
}`}>
|
||||
{sessions[currentIndex].status === 'running' ? '运行中' : sessions[currentIndex].status === 'planning' ? '计划中' : sessions[currentIndex].status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{sessions[currentIndex].tasks.map((task, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm">
|
||||
<div className={`w-3 h-3 rounded ${statusColors[task.status]}`}/>
|
||||
<span className={task.status === 'pending' ? 'text-muted-foreground' : ''}>{task.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-3">
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i - 1 + sessions.length) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
← 上一页
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i + 1) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
下一页 →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 可访问性
|
||||
|
||||
- **键盘导航**:
|
||||
- <kbd>Tab</kbd> - 在交互元素之间导航
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - 激活按钮和卡片
|
||||
- <kbd>方向键</kbd> - 导航轮播会话
|
||||
|
||||
- **ARIA 属性**:
|
||||
- 导航按钮上的 `aria-label`
|
||||
- 可展开部分的 `aria-expanded`
|
||||
- 实时更新的 `aria-live` 区域
|
||||
|
||||
- **屏幕阅读器支持**:
|
||||
- 所有图表都有文字描述
|
||||
- 状态指示器包含文字标签
|
||||
- 导航被正确宣布
|
||||
|
||||
---
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [会话](/features/sessions) - 查看和管理所有会话
|
||||
- [终端仪表板](/features/terminal) - 终端优先监控界面
|
||||
- [队列](/features/queue) - 问题执行队列管理
|
||||
- [内存](/features/memory) - 持久化内存管理
|
||||
- [设置](/features/settings) - 全局应用设置
|
||||
283
docs/zh-CN/features/queue.md
Normal file
283
docs/zh-CN/features/queue.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# 队列管理
|
||||
|
||||
## 一句话概述
|
||||
**队列管理页面提供对问题执行队列的集中控制,配备调度器控件、状态监控和会话池管理。**
|
||||
|
||||
---
|
||||
|
||||
## 解决的痛点
|
||||
|
||||
| 痛点 | 当前状态 | 队列解决方案 |
|
||||
|------|----------|--------------|
|
||||
| **执行无序** | 没有统一的任务队列 | 带分组项目的集中化队列 |
|
||||
| **调度器状态未知** | 不知道调度器是否在运行 | 实时状态指示器(空闲/运行/暂停) |
|
||||
| **无执行控制** | 无法启动/停止队列处理 | 带确认的开始/暂停/停止控件 |
|
||||
| **并发限制** | 同时运行太多会话 | 可配置的最大并发会话数 |
|
||||
| **无可见性** | 不知道队列中有什么 | 统计卡片 + 带状态跟踪的项目列表 |
|
||||
| **资源浪费** | 空闲会话消耗资源 | 带超时配置的会话池概览 |
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
**位置**: `ccw/frontend/src/pages/QueuePage.tsx`(旧版),`ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx`(当前)
|
||||
|
||||
**用途**: 查看和管理问题执行队列,配备调度器控件、进度跟踪和会话池管理。
|
||||
|
||||
**访问方式**: 导航 → 问题 → 队列标签页 或 终端仪表板 → 队列浮动面板
|
||||
|
||||
**布局**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| 队列面板标题栏 |
|
||||
+--------------------------------------------------------------------------+
|
||||
| 调度器状态栏 |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
| | 状态徽章 | | 进度 | | 并发数 (2/2) | |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| 调度器控件 |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
| | 开始 | | 暂停 | | 停止 | | 配置 | |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| 队列项目列表 |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | QueueItemRow(状态、issue_id、session_key、操作) | |
|
||||
| | - 状态图标(待处理/执行中/已完成/被阻塞/失败) | |
|
||||
| | - Issue ID / 项目 ID 显示 | |
|
||||
| | - 会话绑定信息 | |
|
||||
| | - 进度指示器(对于执行中的项目) | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | [更多队列项目...] | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| 会话池概览(可选) |
|
||||
| +--------------------------------------------------------------------------+
|
||||
| | 活动会话 | 空闲会话 | 总会话数 |
|
||||
| +--------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| **调度器状态** | 实时状态指示器(空闲/运行/暂停),带视觉徽章 |
|
||||
| **进度跟踪** | 显示队列总体完成百分比的进度条 |
|
||||
| **开始/暂停/停止控件** | 控制队列执行,停止操作时带确认对话框 |
|
||||
| **并发显示** | 显示当前活动会话数与最大并发会话数 |
|
||||
| **队列项目列表** | 所有队列项目的可滚动列表,附带状态、Issue ID 和会话绑定 |
|
||||
| **状态图标** | 项目状态的视觉指示器(待处理/执行中/已完成/被阻塞/失败) |
|
||||
| **会话池** | 活动会话、空闲会话和总会话数的概览 |
|
||||
| **配置面板** | 调整最大并发会话数和超时设置 |
|
||||
| **空状态** | 队列为空时的友好消息,附带添加项目的说明 |
|
||||
|
||||
---
|
||||
|
||||
## 组件层次结构
|
||||
|
||||
```
|
||||
QueuePage(旧版)/ QueuePanel(当前)
|
||||
├── QueuePanelHeader
|
||||
│ ├── 标题
|
||||
│ └── 标签切换器(队列 | 编排器)
|
||||
├── SchedulerBar(内联在 QueueListColumn 中)
|
||||
│ ├── 状态徽章
|
||||
│ ├── 进度 + 并发数
|
||||
│ └── 控制按钮(播放/暂停/停止)
|
||||
├── QueueItemsList
|
||||
│ └── QueueItemRow(重复)
|
||||
│ ├── 状态图标
|
||||
│ ├── Issue ID / 项目 ID
|
||||
│ ├── 会话绑定
|
||||
│ └── 进度(对于执行中的项目)
|
||||
└── SchedulerPanel(独立)
|
||||
├── 状态部分
|
||||
├── 进度条
|
||||
├── 控制按钮
|
||||
├── 配置部分
|
||||
│ ├── 最大并发会话数
|
||||
│ ├── 会话空闲超时
|
||||
│ └── 恢复键绑定超时
|
||||
└── 会话池概览
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Props API
|
||||
|
||||
### QueuePanel
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `embedded` | `boolean` | `false` | 面板是否嵌入在另一个组件中 |
|
||||
|
||||
### SchedulerPanel
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| - | - | - | 此组件不接受任何 props(所有数据来自 Zustand store) |
|
||||
|
||||
### QueueListColumn
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| - | - | - | 此组件不接受任何 props(所有数据来自 Zustand store) |
|
||||
|
||||
---
|
||||
|
||||
## 状态管理
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | 选择器 | 用途 |
|
||||
|-------|--------|------|
|
||||
| `queueSchedulerStore` | `selectQueueSchedulerStatus` | 当前调度器状态(空闲/运行/暂停) |
|
||||
| `queueSchedulerStore` | `selectSchedulerProgress` | 队列总体完成百分比 |
|
||||
| `queueSchedulerStore` | `selectQueueItems` | 所有队列项目的列表 |
|
||||
| `queueSchedulerStore` | `selectCurrentConcurrency` | 活动会话计数 |
|
||||
| `queueSchedulerStore` | `selectSchedulerConfig` | 调度器配置 |
|
||||
| `queueSchedulerStore` | `selectSessionPool` | 会话池概览 |
|
||||
| `queueSchedulerStore` | `selectSchedulerError` | 错误消息(如果有) |
|
||||
| `issueQueueIntegrationStore` | `selectAssociationChain` | 用于高亮显示的当前关联链 |
|
||||
| `queueExecutionStore` | `selectByQueueItem` | 队列项目的执行数据 |
|
||||
|
||||
### 队列项目状态
|
||||
|
||||
```typescript
|
||||
type QueueItemStatus =
|
||||
| 'pending' // 等待执行
|
||||
| 'executing' // 正在处理中
|
||||
| 'completed' // 成功完成
|
||||
| 'blocked' // 被依赖项阻塞
|
||||
| 'failed'; // 失败并报错
|
||||
```
|
||||
|
||||
### 调度器状态
|
||||
|
||||
```typescript
|
||||
type QueueSchedulerStatus =
|
||||
| 'idle' // 无项目或已停止
|
||||
| 'running' // 活动处理项目
|
||||
| 'paused'; // 临时暂停
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本队列面板
|
||||
|
||||
```tsx
|
||||
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel'
|
||||
|
||||
function QueueSection() {
|
||||
return <QueuePanel />
|
||||
}
|
||||
```
|
||||
|
||||
### 独立调度器面板
|
||||
|
||||
```tsx
|
||||
import { SchedulerPanel } from '@/components/terminal-dashboard/SchedulerPanel'
|
||||
|
||||
function SchedulerControls() {
|
||||
return <SchedulerPanel />
|
||||
}
|
||||
```
|
||||
|
||||
### 嵌入式队列列表列
|
||||
|
||||
```tsx
|
||||
import { QueueListColumn } from '@/components/terminal-dashboard/QueueListColumn'
|
||||
|
||||
function EmbeddedQueue() {
|
||||
return <QueueListColumn />
|
||||
}
|
||||
```
|
||||
|
||||
### 队列 Store 操作
|
||||
|
||||
```tsx
|
||||
import { useQueueSchedulerStore } from '@/stores/queueSchedulerStore'
|
||||
|
||||
function QueueActions() {
|
||||
const startQueue = useQueueSchedulerStore((s) => s.startQueue)
|
||||
const pauseQueue = useQueueSchedulerStore((s) => s.pauseQueue)
|
||||
const stopQueue = useQueueSchedulerStore((s) => s.stopQueue)
|
||||
const updateConfig = useQueueSchedulerStore((s) => s.updateConfig)
|
||||
|
||||
const handleStart = () => startQueue()
|
||||
const handlePause = () => pauseQueue()
|
||||
const handleStop = () => stopQueue()
|
||||
const handleConfig = (config) => updateConfig(config)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleStart}>开始</button>
|
||||
<button onClick={handlePause}>暂停</button>
|
||||
<button onClick={handleStop}>停止</button>
|
||||
<button onClick={() => handleConfig({ maxConcurrentSessions: 4 })}>
|
||||
设置最大值为 4
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置
|
||||
|
||||
### 调度器配置
|
||||
|
||||
| 设置 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `maxConcurrentSessions` | `number` | `2` | 同时运行的最大会话数 |
|
||||
| `sessionIdleTimeoutMs` | `number` | `60000` | 空闲会话超时时间(毫秒) |
|
||||
| `resumeKeySessionBindingTimeoutMs` | `number` | `300000` | 恢复键绑定超时时间(毫秒) |
|
||||
|
||||
### 队列项目结构
|
||||
|
||||
```typescript
|
||||
interface QueueItem {
|
||||
item_id: string;
|
||||
issue_id?: string;
|
||||
sessionKey?: string;
|
||||
status: QueueItemStatus;
|
||||
execution_order: number;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可访问性
|
||||
|
||||
- **键盘导航**:
|
||||
- <kbd>Tab</kbd> - 在队列项目和控件之间导航
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - 激活按钮
|
||||
- <kbd>Escape</kbd> - 关闭对话框
|
||||
|
||||
- **ARIA 属性**:
|
||||
- 控制按钮上的 `aria-label`
|
||||
- 状态更新的 `aria-live` 区域
|
||||
- 当前队列项目的 `aria-current`
|
||||
- 队列项目列表上的 `role="list"`
|
||||
|
||||
- **屏幕阅读器支持**:
|
||||
- 状态变化被宣布
|
||||
- 进度更新被朗读
|
||||
- 错误消息被宣布
|
||||
|
||||
---
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [问题中心](/features/issue-hub) - 统一的问题、队列和发现管理
|
||||
- [终端仪表板](/features/terminal) - 带集成队列面板的终端优先工作空间
|
||||
- [发现](/features/discovery) - 发现会话跟踪
|
||||
- [会话](/features/sessions) - 会话管理和详情
|
||||
612
docs/zh-CN/features/terminal.md
Normal file
612
docs/zh-CN/features/terminal.md
Normal file
@@ -0,0 +1,612 @@
|
||||
# 终端仪表板
|
||||
|
||||
## 一句话概述
|
||||
**终端仪表板提供以终端为首的工作空间,具有可调整大小的窗格、浮动面板和用于会话监控与编排的集成工具。**
|
||||
|
||||
---
|
||||
|
||||
## 解决的痛点
|
||||
|
||||
| 痛点 | 当前状态 | 终端仪表板解决方案 |
|
||||
|------|----------|---------------------|
|
||||
| **终端分散** | 多个终端窗口 | 统一的 tmux 风格网格布局 |
|
||||
| **无上下文关联** | 无法将终端输出与问题关联 | 关联高亮提供程序 |
|
||||
| **面板过多** | 固定布局浪费空间 | 浮动面板(互斥) |
|
||||
| **缺少工具** | 在应用间切换 | 集成问题、队列、检查器、调度器 |
|
||||
| **工作空间有限** | 无法同时查看代码和终端 | 可调整大小的三列布局 |
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
**位置**: `ccw/frontend/src/pages/TerminalDashboardPage.tsx`
|
||||
|
||||
**用途**: 用于多终端会话管理的终端优先布局,配备集成工具和可调整大小的面板。
|
||||
|
||||
**访问方式**: 导航 → 终端仪表板 (`/terminal-dashboard`)
|
||||
|
||||
**布局**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| 仪表板工具栏(面板切换、布局预设、全屏) |
|
||||
+--------------------------------------------------------------------------+
|
||||
| +----------------+-------------------------------------------+------------+ |
|
||||
| | 会话 | 终端网格(tmux 风格) | 文件 | |
|
||||
| | 分组树 | +----------+ +----------+ | 侧边栏 | |
|
||||
| | (可调整大小) | | 终端 1 | | 终端 2 | |(可调整大小)| |
|
||||
| | | +----------+ +----------+ | | |
|
||||
| | | +----------+ +----------+ | | |
|
||||
| | | | 终端 3 | | 终端 4 | | | |
|
||||
| | | +----------+ +----------+ | | |
|
||||
| +----------------+-------------------------------------------+------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| [浮动面板: 问题+队列 或 检查器 或 执行 或 调度器] |
|
||||
+--------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实时演示
|
||||
|
||||
:::demo TerminalDashboardOverview
|
||||
# terminal-dashboard-overview.tsx
|
||||
/**
|
||||
* Terminal Dashboard Overview Demo
|
||||
* Shows the three-column layout with resizable panes and toolbar
|
||||
*/
|
||||
export function TerminalDashboardOverview() {
|
||||
const [fileSidebarOpen, setFileSidebarOpen] = React.useState(true)
|
||||
const [sessionSidebarOpen, setSessionSidebarOpen] = React.useState(true)
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
return (
|
||||
<div className="h-[600px] flex flex-col bg-background">
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-muted/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">Terminal Dashboard</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{['Sessions', 'Files', 'Issues', 'Queue', 'Inspector', 'Scheduler'].map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
onClick={() => {
|
||||
if (item === 'Sessions') setSessionSidebarOpen(!sessionSidebarOpen)
|
||||
else if (item === 'Files') setFileSidebarOpen(!fileSidebarOpen)
|
||||
else setActivePanel(activePanel === item.toLowerCase() ? null : item.toLowerCase())
|
||||
}}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors ${
|
||||
(item === 'Sessions' && sessionSidebarOpen) ||
|
||||
(item === 'Files' && fileSidebarOpen) ||
|
||||
activePanel === item.toLowerCase()
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Layout */}
|
||||
<div className="flex-1 flex min-h-0">
|
||||
{/* Session Sidebar */}
|
||||
{sessionSidebarOpen && (
|
||||
<div className="w-60 border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 space-y-1 text-sm overflow-auto">
|
||||
{['Active Sessions', 'Completed', 'Archived'].map((group) => (
|
||||
<div key={group}>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded hover:bg-accent cursor-pointer">
|
||||
<span className="text-xs">▼</span>
|
||||
<span>{group}</span>
|
||||
</div>
|
||||
<div className="ml-4 space-y-0.5">
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 1
|
||||
</div>
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terminal Grid */}
|
||||
<div className="flex-1 bg-muted/20 p-2">
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-2 h-full">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="bg-background border rounded p-3 font-mono text-xs">
|
||||
<div className="text-green-500 mb-2">$ Terminal {i}</div>
|
||||
<div className="text-muted-foreground">
|
||||
<div>Working directory: /project</div>
|
||||
<div>Type a command to begin...</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Sidebar */}
|
||||
{fileSidebarOpen && (
|
||||
<div className="w-64 border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm overflow-auto">
|
||||
<div className="space-y-1">
|
||||
{['src', 'docs', 'tests', 'package.json', 'README.md'].map((item) => (
|
||||
<div key={item} className="px-2 py-1 rounded hover:bg-accent cursor-pointer flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">📁</span>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Floating Panel */}
|
||||
{activePanel && (
|
||||
<div className="absolute top-12 right-4 w-80 bg-background border rounded-lg shadow-lg">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium capitalize">{activePanel} Panel</span>
|
||||
<button onClick={() => setActivePanel(null)} className="text-xs hover:bg-accent px-2 py-1 rounded">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
{activePanel} content placeholder
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| **三列布局** | 使用 Allotment 的可调整大小窗格:会话树(左)、终端网格(中)、文件侧边栏(右) |
|
||||
| **终端网格** | Tmux 风格的分割窗格,带布局预设(单格、水平分割、垂直分割、2x2 网格) |
|
||||
| **会话分组树** | CLI 会话的分层视图,按标签分组 |
|
||||
| **浮动面板** | 互斥的叠加面板(问题+队列、检查器、执行监控器、调度器) |
|
||||
| **关联高亮** | 终端、问题和队列项之间的跨面板链接 |
|
||||
| **布局预设** | 快速布局按钮:单格窗格、水平分割、垂直分割、2x2 网格 |
|
||||
| **启动 CLI** | 用于创建新 CLI 会话的配置模态框,可选择工具、模型和设置 |
|
||||
| **全屏模式** | 沉浸模式隐藏应用框架(标题栏 + 侧边栏) |
|
||||
| **功能标志** | 面板可见性由功能标志控制(队列、检查器、执行监控器) |
|
||||
|
||||
---
|
||||
|
||||
## 组件层次结构
|
||||
|
||||
```
|
||||
TerminalDashboardPage
|
||||
├── AssociationHighlightProvider(上下文)
|
||||
├── DashboardToolbar
|
||||
│ ├── 布局预设按钮(单格 | 水平分割 | 垂直分割 | 2x2 网格)
|
||||
│ ├── 面板切换(会话 | 文件 | 问题 | 队列 | 检查器 | 执行 | 调度器)
|
||||
│ ├── 全屏切换
|
||||
│ └── 启动 CLI 按钮
|
||||
├── Allotment(三列布局)
|
||||
│ ├── SessionGroupTree
|
||||
│ │ └── 会话分组项(可折叠)
|
||||
│ ├── TerminalGrid
|
||||
│ │ ├── GridGroupRenderer(递归)
|
||||
│ │ └── TerminalPane
|
||||
│ └── FileSidebarPanel
|
||||
│ └── 文件树视图
|
||||
└── FloatingPanel(多个,互斥)
|
||||
├── 问题+队列(分割面板)
|
||||
│ ├── IssuePanel
|
||||
│ └── QueueListColumn
|
||||
├── QueuePanel(功能标志)
|
||||
├── InspectorContent(功能标志)
|
||||
├── ExecutionMonitorPanel(功能标志)
|
||||
└── SchedulerPanel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Props API
|
||||
|
||||
### TerminalDashboardPage
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| - | - | - | 此页面组件不接受任何 props(状态通过 hooks 和 Zustand stores 管理) |
|
||||
|
||||
### DashboardToolbar
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `activePanel` | `PanelId \| null` | `null` | 当前活动的浮动面板 |
|
||||
| `onTogglePanel` | `(panelId: PanelId) => void` | - | 切换面板可见性的回调 |
|
||||
| `isFileSidebarOpen` | `boolean` | `true` | 文件侧边栏可见性状态 |
|
||||
| `onToggleFileSidebar` | `() => void` | - | 切换文件侧边栏回调 |
|
||||
| `isSessionSidebarOpen` | `boolean` | `true` | 会话侧边栏可见性状态 |
|
||||
| `onToggleSessionSidebar` | `() => void` | - | 切换会话侧边栏回调 |
|
||||
| `isFullscreen` | `boolean` | `false` | 全屏模式状态 |
|
||||
| `onToggleFullscreen` | `() => void` | - | 切换全屏回调 |
|
||||
|
||||
### FloatingPanel
|
||||
|
||||
| Prop | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `isOpen` | `boolean` | `false` | 面板打开状态 |
|
||||
| `onClose` | `() => void` | - | 关闭回调 |
|
||||
| `title` | `string` | - | 面板标题 |
|
||||
| `side` | `'left' \| 'right'` | `'left'` | 面板侧边 |
|
||||
| `width` | `number` | `400` | 面板宽度(像素) |
|
||||
| `children` | `ReactNode` | - | 面板内容 |
|
||||
|
||||
---
|
||||
|
||||
## 状态管理
|
||||
|
||||
### 本地状态
|
||||
|
||||
| 状态 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `activePanel` | `PanelId \| null` | 当前活动的浮动面板(互斥) |
|
||||
| `isFileSidebarOpen` | `boolean` | 文件侧边栏可见性 |
|
||||
| `isSessionSidebarOpen` | `boolean` | 会话侧边栏可见性 |
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | 选择器 | 用途 |
|
||||
|-------|--------|------|
|
||||
| `workflowStore` | `selectProjectPath` | 文件侧边栏的当前项目路径 |
|
||||
| `appStore` | `selectIsImmersiveMode` | 全屏模式状态 |
|
||||
| `configStore` | `featureFlags` | 功能标志配置 |
|
||||
| `terminalGridStore` | 网格布局和焦点窗格状态 |
|
||||
| `executionMonitorStore` | 活动执行计数 |
|
||||
| `queueSchedulerStore` | 调度器状态和设置 |
|
||||
|
||||
### 面板 ID 类型
|
||||
|
||||
```typescript
|
||||
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本终端仪表板
|
||||
|
||||
```tsx
|
||||
import { TerminalDashboardPage } from '@/pages/TerminalDashboardPage'
|
||||
|
||||
// 终端仪表板自动在 /terminal-dashboard 渲染
|
||||
// 不需要 props - 布局状态内部管理
|
||||
```
|
||||
|
||||
### 使用 FloatingPanel 组件
|
||||
|
||||
```tsx
|
||||
import { FloatingPanel } from '@/components/terminal-dashboard/FloatingPanel'
|
||||
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel'
|
||||
|
||||
function CustomLayout() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<FloatingPanel
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title="问题"
|
||||
side="left"
|
||||
width={700}
|
||||
>
|
||||
<IssuePanel />
|
||||
</FloatingPanel>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 交互演示
|
||||
|
||||
### 布局预设演示
|
||||
|
||||
:::demo TerminalLayoutPresets
|
||||
# terminal-layout-presets.tsx
|
||||
/**
|
||||
* Terminal Layout Presets Demo
|
||||
* Interactive layout preset buttons
|
||||
*/
|
||||
export function TerminalLayoutPresets() {
|
||||
const [layout, setLayout] = React.useState('grid-2x2')
|
||||
|
||||
const layouts = {
|
||||
single: 'grid-cols-1 grid-rows-1',
|
||||
'split-h': 'grid-cols-2 grid-rows-1',
|
||||
'split-v': 'grid-cols-1 grid-rows-2',
|
||||
'grid-2x2': 'grid-cols-2 grid-rows-2',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Terminal Layout Presets</h3>
|
||||
<div className="flex gap-2">
|
||||
{Object.keys(layouts).map((preset) => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => setLayout(preset)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
layout === preset
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{preset.replace('-', ' ').toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-2 h-64 ${layouts[layout]}`}>
|
||||
{Array.from({ length: layout === 'single' ? 1 : layout.includes('2x') ? 4 : 2 }).map((_, i) => (
|
||||
<div key={i} className="bg-muted/20 border rounded p-4 font-mono text-xs">
|
||||
<div className="text-green-500">$ Terminal {i + 1}</div>
|
||||
<div className="text-muted-foreground mt-1">Ready for input...</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### 浮动面板演示
|
||||
|
||||
:::demo FloatingPanelsDemo
|
||||
# floating-panels-demo.tsx
|
||||
/**
|
||||
* Floating Panels Demo
|
||||
* Mutually exclusive overlay panels
|
||||
*/
|
||||
export function FloatingPanelsDemo() {
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
const panels = [
|
||||
{ id: 'issues', title: 'Issues + Queue', side: 'left', width: 700 },
|
||||
{ id: 'queue', title: 'Queue', side: 'right', width: 400 },
|
||||
{ id: 'inspector', title: 'Inspector', side: 'right', width: 360 },
|
||||
{ id: 'execution', title: 'Execution Monitor', side: 'right', width: 380 },
|
||||
{ id: 'scheduler', title: 'Scheduler', side: 'right', width: 340 },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="relative h-[500px] p-6 bg-background border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold">Floating Panels</h3>
|
||||
<div className="flex gap-2">
|
||||
{panels.map((panel) => (
|
||||
<button
|
||||
key={panel.id}
|
||||
onClick={() => setActivePanel(activePanel === panel.id ? null : panel.id)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
activePanel === panel.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{panel.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[380px] bg-muted/20 border rounded flex items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{activePanel ? `"${panels.find((p) => p.id === activePanel)?.title}" panel is open` : 'Click a button to open a floating panel'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Floating Panel Overlay */}
|
||||
{activePanel && (
|
||||
<div
|
||||
className={`absolute top-16 border rounded-lg shadow-lg bg-background ${
|
||||
panels.find((p) => p.id === activePanel)?.side === 'left' ? 'left-6' : 'right-6'
|
||||
}`}
|
||||
style={{ width: panels.find((p) => p.id === activePanel)?.width }}
|
||||
>
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium">{panels.find((p) => p.id === activePanel)?.title}</span>
|
||||
<button
|
||||
onClick={() => setActivePanel(null)}
|
||||
className="text-xs hover:bg-accent px-2 py-1 rounded"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>Item 1</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500"/>
|
||||
<span>Item 2</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-amber-500"/>
|
||||
<span>Item 3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### 可调整大小窗格演示
|
||||
|
||||
:::demo ResizablePanesDemo
|
||||
# resizable-panes-demo.tsx
|
||||
/**
|
||||
* Resizable Panes Demo
|
||||
* Simulates the Allotment resizable split behavior
|
||||
*/
|
||||
export function ResizablePanesDemo() {
|
||||
const [leftWidth, setLeftWidth] = React.useState(240)
|
||||
const [rightWidth, setRightWidth] = React.useState(280)
|
||||
const [isDragging, setIsDragging] = React.useState(null)
|
||||
|
||||
const handleDragStart = (side) => (e) => {
|
||||
setIsDragging(side)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleMouseMove = (e) => {
|
||||
if (isDragging === 'left') {
|
||||
setLeftWidth(Math.max(180, Math.min(320, e.clientX)))
|
||||
} else if (isDragging === 'right') {
|
||||
setRightWidth(Math.max(200, Math.min(400, window.innerWidth - e.clientX)))
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseUp = () => setIsDragging(null)
|
||||
|
||||
if (isDragging) {
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
return (
|
||||
<div className="h-[400px] flex bg-background border rounded-lg overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
<div style={{ width: leftWidth }} className="border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['Active Sessions', 'Completed'].map((g) => (
|
||||
<div key={g} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{g}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Left Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('left')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'left' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 bg-muted/20 flex items-center justify-center">
|
||||
<span className="text-sm text-muted-foreground">Terminal Grid Area</span>
|
||||
</div>
|
||||
|
||||
{/* Right Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('right')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'right' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div style={{ width: rightWidth }} className="border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['src/', 'docs/', 'tests/'].map((f) => (
|
||||
<div key={f} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{f}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 配置
|
||||
|
||||
### 功能标志
|
||||
|
||||
| 标志 | 控制 |
|
||||
|------|------|
|
||||
| `dashboardQueuePanelEnabled` | 队列面板可见性 |
|
||||
| `dashboardInspectorEnabled` | 检查器面板可见性 |
|
||||
| `dashboardExecutionMonitorEnabled` | 执行监控器面板可见性 |
|
||||
|
||||
### 布局预设
|
||||
|
||||
| 预设 | 布局 |
|
||||
|------|------|
|
||||
| **单格** | 一个终端窗格 |
|
||||
| **水平分割** | 两个窗格并排 |
|
||||
| **垂直分割** | 两个窗格垂直堆叠 |
|
||||
| **2x2 网格** | 2x2 网格中的四个窗格 |
|
||||
|
||||
### 面板类型
|
||||
|
||||
| 面板 | 内容 | 位置 | 功能标志 |
|
||||
|------|------|------|----------|
|
||||
| **问题+队列** | 组合的问题面板 + 队列列表列 | 左侧(叠加) | - |
|
||||
| **队列** | 完整的队列管理面板 | 右侧(叠加) | `dashboardQueuePanelEnabled` |
|
||||
| **检查器** | 关联链检查器 | 右侧(叠加) | `dashboardInspectorEnabled` |
|
||||
| **执行监控器** | 实时执行跟踪 | 右侧(叠加) | `dashboardExecutionMonitorEnabled` |
|
||||
| **调度器** | 队列调度器控制 | 右侧(叠加) | - |
|
||||
|
||||
---
|
||||
|
||||
## 可访问性
|
||||
|
||||
- **键盘导航**:
|
||||
- <kbd>Tab</kbd> - 在工具栏按钮之间导航
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - 激活工具栏按钮
|
||||
- <kbd>Escape</kbd> - 关闭浮动面板
|
||||
- <kbd>F11</kbd> - 切换全屏模式
|
||||
|
||||
- **ARIA 属性**:
|
||||
- 工具栏按钮上的 `aria-label`
|
||||
- 侧边栏切换上的 `aria-expanded`
|
||||
- 非活动浮动面板上的 `aria-hidden`
|
||||
- 浮动面板上的 `role="dialog"`
|
||||
|
||||
- **屏幕阅读器支持**:
|
||||
- 切换面板时宣布面板状态
|
||||
- 布局更改被宣布
|
||||
- 面板打开/关闭时的焦点管理
|
||||
|
||||
---
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [编排器](/features/orchestrator) - 可视化工作流编辑器
|
||||
- [会话](/features/sessions) - 会话管理
|
||||
- [问题中心](/features/issue-hub) - 问题、队列、发现
|
||||
- [资源管理器](/features/explorer) - 文件资源管理器
|
||||
- [队列](/features/queue) - 队列管理独立页面
|
||||
53
docs/zh-CN/index.md
Normal file
53
docs/zh-CN/index.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
layout: home
|
||||
title: Claude Code Workflow
|
||||
titleTemplate: Claude Code Workflow 文档
|
||||
|
||||
hero:
|
||||
name: Claude Code Workflow
|
||||
text: 高级 AI 驱动开发环境
|
||||
tagline: 提升您的编码工作流程,实现智能化开发
|
||||
image:
|
||||
src: /logo.svg
|
||||
alt: Claude Code Workflow
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /zh-CN/guide/ch01-what-is-claude-dms3
|
||||
- theme: alt
|
||||
text: 查看功能
|
||||
link: /zh-CN/features/spec
|
||||
|
||||
features:
|
||||
- title: 🤖 智能编排
|
||||
details: Claude AI 驱动的任务编排,自动拆解复杂项目为可执行的步骤。
|
||||
- title: ⚡ 内置技能
|
||||
details: 27+ 个内置技能,涵盖代码审查、测试、重构等常见开发场景。
|
||||
- title: 🔄 工作流系统
|
||||
details: 四级工作流体系,从基础任务到复杂代理团队,灵活适应各种需求。
|
||||
- title: 💾 持久化记忆
|
||||
details: 跨会话的知识积累和上下文管理,让 AI 学习您的编码偏好。
|
||||
- title: 🎨 可视化界面
|
||||
details: 丰富的 Web 界面,包括仪表板、终端监控、问题追踪等实用工具。
|
||||
- title: 🔌 MCP 集成
|
||||
details: 支持 Model Context Protocol,轻松扩展 AI 能力和工具集。
|
||||
---
|
||||
|
||||
## 什么是 Claude Code Workflow?
|
||||
|
||||
Claude Code Workflow (CCW) 是一个基于 Claude AI 的智能开发环境,通过工作流编排、代理协作和持久化记忆,帮助开发者更高效地完成编码任务。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **智能任务拆解**: 将复杂需求自动分解为可执行的子任务
|
||||
- **代理协作系统**: 多个专业代理协同工作,覆盖代码、测试、文档等各个环节
|
||||
- **上下文记忆**: 跨会话保持项目知识和编码决策
|
||||
- **实时监控**: 终端流监控、问题追踪等可视化工具
|
||||
- **CLI 工具集成**: 灵活的命令行工具,支持多种 AI 模型
|
||||
|
||||
## 快速链接
|
||||
|
||||
- [安装指南](/zh-CN/guide/installation)
|
||||
- [第一个工作流](/zh-CN/guide/first-workflow)
|
||||
- [核心功能](/zh-CN/features/spec)
|
||||
- [组件文档](/zh-CN/components/)
|
||||
Reference in New Issue
Block a user