211 lines
6.4 KiB
JavaScript
211 lines
6.4 KiB
JavaScript
/**
|
|
* 🤖 AGENTE DE ADAPTAÇÃO DE INTERFACE
|
|
*
|
|
* Implementação programática do agente de validação de responsividade
|
|
*/
|
|
|
|
export class UIAdaptationAgent {
|
|
constructor() {
|
|
this.name = 'UIAdaptationAgent';
|
|
this.description = 'Valida responsividade e adaptação de interface';
|
|
|
|
// Personalidade e Background
|
|
this.personality = {
|
|
name: 'Maya "The Responsive"',
|
|
traits: [
|
|
'Perfeccionista visual',
|
|
'Empática com usuários de diferentes dispositivos',
|
|
'Obsessiva com breakpoints',
|
|
'Criativa em soluções de layout',
|
|
'Paciente e meticulosa'
|
|
],
|
|
quirks: [
|
|
'Testa em dispositivos reais sempre que possível',
|
|
'Tem uma coleção mental de todos os tamanhos de tela conhecidos',
|
|
'Fica incomodada quando vê um layout quebrando',
|
|
'Prefere mobile-first em tudo'
|
|
],
|
|
catchphrases: [
|
|
'Isso vai funcionar no mobile?',
|
|
'E no tablet?',
|
|
'Vamos pensar mobile-first!',
|
|
'Esse breakpoint está correto?'
|
|
]
|
|
};
|
|
|
|
this.background = {
|
|
origin: 'Ex-designer que migrou para desenvolvimento após frustrações com layouts quebrados',
|
|
motivation: 'Garantir que todos tenham a mesma experiência, independente do dispositivo',
|
|
experience: '7 anos trabalhando com design responsivo e CSS moderno',
|
|
turningPoint: 'Viu um cliente perder uma venda porque o checkout não funcionava no celular',
|
|
philosophy: 'Design é sobre pessoas, não sobre pixels',
|
|
relationships: {
|
|
withFontQuality: 'Trabalham juntos - ela cuida do layout, eu cuido da tipografia',
|
|
withPerformance: 'Respeita muito, mas às vezes prioriza UX sobre performance',
|
|
withBrowserValidation: 'Aprecia os testes reais que ele faz'
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Valida um componente
|
|
* @param {string|Object} component - Componente ou caminho
|
|
* @param {Object} context - Contexto
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async validate(component, context = {}) {
|
|
const errors = [];
|
|
const warnings = [];
|
|
const metadata = {};
|
|
|
|
const componentPath = typeof component === 'string' ? component : component.path;
|
|
const componentCode = context.code || await this.readComponent(componentPath);
|
|
|
|
if (!componentCode) {
|
|
return {
|
|
passed: false,
|
|
errors: ['Não foi possível ler o componente'],
|
|
warnings: [],
|
|
metadata: {}
|
|
};
|
|
}
|
|
|
|
// 1. Verificar uso de classes Tailwind responsivas
|
|
const hasResponsiveClasses = this.hasResponsiveTailwindClasses(componentCode);
|
|
if (!hasResponsiveClasses && this.isLayoutComponent(componentCode)) {
|
|
warnings.push('Componente de layout pode não ser responsivo');
|
|
}
|
|
|
|
// 2. Verificar uso de unidades fixas (px) vs relativas
|
|
const fixedUnits = this.countFixedUnits(componentCode);
|
|
if (fixedUnits > 5) {
|
|
warnings.push(`Muitas unidades fixas (${fixedUnits}) encontradas. Considere usar unidades relativas`);
|
|
}
|
|
|
|
// 3. Verificar truncamento de texto
|
|
if (this.hasLongTextContent(componentCode) && !componentCode.includes('truncate') && !componentCode.includes('line-clamp')) {
|
|
warnings.push('Textos longos podem não estar sendo truncados adequadamente');
|
|
}
|
|
|
|
// 4. Verificar breakpoints do Tailwind
|
|
const breakpoints = this.extractBreakpoints(componentCode);
|
|
metadata.breakpoints = breakpoints;
|
|
|
|
// 5. Verificar uso de mobile-first
|
|
const hasMobileFirst = this.hasMobileFirstApproach(componentCode);
|
|
if (!hasMobileFirst && this.isLayoutComponent(componentCode)) {
|
|
warnings.push('Componente pode não seguir abordagem mobile-first');
|
|
}
|
|
|
|
// 6. Verificar overflow handling
|
|
if (this.isScrollableComponent(componentCode) && !componentCode.includes('overflow')) {
|
|
warnings.push('Componente scrollável pode não ter tratamento de overflow adequado');
|
|
}
|
|
|
|
const passed = errors.length === 0;
|
|
|
|
return {
|
|
passed,
|
|
errors,
|
|
warnings,
|
|
metadata: {
|
|
hasResponsiveClasses,
|
|
breakpoints,
|
|
fixedUnitsCount: fixedUnits,
|
|
...metadata
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem classes Tailwind responsivas
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasResponsiveTailwindClasses(code) {
|
|
const responsivePatterns = [
|
|
'sm:', 'md:', 'lg:', 'xl:', '2xl:',
|
|
'max-sm:', 'max-md:', 'max-lg:', 'max-xl:'
|
|
];
|
|
return responsivePatterns.some(pattern => code.includes(pattern));
|
|
}
|
|
|
|
/**
|
|
* Conta unidades fixas (px)
|
|
* @param {string} code - Código
|
|
* @returns {number}
|
|
*/
|
|
countFixedUnits(code) {
|
|
const pxMatches = code.match(/\d+px/g);
|
|
return pxMatches ? pxMatches.length : 0;
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem conteúdo de texto longo
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasLongTextContent(code) {
|
|
return code.includes('text') || code.includes('p>') || code.includes('span');
|
|
}
|
|
|
|
/**
|
|
* Extrai breakpoints usados
|
|
* @param {string} code - Código
|
|
* @returns {string[]}
|
|
*/
|
|
extractBreakpoints(code) {
|
|
const breakpoints = [];
|
|
const patterns = ['sm:', 'md:', 'lg:', 'xl:', '2xl:'];
|
|
patterns.forEach(pattern => {
|
|
if (code.includes(pattern)) {
|
|
breakpoints.push(pattern.replace(':', ''));
|
|
}
|
|
});
|
|
return breakpoints;
|
|
}
|
|
|
|
/**
|
|
* Verifica se segue mobile-first
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasMobileFirstApproach(code) {
|
|
// Se tem classes base (sem prefixo) e classes com breakpoints maiores, provavelmente é mobile-first
|
|
return code.includes('className') && this.hasResponsiveTailwindClasses(code);
|
|
}
|
|
|
|
/**
|
|
* Verifica se é componente de layout
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
isLayoutComponent(code) {
|
|
return code.includes('div') || code.includes('section') || code.includes('main') ||
|
|
code.includes('Layout') || code.includes('Container');
|
|
}
|
|
|
|
/**
|
|
* Verifica se é componente scrollável
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
isScrollableComponent(code) {
|
|
return code.includes('scroll') || code.includes('ScrollArea') || code.includes('overflow');
|
|
}
|
|
|
|
/**
|
|
* Lê o conteúdo do componente
|
|
* @param {string} path - Caminho do arquivo
|
|
* @returns {Promise<string|null>}
|
|
*/
|
|
async readComponent(path) {
|
|
try {
|
|
const fs = await import('fs/promises');
|
|
return await fs.readFile(path, 'utf-8');
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|