UNPKG

@hakxel/mantine-ui-server

Version:

MCP server for working with Mantine UI components - provides documentation, generation, and theme utilities

290 lines (289 loc) 10.2 kB
/** * Component generator for the Mantine UI MCP server */ import * as fs from 'fs'; import * as path from 'path'; import { generateBasicComponentTemplate, generateCssModuleTemplate, generateVariants, generateTestFile } from './templates.js'; import { getConfig } from '../utils/config.js'; import { getComponentDocumentation } from '../documentation/fetcher.js'; /** * Generate a Mantine component based on the provided configuration * @param config Component generation configuration * @param outputPath Path to output the generated component * @returns Object containing the paths of the generated files */ export async function generateComponent(config, outputPath) { try { // Validate the component exists in Mantine await validateMantineComponent(config.mantineComponent); // Generate the component files const generatedFiles = await buildComponentFiles(config); // If outputPath is provided, write files to disk if (outputPath) { await writeComponentFiles(generatedFiles, outputPath); } return { files: Object.keys(generatedFiles).reduce((acc, key) => { acc[key] = path.join(outputPath || '', key); return acc; }, {}), content: generatedFiles }; } catch (error) { console.error(`Error generating component:`, error); throw new Error(`Failed to generate component: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate that the specified Mantine component exists * @param componentName Mantine component name */ async function validateMantineComponent(componentName) { try { // Try to get documentation for the component to validate it exists await getComponentDocumentation(componentName); } catch (error) { throw new Error(`Invalid Mantine component: ${componentName}. Please ensure the component name is correct.`); } } /** * Build all component files based on the configuration * @param config Component generation configuration * @returns Object with filenames as keys and content as values */ async function buildComponentFiles(config) { const { name, styling = {}, includeTests = false, variants = [] } = config; const files = {}; // Generate the main component file files[`${name}.tsx`] = generateBasicComponentTemplate(config); // Generate CSS module if requested if (styling.useModule) { files[`${name}.module.css`] = generateCssModuleTemplate(config); } // Generate variants if specified const variantFiles = generateVariants(config); Object.entries(variantFiles).forEach(([variantName, content]) => { files[`${name}${variantName.charAt(0).toUpperCase() + variantName.slice(1)}.tsx`] = content; }); // Generate test file if requested if (includeTests) { files[`${name}.test.tsx`] = generateTestFile(config); } // Generate index file for exports files['index.ts'] = `export { ${name} } from './${name}';\n`; if (config.props && Object.keys(config.props).length > 0) { files['index.ts'] += `export type { ${name}Props } from './${name}';\n`; } // Add variant exports variants.forEach(variant => { const variantName = `${name}${variant.name.charAt(0).toUpperCase() + variant.name.slice(1)}`; files['index.ts'] += `export { ${variantName} } from './${variantName}';\n`; }); return files; } /** * Write generated component files to disk * @param files Object with filenames as keys and content as values * @param outputPath Base path to write files to */ async function writeComponentFiles(files, outputPath) { // Create the output directory if it doesn't exist if (!fs.existsSync(outputPath)) { fs.mkdirSync(outputPath, { recursive: true }); } // Write each file for (const [filename, content] of Object.entries(files)) { const filePath = path.join(outputPath, filename); fs.writeFileSync(filePath, content, 'utf8'); } } /** * Generate a component based on an academic template * @param config Component generation configuration * @param template Academic template name * @param outputPath Path to output the generated component * @returns Object containing the paths of the generated files */ export async function generateAcademicComponent(config, template, outputPath) { // Extend the base configuration with academic-specific props and styling const academicConfig = { ...config, props: { ...config.props, ...getAcademicTemplateProps(template) }, styling: { ...config.styling, ...getAcademicTemplateStyling(template) } }; return generateComponent(academicConfig, outputPath); } /** * Get academic template props based on template type * @param template Academic template name * @returns Props for the academic template */ function getAcademicTemplateProps(template) { switch (template) { case 'publication': return { title: { type: 'string', description: 'Publication title', required: true }, authors: { type: 'string[]', description: 'List of authors', required: true }, journal: { type: 'string', description: 'Journal name', required: false }, date: { type: 'string', description: 'Publication date', required: false }, abstract: { type: 'string', description: 'Publication abstract', required: false }, doi: { type: 'string', description: 'Digital Object Identifier', required: false }, url: { type: 'string', description: 'Link to publication', required: false } }; case 'research': return { title: { type: 'string', description: 'Research project title', required: true }, description: { type: 'string', description: 'Research description', required: true }, imageUrl: { type: 'string', description: 'URL to research image', required: false }, tags: { type: 'string[]', description: 'Research tags', required: false, defaultValue: [] }, date: { type: 'string', description: 'Research date', required: false } }; case 'citation': return { text: { type: 'string', description: 'Citation text', required: true }, source: { type: 'string', description: 'Citation source', required: false }, url: { type: 'string', description: 'Citation URL', required: false } }; case 'timeline': return { items: { type: '{ date: string; title: string; description: string; }[]', description: 'Timeline items', required: true }, current: { type: 'number', description: 'Current active item index', required: false, defaultValue: 0 } }; default: return {}; } } /** * Get academic template styling based on template type * @param template Academic template name * @returns Styling for the academic template */ function getAcademicTemplateStyling(template) { const serverConfig = getConfig(); switch (template) { case 'publication': return { useModule: true, responsive: true, themeOverrides: { padding: '1rem', borderRadius: '0.5rem', border: '1px solid var(--mantine-color-gray-3)', backgroundColor: 'var(--mantine-color-gray-0)' } }; case 'research': return { useModule: true, responsive: true, themeOverrides: { overflow: 'hidden', transition: 'transform 200ms ease, box-shadow 200ms ease', '&:hover': { transform: 'translateY(-5px)', boxShadow: 'var(--mantine-shadow-md)' } } }; case 'citation': return { useModule: true, themeOverrides: { fontStyle: 'italic', padding: '1rem 2rem', borderLeft: '3px solid var(--mantine-color-blue-6)', backgroundColor: 'var(--mantine-color-gray-0)' } }; case 'timeline': return { useModule: true, responsive: true, themeOverrides: { width: '100%', maxWidth: '800px', margin: '0 auto' } }; default: return {}; } }