import type { TailwindClassInfo, TailwindCategory, StyleEntry } from '../types/index.js';
// Tailwind prefix patterns for categorization
const COLOR_PREFIXES = [
'text-', 'bg-', 'border-', 'ring-', 'outline-', 'shadow-',
'accent-', 'caret-', 'fill-', 'stroke-', 'decoration-',
'divide-', 'from-', 'via-', 'to-'
];
const SPACING_PREFIXES = [
'p-', 'px-', 'py-', 'pt-', 'pr-', 'pb-', 'pl-', 'ps-', 'pe-',
'm-', 'mx-', 'my-', 'mt-', 'mr-', 'mb-', 'ml-', 'ms-', 'me-',
'gap-', 'gap-x-', 'gap-y-',
'space-x-', 'space-y-',
'w-', 'h-', 'min-w-', 'min-h-', 'max-w-', 'max-h-',
'inset-', 'top-', 'right-', 'bottom-', 'left-',
'basis-', 'grow-', 'shrink-'
];
const TYPOGRAPHY_PREFIXES = [
'font-', 'text-', 'leading-', 'tracking-', 'indent-',
'align-', 'whitespace-', 'break-', 'hyphens-',
'decoration-', 'underline-', 'list-'
];
// Text size classes that are typography, not color
const TEXT_SIZE_PATTERN = /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/;
const TEXT_ALIGN_PATTERN = /^text-(left|center|right|justify|start|end)$/;
function categorizeClass(className: string): TailwindCategory {
// Handle responsive/state prefixes (e.g., md:text-lg, hover:bg-blue-500)
const baseName = className.includes(':')
? className.split(':').pop()!
: className;
// Special case: text- can be color or typography
if (baseName.startsWith('text-')) {
if (TEXT_SIZE_PATTERN.test(baseName) || TEXT_ALIGN_PATTERN.test(baseName)) {
return 'typography';
}
// Otherwise it's a color
return 'colors';
}
// Check color prefixes
for (const prefix of COLOR_PREFIXES) {
if (baseName.startsWith(prefix)) {
return 'colors';
}
}
// Check spacing prefixes
for (const prefix of SPACING_PREFIXES) {
if (baseName.startsWith(prefix)) {
return 'spacing';
}
}
// Check typography prefixes
for (const prefix of TYPOGRAPHY_PREFIXES) {
if (baseName.startsWith(prefix)) {
return 'typography';
}
}
return 'other';
}
function parseClassToProperty(className: string): { property: string; value: string } {
// Remove responsive/state prefixes for property extraction
const parts = className.split(':');
const baseName = parts.pop()!;
const modifiers = parts.join(':');
// Split by first dash to get property hint
const dashIndex = baseName.indexOf('-');
if (dashIndex === -1) {
return {
property: baseName,
value: modifiers ? `${modifiers}:${baseName}` : baseName
};
}
const prefix = baseName.substring(0, dashIndex);
const value = baseName.substring(dashIndex + 1);
// Map common prefixes to CSS properties
const propertyMap: Record<string, string> = {
'p': 'padding',
'px': 'padding-x',
'py': 'padding-y',
'pt': 'padding-top',
'pr': 'padding-right',
'pb': 'padding-bottom',
'pl': 'padding-left',
'm': 'margin',
'mx': 'margin-x',
'my': 'margin-y',
'mt': 'margin-top',
'mr': 'margin-right',
'mb': 'margin-bottom',
'ml': 'margin-left',
'w': 'width',
'h': 'height',
'bg': 'background-color',
'text': 'color/font-size',
'font': 'font-weight/font-family',
'leading': 'line-height',
'tracking': 'letter-spacing',
'gap': 'gap',
'border': 'border',
'rounded': 'border-radius',
'shadow': 'box-shadow',
'opacity': 'opacity',
'flex': 'flex',
'grid': 'grid',
'justify': 'justify-content',
'items': 'align-items',
'self': 'align-self'
};
const property = propertyMap[prefix] || prefix;
const fullValue = modifiers ? `${modifiers}:${value}` : value;
return { property, value: fullValue };
}
export function parseTailwindClasses(classString: string): TailwindClassInfo[] {
if (!classString || typeof classString !== 'string') {
return [];
}
const classes = classString.split(/\s+/).filter(c => c.length > 0);
return classes.map(className => {
const category = categorizeClass(className);
const { property, value } = parseClassToProperty(className);
return {
className,
category,
property,
value
};
});
}
export function extractTailwindStyles(classString: string): StyleEntry[] {
const parsed = parseTailwindClasses(classString);
return parsed.map(info => ({
property: info.property,
value: info.value,
source: 'tailwind' as const,
raw: info.className
}));
}
export function categorizeTailwindStyles(classString: string): {
colors: StyleEntry[];
spacing: StyleEntry[];
typography: StyleEntry[];
other: StyleEntry[];
} {
const parsed = parseTailwindClasses(classString);
const result = {
colors: [] as StyleEntry[],
spacing: [] as StyleEntry[],
typography: [] as StyleEntry[],
other: [] as StyleEntry[]
};
for (const info of parsed) {
const entry: StyleEntry = {
property: info.property,
value: info.value,
source: 'tailwind',
raw: info.className
};
result[info.category].push(entry);
}
return result;
}