Skip to main content
Glama
component-inference.ts10.8 kB
import type { FlatElement, ComponentContext, ComponentPattern, ChildPattern, PseudoCodeOptions, } from "../types/generator.js"; /** * Pre-defined component patterns for common UI element types * These patterns help identify and properly name components based on common design patterns * Can be extended with additional patterns for specific design systems */ export const COMPONENT_PATTERNS: ComponentPattern[] = [ // Modal/Dialog patterns { rootKeywords: [ "modal", "dialog", "popup", "overlay", "data grid", "datagrid", ], childPatterns: [ { keywords: ["header", "top"], componentSuffix: "Header", priority: 10 }, { keywords: ["footer", "bottom", "actions"], componentSuffix: "Footer", priority: 10, }, { keywords: ["body", "content", "main"], componentSuffix: "Body", priority: 8, }, { keywords: ["title", "heading"], componentSuffix: "Title", priority: 9 }, { keywords: ["close", "dismiss"], componentSuffix: "CloseButton", priority: 7, }, ], }, // Card patterns { rootKeywords: ["card", "panel", "tile"], childPatterns: [ { keywords: ["header", "top"], componentSuffix: "Header", priority: 10 }, { keywords: ["footer", "bottom"], componentSuffix: "Footer", priority: 10, }, { keywords: ["body", "content", "main"], componentSuffix: "Body", priority: 8, }, { keywords: ["title", "heading"], componentSuffix: "Title", priority: 9 }, { keywords: ["image", "media"], componentSuffix: "Media", priority: 7 }, { keywords: ["actions", "buttons"], componentSuffix: "Actions", priority: 6, }, ], }, // Form patterns { rootKeywords: ["form", "formgroup", "fieldset"], childPatterns: [ { keywords: ["field", "input", "control"], componentSuffix: "Field", priority: 10, }, { keywords: ["label"], componentSuffix: "Label", priority: 9 }, { keywords: ["error", "validation"], componentSuffix: "Error", priority: 8, }, { keywords: ["help", "hint"], componentSuffix: "Help", priority: 7 }, { keywords: ["actions", "buttons"], componentSuffix: "Actions", priority: 6, }, ], }, // Table patterns { rootKeywords: ["table", "datagrid", "datatable"], childPatterns: [ { keywords: ["header", "thead"], componentSuffix: "Header", priority: 10, }, { keywords: ["body", "tbody"], componentSuffix: "Body", priority: 10 }, { keywords: ["footer", "tfoot"], componentSuffix: "Footer", priority: 10, }, { keywords: ["row", "tr"], componentSuffix: "Row", priority: 8 }, { keywords: ["cell", "td", "th"], componentSuffix: "Cell", priority: 7 }, ], }, // Navigation patterns { rootKeywords: ["nav", "navigation", "menu", "navbar"], childPatterns: [ { keywords: ["item", "link"], componentSuffix: "Item", priority: 10 }, { keywords: ["brand", "logo"], componentSuffix: "Brand", priority: 9 }, { keywords: ["toggle", "hamburger"], componentSuffix: "Toggle", priority: 8, }, { keywords: ["dropdown"], componentSuffix: "Dropdown", priority: 7 }, ], }, // Layout patterns (top-level only) { rootKeywords: ["header", "footer", "sidebar", "main", "aside"], childPatterns: [], conditions: { level: [0, 1] }, }, // Section patterns { rootKeywords: ["section", "region", "area"], childPatterns: [ { keywords: ["header", "title"], componentSuffix: "Header", priority: 10, }, { keywords: ["content", "body"], componentSuffix: "Content", priority: 8, }, { keywords: ["footer"], componentSuffix: "Footer", priority: 9 }, ], }, // List patterns { rootKeywords: ["list", "menu", "collection"], childPatterns: [ { keywords: ["item", "entry"], componentSuffix: "Item", priority: 10 }, { keywords: ["header"], componentSuffix: "Header", priority: 9 }, { keywords: ["footer"], componentSuffix: "Footer", priority: 8 }, ], }, ]; /** * Main entry point for semantic component inference * Tries to determine the most appropriate component type based on context and naming patterns. */ export const inferSemanticComponent = ({ element, context, options, }: { element: FlatElement; context: ComponentContext; options?: PseudoCodeOptions; }): string | null => { const name = element.name.toLowerCase(); const parentName = context.parentName?.toLowerCase() || ""; const patterns = options?.customPatterns ? [...options.customPatterns, ...COMPONENT_PATTERNS] : COMPONENT_PATTERNS; const childComponent = findChildComponent({ name, parentName, context, patterns, }); if (childComponent) { return childComponent; } const rootComponent = findRootComponent({ name, context, patterns }); if (rootComponent) { return rootComponent; } const heuristicComponent = inferBySimpleHeuristics({ name, context }); return heuristicComponent; }; /** * Tries to identify a child component based on parent context and component patterns * Used to determine if an element should be rendered as a specialized child component. */ export const findChildComponent = ({ name, parentName, context, patterns, }: { name: string; parentName: string; context: ComponentContext; patterns: ComponentPattern[]; }): string | null => { if (!parentName) return null; for (const pattern of patterns) { const parentKeyword = pattern.rootKeywords.find((keyword) => parentName.includes(keyword) ); if (!parentKeyword) continue; let bestMatch: { pattern: ChildPattern; keyword: string } | null = null; let highestPriority = 0; for (const childPattern of pattern.childPatterns) { const matchedKeyword = childPattern.keywords.find((keyword) => name.includes(keyword) ); if (matchedKeyword && childPattern.priority > highestPriority) { bestMatch = { pattern: childPattern, keyword: matchedKeyword }; highestPriority = childPattern.priority; } } if (bestMatch) { const parentComponent = capitalizeComponentName(parentKeyword); return `${parentComponent}${bestMatch.pattern.componentSuffix}`; } } return null; }; /** * Attempts to identify a top-level/root component based on naming patterns * Used to determine the main component type for an element. */ export const findRootComponent = ({ name, context, patterns, }: { name: string; context: ComponentContext; patterns: ComponentPattern[]; }): string | null => { for (const pattern of patterns) { const matchedKeyword = pattern.rootKeywords.find((keyword) => name.includes(keyword) ); if (!matchedKeyword) continue; if ( pattern.conditions?.level && !pattern.conditions.level.includes(context.level) ) { continue; } if (context.parentName) { const parentName = context.parentName.toLowerCase(); const hasParentWithSameKeyword = pattern.rootKeywords.some((keyword) => parentName.includes(keyword) ); if (hasParentWithSameKeyword) { continue; } } return capitalizeComponentName(matchedKeyword); } return null; }; /** * Applies simple heuristics to identify common components * This is a fallback mechanism when the pattern matching doesn't yield results * Uses common naming conventions to identify basic component types. */ export const inferBySimpleHeuristics = ({ name, context, }: { name: string; context: ComponentContext; }): string | null => { const parentName = context.parentName?.toLowerCase() || ""; if (name.includes("container") && context.level <= 1) return "Container"; if (name.includes("wrapper") && context.level <= 2) return "Wrapper"; if (name.includes("label")) return "Label"; if (name.includes("text") && !name.includes("input")) return "Text"; if (name.includes("header") && context.level === 0) return "Header"; if (name.includes("footer") && context.level === 0) return "Footer"; if (name.includes("divider")) return "Divider"; if (name.includes("separator")) return "Separator"; return null; }; /** * Capitalizes and formats component names following React conventions * Handles special cases and ensures proper naming format. */ export function capitalizeComponentName(keyword: string): string { console.log("Capitalize component name:", keyword); return keyword .trim() .split(/\s+/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(""); } /** * Infers the appropriate text component type based on properties * Analyzes font size, weight, and name to determine if text should be heading, paragraph, etc. */ export const inferTextComponent = (element: FlatElement): string => { const name = element.name.toLowerCase(); const fontSize = typeof element.text?.fontSize === "number" ? element.text.fontSize : parseFloat(element.text?.fontSize?.toString() || "16") || 16; const fontWeight = element.text?.fontWeight || 400; if (name.includes("title") || name.includes("heading")) { if (fontSize >= 24 || fontWeight >= 600) return "Title"; return "Subtitle"; } if (name.includes("subtitle")) return "Subtitle"; if (name.includes("caption")) return "Caption"; if (name.includes("label")) return "Label"; if (fontSize >= 24 && fontWeight >= 600) return "Title"; if (fontSize >= 18 && fontWeight >= 500) return "Subtitle"; if (fontSize <= 12) return "Caption"; return "Text"; }; /** * Cleans and formats Figma component names for React usage * Handles various Figma naming patterns and converts them to proper React component names. */ export const cleanComponentName = (figmaName: string): string => { const specialWordsRemoval: string[] = JSON.parse( process.env.SKIP_WORDS_IN_COMPONENTS || "[]" ); if (specialWordsRemoval.some((word) => figmaName.includes(word))) { figmaName = figmaName .split(new RegExp(specialWordsRemoval?.join("|"), "i"))?.[1] ?.trim?.() || ""; } if (figmaName.includes(":")) { figmaName = (figmaName.split(":")[0] || "").trim(); } if (figmaName.includes("|")) { figmaName = (figmaName.split("|")[0] || "").trim(); } return figmaName .split(/\s+/) .filter((word) => word.length > 0) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(""); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toddle-edu/figma-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server