mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +08:00
feat(theme): implement dynamic theme logo with reactive color updates
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Line-style logo for Claude Code Workflow
|
// Line-style logo for Claude Code Workflow
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface CCWLogoProps {
|
interface CCWLogoProps {
|
||||||
@@ -14,11 +15,54 @@ interface CCWLogoProps {
|
|||||||
showDot?: boolean;
|
showDot?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get reactive theme accent color
|
||||||
|
*/
|
||||||
|
function useThemeAccentColor(): string {
|
||||||
|
const [accentColor, setAccentColor] = useState<string>(() => {
|
||||||
|
if (typeof document === 'undefined') return 'hsl(220, 60%, 65%)';
|
||||||
|
const root = document.documentElement;
|
||||||
|
const accentValue = getComputedStyle(root).getPropertyValue('--accent').trim();
|
||||||
|
return accentValue ? `hsl(${accentValue})` : 'hsl(220, 60%, 65%)';
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateAccentColor = () => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const accentValue = getComputedStyle(root).getPropertyValue('--accent').trim();
|
||||||
|
setAccentColor(accentValue ? `hsl(${accentValue})` : 'hsl(220, 60%, 65%)');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
updateAccentColor();
|
||||||
|
|
||||||
|
// Watch for theme changes via MutationObserver
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.attributeName === 'data-theme') {
|
||||||
|
updateAccentColor();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Line-style CCW logo component
|
* Line-style CCW logo component
|
||||||
* Features three horizontal lines with a status dot that follows theme color
|
* Features three horizontal lines with a status dot that follows theme color
|
||||||
*/
|
*/
|
||||||
export function CCWLogo({ size = 24, className, showDot = true }: CCWLogoProps) {
|
export function CCWLogo({ size = 24, className, showDot = true }: CCWLogoProps) {
|
||||||
|
const accentColor = useThemeAccentColor();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={size}
|
width={size}
|
||||||
@@ -27,7 +71,7 @@ export function CCWLogo({ size = 24, className, showDot = true }: CCWLogoProps)
|
|||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className={cn('ccw-logo', className)}
|
className={cn('ccw-logo', className)}
|
||||||
style={{ color: 'hsl(var(--accent))' }}
|
style={{ color: accentColor }}
|
||||||
aria-label="Claude Code Workflow"
|
aria-label="Claude Code Workflow"
|
||||||
>
|
>
|
||||||
{/* Three horizontal lines - line style */}
|
{/* Three horizontal lines - line style */}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function Header({
|
|||||||
to="/"
|
to="/"
|
||||||
className="flex items-center gap-2 text-lg font-semibold hover:opacity-80 transition-opacity"
|
className="flex items-center gap-2 text-lg font-semibold hover:opacity-80 transition-opacity"
|
||||||
>
|
>
|
||||||
<CCWLogo size={24} className="text-primary" />
|
<CCWLogo size={24} />
|
||||||
<span className="hidden sm:inline text-primary">{formatMessage({ id: 'navigation.header.brand' })}</span>
|
<span className="hidden sm:inline text-primary">{formatMessage({ id: 'navigation.header.brand' })}</span>
|
||||||
<span className="sm:hidden text-primary">{formatMessage({ id: 'navigation.header.brandShort' })}</span>
|
<span className="sm:hidden text-primary">{formatMessage({ id: 'navigation.header.brandShort' })}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -870,6 +870,16 @@
|
|||||||
/* ===========================
|
/* ===========================
|
||||||
CCW Logo
|
CCW Logo
|
||||||
=========================== */
|
=========================== */
|
||||||
.ccw-logo {
|
.ccw-logo,
|
||||||
color: hsl(var(--accent));
|
svg.ccw-logo,
|
||||||
|
header .ccw-logo {
|
||||||
|
color: hsl(var(--accent)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure dot inherits color */
|
||||||
|
.ccw-logo circle,
|
||||||
|
svg.ccw-logo circle {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme-specific accent colors are applied automatically via --accent variable */
|
||||||
|
|||||||
@@ -509,9 +509,9 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-container {
|
.section-container {
|
||||||
max-width: 1152px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,13 +529,13 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero-container {
|
.hero-container {
|
||||||
max-width: 1152px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
padding: 0 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +568,7 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero-title {
|
.hero-title {
|
||||||
font-size: 3.5rem;
|
font-size: 2.75rem;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
@@ -863,8 +863,10 @@ onUnmounted(() => {
|
|||||||
grid-template-columns: 1fr 1.2fr;
|
grid-template-columns: 1fr 1.2fr;
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5rem 0;
|
padding: 5rem 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.json-text h2 { font-size: 2.25rem; font-weight: 700; margin-bottom: 1.25rem; color: var(--vp-c-text-1); line-height: 1.2; }
|
.json-text h2 { font-size: 2.25rem; font-weight: 700; margin-bottom: 1.25rem; color: var(--vp-c-text-1); line-height: 1.2; }
|
||||||
.json-text p { font-size: 1.05rem; color: var(--vp-c-text-2); margin-bottom: 2rem; line-height: 1.7; }
|
.json-text p { font-size: 1.05rem; color: var(--vp-c-text-2); margin-bottom: 2rem; line-height: 1.7; }
|
||||||
@@ -910,6 +912,9 @@ onUnmounted(() => {
|
|||||||
grid-template-columns: 0.85fr 1.15fr;
|
grid-template-columns: 0.85fr 1.15fr;
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quickstart-title {
|
.quickstart-title {
|
||||||
@@ -1037,6 +1042,8 @@ onUnmounted(() => {
|
|||||||
.cta-card {
|
.cta-card {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3.5rem 2rem;
|
padding: 3.5rem 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
background: var(--vp-c-bg-soft);
|
background: var(--vp-c-bg-soft);
|
||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
|||||||
64
docs/.vitepress/theme/components/ThemeLogo.vue
Normal file
64
docs/.vitepress/theme/components/ThemeLogo.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
const dotColor = ref('var(--vp-c-primary)')
|
||||||
|
|
||||||
|
function updateDotColor() {
|
||||||
|
if (typeof document === 'undefined') return
|
||||||
|
|
||||||
|
const root = document.documentElement
|
||||||
|
const style = getComputedStyle(root)
|
||||||
|
const primaryColor = style.getPropertyValue('--vp-c-primary').trim()
|
||||||
|
dotColor.value = primaryColor || 'currentColor'
|
||||||
|
}
|
||||||
|
|
||||||
|
let observer: MutationObserver | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateDotColor()
|
||||||
|
|
||||||
|
// Watch for theme changes via MutationObserver
|
||||||
|
observer = new MutationObserver(() => {
|
||||||
|
updateDotColor()
|
||||||
|
})
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme', 'class'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
observer?.disconnect()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
class="theme-logo"
|
||||||
|
aria-label="Claude Code Workflow"
|
||||||
|
>
|
||||||
|
<!-- Three horizontal lines - use currentColor to inherit from text -->
|
||||||
|
<line x1="3" y1="6" x2="18" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="3" y1="12" x2="15" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="3" y1="18" x2="12" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<!-- Status dot - follows theme primary color -->
|
||||||
|
<circle cx="19" cy="17" r="3" :style="{ fill: dotColor }"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.theme-logo {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-logo circle {
|
||||||
|
fill: var(--vp-c-primary);
|
||||||
|
transition: fill 0.3s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import { onBeforeUnmount, onMounted } from 'vue'
|
import { onBeforeUnmount, onMounted } from 'vue'
|
||||||
import { useDynamicIcon } from '../composables/useDynamicIcon'
|
import { useDynamicIcon } from '../composables/useDynamicIcon'
|
||||||
|
import ThemeLogo from '../components/ThemeLogo.vue'
|
||||||
|
|
||||||
let mediaQuery: MediaQueryList | null = null
|
let mediaQuery: MediaQueryList | null = null
|
||||||
let systemThemeChangeHandler: (() => void) | null = null
|
let systemThemeChangeHandler: (() => void) | null = null
|
||||||
@@ -50,6 +51,11 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DefaultTheme.Layout>
|
<DefaultTheme.Layout>
|
||||||
|
<!-- Custom logo in navbar that follows theme color -->
|
||||||
|
<template #nav-bar-title-before>
|
||||||
|
<ThemeLogo class="nav-logo" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #home-hero-after>
|
<template #home-hero-after>
|
||||||
<div class="hero-extensions">
|
<div class="hero-extensions">
|
||||||
<div class="hero-stats">
|
<div class="hero-stats">
|
||||||
@@ -120,6 +126,18 @@ onBeforeUnmount(() => {
|
|||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the default VitePress logo image since we use our custom component */
|
||||||
|
:deep(.VPNavBarTitle .logo) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.skip-link {
|
.skip-link {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -100px;
|
top: -100px;
|
||||||
|
|||||||
@@ -63,10 +63,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Adjust sidebar and content layout */
|
/* Adjust sidebar and content layout */
|
||||||
.VPDoc,
|
/* NOTE: Removed duplicate padding-left - VitePress already handles sidebar layout */
|
||||||
.VPDoc[data-v-343c73d6] {
|
/* .VPDoc, .VPDoc[data-v-343c73d6] { padding-left: var(--vp-sidebar-width) !important; } */
|
||||||
padding-left: var(--vp-sidebar-width) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Right side outline (TOC) adjustments */
|
/* Right side outline (TOC) adjustments */
|
||||||
.VPDocOutline {
|
.VPDocOutline {
|
||||||
@@ -82,34 +80,41 @@
|
|||||||
/* ============================================
|
/* ============================================
|
||||||
* Home Page Override
|
* Home Page Override
|
||||||
* ============================================ */
|
* ============================================ */
|
||||||
.VPHome {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove horizontal padding for home page only (has .VPHome class) */
|
/* Use :has() to detect home page (contains .pro-home) */
|
||||||
.VPHome .VPDoc {
|
.VPContent:has(.pro-home) {
|
||||||
padding-left: 0 !important;
|
padding: 0 !important;
|
||||||
padding-right: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
.VPHome .VPDoc .content-container {
|
|
||||||
padding-left: 0 !important;
|
|
||||||
padding-right: 0 !important;
|
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPHome .VPDoc .content {
|
.Layout:has(.pro-home) {
|
||||||
padding-left: 0 !important;
|
max-width: 100% !important;
|
||||||
padding-right: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPHome .VPContent {
|
/* ProfessionalHome component full width */
|
||||||
|
.pro-home {
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure all sections extend to full width */
|
||||||
|
.pro-home .hero-section,
|
||||||
|
.pro-home .features-section,
|
||||||
|
.pro-home .pipeline-section,
|
||||||
|
.pro-home .json-section,
|
||||||
|
.pro-home .quickstart-section,
|
||||||
|
.pro-home .cta-section {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
padding-right: 0 !important;
|
padding-right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPHomeHero {
|
.VPHomeHero {
|
||||||
padding: 80px 24px;
|
padding: 80px 0;
|
||||||
background: linear-gradient(180deg, var(--vp-c-bg-soft) 0%, var(--vp-c-bg) 100%);
|
background: linear-gradient(180deg, var(--vp-c-bg-soft) 0%, var(--vp-c-bg) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user