| --- |
| 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> |