mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-02 15:23:19 +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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user