| --- |
| import { readFileSync } from 'node:fs'; |
| import { join } from 'node:path'; |
| |
| interface ReferenceItem { |
| class: string; |
| styles?: string | string[] | Record<string, string>; |
| description?: string; |
| comment?: string; // Optional manual comment to append |
| [key: string]: any; // Allow additional properties |
| } |
| |
| interface Props { |
| className?: string; |
| columns?: Array<{ label: string; key: string }>; |
| data?: Array<any>; |
| reference?: Array<ReferenceItem>; // Direct prop for reference data |
| } |
| |
| const { |
| className = "table reference-table", |
| columns, |
| data, |
| reference |
| } = Astro.props; |
| |
| // Use explicit reference prop or data prop |
| const referenceData = reference || data || []; |
| |
| // 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> = {}; |
| |
| // Match CSS variable declarations: --#{$prefix}variable-name: value; |
| // This regex captures the variable name and its value |
| 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(); |
| |
| // Clean up SCSS interpolation syntax (e.g., #{$variable}) |
| value = value.replace(/#\{[^}]+\}/g, '').trim(); |
| |
| // Remove inline comments |
| value = value.replace(/\/\/.*$/gm, '').trim(); |
| |
| // Only store if we have a clean value (not empty after removing interpolations) |
| if (value) { |
| cssVarValues[varName] = value; |
| } |
| } |
| |
| return cssVarValues; |
| } catch (error) { |
| console.warn('Could not parse CSS variables from _root.scss:', error); |
| return {}; |
| } |
| } |
| |
| const cssVarValues = parseCSSVariables(); |
| |
| // Function to add CSS variable value comments |
| function addVarComments(cssValue: string): string { |
| const comments: string[] = []; |
| |
| // Collect resolved values for all CSS variables |
| 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; |
| }); |
| |
| // Append comments after the last semicolon or at the end |
| if (comments.length > 0) { |
| const hasSemicolon = cssValue.trimEnd().endsWith(';'); |
| return `${cssValue}${hasSemicolon ? '' : ';'} ${comments.join(' ')}`; |
| } |
| |
| return cssValue; |
| } |
| |
| // If no explicit columns provided, infer from the first data item |
| const inferredColumns = columns || (() => { |
| if (referenceData.length === 0) { |
| return [ |
| { label: 'Class', key: 'class' }, |
| { label: 'Styles', key: 'styles' } |
| ]; |
| } |
| |
| const firstItem = referenceData[0]; |
| return Object.keys(firstItem) |
| .filter(key => key !== 'comment') // Exclude comment field from columns |
| .map(key => ({ |
| label: key.charAt(0).toUpperCase() + key.slice(1), |
| key: key |
| })); |
| })(); |
| |
| // Transform frontmatter format to table format |
| const tableData = referenceData.map((item: ReferenceItem) => { |
| const transformedItem: Record<string, any> = {}; |
| |
| inferredColumns.forEach(column => { |
| const key = column.key; |
| let value = item[key]; |
| |
| if (key === 'class' && typeof value === 'string' && !value.startsWith('.')) { |
| value = `.${value}`; |
| } |
| |
| if (key === 'styles') { |
| let processedStyles = ''; |
| |
| if (typeof value === 'string') { |
| processedStyles = addVarComments(value); |
| } else if (typeof value === 'object' && !Array.isArray(value)) { |
| // Handle object syntax: { prop: value, prop2: value2 } |
| processedStyles = Object.entries(value) |
| .map(([prop, val]) => { |
| const cssLine = `${prop}: ${val};`; |
| return addVarComments(cssLine); |
| }) |
| .join('<br/>'); |
| } else if (Array.isArray(value)) { |
| processedStyles = value.map((style: any) => { |
| if (typeof style === 'string') { |
| const formattedStyle = style.includes(':') ? style + (style.endsWith(';') ? '' : ';') : style; |
| return addVarComments(formattedStyle); |
| } |
| if (typeof style === 'object') { |
| const cssLine = Object.entries(style).map(([prop, val]) => `${prop}: ${val};`).join(' '); |
| return addVarComments(cssLine); |
| } |
| return style; |
| }).join('<br/>'); |
| } else { |
| processedStyles = value || ''; |
| } |
| |
| // Append manual comment if provided in frontmatter |
| if (item.comment) { |
| processedStyles += `<br/><span class="color-3">/* ${item.comment} */</span>`; |
| } |
| |
| transformedItem[key] = processedStyles; |
| } else { |
| transformedItem[key] = value; |
| } |
| }); |
| |
| return transformedItem; |
| }); |
| --- |
| |
| <div class="table-responsive bd-reference-table"> |
| <table class={className}> |
| <thead> |
| <tr> |
| {inferredColumns.map(column => ( |
| <th scope="col">{column.label}</th> |
| ))} |
| </tr> |
| </thead> |
| <tbody> |
| {tableData.map((row: any) => ( |
| <tr> |
| {inferredColumns.map(column => ( |
| <td> |
| {column.key === 'styles' ? ( |
| <Fragment set:html={row[column.key]} /> |
| ) : ( |
| row[column.key] |
| )} |
| </td> |
| ))} |
| </tr> |
| ))} |
| </tbody> |
| </table> |
| </div> |