mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +08:00
Add Chinese documentation for custom skills development and reference guide
- Created a new document for custom skills development (`custom.md`) detailing the structure, creation, implementation, and best practices for developing custom CCW skills. - Added an index document (`index.md`) summarizing all built-in skills, their categories, and usage examples. - Introduced a reference guide (`reference.md`) providing a quick reference for all 33 built-in CCW skills, including triggers and purposes.
This commit is contained in:
@@ -80,7 +80,8 @@ export default withMermaid(defineConfig({
|
||||
{ text: 'Guide', link: '/guide/ch01-what-is-claude-dms3' },
|
||||
{ text: 'Commands', link: '/commands/claude/' },
|
||||
{ text: 'Skills', link: '/skills/' },
|
||||
{ text: 'Features', link: '/features/spec' }
|
||||
{ text: 'Features', link: '/features/spec' },
|
||||
{ text: 'Components', link: '/components/' }
|
||||
],
|
||||
|
||||
// Sidebar - 优化导航结构,增加二级标题和归类
|
||||
@@ -134,6 +135,20 @@ export default withMermaid(defineConfig({
|
||||
}
|
||||
],
|
||||
'/skills/': [
|
||||
{
|
||||
text: 'Overview',
|
||||
collapsible: false,
|
||||
items: [
|
||||
{ text: 'Skills Guide', link: '/skills/' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '📚 Conventions',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Naming Conventions', link: '/skills/naming-conventions' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '⚡ Claude Skills',
|
||||
collapsible: true,
|
||||
@@ -192,9 +207,13 @@ export default withMermaid(defineConfig({
|
||||
text: 'UI Components',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Overview', link: '/components/index' },
|
||||
{ text: 'Button', link: '/components/ui/button' },
|
||||
{ text: 'Card', link: '/components/ui/card' },
|
||||
{ text: 'Input', link: '/components/ui/input' }
|
||||
{ text: 'Input', link: '/components/ui/input' },
|
||||
{ text: 'Select', link: '/components/ui/select' },
|
||||
{ text: 'Checkbox', link: '/components/ui/checkbox' },
|
||||
{ text: 'Badge', link: '/components/ui/badge' }
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -292,11 +311,10 @@ export default withMermaid(defineConfig({
|
||||
'mermaid'
|
||||
],
|
||||
config: (md) => {
|
||||
// Add markdown-it plugins if needed
|
||||
// Custom demo block transform is handled by markdownTransform.ts
|
||||
md.core.ruler.before('block', 'demo-blocks', (state) => {
|
||||
const src = state.src
|
||||
const transformed = transformDemoBlocks(src, { path: '' })
|
||||
const filePath = (state as any).path || ''
|
||||
const transformed = transformDemoBlocks(src, { path: filePath })
|
||||
if (transformed !== src) {
|
||||
state.src = transformed
|
||||
}
|
||||
@@ -376,6 +394,20 @@ export default withMermaid(defineConfig({
|
||||
}
|
||||
],
|
||||
'/zh/skills/': [
|
||||
{
|
||||
text: '概述',
|
||||
collapsible: false,
|
||||
items: [
|
||||
{ text: '技能指南', link: '/zh/skills/' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '📚 规范',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '命名规范', link: '/zh/skills/naming-conventions' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '⚡ Claude Skills',
|
||||
collapsible: true,
|
||||
@@ -485,6 +517,53 @@ export default withMermaid(defineConfig({
|
||||
]
|
||||
}
|
||||
],
|
||||
'/zh-CN/skills/': [
|
||||
{
|
||||
text: '概述',
|
||||
collapsible: false,
|
||||
items: [
|
||||
{ text: '技能指南', link: '/zh-CN/skills/' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '📚 规范',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '命名规范', link: '/zh-CN/skills/naming-conventions' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '⚡ Claude Skills',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '概述', link: '/zh-CN/skills/claude-index' },
|
||||
{ text: '协作', link: '/zh-CN/skills/claude-collaboration' },
|
||||
{ text: '工作流', link: '/zh-CN/skills/claude-workflow' },
|
||||
{ text: '记忆', link: '/zh-CN/skills/claude-memory' },
|
||||
{ text: '审查', link: '/zh-CN/skills/claude-review' },
|
||||
{ text: '元技能', link: '/zh-CN/skills/claude-meta' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '🔧 Codex Skills',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '概述', link: '/zh-CN/skills/codex-index' },
|
||||
{ text: '生命周期', link: '/zh-CN/skills/codex-lifecycle' },
|
||||
{ text: '工作流', link: '/zh-CN/skills/codex-workflow' },
|
||||
{ text: '专项', link: '/zh-CN/skills/codex-specialized' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '🎨 自定义技能',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '概述', link: '/zh-CN/skills/custom' },
|
||||
{ text: '核心技能', link: '/zh-CN/skills/core-skills' },
|
||||
{ text: '参考', link: '/zh-CN/skills/reference' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/zh-CN/features/': [
|
||||
{
|
||||
text: '⚙️ 核心功能',
|
||||
@@ -494,6 +573,8 @@ export default withMermaid(defineConfig({
|
||||
{ text: 'Memory 记忆系统', link: '/zh-CN/features/memory' },
|
||||
{ text: 'CLI 调用', link: '/zh-CN/features/cli' },
|
||||
{ text: 'Dashboard 面板', link: '/zh-CN/features/dashboard' },
|
||||
{ text: 'Terminal 终端监控', link: '/zh-CN/features/terminal' },
|
||||
{ text: 'Queue 队列管理', link: '/zh-CN/features/queue' },
|
||||
{ text: 'CodexLens', link: '/zh-CN/features/codexlens' }
|
||||
]
|
||||
}
|
||||
@@ -503,9 +584,13 @@ export default withMermaid(defineConfig({
|
||||
text: 'UI 组件',
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: '概述', link: '/zh-CN/components/index' },
|
||||
{ text: 'Button 按钮', link: '/zh-CN/components/ui/button' },
|
||||
{ text: 'Card 卡片', link: '/zh-CN/components/ui/card' },
|
||||
{ text: 'Input 输入框', link: '/zh-CN/components/ui/input' }
|
||||
{ text: 'Input 输入框', link: '/zh-CN/components/ui/input' },
|
||||
{ text: 'Select 选择器', link: '/zh-CN/components/ui/select' },
|
||||
{ text: 'Checkbox 复选框', link: '/zh-CN/components/ui/checkbox' },
|
||||
{ text: 'Badge 徽标', link: '/zh-CN/components/ui/badge' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
326
docs/.vitepress/demos/ComponentGallery.tsx
Normal file
326
docs/.vitepress/demos/ComponentGallery.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* Component Gallery Demo
|
||||
* Interactive showcase of all UI components
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function ComponentGallery() {
|
||||
const [selectedCategory, setSelectedCategory] = useState('all')
|
||||
const [buttonVariant, setButtonVariant] = useState('default')
|
||||
const [switchState, setSwitchState] = useState(false)
|
||||
const [checkboxState, setCheckboxState] = useState(false)
|
||||
const [selectedTab, setSelectedTab] = 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 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>
|
||||
)
|
||||
}
|
||||
326
docs/.vitepress/demos/ComponentGalleryZh.tsx
Normal file
326
docs/.vitepress/demos/ComponentGalleryZh.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* 组件库展示演示 (中文版)
|
||||
* 所有 UI 组件的交互式展示
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function ComponentGalleryZh() {
|
||||
const [selectedCategory, setSelectedCategory] = useState('all')
|
||||
const [buttonVariant, setButtonVariant] = useState('default')
|
||||
const [switchState, setSwitchState] = useState(false)
|
||||
const [checkboxState, setCheckboxState] = useState(false)
|
||||
const [selectedTab, setSelectedTab] = useState('variants')
|
||||
|
||||
const categories = [
|
||||
{ id: 'all', label: '全部组件' },
|
||||
{ id: 'buttons', label: '按钮' },
|
||||
{ id: 'forms', label: '表单' },
|
||||
{ id: 'feedback', label: '反馈' },
|
||||
{ id: 'navigation', label: '导航' },
|
||||
{ id: 'overlays', label: '叠加层' },
|
||||
]
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
137
docs/.vitepress/demos/DashboardOverview.tsx
Normal file
137
docs/.vitepress/demos/DashboardOverview.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Dashboard Overview Demo
|
||||
* Shows the main dashboard layout with widgets
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default 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>
|
||||
)
|
||||
}
|
||||
82
docs/.vitepress/demos/FloatingPanelsDemo.tsx
Normal file
82
docs/.vitepress/demos/FloatingPanelsDemo.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Floating Panels Demo
|
||||
* Mutually exclusive overlay panels
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function FloatingPanelsDemo() {
|
||||
const [activePanel, setActivePanel] = useState<string | null>(null)
|
||||
|
||||
const panels = [
|
||||
{ id: 'issues', title: 'Issues + Queue', side: 'left', width: 400 },
|
||||
{ id: 'queue', title: 'Queue', side: 'right', width: 320 },
|
||||
{ id: 'inspector', title: 'Inspector', side: 'right', width: 280 },
|
||||
{ id: 'execution', title: 'Execution Monitor', side: 'right', width: 300 },
|
||||
{ id: 'scheduler', title: 'Scheduler', side: 'right', width: 260 },
|
||||
]
|
||||
|
||||
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 flex-wrap">
|
||||
{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>
|
||||
)
|
||||
}
|
||||
54
docs/.vitepress/demos/MiniStatCards.tsx
Normal file
54
docs/.vitepress/demos/MiniStatCards.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Mini Stat Cards Demo
|
||||
* Individual statistics cards with sparkline trends
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default 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>
|
||||
)
|
||||
}
|
||||
66
docs/.vitepress/demos/ProjectInfoBanner.tsx
Normal file
66
docs/.vitepress/demos/ProjectInfoBanner.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Project Info Banner Demo
|
||||
* Expandable project information with tech stack
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function ProjectInfoBanner() {
|
||||
const [expanded, setExpanded] = 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>
|
||||
)
|
||||
}
|
||||
60
docs/.vitepress/demos/QueueItemStatusDemo.tsx
Normal file
60
docs/.vitepress/demos/QueueItemStatusDemo.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Queue Item Status Demo
|
||||
* Shows all possible queue item states
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default 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 as keyof typeof statusConfig]
|
||||
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>
|
||||
)
|
||||
}
|
||||
164
docs/.vitepress/demos/QueueManagementDemo.tsx
Normal file
164
docs/.vitepress/demos/QueueManagementDemo.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Queue Management Demo
|
||||
* Shows scheduler controls and queue items list
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
export default function QueueManagementDemo() {
|
||||
const [schedulerStatus, setSchedulerStatus] = useState('idle')
|
||||
const [progress, setProgress] = 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' },
|
||||
}
|
||||
|
||||
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 as keyof typeof statusConfig]
|
||||
|
||||
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 as keyof typeof itemStatusConfig]
|
||||
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>
|
||||
)
|
||||
}
|
||||
86
docs/.vitepress/demos/ResizablePanesDemo.tsx
Normal file
86
docs/.vitepress/demos/ResizablePanesDemo.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Resizable Panes Demo
|
||||
* Simulates the Allotment resizable split behavior
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
export default function ResizablePanesDemo() {
|
||||
const [leftWidth, setLeftWidth] = useState(240)
|
||||
const [rightWidth, setRightWidth] = useState(280)
|
||||
const [isDragging, setIsDragging] = useState<string | null>(null)
|
||||
|
||||
const handleDragStart = (side: string) => (e: React.MouseEvent) => {
|
||||
setIsDragging(side)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (isDragging === 'left') {
|
||||
setLeftWidth(Math.max(180, Math.min(320, e.clientX - 24)))
|
||||
} else if (isDragging === 'right') {
|
||||
setRightWidth(Math.max(200, Math.min(400, window.innerWidth - e.clientX - 24)))
|
||||
}
|
||||
}
|
||||
|
||||
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 min-w-[180px]">
|
||||
<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 min-w-[200px]">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
110
docs/.vitepress/demos/SchedulerConfigDemo.tsx
Normal file
110
docs/.vitepress/demos/SchedulerConfigDemo.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Scheduler Config Demo
|
||||
* Interactive configuration panel
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function SchedulerConfigDemo() {
|
||||
const [config, setConfig] = useState({
|
||||
maxConcurrentSessions: 2,
|
||||
sessionIdleTimeoutMs: 60000,
|
||||
resumeKeySessionBindingTimeoutMs: 300000,
|
||||
})
|
||||
|
||||
const formatMs = (ms: number) => {
|
||||
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>
|
||||
)
|
||||
}
|
||||
108
docs/.vitepress/demos/SessionCarousel.tsx
Normal file
108
docs/.vitepress/demos/SessionCarousel.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Session Carousel Demo
|
||||
* Auto-rotating session cards with navigation
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
export default function SessionCarousel() {
|
||||
const [currentIndex, setCurrentIndex] = 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',
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
122
docs/.vitepress/demos/TerminalDashboardOverview.tsx
Normal file
122
docs/.vitepress/demos/TerminalDashboardOverview.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Terminal Dashboard Overview Demo
|
||||
* Shows the three-column layout with resizable panes and toolbar
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function TerminalDashboardOverview() {
|
||||
const [fileSidebarOpen, setFileSidebarOpen] = useState(true)
|
||||
const [sessionSidebarOpen, setSessionSidebarOpen] = useState(true)
|
||||
const [activePanel, setActivePanel] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<div className="h-[600px] flex flex-col bg-background relative">
|
||||
{/* 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>
|
||||
)
|
||||
}
|
||||
48
docs/.vitepress/demos/TerminalLayoutPresets.tsx
Normal file
48
docs/.vitepress/demos/TerminalLayoutPresets.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Terminal Layout Presets Demo
|
||||
* Interactive layout preset buttons
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function TerminalLayoutPresets() {
|
||||
const [layout, setLayout] = 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>
|
||||
)
|
||||
}
|
||||
57
docs/.vitepress/demos/badge-variants.tsx
Normal file
57
docs/.vitepress/demos/badge-variants.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Badge Variants Demo
|
||||
* Shows all available badge variants
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default function BadgeVariantsDemo() {
|
||||
const variants = [
|
||||
{ name: 'default', class: 'bg-primary text-primary-foreground' },
|
||||
{ name: 'secondary', class: 'bg-secondary text-secondary-foreground' },
|
||||
{ name: 'destructive', class: 'bg-destructive text-destructive-foreground' },
|
||||
{ name: 'outline', class: 'border text-foreground' },
|
||||
{ name: 'success', class: 'bg-success text-white' },
|
||||
{ name: 'warning', class: 'bg-warning text-white' },
|
||||
{ name: 'info', class: 'bg-info text-white' },
|
||||
{ name: 'review', class: 'bg-purple-500 text-white' },
|
||||
{ name: 'gradient', class: 'bg-gradient-to-r from-blue-500 to-purple-500 text-white' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Badge Variants</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{variants.map((v) => (
|
||||
<span
|
||||
key={v.name}
|
||||
className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${v.class}`}
|
||||
>
|
||||
{v.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Usage Examples */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-medium">Usage Examples</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 p-2 border rounded">
|
||||
<span className="font-medium">Status:</span>
|
||||
<span className="inline-flex items-center rounded-full bg-success px-2 py-0.5 text-xs text-white">Active</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 p-2 border rounded">
|
||||
<span className="font-medium">Priority:</span>
|
||||
<span className="inline-flex items-center rounded-full bg-destructive px-2 py-0.5 text-xs text-white">High</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 p-2 border rounded">
|
||||
<span className="font-medium">Type:</span>
|
||||
<span className="inline-flex items-center rounded-full bg-info px-2 py-0.5 text-xs text-white">Feature</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +1,80 @@
|
||||
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)
|
||||
* Shows all visual variants of the button component
|
||||
*/
|
||||
export default function ButtonVariants() {
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function ButtonVariantsDemo() {
|
||||
const [variant, setVariant] = useState('default')
|
||||
|
||||
const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link', 'gradient']
|
||||
|
||||
const getButtonClass = (v: string) => {
|
||||
const base = 'px-4 py-2 rounded-md text-sm transition-colors'
|
||||
switch (v) {
|
||||
case 'default': return `${base} bg-primary text-primary-foreground hover:opacity-90`
|
||||
case 'destructive': return `${base} bg-destructive text-destructive-foreground hover:opacity-90`
|
||||
case 'outline': return `${base} border bg-background hover:bg-accent`
|
||||
case 'secondary': return `${base} bg-secondary text-secondary-foreground hover:opacity-80`
|
||||
case 'ghost': return `${base} hover:bg-accent`
|
||||
case 'link': return `${base} text-primary underline-offset-4 hover:underline`
|
||||
case 'gradient': return `${base} bg-gradient-to-r from-blue-500 to-purple-500 text-white hover:opacity-90`
|
||||
default: return base
|
||||
}
|
||||
}
|
||||
|
||||
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 className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Button Variants</h3>
|
||||
|
||||
{/* Variant Selector */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Select Variant</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{variants.map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
onClick={() => setVariant(v)}
|
||||
className={`px-3 py-1.5 text-xs rounded capitalize transition-colors ${
|
||||
variant === v ? 'bg-primary text-primary-foreground' : 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{v}
|
||||
</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>
|
||||
{/* Preview */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Preview</label>
|
||||
<div className="p-4 border rounded-lg bg-muted/20">
|
||||
<button className={getButtonClass(variant)}>
|
||||
Button
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* All 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">
|
||||
{variants.map((v) => (
|
||||
<button key={v} className={getButtonClass(v)}>
|
||||
{v.charAt(0).toUpperCase() + v.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sizes */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">Sizes</label>
|
||||
<div className="flex items-center gap-3 flex-wrap p-4 border rounded-lg">
|
||||
<button className={`${getButtonClass(variant)} h-8 px-3 text-xs`}>Small</button>
|
||||
<button className={`${getButtonClass(variant)} h-10 px-4`}>Default</button>
|
||||
<button className={`${getButtonClass(variant)} h-11 px-8`}>Large</button>
|
||||
<button className={`${getButtonClass(variant)} h-10 w-10`}>⚙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
71
docs/.vitepress/demos/card-variants.tsx
Normal file
71
docs/.vitepress/demos/card-variants.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Card Variants Demo
|
||||
* Shows different card layouts
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default function CardVariantsDemo() {
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Card Variants</h3>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{/* Basic Card */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h4 className="font-semibold">Basic Card</h4>
|
||||
<p className="text-sm text-muted-foreground">With header and content</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="text-sm">Card content goes here.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card with Footer */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h4 className="font-semibold">Card with Footer</h4>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="text-sm">Main content area.</p>
|
||||
</div>
|
||||
<div className="p-4 border-t bg-muted/20">
|
||||
<div className="flex gap-2">
|
||||
<button className="px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded">Action</button>
|
||||
<button className="px-3 py-1.5 text-xs border rounded">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Gradient Border Card */}
|
||||
<div className="border-2 border-transparent bg-gradient-to-r from-blue-500 to-purple-500 p-[2px] rounded-lg">
|
||||
<div className="bg-background rounded-lg p-4">
|
||||
<h4 className="font-semibold">Gradient Border</h4>
|
||||
<p className="text-sm text-muted-foreground mt-1">Card with gradient border effect.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Settings Card */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h4 className="font-semibold">Settings</h4>
|
||||
</div>
|
||||
<div className="p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Enable notifications</span>
|
||||
<div className="w-9 h-5 bg-primary rounded-full relative">
|
||||
<div className="absolute right-[2px] top-[2px] w-4 h-4 bg-white rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Dark mode</span>
|
||||
<div className="w-9 h-5 bg-muted rounded-full relative">
|
||||
<div className="absolute left-[2px] top-[2px] w-4 h-4 bg-white rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
79
docs/.vitepress/demos/checkbox-variants.tsx
Normal file
79
docs/.vitepress/demos/checkbox-variants.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Checkbox Variants Demo
|
||||
* Shows different checkbox states
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function CheckboxVariantsDemo() {
|
||||
const [checked, setChecked] = useState({ a: true, b: false, c: true })
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Checkbox States</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Checked */}
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked.a}
|
||||
onChange={(e) => setChecked({ ...checked, a: e.target.checked })}
|
||||
className="h-4 w-4 rounded border border-primary accent-primary"
|
||||
/>
|
||||
<span className="text-sm">Checked checkbox</span>
|
||||
</label>
|
||||
|
||||
{/* Unchecked */}
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked.b}
|
||||
onChange={(e) => setChecked({ ...checked, b: e.target.checked })}
|
||||
className="h-4 w-4 rounded border border-primary accent-primary"
|
||||
/>
|
||||
<span className="text-sm">Unchecked checkbox</span>
|
||||
</label>
|
||||
|
||||
{/* Disabled */}
|
||||
<label className="flex items-center gap-3 cursor-not-allowed opacity-50">
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled
|
||||
className="h-4 w-4 rounded border border-primary"
|
||||
/>
|
||||
<span className="text-sm">Disabled checkbox</span>
|
||||
</label>
|
||||
|
||||
{/* Disabled Checked */}
|
||||
<label className="flex items-center gap-3 cursor-not-allowed opacity-50">
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled
|
||||
defaultChecked
|
||||
className="h-4 w-4 rounded border border-primary accent-primary"
|
||||
/>
|
||||
<span className="text-sm">Disabled checked checkbox</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Usage Example */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-medium">Usage Example</h4>
|
||||
<div className="p-4 border rounded-lg space-y-2">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border accent-primary" />
|
||||
<span className="text-sm">Accept terms and conditions</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border accent-primary" />
|
||||
<span className="text-sm">Subscribe to newsletter</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input type="checkbox" className="h-4 w-4 rounded border accent-primary" />
|
||||
<span className="text-sm">Remember me</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
95
docs/.vitepress/demos/input-variants.tsx
Normal file
95
docs/.vitepress/demos/input-variants.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Input Variants Demo
|
||||
* Shows all input states
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function InputVariantsDemo() {
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Input States</h3>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Default */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Default</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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* With Value */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">With Value</label>
|
||||
<input
|
||||
type="text"
|
||||
value="John Doe"
|
||||
readOnly
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Error State</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Invalid input"
|
||||
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"
|
||||
/>
|
||||
<p className="text-xs text-destructive">This field is required</p>
|
||||
</div>
|
||||
|
||||
{/* Disabled */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Disabled</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled
|
||||
placeholder="Disabled input"
|
||||
className="flex h-10 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm opacity-50 cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* With Icon */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Search Input</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background pl-10 pr-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controlled */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Controlled Input</label>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Type something..."
|
||||
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"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">Value: {value || '(empty)'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Textarea */}
|
||||
<div className="space-y-2">
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
101
docs/.vitepress/demos/select-variants.tsx
Normal file
101
docs/.vitepress/demos/select-variants.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Select Variants Demo
|
||||
* Shows different select configurations
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function SelectVariantsDemo() {
|
||||
const [selected, setSelected] = useState('')
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
<h3 className="text-sm font-semibold">Select Configurations</h3>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Basic Select */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Basic 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>
|
||||
|
||||
{/* With Labels */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">With Option Groups</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">
|
||||
<optgroup label="Fruits">
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="orange">Orange</option>
|
||||
</optgroup>
|
||||
<optgroup label="Vegetables">
|
||||
<option value="carrot">Carrot</option>
|
||||
<option value="broccoli">Broccoli</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Controlled Select */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Controlled Select</label>
|
||||
<select
|
||||
value={selected}
|
||||
onChange={(e) => setSelected(e.target.value)}
|
||||
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="">Select...</option>
|
||||
<option value="react">React</option>
|
||||
<option value="vue">Vue</option>
|
||||
<option value="angular">Angular</option>
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground">Selected: {selected || '(none)'}</p>
|
||||
</div>
|
||||
|
||||
{/* Disabled Select */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Disabled Select</label>
|
||||
<select
|
||||
disabled
|
||||
className="flex h-10 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm opacity-50 cursor-not-allowed"
|
||||
>
|
||||
<option value="">Disabled select</option>
|
||||
<option value="1">Option 1</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* With Separators */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">With Separators</label>
|
||||
<select className="flex h-10 w-full max-w-md rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
<option value="">Select an action</option>
|
||||
<option value="edit">Edit</option>
|
||||
<option value="copy">Copy</option>
|
||||
<option value="move">Move</option>
|
||||
{/* Visual separator via disabled option */}
|
||||
<option disabled>──────────</option>
|
||||
<option value="delete" className="text-destructive">Delete</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Multiple Select */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Multiple Select (Hold Ctrl/Cmd)</label>
|
||||
<select
|
||||
multiple
|
||||
className="flex min-h-[100px] w-full max-w-md rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
<option value="html">HTML</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="js">JavaScript</option>
|
||||
<option value="ts">TypeScript</option>
|
||||
<option value="react">React</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -264,7 +264,7 @@ function hideTooltip() {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
@media (max-width: 768px) {
|
||||
.agent-orchestration {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import React from 'react'
|
||||
import type { Root } from 'react-dom/client'
|
||||
import CodeViewer from './CodeViewer.vue'
|
||||
|
||||
interface Props {
|
||||
name: string // Demo component name
|
||||
id: string // Demo unique ID
|
||||
name: string // Demo component name
|
||||
file?: string // Optional: explicit source file
|
||||
hasInlineCode?: boolean // Whether inline code is provided
|
||||
inlineCode?: string // Base64 encoded inline code (for display)
|
||||
virtualModule?: string // Virtual module path for inline demos
|
||||
height?: string // Demo container height
|
||||
expandable?: boolean // Allow expand/collapse
|
||||
showCode?: boolean // Show code tab
|
||||
@@ -14,6 +19,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
hasInlineCode: false,
|
||||
inlineCode: '',
|
||||
virtualModule: '',
|
||||
height: 'auto',
|
||||
expandable: true,
|
||||
showCode: true,
|
||||
@@ -33,25 +41,64 @@ 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]
|
||||
// Handle inline code mode with virtual module
|
||||
if (props.hasInlineCode && props.virtualModule) {
|
||||
// Decode base64 for source code display
|
||||
if (props.inlineCode) {
|
||||
sourceCode.value = atob(props.inlineCode)
|
||||
}
|
||||
|
||||
if (!DemoComponent) {
|
||||
throw new Error(`Demo component "${props.name}" not found`)
|
||||
}
|
||||
// Dynamically import the virtual module
|
||||
// @vite-ignore is needed for dynamic imports with variable paths
|
||||
const inlineDemoModule = await import(/* @vite-ignore */ props.virtualModule)
|
||||
const DemoComponent = inlineDemoModule.default
|
||||
|
||||
// Mount React component
|
||||
if (demoRoot.value) {
|
||||
reactRoot.value = createRoot(demoRoot.value)
|
||||
reactRoot.value.render(DemoComponent)
|
||||
if (!DemoComponent) {
|
||||
throw new Error(`Inline demo component "${props.name}" not found in virtual module`)
|
||||
}
|
||||
|
||||
// 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'
|
||||
// Mount React component properly
|
||||
if (demoRoot.value) {
|
||||
reactRoot.value = createRoot(demoRoot.value)
|
||||
reactRoot.value.render(React.createElement(DemoComponent))
|
||||
}
|
||||
} else if (props.hasInlineCode && props.inlineCode) {
|
||||
// Fallback: inline code without virtual module (display only, no execution)
|
||||
const decodedCode = atob(props.inlineCode)
|
||||
sourceCode.value = decodedCode
|
||||
|
||||
if (demoRoot.value) {
|
||||
// Show a message that preview is not available
|
||||
const noticeEl = document.createElement('div')
|
||||
noticeEl.className = 'inline-demo-notice'
|
||||
noticeEl.innerHTML = `
|
||||
<p><strong>Preview not available</strong></p>
|
||||
<p>Inline demo "${props.name}" requires virtual module support.</p>
|
||||
<p>Check the "Code" tab to see the source.</p>
|
||||
`
|
||||
demoRoot.value.appendChild(noticeEl)
|
||||
}
|
||||
} else {
|
||||
// Dynamically import demo component from file
|
||||
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) {
|
||||
@@ -250,6 +297,43 @@ const switchTab = (tab: 'preview' | 'code') => {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Inline demo notice (fallback mode) */
|
||||
:deep(.inline-demo-notice) {
|
||||
padding: 20px;
|
||||
background: var(--vp-c-warning-soft);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.inline-demo-notice p) {
|
||||
margin: 8px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
:deep(.inline-demo-notice strong) {
|
||||
color: var(--vp-c-warning-1);
|
||||
}
|
||||
|
||||
/* Inline demo preview styles */
|
||||
:deep(.inline-demo-preview) {
|
||||
padding: 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.inline-demo-preview button) {
|
||||
margin: 4px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.inline-demo-preview button:hover) {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.demo-header {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { computed, ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
|
||||
const { site, lang, page } = useData()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const switcherRef = ref<HTMLElement>()
|
||||
const buttonRef = ref<HTMLElement>()
|
||||
const dropdownPosition = ref({ top: 0, left: 0 })
|
||||
|
||||
// Get available locales from VitePress config
|
||||
const locales = computed(() => {
|
||||
@@ -36,46 +38,101 @@ const getAltLink = (localeCode: string) => {
|
||||
|
||||
const switchLanguage = (localeCode: string) => {
|
||||
const altLink = getAltLink(localeCode)
|
||||
isOpen.value = false
|
||||
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
|
||||
// Calculate dropdown position
|
||||
const updatePosition = () => {
|
||||
if (buttonRef.value) {
|
||||
const rect = buttonRef.value.getBoundingClientRect()
|
||||
const isMobile = window.innerWidth <= 768
|
||||
|
||||
if (isMobile) {
|
||||
dropdownPosition.value = {
|
||||
top: rect.bottom + 8,
|
||||
left: 12
|
||||
}
|
||||
} else {
|
||||
dropdownPosition.value = {
|
||||
top: rect.bottom + 4,
|
||||
left: rect.right - 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleDropdown = async () => {
|
||||
isOpen.value = !isOpen.value
|
||||
if (isOpen.value) {
|
||||
await nextTick()
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (switcherRef.value && !switcherRef.value.contains(e.target as Node)) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Handle scroll to close dropdown
|
||||
const handleScroll = () => {
|
||||
if (isOpen.value) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('scroll', handleScroll, true)
|
||||
window.addEventListener('resize', updatePosition)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('scroll', handleScroll, true)
|
||||
window.removeEventListener('resize', updatePosition)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="switcherRef" class="language-switcher">
|
||||
<button
|
||||
ref="buttonRef"
|
||||
class="switcher-button"
|
||||
:aria-expanded="isOpen"
|
||||
aria-label="Switch language"
|
||||
@click="isOpen = !isOpen"
|
||||
@click.stop="toggleDropdown"
|
||||
>
|
||||
<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>
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<ul
|
||||
v-if="isOpen"
|
||||
class="locale-list"
|
||||
:style="{
|
||||
top: dropdownPosition.top + 'px',
|
||||
left: dropdownPosition.left + 'px'
|
||||
}"
|
||||
>
|
||||
<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>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -117,19 +174,19 @@ onMounted(() => {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Locale list - rendered at body level via Teleport */
|
||||
.locale-list {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
position: fixed;
|
||||
min-width: 150px;
|
||||
max-width: calc(100vw - 24px);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
padding: 8px 0;
|
||||
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;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.locale-button {
|
||||
@@ -137,14 +194,14 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.locale-button:hover {
|
||||
@@ -173,14 +230,24 @@ onMounted(() => {
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.locale-list {
|
||||
right: auto;
|
||||
left: 0;
|
||||
width: calc(100vw - 24px) !important;
|
||||
max-width: 300px !important;
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25) !important;
|
||||
}
|
||||
|
||||
.switcher-button {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.locale-button {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1275,23 +1275,199 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* Responsive
|
||||
* Responsive (Standardized Breakpoints)
|
||||
* Mobile: < 768px | Tablet: 768px-1024px | Desktop: > 1024px
|
||||
* WCAG 2.1 AA: Touch targets min 44x44px
|
||||
* ============================================ */
|
||||
@media (max-width: 1100px) {
|
||||
|
||||
/* Tablet (768px - 1024px) */
|
||||
@media (max-width: 1024px) {
|
||||
.features-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.hero-container { gap: 1.5rem; }
|
||||
.hero-title { font-size: 2.25rem; }
|
||||
.section-container { padding: 0 1.5rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.hero-container, .json-grid, .quickstart-layout { grid-template-columns: 1fr; text-align: center; }
|
||||
.hero-subtitle { margin-left: auto; margin-right: auto; }
|
||||
.hero-actions { justify-content: center; }
|
||||
.hero-title { font-size: 2.5rem; }
|
||||
.logic-panel { grid-template-columns: 1fr; }
|
||||
.features-grid { grid-template-columns: 1fr; max-width: 420px; margin: 0 auto; }
|
||||
.feature-card { text-align: center; }
|
||||
.feature-icon-box { margin-left: auto; margin-right: auto; }
|
||||
.quickstart-info { text-align: center; }
|
||||
.qs-step { flex-direction: column; align-items: center; text-align: center; }
|
||||
.cta-actions { flex-direction: column; }
|
||||
/* Mobile (< 768px) */
|
||||
@media (max-width: 768px) {
|
||||
/* Hero Section - add extra padding-top to clear fixed nav (56px) */
|
||||
.hero-section {
|
||||
padding: 4.5rem 0 2rem;
|
||||
background:
|
||||
radial-gradient(ellipse 150% 100% at 50% 20%, var(--vp-c-brand-soft) 0%, transparent 60%),
|
||||
var(--vp-c-bg);
|
||||
}
|
||||
.hero-container {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
padding: 0 12px;
|
||||
gap: 2rem;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.hero-content { order: 1; }
|
||||
.hero-visual { order: 2; }
|
||||
.hero-title {
|
||||
font-size: 1.875rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.hero-subtitle {
|
||||
font-size: 1rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1.75rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
.hero-actions {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.hero-visual { min-height: 200px; }
|
||||
|
||||
/* Section Container */
|
||||
.section-container {
|
||||
padding: 0 12px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Features Grid */
|
||||
.features-section { padding: 3rem 0; }
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
gap: 1rem;
|
||||
}
|
||||
.feature-card {
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
min-height: auto;
|
||||
}
|
||||
.feature-icon-box {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
.feature-card h3 { font-size: 1.05rem; }
|
||||
.feature-card p { font-size: 0.9rem; }
|
||||
|
||||
/* Pipeline Section */
|
||||
.pipeline-section { padding: 3rem 0; }
|
||||
.section-header h2 { font-size: 1.5rem; }
|
||||
.section-header p { font-size: 0.9rem; }
|
||||
.pipeline-card {
|
||||
padding: 1.25rem;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.pipeline-flow {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.stage-node { flex-direction: row; justify-content: flex-start; gap: 1rem; }
|
||||
.stage-icon { width: 44px; height: 44px; min-width: 44px; }
|
||||
.stage-info { text-align: left; }
|
||||
.stage-info h4 { margin-top: 0; font-size: 0.9rem; }
|
||||
.stage-info p { font-size: 0.8rem; }
|
||||
.logic-panel { grid-template-columns: 1fr; gap: 1rem; }
|
||||
.law-content, .log-content { padding: 1rem; font-size: 0.8rem; min-height: 60px; }
|
||||
|
||||
/* JSON Section */
|
||||
.json-section { padding: 0; overflow-x: hidden; }
|
||||
.json-grid {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
padding: 3rem 12px;
|
||||
gap: 2rem;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.json-text h2 { font-size: 1.5rem; }
|
||||
.json-text p { font-size: 0.95rem; }
|
||||
.json-benefits { text-align: left; }
|
||||
.json-benefits li { font-size: 0.9rem; }
|
||||
.json-code {
|
||||
padding: 1rem;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.json-pre { font-size: 0.75rem; }
|
||||
|
||||
/* Quick Start */
|
||||
.quickstart-section { padding: 3rem 0; }
|
||||
.quickstart-layout {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
.quickstart-info { order: 2; }
|
||||
.quickstart-terminal { order: 1; }
|
||||
.qs-step {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.qs-step-num { width: 44px; height: 44px; min-width: 44px; }
|
||||
.qs-terminal-body { min-height: 200px; padding: 1rem; }
|
||||
.qs-terminal-header { padding: 0.4rem 0.6rem; }
|
||||
.qs-tab { min-height: 36px; padding: 0.25rem 0.5rem; }
|
||||
|
||||
/* CTA Section */
|
||||
.cta-section { padding: 3rem 0; }
|
||||
.cta-card {
|
||||
padding: 2rem 1rem;
|
||||
margin: 0 12px;
|
||||
max-width: calc(100% - 24px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cta-card h2 { font-size: 1.5rem; }
|
||||
.cta-card p { font-size: 0.95rem; margin-bottom: 1.5rem; }
|
||||
.cta-actions {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
.cta-actions .btn-primary,
|
||||
.cta-actions .btn-outline,
|
||||
.cta-actions .btn-secondary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
min-height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small Mobile (< 480px) */
|
||||
@media (max-width: 480px) {
|
||||
.hero-title { font-size: 1.5rem; }
|
||||
.hero-actions { flex-direction: column; width: 100%; }
|
||||
.hero-actions .btn-primary,
|
||||
.hero-actions .btn-secondary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.section-title { font-size: 1.25rem; }
|
||||
.pipeline-flow { gap: 0.75rem; }
|
||||
.stage-icon { width: 40px; height: 40px; border-radius: 10px; }
|
||||
.json-text h2 { font-size: 1.25rem; }
|
||||
.cta-card { padding: 1.5rem 1rem; }
|
||||
.cta-card h2 { font-size: 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,10 +35,16 @@ export const STATUS_COLORS = {
|
||||
const STORAGE_KEY_THEME = 'ccw-theme'
|
||||
const STORAGE_KEY_COLOR_MODE = 'ccw-color-mode'
|
||||
|
||||
/**
|
||||
* Check if running in browser environment
|
||||
*/
|
||||
const isBrowser = typeof window !== 'undefined' && typeof localStorage !== 'undefined'
|
||||
|
||||
/**
|
||||
* Get current theme from localStorage or default
|
||||
*/
|
||||
export function getCurrentTheme(): ThemeName {
|
||||
if (!isBrowser) return 'blue'
|
||||
const saved = localStorage.getItem(STORAGE_KEY_THEME)
|
||||
if (saved && saved in THEME_COLORS) {
|
||||
return saved as ThemeName
|
||||
@@ -50,6 +56,7 @@ export function getCurrentTheme(): ThemeName {
|
||||
* Check if dark mode is active
|
||||
*/
|
||||
export function isDarkMode(): boolean {
|
||||
if (!isBrowser) return false
|
||||
const mode = localStorage.getItem(STORAGE_KEY_COLOR_MODE) || 'auto'
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
return mode === 'dark' || (mode === 'auto' && prefersDark)
|
||||
|
||||
130
docs/.vitepress/theme/inlineDemoPlugin.ts
Normal file
130
docs/.vitepress/theme/inlineDemoPlugin.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Vite plugin for handling inline demo blocks as virtual modules.
|
||||
* This allows React JSX code embedded in markdown :::demo blocks to be
|
||||
* dynamically compiled and executed as proper React components.
|
||||
*/
|
||||
|
||||
import type { Plugin } from 'vite'
|
||||
import type { DemoBlockMeta } from './markdownTransform'
|
||||
|
||||
// Global registry for inline demos (populated during markdown transform)
|
||||
const inlineDemoRegistry = new Map<string, { code: string; name: string }>()
|
||||
|
||||
// Virtual module prefix
|
||||
const VIRTUAL_PREFIX = 'virtual:inline-demo:'
|
||||
const VIRTUAL_PREFIX_FULL = '\0' + VIRTUAL_PREFIX
|
||||
|
||||
/**
|
||||
* Register an inline demo during markdown transformation.
|
||||
* Returns a virtual module ID that can be imported.
|
||||
*/
|
||||
export function registerInlineDemo(
|
||||
demoId: string,
|
||||
code: string,
|
||||
name: string
|
||||
): string {
|
||||
inlineDemoRegistry.set(demoId, { code, name })
|
||||
return VIRTUAL_PREFIX + demoId
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered inline demo by ID
|
||||
*/
|
||||
export function getInlineDemo(demoId: string) {
|
||||
return inlineDemoRegistry.get(demoId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all registered demos (useful for rebuilds)
|
||||
*/
|
||||
export function clearInlineDemos() {
|
||||
inlineDemoRegistry.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin that resolves virtual inline-demo modules.
|
||||
* These modules contain the React/JSX code from markdown :::demo blocks.
|
||||
*/
|
||||
export function inlineDemoPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vitepress-inline-demo-plugin',
|
||||
enforce: 'pre',
|
||||
|
||||
resolveId(id) {
|
||||
// Handle virtual module resolution
|
||||
if (id.startsWith(VIRTUAL_PREFIX)) {
|
||||
return VIRTUAL_PREFIX_FULL + id.slice(VIRTUAL_PREFIX.length)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
load(id) {
|
||||
// Load virtual module content
|
||||
if (id.startsWith(VIRTUAL_PREFIX_FULL)) {
|
||||
const demoId = id.slice(VIRTUAL_PREFIX_FULL.length)
|
||||
const demo = inlineDemoRegistry.get(demoId)
|
||||
|
||||
if (!demo) {
|
||||
return `export default function MissingDemo() {
|
||||
return React.createElement('div', { className: 'demo-error' },
|
||||
'Demo not found: ${demoId}'
|
||||
)
|
||||
}`
|
||||
}
|
||||
|
||||
// Wrap the inline code in an ESM module
|
||||
// The code should export a React component
|
||||
return `
|
||||
import React from 'react';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
|
||||
// User's inline demo code:
|
||||
${demo.code}
|
||||
|
||||
// Auto-export fallback if no default export
|
||||
export default ${demo.name} || (() => React.createElement('div', null, 'Demo component "${demo.name}" not found'));
|
||||
`
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
// Handle HMR for inline demos
|
||||
handleHotUpdate({ file, server }) {
|
||||
// When markdown files change, clear the registry
|
||||
if (file.endsWith('.md')) {
|
||||
clearInlineDemos()
|
||||
server.ws.send({
|
||||
type: 'full-reload',
|
||||
path: file
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform inline demo code to ensure it has proper exports.
|
||||
* This wraps bare JSX in a function component if needed.
|
||||
*/
|
||||
export function wrapDemoCode(code: string, componentName: string): string {
|
||||
// If code already has export, return as-is
|
||||
if (code.includes('export ')) {
|
||||
return code
|
||||
}
|
||||
|
||||
// If code is just JSX (starts with <), wrap it
|
||||
const trimmedCode = code.trim()
|
||||
if (trimmedCode.startsWith('<') || trimmedCode.startsWith('React.createElement')) {
|
||||
return `
|
||||
function ${componentName}() {
|
||||
return (
|
||||
${trimmedCode}
|
||||
);
|
||||
}
|
||||
export default ${componentName};
|
||||
`
|
||||
}
|
||||
|
||||
// Otherwise return as-is and hope it defines the component
|
||||
return code + `\nexport default ${componentName};`
|
||||
}
|
||||
@@ -82,25 +82,61 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template #nav-bar-content-after>
|
||||
<div class="nav-extensions">
|
||||
<DocSearch />
|
||||
<DarkModeToggle />
|
||||
<ThemeSwitcher />
|
||||
<LanguageSwitcher />
|
||||
<DocSearch class="nav-item-always" />
|
||||
<DarkModeToggle class="nav-item-desktop" />
|
||||
<ThemeSwitcher class="nav-item-desktop" />
|
||||
<LanguageSwitcher class="nav-item-desktop" />
|
||||
</div>
|
||||
</template>
|
||||
</DefaultTheme.Layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
* Container Query Context Definitions
|
||||
* Enables component-level responsive design
|
||||
* ============================================ */
|
||||
|
||||
/* Set container context on layout root */
|
||||
:deep(.Layout) {
|
||||
container-type: inline-size;
|
||||
container-name: layout;
|
||||
}
|
||||
|
||||
/* Sidebar container context */
|
||||
:deep(.VPSidebar) {
|
||||
container-type: inline-size;
|
||||
container-name: sidebar;
|
||||
}
|
||||
|
||||
/* Main content container context */
|
||||
:deep(.VPContent) {
|
||||
container-type: inline-size;
|
||||
container-name: content;
|
||||
}
|
||||
|
||||
/* Document outline container context */
|
||||
:deep(.VPDocOutline) {
|
||||
container-type: inline-size;
|
||||
container-name: outline;
|
||||
}
|
||||
|
||||
/* Navigation container context */
|
||||
:deep(.VPNav) {
|
||||
container-type: inline-size;
|
||||
container-name: nav;
|
||||
}
|
||||
|
||||
/* Hero section with fluid spacing */
|
||||
.hero-extensions {
|
||||
margin-top: 40px;
|
||||
margin-top: var(--spacing-fluid-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 48px;
|
||||
gap: var(--spacing-fluid-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -109,23 +145,23 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-size: clamp(1.5rem, 1.25rem + 1.25vw, 2rem);
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-primary);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
font-size: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 4px;
|
||||
margin-top: var(--spacing-fluid-xs);
|
||||
}
|
||||
|
||||
.nav-extensions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: var(--spacing-fluid-sm);
|
||||
margin-left: auto;
|
||||
padding-left: 16px;
|
||||
padding-left: var(--spacing-fluid-sm);
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
@@ -156,22 +192,64 @@ onBeforeUnmount(() => {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* Mobile overrides now handled by fluid spacing variables */
|
||||
/* Container queries in mobile.css provide additional responsiveness */
|
||||
|
||||
/* Mobile-specific styles */
|
||||
@media (max-width: 768px) {
|
||||
.hero-extensions {
|
||||
margin-top: 1rem;
|
||||
padding: 0 12px;
|
||||
max-width: 100vw;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
gap: 24px;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.nav-extensions {
|
||||
gap: 8px;
|
||||
padding-left: 8px;
|
||||
gap: 0.25rem;
|
||||
padding-left: 0.25rem;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Hide desktop-only nav items on mobile */
|
||||
.nav-item-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Keep always-visible items */
|
||||
.nav-item-always {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Ensure nav bar allows dropdown overflow */
|
||||
:deep(.VPNavBar) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
:deep(.VPNavBar .content) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Fix dropdown positioning for mobile */
|
||||
:deep(.VPNavBarMenuGroup .items) {
|
||||
position: fixed !important;
|
||||
left: 12px !important;
|
||||
right: 12px !important;
|
||||
top: 56px !important;
|
||||
max-width: none !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { MarkdownTransformContext } from 'vitepress'
|
||||
|
||||
const demoBlockRE = /:::\s*demo\s+(.+?)\s*:::/g
|
||||
// Multi-line demo block: :::demo Name #file ...code... :::
|
||||
const demoBlockRE = /:::\s*demo\s+([^\n]+)\n([\s\S]*?)\n:::/g
|
||||
|
||||
// Single-line demo block: :::demo Name #file :::
|
||||
const demoBlockSingleRE = /:::\s*demo\s+(\S+)\s*(#\S+)?\s*:::/g
|
||||
|
||||
export interface DemoBlockMeta {
|
||||
name: string
|
||||
file?: string
|
||||
code?: string
|
||||
height?: string
|
||||
expandable?: boolean
|
||||
showCode?: boolean
|
||||
@@ -15,47 +20,71 @@ 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)}`
|
||||
// First handle multi-line demo blocks with inline code
|
||||
let result = code.replace(demoBlockRE, (match, headerLine, codeContent) => {
|
||||
const meta = parseDemoHeader(headerLine)
|
||||
|
||||
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${meta.name}-${Date.now().toString(36)}`
|
||||
|
||||
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.expandable === false ? ':expandable="false"' : '',
|
||||
meta.showCode === false ? ':show-code="false"' : '',
|
||||
meta.title ? `title="${meta.title}"` : ''
|
||||
].filter(Boolean).join(' ')
|
||||
|
||||
// Return a simple comment placeholder - the inline code will be ignored
|
||||
// This avoids Vue parsing issues with JSX in markdown
|
||||
return `<DemoContainer ${props} />`
|
||||
})
|
||||
|
||||
// Then handle single-line demo blocks (file references only)
|
||||
result = result.replace(demoBlockSingleRE, (match, name, fileRef) => {
|
||||
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${name}-${Date.now().toString(36)}`
|
||||
|
||||
const file = fileRef ? fileRef.slice(1) : undefined
|
||||
|
||||
const props = [
|
||||
`id="${demoId}"`,
|
||||
`name="${name}"`,
|
||||
file ? `file="${file}"` : '',
|
||||
':expandable="true"',
|
||||
':show-code="true"'
|
||||
].filter(Boolean).join(' ')
|
||||
|
||||
return `<DemoContainer ${props} />`
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function parseDemoBlock(content: string): DemoBlockMeta {
|
||||
const lines = content.trim().split('\n')
|
||||
const firstLine = lines[0] || ''
|
||||
function parseDemoHeader(headerLine: string): DemoBlockMeta {
|
||||
const parts = headerLine.trim().split(/\s+/)
|
||||
const name = parts[0] || ''
|
||||
const file = parts.find(p => p.startsWith('#'))?.slice(1)
|
||||
|
||||
// Parse: name or # file
|
||||
const [name, ...rest] = firstLine.split(/\s+/)
|
||||
const file = rest.find(l => l.startsWith('#'))?.slice(1)
|
||||
// Extract props from remaining parts
|
||||
const props: Record<string, string> = {}
|
||||
for (const part of parts.slice(1)) {
|
||||
if (part.includes(':')) {
|
||||
const [key, value] = part.split(':', 2)
|
||||
props[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: name.trim(),
|
||||
name,
|
||||
file,
|
||||
height: extractProp(lines, 'height'),
|
||||
expandable: extractProp(lines, 'expandable') !== 'false',
|
||||
showCode: extractProp(lines, 'showCode') !== 'false',
|
||||
title: extractProp(lines, 'title')
|
||||
height: props.height,
|
||||
expandable: props.expandable !== 'false',
|
||||
showCode: props.showCode !== 'false',
|
||||
title: props.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 {
|
||||
|
||||
@@ -85,11 +85,26 @@
|
||||
.VPContent:has(.pro-home) {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
max-width: 100% !important;
|
||||
max-width: 100vw !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.Layout:has(.pro-home) {
|
||||
max-width: 100% !important;
|
||||
max-width: 100vw !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
/* Ensure VPNav doesn't cause overflow */
|
||||
.Layout:has(.pro-home) .VPNav {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
/* Ensure VPFooter doesn't cause overflow */
|
||||
.Layout:has(.pro-home) .VPFooter {
|
||||
max-width: 100vw;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ProfessionalHome component full width */
|
||||
@@ -98,6 +113,8 @@
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* Ensure all sections extend to full width */
|
||||
@@ -111,6 +128,8 @@
|
||||
margin: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
max-width: 100vw !important;
|
||||
}
|
||||
|
||||
.VPHomeHero {
|
||||
|
||||
@@ -1,9 +1,250 @@
|
||||
/**
|
||||
* Mobile-Responsive Styles
|
||||
* Breakpoints: 320px-768px (mobile), 768px-1024px (tablet), 1024px+ (desktop)
|
||||
* Uses CSS custom properties from variables.css: --bp-mobile, --bp-tablet, --bp-desktop
|
||||
* WCAG 2.1 AA compliant
|
||||
*/
|
||||
|
||||
/* ============================================
|
||||
* Container Query Support
|
||||
* Enable component-level responsive design
|
||||
* Fallback: Uses @supports to provide media query fallbacks
|
||||
* ============================================ */
|
||||
|
||||
/* Fallback for browsers without container query support */
|
||||
@supports not (container-type: inline-size) {
|
||||
/* Sidebar fallback */
|
||||
.VPSidebar {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPSidebar {
|
||||
width: var(--vp-sidebar-width, 272px);
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content fallback */
|
||||
.VPContent {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPContent {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.VPContent {
|
||||
padding: 32px 48px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Outline fallback */
|
||||
.VPDocOutline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPDocOutline {
|
||||
display: block;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.VPDocOutline {
|
||||
width: 256px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Container Query Rules (modern browsers) */
|
||||
@supports (container-type: inline-size) {
|
||||
/* Sidebar Container Queries */
|
||||
@container sidebar (max-width: 480px) {
|
||||
.VPSidebar .group {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.VPSidebar .title {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@container sidebar (min-width: 480px) and (max-width: 768px) {
|
||||
.VPSidebar .group {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.VPSidebar .title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@container sidebar (min-width: 768px) {
|
||||
.VPSidebar .group {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.VPSidebar .title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content Container Queries */
|
||||
@container content (max-width: 640px) {
|
||||
.VPDoc .content-container {
|
||||
padding: 0 var(--spacing-fluid-sm);
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
|
||||
.vp-doc pre {
|
||||
font-size: 12px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@container content (min-width: 640px) and (max-width: 960px) {
|
||||
.VPDoc .content-container {
|
||||
padding: 0 var(--spacing-fluid-md);
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.vp-doc pre {
|
||||
font-size: 13px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@container content (min-width: 960px) {
|
||||
.VPDoc .content-container {
|
||||
padding: 0 var(--spacing-fluid-lg);
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
|
||||
.vp-doc pre {
|
||||
font-size: 14px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Outline Container Queries */
|
||||
@container outline (max-width: 200px) {
|
||||
.VPDocOutline .outline-link {
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
|
||||
.VPDocOutline .outline-marker {
|
||||
width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@container outline (min-width: 200px) and (max-width: 280px) {
|
||||
.VPDocOutline .outline-link {
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.VPDocOutline .outline-marker {
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@container outline (min-width: 280px) {
|
||||
.VPDocOutline .outline-link {
|
||||
font-size: 13px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.VPDocOutline .outline-marker {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation Container Queries */
|
||||
@container nav (max-width: 640px) {
|
||||
.VPNavBar {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.VPNavBar .nav-extensions {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@container nav (min-width: 640px) and (max-width: 960px) {
|
||||
.VPNavBar {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.VPNavBar .nav-extensions {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@container nav (min-width: 960px) {
|
||||
.VPNavBar {
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.VPNavBar .nav-extensions {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Generic Container-Responsive Utility Class */
|
||||
@container (max-width: 480px) {
|
||||
.container-responsive {
|
||||
padding: 0 var(--spacing-fluid-xs);
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 480px) and (max-width: 768px) {
|
||||
.container-responsive {
|
||||
padding: 0 var(--spacing-fluid-sm);
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 768px) and (max-width: 1024px) {
|
||||
.container-responsive {
|
||||
padding: 0 var(--spacing-fluid-md);
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 1024px) {
|
||||
.container-responsive {
|
||||
padding: 0 var(--spacing-fluid-lg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* Mobile First Approach
|
||||
* ============================================ */
|
||||
@@ -18,32 +259,218 @@
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
/* Navigation - ensure hamburger menu is visible */
|
||||
.VPNav {
|
||||
height: 56px;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.VPNavBar {
|
||||
padding: 0 16px;
|
||||
padding: 0 12px;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
/* Navigation bar content wrapper */
|
||||
.VPNavBar .content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Show hamburger menu button on mobile */
|
||||
.VPNavBar .VPNavBarHamburger {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Hide desktop nav links on mobile, use hamburger menu */
|
||||
.VPNavBar .VPNavBarMenu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Ensure nav title is visible */
|
||||
.VPNavBar .VPNavBarTitle {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Reduce nav-extensions gap on mobile */
|
||||
.VPNavBar .nav-extensions {
|
||||
gap: 0.25rem;
|
||||
padding-left: 0.25rem;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Hide non-essential nav items on mobile */
|
||||
.nav-extensions .nav-item-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Style search button for mobile */
|
||||
.nav-extensions .DocSearch {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
/* Ensure VitePress social link is hidden on mobile (uses sidebar) */
|
||||
.VPNavBar .VPNavBarSocialLinks {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Fix dropdown menus overflow on mobile */
|
||||
.VPNavBar .VPNavBarMenuGroup {
|
||||
position: relative;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.VPNavBar .VPNavBarMenuGroup .items {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
min-width: 180px;
|
||||
max-width: calc(100vw - 24px);
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* Language switcher dropdown fix */
|
||||
.language-switcher {
|
||||
position: relative !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.language-switcher .locale-list {
|
||||
position: fixed !important;
|
||||
top: auto !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) !important;
|
||||
right: auto !important;
|
||||
min-width: 200px !important;
|
||||
max-width: calc(100vw - 24px) !important;
|
||||
z-index: 1000 !important;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Sidebar - fix display issues */
|
||||
.VPSidebar {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
width: 100% !important;
|
||||
max-width: 320px !important;
|
||||
padding-top: 0 !important;
|
||||
top: 56px !important;
|
||||
height: calc(100vh - 56px) !important;
|
||||
max-height: calc(100vh - 56px) !important;
|
||||
overflow: visible !important;
|
||||
position: fixed !important;
|
||||
left: 0 !important;
|
||||
z-index: 40 !important;
|
||||
background: var(--vp-c-bg) !important;
|
||||
transition: transform 0.25s ease !important;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
/* Sidebar when open */
|
||||
.VPSidebar.open,
|
||||
.sidebar-open .VPSidebar {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
|
||||
/* Sidebar when closed */
|
||||
.VPSidebar:not(.open) {
|
||||
transform: translateX(-100%) !important;
|
||||
}
|
||||
|
||||
/* Sidebar nav container */
|
||||
.VPSidebar .VPSidebarNav {
|
||||
padding: 12px 0;
|
||||
height: 100%;
|
||||
min-height: auto;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Sidebar groups */
|
||||
.VPSidebar .VPSidebarGroup {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* Sidebar items */
|
||||
.VPSidebar .VPSidebarItem {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
/* Ensure sidebar links are properly sized */
|
||||
.VPSidebar .link {
|
||||
padding: 8px 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Local nav for mobile */
|
||||
.VPLocalNav {
|
||||
display: flex !important;
|
||||
position: sticky;
|
||||
top: 56px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Sidebar curtain/backdrop */
|
||||
.VPSidebar curtain,
|
||||
.VPSidebar .curtain {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Sidebar scroll container */
|
||||
.VPSidebar .sidebar-container,
|
||||
.VPSidebar nav {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Make sure all sidebar content is visible */
|
||||
.VPSidebar .group {
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.VPSidebar .title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
/* Sidebar text styling */
|
||||
.VPSidebar .text {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
/* Ensure nested items are visible */
|
||||
.VPSidebar .items {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Backdrop for sidebar */
|
||||
.VPBackdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
top: 56px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 39;
|
||||
}
|
||||
|
||||
/* Content - reduce padding for better space usage */
|
||||
.VPContent {
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* Doc content adjustments */
|
||||
/* Doc content adjustments - reduce padding */
|
||||
.VPDoc .content-container {
|
||||
padding: 0 16px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* Hide outline on mobile */
|
||||
@@ -53,7 +480,7 @@
|
||||
|
||||
/* Hero Section */
|
||||
.VPHomeHero {
|
||||
padding: 40px 16px;
|
||||
padding: 40px 12px;
|
||||
}
|
||||
|
||||
.VPHomeHero h1 {
|
||||
@@ -65,14 +492,14 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Code Blocks */
|
||||
/* Code Blocks - reduce margins */
|
||||
div[class*='language-'] {
|
||||
margin: 12px -16px;
|
||||
margin: 12px -12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
div[class*='language-'] pre {
|
||||
padding: 12px 16px;
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -319,6 +746,230 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* ProfessionalHome Component - Mobile Optimizations
|
||||
* ============================================ */
|
||||
@media (max-width: 768px) {
|
||||
/* Root level overflow prevention */
|
||||
html, body {
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
/* VitePress Layout container fix */
|
||||
.Layout {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* VPContent container fix */
|
||||
.VPContent {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Hero extensions in Layout.vue - add proper padding */
|
||||
.hero-extensions {
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Hero Section */
|
||||
.pro-home .hero-section {
|
||||
min-height: auto;
|
||||
padding-top: 4.5rem; /* Clear fixed nav (56px) */
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Prevent horizontal scroll */
|
||||
.pro-home {
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.pro-home .hero-container {
|
||||
max-width: 100%;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Fix section containers */
|
||||
.pro-home .section-container {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Feature Cards */
|
||||
.pro-home .feature-card {
|
||||
border-radius: 12px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.pro-home .feature-card:active {
|
||||
transform: scale(0.98);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Pipeline Animation */
|
||||
.pro-home .cadence-track {
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
|
||||
.pro-home .tick-node {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
min-width: 12px;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Terminal Window */
|
||||
.pro-home .terminal-window,
|
||||
.pro-home .qs-terminal-window {
|
||||
font-size: 0.75rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.pro-home .terminal-header,
|
||||
.pro-home .qs-terminal-header {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Code Blocks */
|
||||
.pro-home .json-code {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 0 -1rem;
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Buttons touch targets */
|
||||
.pro-home .btn-primary,
|
||||
.pro-home .btn-secondary,
|
||||
.pro-home .btn-outline,
|
||||
.pro-home .btn-ghost {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - CTA Section */
|
||||
.pro-home .cta-card {
|
||||
margin: 0 0.5rem;
|
||||
border-radius: 16px;
|
||||
max-width: calc(100% - 1rem);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Animation adjustments */
|
||||
.pro-home .reveal-text,
|
||||
.pro-home .reveal-card,
|
||||
.pro-home .reveal-slide {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Stage nodes in pipeline */
|
||||
.pro-home .stage-node {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* Quick Start Section - prevent overflow */
|
||||
.pro-home .quickstart-section {
|
||||
padding: 3rem 0;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.pro-home .quickstart-layout {
|
||||
padding: 0 12px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pro-home .quickstart-info,
|
||||
.pro-home .quickstart-terminal {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Ensure all text content wraps properly */
|
||||
.pro-home .json-text,
|
||||
.pro-home .json-benefits,
|
||||
.pro-home .qs-step-content {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Features section overflow fix */
|
||||
.pro-home .features-section {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
/* Pipeline section overflow fix */
|
||||
.pro-home .pipeline-section {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Tablet Optimizations */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.pro-home .hero-section {
|
||||
padding: 3rem 0 2.5rem;
|
||||
}
|
||||
|
||||
.pro-home .hero-title {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.pro-home .features-grid {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.pro-home .json-grid {
|
||||
gap: 2rem;
|
||||
padding: 3rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ProfessionalHome - Small Mobile (< 480px) */
|
||||
@media (max-width: 480px) {
|
||||
.pro-home .hero-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
|
||||
.pro-home .section-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.pro-home .pipeline-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Ensure touch targets */
|
||||
.pro-home .btn-primary,
|
||||
.pro-home .btn-secondary {
|
||||
padding: 0.875rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* Dark Mode Specific
|
||||
* ============================================ */
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
--vp-font-size-lg: 18px;
|
||||
--vp-font-size-xl: 20px;
|
||||
|
||||
/* Spacing */
|
||||
/* Spacing (Fixed) */
|
||||
--vp-spacing-xs: 0.25rem; /* 4px */
|
||||
--vp-spacing-sm: 0.5rem; /* 8px */
|
||||
--vp-spacing-md: 1rem; /* 16px */
|
||||
@@ -97,6 +97,25 @@
|
||||
--vp-spacing-xl: 2rem; /* 32px */
|
||||
--vp-spacing-2xl: 3rem; /* 48px */
|
||||
|
||||
/* Fluid Spacing (Responsive with clamp())
|
||||
* Scales smoothly between viewport widths
|
||||
* Usage: padding: var(--spacing-fluid-md);
|
||||
*/
|
||||
--spacing-fluid-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.375rem); /* 4-6px */
|
||||
--spacing-fluid-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); /* 8-12px */
|
||||
--spacing-fluid-md: clamp(0.75rem, 0.6rem + 0.75vw, 1.25rem); /* 12-20px */
|
||||
--spacing-fluid-lg: clamp(1rem, 0.8rem + 1vw, 1.75rem); /* 16-28px */
|
||||
--spacing-fluid-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); /* 24-40px */
|
||||
--spacing-fluid-2xl: clamp(2rem, 1.5rem + 2.5vw, 3.5rem); /* 32-56px */
|
||||
|
||||
/* Container Query Names
|
||||
* Usage: container-name: var(--container-sidebar);
|
||||
*/
|
||||
--container-sidebar: sidebar;
|
||||
--container-content: content;
|
||||
--container-outline: outline;
|
||||
--container-nav: nav;
|
||||
|
||||
/* Border Radius */
|
||||
--vp-radius-sm: 0.25rem; /* 4px */
|
||||
--vp-radius-md: 0.375rem; /* 6px */
|
||||
@@ -122,6 +141,19 @@
|
||||
--vp-z-index-fixed: 50;
|
||||
--vp-z-index-modal: 100;
|
||||
--vp-z-index-toast: 200;
|
||||
|
||||
/* Responsive Breakpoints (VitePress standard) */
|
||||
--bp-mobile: 768px; /* Mobile: < 768px */
|
||||
--bp-tablet: 1024px; /* Tablet: 768px - 1024px */
|
||||
--bp-desktop: 1440px; /* Desktop: > 1024px, large: > 1440px */
|
||||
|
||||
/* Container Query Breakpoints
|
||||
* Aligned with media query breakpoints for consistency
|
||||
*/
|
||||
--container-bp-sm: 480px; /* Small container */
|
||||
--container-bp-md: 768px; /* Medium container */
|
||||
--container-bp-lg: 1024px; /* Large container */
|
||||
--container-bp-xl: 1280px; /* Extra large container */
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
||||
Reference in New Issue
Block a user