// Renders the parsed component contracts plus the token vocabulary as one markdown page. The // point is an agent picking a component and a token WITHOUT opening source — so every table here // is derived, never typed by hand, and `catalog.test.ts` fails the build when the file drifts. import { BUTTON_VARIANTS, SIZES, TONES } from '../components/variants'; import { breakpointTokens, COLOR_ROLES, durationTokens, easingTokens, fontSizeTokens, fontWeightTokens, lineHeightTokens, radiusTokens, spaceTokens, strokeTokens, zTokens, } from '../tokens/tokens'; import type { ComponentDoc } from './parse-component'; export const CATALOG_BANNER = ''; /** Pipes inside a union type would end the markdown cell. */ function cell(text: string): string { return text.replace(/\|/g, '\\|'); } function table(headers: readonly string[], rows: readonly (readonly string[])[]): string { const head = `| ${headers.join(' | ')} |`; const rule = `|${headers.map(() => '---').join('|')}|`; return [head, rule, ...rows.map((row) => `| ${row.join(' | ')} |`)].join('\n'); } function scaleTable(title: string, tokens: Readonly>): string { const rows = Object.entries(tokens).map(([name, value]) => [`\`${name}\``, `\`${cell(value)}\``]); return `### ${title}\n\n${table(['Token', 'Value'], rows)}`; } function componentSection(doc: ComponentDoc): string { const rows = doc.props.map((prop) => [ `\`${prop.name}\``, `\`${cell(prop.type)}\``, prop.required ? 'yes' : '—', cell(prop.doc), ]); const props = rows.length === 0 ? '_No props._' : table(['Prop', 'Type', 'Required', 'Notes'], rows); return `### ${doc.name}\n\n${doc.summary}\n\n${props}`; } export function renderCatalog(components: readonly ComponentDoc[]): string { const index = components.map((doc) => `\`${doc.name}\``).join(' · '); return `${CATALOG_BANNER} # @ultimat3/ui catalog Every component and every token, projected from source. Import all of it from \`@ultimat3/ui\`. ${components.length} components: ${index} ## Vocabulary One size scale, one tone scale, one variant scale — shared by every component that has them. ${table( ['Scale', 'Values'], [ ['`Size`', SIZES.map((size) => `\`${size}\``).join(' · ')], ['`Tone`', TONES.map((tone) => `\`${tone}\``).join(' · ')], ['`ButtonVariant`', BUTTON_VARIANTS.map((variant) => `\`${variant}\``).join(' · ')], [ '`SpaceStep`', Object.keys(spaceTokens) .map((step) => `\`${step}\``) .join(' · '), ], ], )} ## Components ${components.map(componentSection).join('\n\n')} ## Tokens Colour roles are the only colours that exist. Use them through SCSS (\`t.role('accent')\`) or CSS (\`rgb(var(--color-accent) / 0.12)\`); \`colorRgb(theme, role)\` resolves one for canvas, charts and email. ### Colour roles ${table( ['Role', 'Custom property'], COLOR_ROLES.map((role) => [`\`${role}\``, `\`--color-${role}\``]), )} ${scaleTable('Space — `--space-*`', spaceTokens)} ${scaleTable('Radius — `--radius-*`', radiusTokens)} ${scaleTable('Stroke — `--stroke-*`', strokeTokens)} ${scaleTable('Font size — `--text-*`', fontSizeTokens)} ${scaleTable('Font weight — `--weight-*`', fontWeightTokens)} ${scaleTable('Line height — `--leading-*`', lineHeightTokens)} ${scaleTable('Duration — `--duration-*`', durationTokens)} ${scaleTable('Easing — `--easing-*`', easingTokens)} ${scaleTable('Z-index — `--z-*`', zTokens)} ${scaleTable('Breakpoints — `respond-to` · `respond-down` · `respond-between`', breakpointTokens)} \`@include t.respond-to(md)\` is \`md\` and wider, \`t.respond-down(md)\` is narrower than \`md\`, and \`t.respond-between(md, lg)\` is \`md\` up to \`lg\`. A max-width arm stops 0.02px under its rung, so no width matches two of them. ### Computed lengths ${table( ['Function', 'Answers'], [ ['`t.rem(24px)`', '`1.5rem` — px, rem or a unitless px count in, rem out'], [ '`t.fluid(1rem, 2rem, 20rem, 80rem)`', 'one `clamp()`: the first size at a 20rem viewport, the second at 80rem, linear between', ], ], )} `; }