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:
catlog22
2026-03-01 10:52:46 +08:00
parent a753327acc
commit 2fb93d20e0
34 changed files with 6908 additions and 257 deletions

View File

@@ -0,0 +1 @@
.ace-tool/

View File

@@ -1,5 +1,7 @@
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
import { withMermaid } from 'vitepress-plugin-mermaid' import { withMermaid } from 'vitepress-plugin-mermaid'
import { transformDemoBlocks } from './theme/markdownTransform'
import path from 'path'
const repoName = process.env.GITHUB_REPOSITORY?.split('/')[1] const repoName = process.env.GITHUB_REPOSITORY?.split('/')[1]
const isUserOrOrgSite = Boolean(repoName && repoName.endsWith('.github.io')) const isUserOrOrgSite = Boolean(repoName && repoName.endsWith('.github.io'))
@@ -11,7 +13,7 @@ const base =
export default withMermaid(defineConfig({ export default withMermaid(defineConfig({
title: 'Claude Code Workflow Documentation', title: 'Claude Code Workflow Documentation',
description: 'Claude Code Workspace - Advanced AI-Powered Development Environment', description: 'Claude Code Workspace - Advanced AI-Powered Development Environment',
lang: 'zh-CN', lang: 'en-US',
base, base,
// Ignore dead links for incomplete docs // Ignore dead links for incomplete docs
@@ -44,12 +46,22 @@ export default withMermaid(defineConfig({
// Vite build/dev optimizations // Vite build/dev optimizations
vite: { 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: { optimizeDeps: {
include: ['flexsearch'] include: ['flexsearch', 'react', 'react-dom']
}, },
build: { build: {
target: 'es2019', target: 'es2019',
cssCodeSplit: true 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: 'Guide', link: '/guide/ch01-what-is-claude-dms3' },
{ text: 'Commands', link: '/commands/claude/' }, { text: 'Commands', link: '/commands/claude/' },
{ text: 'Skills', link: '/skills/' }, { text: 'Skills', link: '/skills/' },
{ text: 'Features', link: '/features/spec' }, { text: 'Features', link: '/features/spec' }
{
text: 'Languages',
items: [
{ text: '简体中文', link: '/zh/guide/ch01-what-is-claude-dms3' }
]
}
], ],
// Sidebar - 优化导航结构,增加二级标题和归类 // 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/': [ '/mcp/': [
{ {
text: '🔗 MCP Tools', text: '🔗 MCP Tools',
@@ -276,6 +293,14 @@ export default withMermaid(defineConfig({
], ],
config: (md) => { config: (md) => {
// Add markdown-it plugins if needed // 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/guide/ch01-what-is-claude-dms3' },
{ text: '命令', link: '/zh/commands/claude/' }, { text: '命令', link: '/zh/commands/claude/' },
{ text: '技能', link: '/zh/skills/claude-index' }, { text: '技能', link: '/zh/skills/claude-index' },
{ text: '功能', link: '/zh/features/spec' }, { text: '功能', link: '/zh/features/spec' }
{
text: '语言',
items: [
{ text: 'English', link: '/guide/ch01-what-is-claude-dms3' }
]
}
], ],
sidebar: { sidebar: {
'/zh/guide/': [ '/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' }
]
}
]
}
}
} }
} }
})) }))

View 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 |

View 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>
)
}

View 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>

View 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>

View 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>

View 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>

View File

@@ -7,9 +7,16 @@ import Breadcrumb from './components/Breadcrumb.vue'
import PageToc from './components/PageToc.vue' import PageToc from './components/PageToc.vue'
import ProfessionalHome from './components/ProfessionalHome.vue' import ProfessionalHome from './components/ProfessionalHome.vue'
import Layout from './layouts/Layout.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/variables.css'
import './styles/custom.css' import './styles/custom.css'
import './styles/mobile.css' import './styles/mobile.css'
import './styles/demo.css'
export default { export default {
extends: DefaultTheme, extends: DefaultTheme,
@@ -23,5 +30,11 @@ export default {
app.component('Breadcrumb', Breadcrumb) app.component('Breadcrumb', Breadcrumb)
app.component('PageToc', PageToc) app.component('PageToc', PageToc)
app.component('ProfessionalHome', ProfessionalHome) 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)
} }
} }

View File

