mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: enhance CoordinatorEmptyState and ThemeSelector with gradient utilities and improved layout
This commit is contained in:
@@ -49,27 +49,18 @@ export function CoordinatorEmptyState({
|
||||
{/* Animated Gradient Orbs - Using gradient utility classes */}
|
||||
<div className="absolute top-20 left-20 w-72 h-72 rounded-full blur-3xl animate-pulse bg-gradient-primary opacity-15" />
|
||||
<div
|
||||
className="absolute bottom-20 right-20 w-96 h-96 rounded-full blur-3xl animate-pulse opacity-15"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, hsl(var(--secondary)) 0%, transparent 70%)',
|
||||
animationDelay: '1s',
|
||||
}}
|
||||
className="absolute bottom-20 right-20 w-96 h-96 rounded-full blur-3xl animate-pulse opacity-15 bg-gradient-secondary"
|
||||
style={{ animationDelay: '1s' }}
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 rounded-full blur-3xl animate-pulse bg-gradient-primary opacity-10" style={{ animationDelay: '2s' }} />
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 max-w-2xl mx-auto px-8 text-center">
|
||||
{/* Hero Icon */}
|
||||
{/* Hero Icon - Using gradient brand background */}
|
||||
<div className="relative mb-8 inline-block">
|
||||
<div
|
||||
className="absolute inset-0 rounded-full blur-2xl opacity-40 animate-pulse"
|
||||
style={{ background: 'hsl(var(--primary))' }}
|
||||
/>
|
||||
<div
|
||||
className="relative p-6 rounded-full shadow-2xl text-white"
|
||||
style={{ background: 'hsl(var(--primary))' }}
|
||||
>
|
||||
<div className="absolute inset-0 rounded-full blur-2xl opacity-40 animate-pulse bg-gradient-brand" />
|
||||
<div className="relative p-6 rounded-full shadow-2xl text-white bg-primary hover-glow-primary">
|
||||
<Rocket className="w-16 h-16" strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,23 +75,15 @@ export function CoordinatorEmptyState({
|
||||
{formatMessage({ id: 'coordinator.emptyState.subtitle' })}
|
||||
</p>
|
||||
|
||||
{/* Start Button - Using primary theme color */}
|
||||
{/* Start Button - Using gradient and glow utilities */}
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={onStart}
|
||||
disabled={disabled}
|
||||
className="group relative px-8 py-6 text-lg font-semibold shadow-lg hover:shadow-xl transition-all duration-300"
|
||||
style={{
|
||||
background: 'hsl(var(--primary))',
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
className="group relative px-8 py-6 text-lg font-semibold shadow-lg hover:shadow-xl transition-all duration-300 bg-primary text-primary-foreground hover-glow-primary"
|
||||
>
|
||||
<Play className="w-6 h-6 mr-2 group-hover:scale-110 transition-transform" />
|
||||
{formatMessage({ id: 'coordinator.emptyState.startButton' })}
|
||||
<div
|
||||
className="absolute inset-0 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity blur-xl"
|
||||
style={{ background: 'hsl(var(--primary) / 0.3)' }}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{/* Feature Cards */}
|
||||
@@ -175,38 +158,26 @@ export function CoordinatorEmptyState({
|
||||
{/* Quick Start Guide */}
|
||||
<div className="mt-12 text-left bg-card/50 backdrop-blur-sm rounded-xl p-6 border border-border">
|
||||
<h3 className="font-semibold mb-4 text-foreground flex items-center gap-2">
|
||||
<span
|
||||
className="w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-semibold"
|
||||
style={{ background: 'hsl(var(--primary))' }}
|
||||
>
|
||||
✓
|
||||
<span className="w-6 h-6 rounded-full flex items-center justify-center text-primary-foreground text-xs font-semibold bg-primary">
|
||||
ok
|
||||
</span>
|
||||
{formatMessage({ id: 'coordinator.emptyState.quickStart.title' })}
|
||||
</h3>
|
||||
<div className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white"
|
||||
style={{ background: 'hsl(var(--primary))' }}
|
||||
>
|
||||
<span className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white bg-primary">
|
||||
1
|
||||
</span>
|
||||
<p>{formatMessage({ id: 'coordinator.emptyState.quickStart.step1' })}</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white"
|
||||
style={{ background: 'hsl(var(--secondary))' }}
|
||||
>
|
||||
<span className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white bg-secondary">
|
||||
2
|
||||
</span>
|
||||
<p>{formatMessage({ id: 'coordinator.emptyState.quickStart.step2' })}</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white"
|
||||
style={{ background: 'hsl(var(--accent))' }}
|
||||
>
|
||||
<span className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 mt-0.5 text-white bg-accent">
|
||||
3
|
||||
</span>
|
||||
<p>{formatMessage({ id: 'coordinator.emptyState.quickStart.step3' })}</p>
|
||||
|
||||
@@ -192,25 +192,31 @@ export function ThemeSelector() {
|
||||
/>
|
||||
|
||||
{/* Preview Swatches */}
|
||||
<div className="flex gap-2 items-center">
|
||||
<span className="text-xs text-text-secondary mr-2">
|
||||
<div className="flex gap-3 items-end">
|
||||
<span className="text-xs text-text-secondary pb-1">
|
||||
{formatMessage({ id: 'theme.preview' })}:
|
||||
</span>
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--bg') }}
|
||||
title="Background"
|
||||
/>
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--surface') }}
|
||||
title="Surface"
|
||||
/>
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--accent') }}
|
||||
title="Accent"
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--bg') }}
|
||||
/>
|
||||
<span className="text-[10px] text-text-tertiary">{formatMessage({ id: 'theme.preview.background' })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--surface') }}
|
||||
/>
|
||||
<span className="text-[10px] text-text-tertiary">{formatMessage({ id: 'theme.preview.surface' })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div
|
||||
className="w-10 h-10 rounded border-2 border-border shadow-sm"
|
||||
style={{ backgroundColor: getPreviewColor('--accent') }}
|
||||
/>
|
||||
<span className="text-[10px] text-text-tertiary">{formatMessage({ id: 'theme.preview.accent' })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save and Reset Buttons */}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
"current": "Current theme: {name}",
|
||||
"hueValue": "Hue: {value}°",
|
||||
"preview": "Preview",
|
||||
"preview.background": "Background",
|
||||
"preview.surface": "Card",
|
||||
"preview.accent": "Accent",
|
||||
"save": "Save Custom Theme",
|
||||
"reset": "Reset to Preset"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
"current": "当前主题: {name}",
|
||||
"hueValue": "色调: {value}°",
|
||||
"preview": "预览",
|
||||
"preview.background": "背景",
|
||||
"preview.surface": "卡片",
|
||||
"preview.accent": "强调色",
|
||||
"save": "保存自定义主题",
|
||||
"reset": "重置为预设"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ const gradientPlugin = plugin(function({ addUtilities, addComponents }) {
|
||||
'.bg-gradient-primary': {
|
||||
backgroundImage: 'radial-gradient(circle, hsl(var(--accent)) 0%, transparent 70%)',
|
||||
},
|
||||
'.bg-gradient-secondary': {
|
||||
backgroundImage: 'radial-gradient(circle, hsl(var(--secondary)) 0%, transparent 70%)',
|
||||
},
|
||||
'.bg-gradient-brand': {
|
||||
backgroundImage: 'linear-gradient(to right, hsl(var(--primary)), hsl(var(--secondary)))',
|
||||
},
|
||||
@@ -17,6 +20,19 @@ const gradientPlugin = plugin(function({ addUtilities, addComponents }) {
|
||||
'.bg-gradient-conic': {
|
||||
backgroundImage: 'conic-gradient(var(--tw-gradient-stops))',
|
||||
},
|
||||
// Hover glow effect utilities
|
||||
'.hover-glow': {
|
||||
transition: 'box-shadow 0.3s ease-in-out',
|
||||
'&:hover': {
|
||||
boxShadow: '0 0 40px 10px hsl(var(--accent) / 0.7)',
|
||||
},
|
||||
},
|
||||
'.hover-glow-primary': {
|
||||
transition: 'box-shadow 0.3s ease-in-out',
|
||||
'&:hover': {
|
||||
boxShadow: '0 0 40px 10px hsl(var(--primary) / 0.5)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Gradient border component
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
import { afterEach, describe, it, mock } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const cliCommandPath = new URL('../dist/commands/cli.js', import.meta.url).href;
|
||||
const cliExecutorPath = new URL('../dist/tools/cli-executor.js', import.meta.url).href;
|
||||
const historyStorePath = new URL('../dist/tools/cli-history-store.js', import.meta.url).href;
|
||||
|
||||
function stubHttpRequest() {
|
||||
mock.method(http, 'request', () => {
|
||||
@@ -35,8 +39,17 @@ describe('ccw cli exec --final', async () => {
|
||||
it('writes only finalOutput to stdout (no banner/summary)', async () => {
|
||||
stubHttpRequest();
|
||||
|
||||
const testHome = mkdtempSync(join(tmpdir(), 'ccw-cli-final-only-'));
|
||||
const prevHome = process.env.CCW_DATA_DIR;
|
||||
process.env.CCW_DATA_DIR = testHome;
|
||||
|
||||
// Ensure the CLI doesn't wait for stdin in Node's test runner environment.
|
||||
const prevStdinIsTty = process.stdin.isTTY;
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
|
||||
|
||||
const cliModule = await import(cliCommandPath);
|
||||
const cliExecutorModule = await import(cliExecutorPath);
|
||||
const historyStoreModule = await import(historyStorePath);
|
||||
|
||||
const stdoutWrites = [];
|
||||
mock.method(process.stdout, 'write', (chunk) => {
|
||||
@@ -72,5 +85,14 @@ describe('ccw cli exec --final', async () => {
|
||||
await cliModule.cliCommand('exec', [], { prompt: 'Hello', tool: 'gemini', final: true });
|
||||
|
||||
assert.equal(stdoutWrites.join(''), 'FINAL');
|
||||
|
||||
try {
|
||||
historyStoreModule?.closeAllStores?.();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: prevStdinIsTty, configurable: true });
|
||||
process.env.CCW_DATA_DIR = prevHome;
|
||||
rmSync(testHome, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user