workflows.tsโข12.6 kB
import { z } from 'zod';
import { StorybookTools } from './storybook.js';
import { TailwindTools } from './tailwind.js';
import { AnimationTools } from './animation.js';
import { PlaywrightTools } from './playwright.js';
import { ComponentTools } from './components.js';
// Removed DesignToCode workflow since it depends on Figma
const OptimizeUXSchema = z.object({
url: z.string().url(),
analyses: z.array(z.enum(['performance', 'accessibility', 'usability', 'mobile', 'seo'])).optional()
});
const BuildDesignSystemSchema = z.object({
source: z.enum(['storybook', 'code']),
sourceId: z.string(),
includeTokens: z.boolean().default(true),
includeComponents: z.boolean().default(true),
includeDocumentation: z.boolean().default(true)
});
export class WorkflowTools {
private storybookTools: StorybookTools;
private tailwindTools: TailwindTools;
private animationTools: AnimationTools;
private playwrightTools: PlaywrightTools;
private componentTools: ComponentTools;
constructor() {
this.storybookTools = new StorybookTools();
this.tailwindTools = new TailwindTools();
this.animationTools = new AnimationTools();
this.playwrightTools = new PlaywrightTools();
this.componentTools = new ComponentTools();
}
// Removed designToCode method as it depends on Figma API
async optimizeUX(args: any) {
const params = OptimizeUXSchema.parse(args);
const analyses = params.analyses || ['performance', 'accessibility', 'usability'];
try {
const results: any = {
url: params.url,
timestamp: new Date().toISOString(),
analyses: {},
recommendations: [],
overallScore: 0
};
// Performance Analysis
if (analyses.includes('performance')) {
results.analyses.performance = await this.analyzePerformance(params.url);
}
// Accessibility Analysis
if (analyses.includes('accessibility')) {
results.analyses.accessibility = await this.analyzeAccessibility(params.url);
}
// Usability Analysis
if (analyses.includes('usability')) {
results.analyses.usability = await this.analyzeUsability(params.url);
}
// Mobile Analysis
if (analyses.includes('mobile')) {
results.analyses.mobile = await this.analyzeMobile(params.url);
}
// SEO Analysis
if (analyses.includes('seo')) {
results.analyses.seo = await this.analyzeSEO(params.url);
}
// Calculate overall score
const scores = Object.values(results.analyses).map((a: any) => a.score || 0);
results.overallScore = scores.reduce((a, b) => a + b, 0) / scores.length;
// Generate recommendations
results.recommendations = this.generateUXRecommendations(results.analyses);
return {
content: [
{
type: 'text',
text: JSON.stringify(results, null, 2)
}
]
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error optimizing UX: ${error.message}`
}
],
isError: true
};
}
}
async buildDesignSystem(args: any) {
const params = BuildDesignSystemSchema.parse(args);
try {
const designSystem: any = {
name: 'Design System',
version: '1.0.0',
source: params.source,
created: new Date().toISOString(),
tokens: {},
components: [],
documentation: {}
};
switch (params.source) {
case 'storybook':
// Build from Storybook
if (params.includeComponents) {
const stories = await this.storybookTools.getStories({ url: params.sourceId });
designSystem.components = this.extractStorybookComponents(stories);
}
break;
case 'code':
// Build from code analysis
designSystem.components = await this.analyzeCodeComponents(params.sourceId);
break;
}
if (params.includeDocumentation) {
designSystem.documentation = this.generateSystemDocumentation(designSystem);
}
// Generate output files
const outputs = {
tokens: params.includeTokens ? this.generateTokenFiles(designSystem.tokens) : null,
components: params.includeComponents ? this.generateComponentFiles(designSystem.components) : null,
documentation: params.includeDocumentation ? this.generateDocFiles(designSystem.documentation) : null
};
return {
content: [
{
type: 'text',
text: JSON.stringify({
designSystem,
outputs,
summary: {
tokenCategories: Object.keys(designSystem.tokens).length,
componentCount: designSystem.components.length,
documentationPages: Object.keys(designSystem.documentation).length
}
}, null, 2)
}
]
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error building design system: ${error.message}`
}
],
isError: true
};
}
}
private getDefaultDesignTokens(): any {
// Return default design tokens
return {
colors: {
primary: '#007AFF',
secondary: '#5856D6',
success: '#34C759',
warning: '#FF9500',
error: '#FF3B30',
neutral: {
100: '#F2F2F7',
200: '#E5E5EA',
300: '#C7C7CC',
400: '#8E8E93',
500: '#636366',
600: '#48484A',
700: '#363638',
800: '#2C2C2E',
900: '#1C1C1E'
}
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px'
},
typography: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace']
},
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px'
}
},
borderRadius: {
none: '0',
sm: '4px',
md: '8px',
lg: '12px',
full: '9999px'
}
};
}
// Removed generateComponentFromDesign as it depends on Figma
private generateStyles(styling: string, tokens: any): any {
switch (styling) {
case 'tailwind':
return {
classes: 'bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-dark'
};
case 'css':
return {
backgroundColor: tokens.colors.primary,
color: 'white',
padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,
borderRadius: tokens.borderRadius.md
};
case 'styled-components':
case 'emotion':
return {
template: `
background-color: \${props => props.theme.colors.primary};
color: white;
padding: \${props => props.theme.spacing.sm} \${props => props.theme.spacing.md};
border-radius: \${props => props.theme.borderRadius.md};
`
};
default:
return {};
}
}
private async analyzePerformance(url: string): Promise<any> {
// Simulate performance analysis
return {
score: 85,
metrics: {
firstContentfulPaint: '1.2s',
largestContentfulPaint: '2.4s',
totalBlockingTime: '150ms',
cumulativeLayoutShift: 0.05,
speedIndex: '3.2s'
},
opportunities: [
'Reduce JavaScript bundle size',
'Optimize images',
'Enable text compression'
]
};
}
private async analyzeAccessibility(url: string): Promise<any> {
// Simulate accessibility analysis
return {
score: 92,
violations: [
{
rule: 'color-contrast',
impact: 'serious',
elements: 3
}
],
passes: [
'aria-roles',
'button-name',
'image-alt',
'label'
]
};
}
private async analyzeUsability(url: string): Promise<any> {
// Simulate usability analysis
return {
score: 78,
findings: {
navigation: 'Clear and intuitive',
formUsability: 'Good, but could improve error messages',
readability: 'Excellent typography and spacing',
interactionFeedback: 'Needs improvement on loading states'
}
};
}
private async analyzeMobile(url: string): Promise<any> {
// Simulate mobile analysis
return {
score: 88,
viewport: 'Properly configured',
touchTargets: 'Most are appropriately sized',
textSize: 'Readable without zooming',
responsive: true,
issues: [
'Some buttons too small for touch',
'Horizontal scrolling on smaller devices'
]
};
}
private async analyzeSEO(url: string): Promise<any> {
// Simulate SEO analysis
return {
score: 75,
meta: {
title: 'Present',
description: 'Present',
keywords: 'Missing',
ogTags: 'Partial'
},
structured: false,
sitemap: false,
robots: true
};
}
private generateUXRecommendations(analyses: any): string[] {
const recommendations = [];
if (analyses.performance?.score < 90) {
recommendations.push('Optimize page load performance for better user experience');
}
if (analyses.accessibility?.violations?.length > 0) {
recommendations.push('Fix accessibility violations to ensure inclusive design');
}
if (analyses.mobile?.issues?.length > 0) {
recommendations.push('Improve mobile experience with better touch targets');
}
if (analyses.seo?.score < 80) {
recommendations.push('Enhance SEO with better meta tags and structured data');
}
return recommendations;
}
// Removed extractFigmaComponents as it depends on Figma API
private extractStorybookComponents(stories: any): any[] {
// Extract components from Storybook stories
return [
{
name: 'Button',
stories: ['Default', 'Primary', 'Secondary'],
props: ['variant', 'size', 'disabled']
}
];
}
private async analyzeCodeComponents(path: string): Promise<any[]> {
// Analyze code to extract components
return [
{
name: 'Header',
file: 'Header.tsx',
props: ['title', 'navigation']
},
{
name: 'Footer',
file: 'Footer.tsx',
props: ['links', 'copyright']
}
];
}
private generateSystemDocumentation(designSystem: any): any {
return {
overview: 'Design System Overview',
gettingStarted: 'Installation and setup guide',
tokens: 'Design tokens documentation',
components: 'Component library documentation',
patterns: 'Design patterns and best practices',
contributing: 'Contributing guidelines'
};
}
private generateTokenFiles(tokens: any): any {
return {
'tokens.json': JSON.stringify(tokens, null, 2),
'tokens.css': this.tokensToCSS(tokens),
'tokens.js': `export default ${JSON.stringify(tokens, null, 2)}`
};
}
private generateComponentFiles(components: any[]): any {
const files: any = {};
components.forEach(comp => {
files[`${comp.name}.component.js`] = '// Component implementation';
files[`${comp.name}.stories.js`] = '// Storybook stories';
files[`${comp.name}.test.js`] = '// Component tests';
});
return files;
}
private generateDocFiles(documentation: any): any {
const files: any = {};
Object.entries(documentation).forEach(([key, content]) => {
files[`${key}.md`] = content;
});
return files;
}
private tokensToCSS(tokens: any): string {
let css = ':root {\n';
// Colors
Object.entries(tokens.colors || {}).forEach(([key, value]: [string, any]) => {
if (typeof value === 'string') {
css += ` --color-${key}: ${value};\n`;
} else if (typeof value === 'object') {
Object.entries(value).forEach(([subKey, subValue]) => {
css += ` --color-${key}-${subKey}: ${subValue};\n`;
});
}
});
// Spacing
Object.entries(tokens.spacing || {}).forEach(([key, value]) => {
css += ` --spacing-${key}: ${value};\n`;
});
css += '}';
return css;
}
}