Files
2025-11-29 18:45:50 +08:00

944 lines
24 KiB
Markdown

---
description: Scaffold shadcn/ui components with distinctive design, accessibility, and animation best practices built-in. Prevents generic aesthetics from the start.
---
# Component Generator Command
<command_purpose> Generate shadcn/ui components with distinctive design patterns, deep customization, accessibility features, and engaging animations built-in. Prevents generic "AI aesthetic" by providing branded templates from the start. </command_purpose>
## Introduction
<role>Senior Component Architect with expertise in shadcn/ui, React 19 with hooks, Tailwind CSS, accessibility, and distinctive design patterns</role>
**Design Philosophy**: Start with distinctive, accessible, engaging components rather than fixing generic patterns later.
## Prerequisites
<requirements>
- Tanstack Start project with Vue 3
- shadcn/ui component library installed
- Tailwind 4 CSS configured with custom theme (or will be created)
- (Optional) shadcn/ui MCP server for component API validation
- (Optional) Existing `composables/useDesignSystem.ts` for consistent patterns
</requirements>
## Command Usage
```bash
/es-component <type> <name> [options]
```
### Arguments:
- `<type>`: Component type (button, card, form, modal, hero, navigation, etc.)
- `<name>`: Component name in PascalCase (e.g., `PrimaryButton`, `FeatureCard`)
- `[options]`: Optional flags:
- `--theme <dark|light|custom>`: Theme variant
- `--animations <minimal|standard|rich>`: Animation complexity
- `--accessible`: Include enhanced accessibility features (default: true)
- `--output <path>`: Custom output path (default: `components/`)
### Examples:
```bash
# Generate primary button component
/es-component button PrimaryButton
# Generate feature card with rich animations
/es-component card FeatureCard --animations rich
# Generate hero section with custom theme
/es-component hero LandingHero --theme custom
# Generate modal with custom output path
/es-component modal ConfirmDialog --output components/dialogs/
```
## Main Tasks
### 1. Project Context Analysis
<thinking>
First, I need to understand existing design system, theme configuration, and component patterns.
This ensures generated components match existing project aesthetics.
</thinking>
#### Immediate Actions:
<task_list>
- [ ] Check for `tailwind.config.ts` and extract custom theme (fonts, colors, animations)
- [ ] Check for `composables/useDesignSystem.ts` and extract existing variants
- [ ] Check for `app.config.ts` and extract shadcn/ui global customization
- [ ] Scan existing components for naming conventions and structure patterns
- [ ] Determine if design system is established or needs creation
</task_list>
#### Output Summary:
<summary_format>
📦 **Project Context**:
- Custom fonts: Found/Not Found (Inter ❌ or Custom ✅)
- Brand colors: Found/Not Found (Purple ❌ or Custom ✅)
- Design system composable: Exists/Missing
- Component count: X components found
- Naming convention: Detected pattern
</summary_format>
### 2. Validate Component Type with MCP (if available)
<thinking>
If shadcn/ui MCP is available, validate that the requested component type exists
and get accurate props/slots before generating.
</thinking>
#### MCP Validation:
<mcp_workflow>
If shadcn/ui MCP available:
1. Query `shadcn-ui.list_components()` to get available components
2. Map component type to shadcn/ui component:
- `button``Button`
- `card``Card`
- `modal``Dialog`
- `form``UForm` + `Input`/`UTextarea`/etc.
- `hero` → Custom layout with `Button`, `Card`
- `navigation``UTabs` or custom
3. Query `shadcn-ui.get_component("Button")` for accurate props
4. Use real props in generated component (prevent hallucination)
If MCP not available:
- Use documented shadcn/ui API
- Include comment: "// TODO: Verify props with shadcn/ui docs"
</mcp_workflow>
### 3. Generate Component with Design Best Practices
<thinking>
Generate React component with:
1. Distinctive typography (custom fonts, not Inter)
2. Brand colors (custom palette, not purple)
3. Rich animations (transitions, micro-interactions)
4. Deep shadcn/ui customization (ui prop + utilities)
5. Accessibility features (ARIA, keyboard, focus states)
6. Responsive design
</thinking>
#### Component Templates by Type:
#### Button Component
<button_template>
```tsx
<!-- app/components/PrimaryButton.tsx -->
<script setup lang="ts">
import { computed } from 'react';
interface Props {
/** Button label */
label?: string;
/** Icon name (Iconify format) */
icon?: string;
/** Loading state */
loading?: boolean;
/** Disabled state */
disabled?: boolean;
/** Button size */
size?: 'sm' | 'md' | 'lg' | 'xl';
/** Full width */
fullWidth?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
label: '',
icon: '',
loading: false,
disabled: false,
size: 'lg',
fullWidth: false
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
const buttonClasses = computed(() => ({
'w-full': props.fullWidth
}));
<Button
:color="primary"
:size="size"
loading={loading"
disabled={disabled || loading"
:icon="icon"
:ui="{
font: 'font-heading tracking-wide',
rounded: 'rounded-full',
padding: {
sm: 'px-4 py-2',
md: 'px-6 py-3',
lg: 'px-8 py-4',
xl: 'px-10 py-5'
},
shadow: 'shadow-lg hover:shadow-xl'
}"
:class="[
'transition-all duration-300 ease-out',
'hover:scale-105 hover:-rotate-1',
'active:scale-95 active:rotate-0',
'focus:outline-none',
'focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2',
'motion-safe:hover:scale-105',
'motion-reduce:hover:bg-primary-700',
buttonClasses
]"
onClick={emit('click', $event)"
>
<span class="inline-flex items-center gap-2">
<slot>{ label}</slot>
<!-- Animated icon on hover -->
<Icon
{&& "icon && !loading"
:name="icon"
class="
transition-transform duration-300
group-hover:translate-x-1 group-hover:-translate-y-0.5
"
/>
</span>
</Button>
```
**Usage Example**:
```tsx
const handleClick = () => {
console.log('Clicked!');
};
<PrimaryButton
label="Get Started"
icon="i-heroicons-arrow-right"
size="lg"
onClick={handleClick"
/>
```
</button_template>
#### Card Component
<card_template>
```tsx
<!-- app/components/FeatureCard.tsx -->
<script setup lang="ts">
import { ref } from 'react';
interface Props {
/** Card title */
title: string;
/** Card description */
description?: string;
/** Icon name */
icon?: string;
/** Enable hover effects */
hoverable?: boolean;
/** Card variant */
variant?: 'default' | 'elevated' | 'outlined';
}
const props = withDefaults(defineProps<Props>(), {
description: '',
icon: '',
hoverable: true,
variant: 'elevated'
});
const isHovered = ref(false);
const cardUi = computed(() => ({
background: 'bg-white dark:bg-brand-midnight',
ring: props.variant === 'outlined' ? 'ring-1 ring-brand-coral/20' : '',
rounded: 'rounded-2xl',
shadow: props.variant === 'elevated' ? 'shadow-xl hover:shadow-2xl' : 'shadow-md',
body: {
padding: 'p-8',
background: 'bg-gradient-to-br from-white to-gray-50 dark:from-brand-midnight dark:to-gray-900'
}
}));
<Card
:ui="cardUi"
:class="[
'transition-all duration-300',
hoverable && 'hover:-translate-y-2 hover:rotate-1 cursor-pointer',
'motion-safe:hover:-translate-y-2',
'motion-reduce:hover:shadow-xl'
]"
onMouseEnter="isHovered = true"
onMouseLeave="isHovered = false"
>
<div class="space-y-4">
<!-- Icon -->
<div
{&& "icon"
:class="[
'inline-flex items-center justify-center',
'w-16 h-16 rounded-2xl',
'bg-gradient-to-br from-brand-coral to-brand-ocean',
'transition-transform duration-300',
isHovered && 'scale-110 rotate-3'
]"
>
<Icon
:name="icon"
class="w-8 h-8 text-white"
/>
</div>
<!-- Title -->
<h3
:class="[
'font-heading text-2xl',
'text-brand-midnight dark:text-white',
'transition-colors duration-300',
isHovered && 'text-brand-coral'
]"
>
{ title}
</h3>
<!-- Description -->
<p
{&& "description"
class="text-gray-700 dark:text-gray-300 leading-relaxed"
>
{ description}
</p>
<!-- Default slot for custom content -->
<div {&& "$slots.default">
<slot />
</div>
<!-- Footer slot -->
<div {&& "$slots.footer" class="pt-4 border-t border-gray-200 dark:border-gray-700">
<slot name="footer" />
</div>
</div>
</Card>
```
**Usage Example**:
```tsx
<FeatureCard
title="Fast Deployment"
description="Deploy to the edge in seconds with Cloudflare Workers"
icon="i-heroicons-rocket-launch"
hoverable
>
<template #footer>
<PrimaryButton label="Learn More" size="sm" />
</FeatureCard>
```
</card_template>
#### Form Component
<form_template>
```tsx
<!-- app/components/ContactForm.tsx -->
<script setup lang="ts">
import { ref, reactive } from 'react';
import { z } from 'zod';
import type { FormSubmitEvent } from '#ui/types';
// Validation schema
const schema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
message: z.string().min(10, 'Message must be at least 10 characters')
});
type Schema = z.output<typeof schema>;
const formData = reactive<Schema>({
name: '',
email: '',
message: ''
});
const isSubmitting = ref(false);
const showSuccess = ref(false);
const showError = ref(false);
const errorMessage = ref('');
const onSubmit = async (event: FormSubmitEvent<Schema>) => {
isSubmitting.value = true;
showSuccess.value = false;
showError.value = false;
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Form submitted:', event.data);
showSuccess.value = true;
// Reset form
formData.name = '';
formData.email = '';
formData.message = '';
// Hide success message after 5 seconds
setTimeout(() => {
showSuccess.value = false;
}, 5000);
} catch (error) {
showError.value = true;
errorMessage.value = 'Failed to submit form. Please try again.';
} finally {
isSubmitting.value = false;
}
};
<div class="space-y-6">
<!-- Success Alert -->
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0 translate-y-2 scale-95"
enter-to-class="opacity-100 translate-y-0 scale-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<Alert
{&& "showSuccess"
color="green"
icon="i-heroicons-check-circle"
title="Success!"
description="Your message has been sent successfully."
:closable="true"
:ui="{ rounded: 'rounded-xl', padding: 'p-4' }"
onClose="showSuccess = false"
/>
</Transition>
<!-- Error Alert -->
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0 translate-y-2"
enter-to-class="opacity-100 translate-y-0"
>
<Alert
{&& "showError"
color="red"
icon="i-heroicons-x-circle"
title="Error"
:description="errorMessage"
:closable="true"
onClose="showError = false"
/>
</Transition>
<!-- Form -->
<UForm
:schema="schema"
:state="formData"
class="space-y-6"
onSubmit="onSubmit"
>
<!-- Name Field -->
<UFormGroup
label="Name"
name="name"
required
:ui="{ label: { base: 'font-medium text-sm' } }"
>
<Input
value="formData.name"
placeholder="Your name"
icon="i-heroicons-user"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-4 py-3' },
icon: { leading: { padding: { sm: 'ps-11' } } }
}"
class="transition-all duration-200 focus-within:ring-2 focus-within:ring-brand-coral"
/>
</UFormGroup>
<!-- Email Field -->
<UFormGroup
label="Email"
name="email"
required
>
<Input
value="formData.email"
type="email"
placeholder="your@email.com"
icon="i-heroicons-envelope"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-4 py-3' }
}"
class="transition-all duration-200 focus-within:ring-2 focus-within:ring-brand-coral"
/>
</UFormGroup>
<!-- Message Field -->
<UFormGroup
label="Message"
name="message"
required
>
<UTextarea
value="formData.message"
placeholder="Your message..."
:rows="5"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-4 py-3' }
}"
class="transition-all duration-200 focus-within:ring-2 focus-within:ring-brand-coral"
/>
</UFormGroup>
<!-- Submit Button -->
<Button
type="submit"
loading={isSubmitting"
disabled={isSubmitting"
color="primary"
size="lg"
:ui="{
font: 'font-heading',
rounded: 'rounded-full',
padding: { lg: 'px-8 py-4' }
}"
class="
w-full
transition-all duration-300
hover:scale-105 hover:shadow-xl
active:scale-95
motion-safe:hover:scale-105
motion-reduce:hover:bg-primary-700
"
>
<span class="inline-flex items-center gap-2">
<Icon
{&& "!isSubmitting"
name="i-heroicons-paper-airplane"
class="transition-transform duration-300 group-hover:translate-x-1 group-hover:-translate-y-0.5"
/>
{ isSubmitting ? 'Sending...' : 'Send Message'}
</span>
</Button>
</UForm>
</div>
```
</form_template>
#### Hero Component
<hero_template>
```tsx
<!-- app/components/LandingHero.tsx -->
<script setup lang="ts">
interface Props {
/** Hero title */
title: string;
/** Hero subtitle */
subtitle?: string;
/** Primary CTA label */
primaryCta?: string;
/** Secondary CTA label */
secondaryCta?: string;
}
const props = withDefaults(defineProps<Props>(), {
subtitle: '',
primaryCta: 'Get Started',
secondaryCta: 'Learn More'
});
const emit = defineEmits<{
primaryClick: [];
secondaryClick: [];
}>();
<div class="relative min-h-screen flex items-center justify-center overflow-hidden">
<!-- Atmospheric Background -->
<div class="absolute inset-0 bg-gradient-to-br from-brand-cream via-white to-brand-ocean/10" />
<!-- Animated Gradient Orbs -->
<div
class="absolute top-20 left-20 w-96 h-96 bg-brand-coral/20 rounded-full blur-3xl animate-pulse"
aria-hidden="true"
/>
<div
class="absolute bottom-20 right-20 w-96 h-96 bg-brand-ocean/20 rounded-full blur-3xl animate-pulse"
style="animation-delay: 1s;"
aria-hidden="true"
/>
<!-- Subtle Pattern Overlay -->
<div
class="absolute inset-0 opacity-5"
style="background-image: radial-gradient(circle, #000 1px, transparent 1px); background-size: 20px 20px;"
aria-hidden="true"
/>
<!-- Content -->
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24">
<div class="text-center space-y-8">
<!-- Animated Badge -->
<div
class="
inline-flex items-center gap-2
px-4 py-2 rounded-full
bg-brand-coral/10 border border-brand-coral/20
text-brand-coral font-medium
animate-in slide-in-from-top duration-500
"
>
<Icon name="i-heroicons-sparkles" class="w-4 h-4 animate-pulse" />
<span class="text-sm">New: Now on Cloudflare Workers</span>
</div>
<!-- Title -->
<h1
class="
font-heading text-6xl sm:text-7xl lg:text-8xl
tracking-tighter leading-none
text-brand-midnight dark:text-white
animate-in slide-in-from-top duration-700
"
style="animation-delay: 100ms;"
>
{ title}
</h1>
<!-- Subtitle -->
<p
{&& "subtitle"
class="
max-w-2xl mx-auto
text-xl sm:text-2xl leading-relaxed
text-gray-700 dark:text-gray-300
animate-in slide-in-from-top duration-700
"
style="animation-delay: 200ms;"
>
{ subtitle}
</p>
<!-- CTAs -->
<div
class="
flex flex-col sm:flex-row items-center justify-center gap-4
animate-in slide-in-from-top duration-700
"
style="animation-delay: 300ms;"
>
<Button
color="primary"
size="xl"
:ui="{
font: 'font-heading tracking-wide',
rounded: 'rounded-full',
padding: { xl: 'px-10 py-5' }
}"
class="
transition-all duration-300
hover:scale-110 hover:-rotate-2 hover:shadow-2xl
active:scale-95 active:rotate-0
motion-safe:hover:scale-110
"
onClick={emit('primaryClick')"
>
<span class="inline-flex items-center gap-2">
{ primaryCta}
<Icon
name="i-heroicons-arrow-right"
class="transition-transform duration-300 group-hover:translate-x-2"
/>
</span>
</Button>
<Button
color="gray"
variant="outline"
size="xl"
:ui="{
font: 'font-sans',
rounded: 'rounded-full'
}"
class="
transition-all duration-300
hover:scale-105 hover:shadow-lg
active:scale-95
"
onClick={emit('secondaryClick')"
>
{ secondaryCta}
</Button>
</div>
<!-- Slot for additional content -->
<div {&& "$slots.default" class="mt-12">
<slot />
</div>
</div>
</div>
</div>
```
</hero_template>
### 4. Create Design System Composable (if missing)
<thinking>
If design system composable doesn't exist, generate it to ensure consistency
across all components.
</thinking>
<design_system_composable>
```typescript
// composables/useDesignSystem.ts
import type { ButtonProps } from '#ui/types';
export const useDesignSystem = () => {
/**
* Button Variants
*/
const button = {
primary: {
color: 'primary',
size: 'lg',
ui: {
font: 'font-heading tracking-wide',
rounded: 'rounded-full',
padding: { lg: 'px-8 py-4' },
shadow: 'shadow-lg hover:shadow-xl'
},
class: 'transition-all duration-300 hover:scale-105 hover:-rotate-1 active:scale-95 active:rotate-0'
} as ButtonProps,
secondary: {
color: 'gray',
variant: 'outline',
size: 'md',
ui: {
font: 'font-sans',
rounded: 'rounded-lg'
},
class: 'transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800'
} as ButtonProps,
ghost: {
variant: 'ghost',
size: 'md',
ui: {
font: 'font-sans'
},
class: 'transition-colors duration-200'
} as ButtonProps
};
/**
* Card Variants
*/
const card = {
elevated: {
ui: {
background: 'bg-white dark:bg-brand-midnight',
rounded: 'rounded-2xl',
shadow: 'shadow-xl hover:shadow-2xl',
body: { padding: 'p-8' }
},
class: 'transition-all duration-300 hover:-translate-y-1'
},
outlined: {
ui: {
background: 'bg-white dark:bg-brand-midnight',
ring: 'ring-1 ring-brand-coral/20',
rounded: 'rounded-2xl',
body: { padding: 'p-8' }
},
class: 'transition-all duration-300 hover:ring-brand-coral/40'
}
};
/**
* Animation Presets
*/
const animations = {
fadeIn: 'animate-in fade-in duration-500',
slideUp: 'animate-in slide-in-from-bottom duration-500',
slideDown: 'animate-in slide-in-from-top duration-500',
scaleIn: 'animate-in zoom-in duration-300',
hover: {
scale: 'transition-transform duration-300 hover:scale-105',
lift: 'transition-all duration-300 hover:-translate-y-1',
shadow: 'transition-shadow duration-300 hover:shadow-xl'
}
};
return {
button,
card,
animations
};
};
```
</design_system_composable>
### 5. Generate Component Files
<thinking>
Create the actual files in the filesystem with proper naming and structure.
</thinking>
#### File Creation:
<file_creation_steps>
1. **Determine output path**:
- Default: `components/<ComponentName>.react`
- Custom: User-specified `--output` path
2. **Generate component file**:
- Use template for component type
- Replace placeholders with actual names
- Include TypeScript types
- Include JSDoc comments
- Include usage examples in comments
3. **Update or create design system composable** (if needed):
- Path: `composables/useDesignSystem.ts`
- Add new variants if applicable
4. **Generate Storybook story** (optional, if Storybook detected):
- Path: `components/<ComponentName>.stories.ts`
5. **Generate test file** (optional):
- Path: `components/<ComponentName>.spec.ts`
</file_creation_steps>
### 6. Validate Generated Component
<thinking>
Run validation to ensure generated component follows best practices.
</thinking>
#### Validation Checks:
<validation_checklist>
- [ ] Uses custom fonts (not Inter/Roboto)
- [ ] Uses brand colors (not default purple)
- [ ] Includes animations/transitions
- [ ] Has hover states on interactive elements
- [ ] Has focus states (focus-visible rings)
- [ ] Respects reduced motion (motion-safe/motion-reduce)
- [ ] Includes ARIA labels where needed
- [ ] Uses shadcn/ui components (not reinventing)
- [ ] Deep customization with `ui` prop
- [ ] TypeScript props interface
- [ ] JSDoc comments
- [ ] Accessible (keyboard navigation, screen readers)
- [ ] Responsive design
- [ ] Dark mode support
</validation_checklist>
## Output Format
<output_format>
```
✅ Component Generated: <ComponentName>
📁 Files Created:
- app/components/<ComponentName>.tsx (primary component)
- composables/useDesignSystem.ts (updated/created)
🎨 Design Features:
✅ Custom typography (font-heading)
✅ Brand colors (brand-coral, brand-ocean)
✅ Rich animations (hover:scale-105, transitions)
✅ Deep shadcn/ui customization (ui prop)
✅ Accessibility features (ARIA, focus states)
✅ Reduced motion support (motion-safe)
✅ Responsive design
✅ Dark mode support
📖 Usage Example:
```tsx
import { <ComponentName> } from '#components';
// Your component logic
<<ComponentName>
prop1="value1"
prop2="value2"
onEvent="handleEvent"
/>
```
🔍 Next Steps:
1. Review generated component in `components/<ComponentName>.react`
2. Customize props/styles as needed
3. Test accessibility with keyboard navigation
4. Test animations with reduced motion preference
5. Run `/es-design-review` to validate design patterns
```
</output_format>
## Success Criteria
✅ Component generated successfully when:
- File created at correct path
- Uses distinctive design patterns (not generic)
- Includes all accessibility features
- Includes rich animations
- TypeScript types included
- Usage examples in comments
- Follows project conventions
## Post-Generation Actions
After generating component:
1. **Review code**: Open generated file and review
2. **Test component**: Add to a page and test interactions
3. **Validate design**: Run `/es-design-review` if needed
4. **Document**: Add to component library docs/Storybook
## Notes
- This command generates **starting templates** with best practices built-in
- Components are **fully customizable** after generation
- **Design system composable** ensures consistency across all generated components
- Use **shadcn/ui MCP** (if available) to prevent prop hallucination
- All generated components follow **WCAG 2.1 AA** accessibility standards
- Generated components respect **user's reduced motion preference**