blob: 14ab1717b914e47124601c4709d27bb6ed630222 [file] [log] [blame] [edit]
---
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
interface Props {
utility: string | string[]; // The utility key(s) from the metadata (e.g., "font-size" or ["font-size", "text-size"])
className?: string;
}
const {
utility,
className = "table reference-table"
} = Astro.props;
// Normalize to array
const utilities = Array.isArray(utility) ? utility : [utility];
// Parse CSS variables from _root.scss at build time
function parseCSSVariables(): Record<string, string> {
try {
const projectRoot = process.cwd();
const rootScssPath = join(projectRoot, 'scss/_root.scss');
const scssContent = readFileSync(rootScssPath, 'utf-8');
const cssVarValues: Record<string, string> = {};
const varRegex = /--#\{\$prefix\}([a-z0-9-]+):\s*([^;]+);/gi;
let match;
while ((match = varRegex.exec(scssContent)) !== null) {
const varName = `--bs-${match[1]}`;
let value = match[2].trim();
value = value.replace(/#\{[^}]+\}/g, '').trim();
value = value.replace(/\/\/.*$/gm, '').trim();
if (value) {
cssVarValues[varName] = value;
}
}
return cssVarValues;
} catch (error) {
console.warn('Could not parse CSS variables from _root.scss:', error);
return {};
}
}
// Load utilities metadata
function loadUtilitiesMetadata(): any {
try {
const projectRoot = process.cwd();
const metadataPath = join(projectRoot, 'dist/css/bootstrap-utilities.metadata.json');
const metadataContent = readFileSync(metadataPath, 'utf-8');
return JSON.parse(metadataContent);
} catch (error) {
console.warn('Could not load utilities metadata:', error);
return { utilities: {} };
}
}
// Parse compiled CSS to extract styles for given class selectors
function parseCompiledCSS(classNames: string[]): Record<string, string[]> {
try {
const projectRoot = process.cwd();
const bootstrapCssPath = join(projectRoot, 'dist/css/bootstrap.css');
const cssContent = readFileSync(bootstrapCssPath, 'utf-8');
const classStyles: Record<string, string[]> = {};
classNames.forEach(className => {
// Match ONLY single class selectors: .classname { declarations }
const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const selectorRegex = new RegExp(`(?:^|\\n)\\s*\\.${escapedClass}\\s*\\{([^}]+)\\}`, 'gm');
let match;
let foundDeclarations: string[] = [];
while ((match = selectorRegex.exec(cssContent)) !== null) {
const declarations = match[1]
.split(';')
.map(decl => decl.trim())
.filter(decl => decl.length > 0)
.map(decl => `${decl};`);
if (declarations.length > 0) {
foundDeclarations = declarations;
break;
}
}
classStyles[className] = foundDeclarations;
});
return classStyles;
} catch (error) {
console.warn('Could not parse compiled CSS:', error);
return {};
}
}
const cssVarValues = parseCSSVariables();
const metadata = loadUtilitiesMetadata();
// Collect classes from all specified utilities
let allClasses: string[] = [];
utilities.forEach(util => {
const utilityMeta = metadata.utilities[util];
if (!utilityMeta) {
console.warn(`Utility "${util}" not found in metadata. Available utilities: ${Object.keys(metadata.utilities).join(', ')}`);
return;
}
const classes = utilityMeta.classes || [];
allClasses = allClasses.concat(classes);
});
if (allClasses.length === 0) {
throw new Error(`No classes found for utilities: ${utilities.join(', ')}`);
}
const classStyles = parseCompiledCSS(allClasses);
// Function to add CSS variable value comments
function addVarComments(cssValue: string): string {
const comments: string[] = [];
cssValue.replace(/var\((--[a-z0-9-]+)\)/gi, (match, varName) => {
const resolvedValue = cssVarValues[varName];
if (resolvedValue) {
comments.push(`<span class="fg-3">/* ${resolvedValue} */</span>`);
}
return match;
});
if (comments.length > 0) {
const hasSemicolon = cssValue.trimEnd().endsWith(';');
return `${cssValue}${hasSemicolon ? '' : ';'} ${comments.join(' ')}`;
}
return cssValue;
}
// Build table data
const tableData = allClasses.map(cls => {
const styles = classStyles[cls] || [];
const formattedStyles = styles.map(style => addVarComments(style)).join('<br/>');
return {
class: `.${cls}`,
styles: formattedStyles || '<em>Not found</em>'
};
});
---
<div class="table-responsive bd-reference-table">
<table class={className}>
<thead>
<tr>
<th scope="col">Class</th>
<th scope="col">Styles</th>
</tr>
</thead>
<tbody>
{tableData.map((row) => (
<tr>
<td>{row.class}</td>
<td><Fragment set:html={row.styles} /></td>
</tr>
))}
</tbody>
</table>
</div>