Files
ai-proj-helper/skills-dev/frontend-design-plugin/skills/SKILL.md
John Qiu de25f096e7 feat(sync): add install-skills.sh + install metadata to all 62 plugins
- Add install_name, install_type, dir_category fields to all 62 plugin.json files
  to resolve name-mapping and skill-vs-command routing issues
- Add install-skills.sh: idempotent cross-machine skill sync script
  - Routes skill→~/.claude/skills/<name>/, command→~/.claude/commands/<name>.md
  - rsync full skills/ directory (preserves multi-file skills like dev-test, req-deploy)
  - State file ~/.claude/.installed-skills.json tracks installed versions
  - Conflict detection: warns before overwriting locally modified files
  - --dry-run, --category, --force, --cleanup, --list flags
- Add 9 new plugins migrated from local ~/.claude (agent-swarm, ai-chat,
  defect-analysis, executing-plans, finishing-branch, frontend-design,
  req-audit, req-lookback, req-retro)
- Add update-plugin-meta.py helper used to bulk-update plugin.json
- Fix siyuan SKILL.md: remove hardcoded server credentials, use env vars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 23:55:26 +09:30

696 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
arguments: [component|page|storybook] <description>
---
# Frontend Design 前端设计技能
创建高质量、有设计感的前端界面和组件,支持 Storybook 组件开发。
---
## 命令格式
| 命令 | 功能 | 示例 |
|------|------|------|
| `/frontend-design component <描述>` | 创建 React/Vue 组件 | `/frontend-design component 产品卡片` |
| `/frontend-design page <描述>` | 创建完整页面 | `/frontend-design page 登录页` |
| `/frontend-design storybook <描述>` | 创建带 Storybook 的组件 | `/frontend-design storybook 按钮组件` |
---
## 设计原则
### 1. 设计思维先行
在编码前,明确以下问题:
- **目的**:这个界面解决什么问题?谁在使用?
- **调性**:选择一个明确的美学方向
- **差异化**:什么让这个设计令人难忘?
### 2. 美学方向选择
| 风格 | 特点 | 适用场景 |
|------|------|----------|
| 极简主义 | 大量留白、精炼元素 | 工具类、专业平台 |
| 现代商务 | 清晰层次、专业配色 | 企业官网、B2B |
| 活力年轻 | 鲜艳色彩、动感动画 | 消费品、社交 |
| 奢华精致 | 深色调、金属质感 | 高端品牌、金融 |
| 自然有机 | 柔和曲线、自然色系 | 健康、环保 |
| 复古怀旧 | 经典字体、做旧质感 | 文化、艺术 |
| 未来科技 | 渐变、玻璃拟态 | 科技、创新 |
### 3. 避免的设计陷阱
**禁止使用**
- 过度使用的字体Inter、Roboto、Arial
- 陈词滥调的配色:紫色渐变白底
- 千篇一律的布局
- 缺乏个性的通用组件
**应该追求**
- 独特的字体组合
- 有意图的配色方案
- 打破常规的布局
- 有记忆点的细节
---
## Storybook 组件开发
### 项目结构
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.stories.tsx
│ │ ├── Button.module.css
│ │ └── index.ts
│ ├── Card/
│ │ ├── Card.tsx
│ │ ├── Card.stories.tsx
│ │ ├── Card.module.css
│ │ └── index.ts
│ └── index.ts
├── styles/
│ ├── variables.css
│ ├── typography.css
│ └── animations.css
└── .storybook/
├── main.ts
└── preview.ts
```
### 组件模板
#### 1. 组件文件 (Component.tsx)
```tsx
import React from 'react';
import styles from './Component.module.css';
export interface ComponentProps {
/** 组件变体 */
variant?: 'primary' | 'secondary' | 'outline';
/** 尺寸 */
size?: 'sm' | 'md' | 'lg';
/** 是否禁用 */
disabled?: boolean;
/** 子元素 */
children: React.ReactNode;
/** 点击事件 */
onClick?: () => void;
}
export const Component: React.FC<ComponentProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
children,
onClick,
}) => {
return (
<div
className={`${styles.component} ${styles[variant]} ${styles[size]}`}
data-disabled={disabled}
onClick={disabled ? undefined : onClick}
>
{children}
</div>
);
};
```
#### 2. Storybook Stories (Component.stories.tsx)
```tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Component } from './Component';
const meta: Meta<typeof Component> = {
title: 'Components/Component',
component: Component,
tags: ['autodocs'],
parameters: {
layout: 'centered',
docs: {
description: {
component: '组件描述文档',
},
},
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline'],
description: '组件变体样式',
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg'],
description: '组件尺寸',
},
disabled: {
control: 'boolean',
description: '是否禁用',
},
},
};
export default meta;
type Story = StoryObj<typeof Component>;
/** 默认状态 */
export const Default: Story = {
args: {
children: '默认组件',
},
};
/** 主要变体 */
export const Primary: Story = {
args: {
variant: 'primary',
children: '主要按钮',
},
};
/** 次要变体 */
export const Secondary: Story = {
args: {
variant: 'secondary',
children: '次要按钮',
},
};
/** 不同尺寸 */
export const Sizes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Component size="sm"></Component>
<Component size="md"></Component>
<Component size="lg"></Component>
</div>
),
};
/** 禁用状态 */
export const Disabled: Story = {
args: {
disabled: true,
children: '禁用状态',
},
};
```
#### 3. 样式文件 (Component.module.css)
```css
.component {
/* 基础样式 */
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
font-family: var(--font-sans);
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
/* 变体 */
.primary {
background: var(--color-primary);
color: white;
}
.primary:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--color-primary-shadow);
}
.secondary {
background: var(--color-secondary);
color: var(--color-text);
}
.outline {
background: transparent;
border: 2px solid var(--color-border);
color: var(--color-text);
}
/* 尺寸 */
.sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.md {
padding: 0.75rem 1.5rem;
font-size: 1rem;
}
.lg {
padding: 1rem 2rem;
font-size: 1.125rem;
}
/* 状态 */
[data-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
```
---
## 设计系统变量
### CSS 变量模板
```css
:root {
/* 颜色 */
--color-primary: #0066ff;
--color-primary-dark: #0052cc;
--color-primary-light: #4d94ff;
--color-primary-shadow: rgba(0, 102, 255, 0.25);
--color-secondary: #f0f4f8;
--color-accent: #ff6b35;
--color-text: #1a1a2e;
--color-text-muted: #64748b;
--color-text-inverse: #ffffff;
--color-background: #ffffff;
--color-surface: #f8fafc;
--color-border: #e2e8f0;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* 字体 */
--font-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-display: 'Clash Display', var(--font-sans);
--font-mono: 'JetBrains Mono', monospace;
/* 字号 */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
--text-4xl: 2.5rem;
/* 间距 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
/* 圆角 */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-xl: 1.5rem;
--radius-full: 9999px;
/* 阴影 */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* 动画 */
--duration-fast: 150ms;
--duration-normal: 300ms;
--duration-slow: 500ms;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* 暗色主题 */
[data-theme="dark"] {
--color-text: #f1f5f9;
--color-text-muted: #94a3b8;
--color-background: #0f172a;
--color-surface: #1e293b;
--color-border: #334155;
}
```
---
## 常用组件示例
### 1. 产品卡片 (ProductCard)
```tsx
// ProductCard.tsx
import React from 'react';
import styles from './ProductCard.module.css';
export interface ProductCardProps {
image: string;
title: string;
location: string;
rating: number;
reviewCount: number;
price: number;
originalPrice?: number;
tags?: string[];
onAddToCart?: () => void;
}
export const ProductCard: React.FC<ProductCardProps> = ({
image,
title,
location,
rating,
reviewCount,
price,
originalPrice,
tags = [],
onAddToCart,
}) => {
return (
<article className={styles.card}>
<div className={styles.imageWrapper}>
<img src={image} alt={title} className={styles.image} />
{tags.length > 0 && (
<div className={styles.tags}>
{tags.map((tag) => (
<span key={tag} className={styles.tag} data-tag={tag}>
{tag}
</span>
))}
</div>
)}
</div>
<div className={styles.content}>
<h3 className={styles.title}>{title}</h3>
<p className={styles.location}>📍 {location}</p>
<div className={styles.rating}>
<span className={styles.stars}> {rating.toFixed(1)}</span>
<span className={styles.reviewCount}>({reviewCount})</span>
</div>
<div className={styles.priceRow}>
<div className={styles.price}>
<span className={styles.currency}>¥</span>
<span className={styles.amount}>{price}</span>
<span className={styles.suffix}></span>
</div>
{originalPrice && (
<span className={styles.originalPrice}>¥{originalPrice}</span>
)}
</div>
<button className={styles.addButton} onClick={onAddToCart}>
</button>
</div>
</article>
);
};
```
```tsx
// ProductCard.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ProductCard } from './ProductCard';
const meta: Meta<typeof ProductCard> = {
title: 'Components/ProductCard',
component: ProductCard,
tags: ['autodocs'],
parameters: {
layout: 'centered',
backgrounds: {
default: 'light',
},
},
};
export default meta;
type Story = StoryObj<typeof ProductCard>;
export const Default: Story = {
args: {
image: 'https://images.unsplash.com/photo-1494947665470-20322015e3a8',
title: '袋鼠岛一日游',
location: '阿德莱德出发',
rating: 4.8,
reviewCount: 126,
price: 389,
tags: ['热卖', '含午餐'],
},
};
export const WithDiscount: Story = {
args: {
...Default.args,
originalPrice: 499,
tags: ['特惠', '限时'],
},
};
export const Grid: Story = {
render: () => (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 300px)',
gap: '1.5rem'
}}>
<ProductCard
image="https://images.unsplash.com/photo-1494947665470-20322015e3a8"
title="袋鼠岛一日游"
location="阿德莱德出发"
rating={4.8}
reviewCount={126}
price={389}
tags={['热卖']}
/>
<ProductCard
image="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
title="巴罗莎谷酒庄之旅"
location="阿德莱德出发"
rating={4.9}
reviewCount={89}
price={299}
originalPrice={399}
tags={['特惠', '含品酒']}
/>
<ProductCard
image="https://images.unsplash.com/photo-1540202403-b7abd6747a18"
title="海豚巡航体验"
location="格雷尔海滩"
rating={4.7}
reviewCount={234}
price={159}
tags={['亲子']}
/>
</div>
),
};
```
### 2. 按钮组件 (Button)
```tsx
// Button.tsx
import React from 'react';
import styles from './Button.module.css';
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
fullWidth?: boolean;
loading?: boolean;
disabled?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
children: React.ReactNode;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
fullWidth = false,
loading = false,
disabled = false,
leftIcon,
rightIcon,
children,
onClick,
}) => {
return (
<button
className={`
${styles.button}
${styles[variant]}
${styles[size]}
${fullWidth ? styles.fullWidth : ''}
`}
disabled={disabled || loading}
onClick={onClick}
>
{loading ? (
<span className={styles.spinner} />
) : (
<>
{leftIcon && <span className={styles.icon}>{leftIcon}</span>}
<span>{children}</span>
{rightIcon && <span className={styles.icon}>{rightIcon}</span>}
</>
)}
</button>
);
};
```
---
## Storybook 配置
### .storybook/main.ts
```ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
```
### .storybook/preview.ts
```ts
import type { Preview } from '@storybook/react';
import '../src/styles/variables.css';
import '../src/styles/typography.css';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'gray', value: '#f8fafc' },
{ name: 'dark', value: '#0f172a' },
],
},
},
globalTypes: {
theme: {
description: 'Global theme for components',
defaultValue: 'light',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: ['light', 'dark'],
dynamicTitle: true,
},
},
},
};
export default preview;
```
---
## 快速启动命令
### 创建新组件
```bash
# 创建组件目录
mkdir -p src/components/ComponentName
# 创建文件
touch src/components/ComponentName/{ComponentName.tsx,ComponentName.stories.tsx,ComponentName.module.css,index.ts}
```
### 安装 Storybook
```bash
# 初始化 Storybook
npx storybook@latest init
# 安装额外插件
npm install -D @storybook/addon-a11y @storybook/addon-interactions
# 启动 Storybook
npm run storybook
```
---
## 设计检查清单
### 组件质量检查
- [ ] Props 接口定义完整,带 JSDoc 注释
- [ ] 支持必要的变体variant和尺寸size
- [ ] 处理禁用和加载状态
- [ ] 支持自定义 className
- [ ] 键盘可访问性
- [ ] 屏幕阅读器友好
### Storybook 质量检查
- [ ] 所有变体都有对应 Story
- [ ] argTypes 配置完整
- [ ] 包含组件文档描述
- [ ] 交互状态可测试
- [ ] 响应式展示
### 视觉质量检查
- [ ] 字体选择有特色
- [ ] 配色方案协调
- [ ] 动画流畅自然
- [ ] 间距一致
- [ ] 暗色主题支持