@@ -3,6 +3,7 @@ import DefaultTheme from 'vitepress/theme'
import { onBeforeUnmount, onMounted } from 'vue' import { onBeforeUnmount, onMounted } from 'vue'
import { useDynamicIcon } from '../composables/useDynamicIcon' import { useDynamicIcon } from '../composables/useDynamicIcon'
import ThemeLogo from '../components/ThemeLogo.vue' import ThemeLogo from '../components/ThemeLogo.vue'
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
let mediaQuery: MediaQueryList | null = null let mediaQuery: MediaQueryList | null = null
let systemThemeChangeHandler: (() => void) | null = null let systemThemeChangeHandler: (() => void) | null = null
@@ -84,6 +85,7 @@ onBeforeUnmount(() => {
<DocSearch /> <DocSearch />
<DarkModeToggle /> <DarkModeToggle />
<ThemeSwitcher /> <ThemeSwitcher />
<LanguageSwitcher />
</div> </div>
</template> </template>
</DefaultTheme.Layout> </DefaultTheme.Layout>

View 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)
}
}
}

View 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
View 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
View 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)

View 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
View 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)

View 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
View 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)

View 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)

View File

@@ -1,7 +1,6 @@
# Dashboard # Dashboard
## One-Liner ## 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.** **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` **Location**: `ccw/frontend/src/pages/HomePage.tsx`
**Purpose**: Dashboard home page providing project overview, statistics, workflow status, and recent activity monitoring. **Purpose**: Dashboard home page providing project overview, statistics, workflow status, and recent activity monitoring.
**Access**: Navigation → Dashboard (default home page) **Access**: Navigation → Dashboard (default home page at `/`)
### Layout
**Layout**:
``` ```
+--------------------------------------------------------------------------+ +--------------------------------------------------------------------------+
| Dashboard Header (title + refresh) | | 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 ## Core Features
| Feature | Description | | Feature | Description |
@@ -68,85 +209,399 @@
--- ---
## Usage Guide ## Component Hierarchy
### Basic Workflow ```
HomePage
1. **View Project Overview**: Check the project info banner for tech stack and development index ├── DashboardHeader
2. **Monitor Statistics**: Review mini stat cards for current project metrics and trends │ ├── Title
3. **Track Workflow Status**: View pie chart for session status distribution │ └── Refresh Action Button
4. **Browse Active Sessions**: Use session carousel to see task details and progress ├── WorkflowTaskWidget
5. **Access Recent Work**: Switch between All Tasks/Workflow/Lite Tasks tabs to find specific sessions │ ├── ProjectInfoBanner (expandable)
│ │ ├── Project Name & Description
### Key Interactions │ │ ├── Tech Stack Badges
│ │ ├── Quick Stats Cards
| Interaction | How to Use | │ │ ├── Index Status Indicator
|-------------|------------| │ │ ├── Architecture Section
| **Expand Project Details** | Click the chevron button in the project banner to show architecture, components, patterns | │ │ ├── Key Components
| **Navigate Sessions** | Click arrow buttons or wait for auto-rotation (5s interval) in the carousel | │ │ └── Design Patterns
| **View Session Details** | Click on any session card to navigate to session detail page | │ ├── Stats Section
| **Filter Recent Tasks** | Click tab buttons to filter by task type (All/Workflow/Lite) | │ │ └── MiniStatCard (6 cards with Sparkline)
| **Refresh Dashboard** | Click the refresh button in the header to reload all data | │ ├── WorkflowStatusChart
│ │ └── Pie Chart with Legend
### Index Status Indicator │ └── SessionCarousel
│ ├── Navigation Arrows
| Status | Icon | Meaning | │ └── Session Cards (Task List)
|--------|------|---------| └── RecentSessionsWidget
| **Building** | Pulsing blue dot | Code index is being built/updated | ├── Tab Navigation (All | Workflow | Lite)
| **Completed** | Green dot | Index is up-to-date | ├── Task Grid
| **Idle** | Gray dot | Index status is unknown/idle | │ └── TaskItemCard
| **Failed** | Red dot | Index build failed | └── Loading/Empty States
```
--- ---
## Components Reference ## Props API
### Main Components ### HomePage Component
| Component | Location | Purpose | | Prop | Type | Default | Description |
|-----------|----------|---------| |------|------|---------|-------------|
| `DashboardHeader` | `@/components/dashboard/DashboardHeader.tsx` | Page header with title and refresh action | | - | - | - | This page component accepts no props (data fetched via hooks) |
| `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 |
### State Management ### WorkflowTaskWidget
- **Local state**: | Prop | Type | Default | Description |
- `hasError` - Error tracking for critical failures |------|------|---------|-------------|
- `projectExpanded` - Project info banner expansion state | `className` | `string` | `undefined` | Additional CSS classes for styling |
- `currentSessionIndex` - Active session in carousel
- `activeTab` - Recent sessions widget filter tab
- **Custom hooks**: ### RecentSessionsWidget
- `useWorkflowStatusCounts` - Session status distribution data
- `useDashboardStats` - Statistics with auto-refresh (60s) | Prop | Type | Default | Description |
- `useProjectOverview` - Project information and tech stack |------|------|---------|-------------|
- `useIndexStatus` - Real-time index status (30s refresh) | `className` | `string` | `undefined` | Additional CSS classes |
- `useSessions` - Active sessions data | `maxItems` | `number` | `6` | Maximum number of items to display |
- `useLiteTasks` - Lite tasks data for recent widget
--- ---
## 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 | // The dashboard is automatically rendered at the root route (/)
|-----------|----------| // No props needed - data is fetched via hooks
| Dashboard stats | 60 seconds | ```
| Index status | 30 seconds |
| Discovery sessions | 3 seconds (on discovery page) | ### 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 ## Related Links
- [Sessions](/features/sessions) - View and manage all sessions - [Sessions](/features/sessions) - View and manage all sessions
- [Terminal Dashboard](/features/terminal) - Terminal-first monitoring interface
- [Queue](/features/queue) - Issue execution queue management - [Queue](/features/queue) - Issue execution queue management
- [Discovery](/features/discovery) - Discovery session tracking
- [Memory](/features/memory) - Persistent memory management - [Memory](/features/memory) - Persistent memory management
- [System Settings](/features/system-settings) - Global application settings - [Settings](/features/settings) - Global application settings

View File

@@ -1,8 +1,7 @@
# Queue # Queue Management
## One-Liner ## One-Liner
**The Queue Management page provides centralized control over issue execution queues with scheduler controls, status monitoring, and session pool management.**
**The Queue page manages issue execution queues, displaying grouped tasks and solutions with conflict detection and merge capabilities.**
--- ---
@@ -11,153 +10,627 @@
| Pain Point | Current State | Queue Solution | | Pain Point | Current State | Queue Solution |
|------------|---------------|----------------| |------------|---------------|----------------|
| **Disorganized execution** | No unified task queue | Centralized queue with grouped items | | **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 | | **Unknown scheduler status** | Can't tell if scheduler is running | Real-time status indicator (idle/running/paused) |
| **Manual queue management** | No way to control execution | Activate/deactivate/delete with actions | | **No execution control** | Can't start/stop queue processing | Start/Pause/Stop controls with confirmation |
| **Duplicate handling** | Confusing duplicate items | Merge queues functionality | | **Concurrency limits** | Too many simultaneous sessions | Configurable max concurrent sessions |
| **No visibility** | Don't know what's queued | Stats cards with items/groups/tasks/solutions counts | | **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 **Access**: Navigation → Issues → Queue tab OR Terminal Dashboard → Queue floating panel
### Layout
**Layout**:
``` ```
+--------------------------------------------------------------------------+ +--------------------------------------------------------------------------+
| Queue Title [Refresh] | | Queue Panel Header |
+--------------------------------------------------------------------------+ +--------------------------------------------------------------------------+
| Stats Cards | | Scheduler Status Bar |
| +-------------+ +-------------+ +-------------+ +-------------+ | | +----------------+ +-------------+ +-------------------------------+ |
| | Total Items | | Groups | | Tasks | | Solutions | | | | 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 | | | | QueueItemRow (status, issue_id, session_key, actions) | |
| | - Queue info | | | | - Status icon (pending/executing/completed/blocked/failed) | |
| | - Grouped items preview | | | | - Issue ID / Item ID display | |
| | - Action buttons (Activate/Deactivate/Delete/Merge) | | | | - 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 ## Core Features
| Feature | Description | | Feature | Description |
|---------|-------------| |---------|-------------|
| **Stats Cards** | Four metric cards showing total items, groups, tasks, and solutions counts | | **Scheduler Status** | Real-time status indicator (idle/running/paused) with visual badge |
| **Conflicts Warning** | Banner alert when conflicts exist, showing count and description | | **Progress Tracking** | Progress bar showing overall queue completion percentage |
| **Queue Card** | Displays queue information with grouped items preview and action buttons | | **Start/Pause/Stop Controls** | Control queue execution with confirmation dialog for stop action |
| **Activate/Deactivate** | Toggle queue active state for execution control | | **Concurrency Display** | Shows current active sessions vs max concurrent sessions |
| **Delete Queue** | Remove queue with confirmation dialog | | **Queue Items List** | Scrollable list of all queue items with status, issue ID, and session binding |
| **Merge Queues** | Combine multiple queues (if multiple exist) | | **Status Icons** | Visual indicators for item status (pending/executing/completed/blocked/failed) |
| **Status Footer** | Visual indicator showing if queue is ready (active) or pending (inactive/conflicts) | | **Session Pool** | Overview of active, idle, and total sessions in the pool |
| **Loading State** | Skeleton placeholders during data fetch | | **Config Panel** | Adjust max concurrent sessions and timeout settings |
| **Empty State** | Friendly message when no queue exists | | **Empty State** | Friendly message when queue is empty with instructions to add items |
--- ---
## Usage Guide ## Component Hierarchy
### Basic Workflow ```
QueuePage (legacy) / QueuePanel (current)
1. **Check Queue Status**: Review stats cards and status footer to understand queue state ├── QueuePanelHeader
2. **Review Conflicts**: If conflicts warning is shown, resolve conflicts before activation │ ├── Title
3. **Activate Queue**: Click "Activate" to enable queue for execution (only if no conflicts) │ └── Tab Switcher (Queue | Orchestrator)
4. **Deactivate Queue**: Click "Deactivate" to pause execution ├── SchedulerBar (inline in QueueListColumn)
5. **Delete Queue**: Click "Delete" to remove the queue (requires confirmation) │ ├── Status Badge
6. **Merge Queues**: Use merge action to combine multiple queues when applicable │ ├── Progress + Concurrency
│ └── Control Buttons (Play/Pause/Stop)
### Key Interactions ├── QueueItemsList
│ └── QueueItemRow (repeating)
| Interaction | How to Use | │ ├── Status Icon
|-------------|------------| │ ├── Issue ID / Item ID
| **Refresh queue** | Click the refresh button to reload queue data | │ ├── Session Binding
| **Activate queue** | Click the Activate button on the queue card | │ └── Progress (for executing items)
| **Deactivate queue** | Click the Deactivate button to pause the queue | └── SchedulerPanel (standalone)
| **Delete queue** | Click the Delete button, confirm in the dialog | ├── Status Section
| **Merge queues** | Select source and target queues, click merge | ├── Progress Bar
| **View status** | Check the status footer for ready/pending indication | ├── Control Buttons
├── Config Section
### Queue Status │ ├── Max Concurrent Sessions
│ ├── Session Idle Timeout
| Status | Condition | Appearance | │ └── Resume Key Binding Timeout
|--------|-----------|------------| └── Session Pool Overview
| **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
--- ---
## Components Reference ## Props API
### Main Components ### QueuePanel
| Component | Location | Purpose | | Prop | Type | Default | Description |
|-----------|----------|---------| |------|------|---------|-------------|
| `QueuePage` | `@/pages/QueuePage.tsx` | Main queue management page | | `embedded` | `boolean` | `false` | Whether panel is embedded in another component |
| `QueueCard` | `@/components/issue/queue/QueueCard.tsx` | Queue display with actions |
| `QueuePageSkeleton` | (internal to QueuePage) | Loading placeholder |
| `QueueEmptyState` | (internal to QueuePage) | Empty state display |
### State Management ### SchedulerPanel
- **Local state**: | Prop | Type | Default | Description |
- None (all data from hooks) |------|------|---------|-------------|
| - | - | - | This component accepts no props (all data from Zustand store) |
- **Custom hooks**: ### QueueListColumn
- `useIssueQueue` - Queue data fetching
- `useQueueMutations` - Queue operations (activate, deactivate, delete, merge)
### 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 | ## State Management
| `isDeactivating` | Disable deactivate button during deactivation |
| `isDeleting` | Disable delete button during deletion | ### Zustand Stores
| `isMerging` | Disable merge button during merge operation |
| 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 ## 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 ```typescript
interface QueueData { interface QueueItem {
tasks: Task[]; item_id: string;
solutions: Solution[]; issue_id?: string;
conflicts: Conflict[]; sessionKey?: string;
grouped_items: Record<string, GroupedItem>; 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 ## Related Links
- [Issue Hub](/features/issue-hub) - Unified issues, queue, and discovery management - [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 - [Discovery](/features/discovery) - Discovery session tracking
- [Issues Panel](/features/issue-hub) - Issue list and GitHub sync - [Sessions](/features/sessions) - Session management and details

View File

@@ -1,7 +1,6 @@
# Terminal Dashboard # Terminal Dashboard
## One-Liner ## One-Liner
**The Terminal Dashboard provides a terminal-first workspace with resizable panes, floating panels, and integrated tools for session monitoring and orchestration.** **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` **Location**: `ccw/frontend/src/pages/TerminalDashboardPage.tsx`
**Purpose**: Terminal-first layout for multi-terminal session management with integrated tools and resizable panels. **Purpose**: Terminal-first layout for multi-terminal session management with integrated tools and resizable panels.
**Access**: Navigation → Terminal Dashboard **Access**: Navigation → Terminal Dashboard (`/terminal-dashboard`)
### Layout
**Layout**:
``` ```
+--------------------------------------------------------------------------+ +--------------------------------------------------------------------------+
| Dashboard Toolbar (panel toggles, layout presets, fullscreen) | | 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 ## Core Features
| Feature | Description | | 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 | ### TerminalDashboardPage
|-------------|------------|
| **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 |
### Panel Types | Prop | Type | Default | Description |
|------|------|---------|-------------|
| - | - | - | This page component accepts no props (state managed via hooks and Zustand stores) |
| Panel | Content | Position | ### DashboardToolbar
|-------|---------|----------|
| **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) | | Prop | Type | Default | Description |
| **Queue** | Full queue management panel | Right (overlay, feature flag) | |------|------|---------|-------------|
| **Inspector** | Association chain inspector | Right (overlay, feature flag) | | `activePanel` | `PanelId \| null` | `null` | Currently active floating panel |
| **Execution Monitor** | Real-time execution tracking | Right (overlay, feature flag) | | `onTogglePanel` | `(panelId: PanelId) => void` | - | Callback to toggle panel visibility |
| **Scheduler** | Queue scheduler controls | Right (overlay) | | `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 ### Layout Presets
@@ -106,59 +590,36 @@
| **Split-V** | Two panes stacked vertically | | **Split-V** | Two panes stacked vertically |
| **Grid-2x2** | Four panes in 2x2 grid | | **Grid-2x2** | Four panes in 2x2 grid |
--- ### Panel Types
## Components Reference | Panel | Content | Position | Feature Flag |
|-------|---------|----------|--------------|
### Main Components | **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) | - |
| **Queue** | Full queue management panel | Right (overlay) | `dashboardQueuePanelEnabled` |
| Component | Location | Purpose | | **Inspector** | Association chain inspector | Right (overlay) | `dashboardInspectorEnabled` |
|-----------|----------|---------| | **Execution Monitor** | Real-time execution tracking | Right (overlay) | `dashboardExecutionMonitorEnabled` |
| `TerminalDashboardPage` | `@/pages/TerminalDashboardPage.tsx` | Main terminal dashboard page | | **Scheduler** | Queue scheduler controls | Right (overlay) | - |
| `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
--- ---
## 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 - **ARIA Attributes**:
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler'; - `aria-label` on toolbar buttons
``` - `aria-expanded` on sidebar toggles
- `aria-hidden` on inactive floating panels
- `role="dialog"` on floating panels
### Feature Flags - **Screen Reader Support**:
- Panel state announced when toggled
| Flag | Controls | - Layout changes announced
|------|----------| - Focus management when panels open/close
| `dashboardQueuePanelEnabled` | Queue panel visibility |
| `dashboardInspectorEnabled` | Inspector panel visibility |
| `dashboardExecutionMonitorEnabled` | Execution Monitor panel visibility |
--- ---
@@ -168,3 +629,4 @@ type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
- [Sessions](/features/sessions) - Session management - [Sessions](/features/sessions) - Session management
- [Issue Hub](/features/issue-hub) - Issues, queue, discovery - [Issue Hub](/features/issue-hub) - Issues, queue, discovery
- [Explorer](/features/explorer) - File explorer - [Explorer](/features/explorer) - File explorer
- [Queue](/features/queue) - Queue management standalone page

View File

@@ -19,7 +19,12 @@
"vue": "^3.4.0" "vue": "^3.4.0"
}, },
"dependencies": { "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": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"

View 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 组件通过 CVAclass-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

View 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)

View 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)

View 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)

View 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)

View 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)

View 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)

View 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可展开
│ │ ├── 项目名称和描述
│ │ ├── 技术栈徽章
│ │ ├── 快速统计卡片
│ │ ├── 索引状态指示器
│ │ ├── 架构部分
│ │ ├── 关键组件
│ │ └── 设计模式
│ ├── 统计部分
│ │ └── MiniStatCard6 个卡片,带迷你趋势图)
│ ├── 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) - 全局应用设置

View 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) - 会话管理和详情

View 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
View 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/)