UNPKG

fumadocs-ui

Version:

The framework for building a documentation website in Next.js

138 lines (137 loc) 4.81 kB
import * as styles from './styles.js'; import plugin from 'tailwindcss/plugin'; import merge from 'lodash.merge'; import parser from 'postcss-selector-parser'; function inWhere(selector, { className, prefix, modifier }) { const prefixedNot = prefix(`.not-${className}`).slice(1); const selectorPrefix = selector.startsWith('>') ? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} ` : ''; // Parse the selector, if every component ends in the same pseudo element(s) then move it to the end const [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector); if (trailingPseudo) { return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}`; } return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))`; } function configToCss(config = {}, { className, modifier, prefix }) { function updateSelector(k, v) { if (Array.isArray(v)) { return [k, v]; } if (typeof v === 'object' && v !== null) { const nested = Object.values(v).some((prop) => typeof prop === 'object'); if (nested) { return [ inWhere(k, { className, modifier, prefix }), v, Object.fromEntries(Object.entries(v).map(([k, v]) => updateSelector(k, v))), ]; } return [inWhere(k, { className, modifier, prefix }), v]; } return [k, v]; } const css = config.css ?? []; return Object.fromEntries(Object.entries(merge({}, ...(Array.isArray(css) ? css : [css]))).map(([k, v]) => updateSelector(k, v))); } const parseSelector = parser(); function commonTrailingPseudos(selector) { const ast = parseSelector.astSync(selector); const matrix = []; // Put the pseudo elements in reverse order in a sparse, column-major 2D array for (const [i, sel] of ast.nodes.entries()) { for (const [j, child] of [...sel.nodes].reverse().entries()) { // We only care about pseudo elements if (child.type !== 'pseudo' || !child.value.startsWith('::')) { break; } matrix[j] = matrix[j] || []; matrix[j][i] = child; } } const trailingPseudos = parser.selector({ value: '', }); // At this point the pseudo elements are in a column-major 2D array // This means each row contains one "column" of pseudo elements from each selector // We can compare all the pseudo elements in a row to see if they are the same for (const pseudos of matrix) { // It's a sparse 2D array so there are going to be holes in the rows // We skip those if (!pseudos) { continue; } const values = new Set(pseudos.map((p) => p.value)); // The pseudo elements are not the same if (values.size > 1) { break; } pseudos.forEach((pseudo) => pseudo.remove()); trailingPseudos.prepend(pseudos[0]); } if (trailingPseudos.nodes.length) { return [trailingPseudos.toString(), ast.toString()]; } return [null, selector]; } const SELECTORS = [ ['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'], ['h1'], ['h2'], ['h3'], ['h4'], ['h5'], ['h6'], ['p'], ['a'], ['blockquote'], ['figure'], ['figcaption'], ['strong'], ['em'], ['kbd'], ['code'], ['pre'], ['ol'], ['ul'], ['li'], ['table'], ['thead'], ['tr'], ['th'], ['td'], ['img'], ['video'], ['hr'], ['lead', '[class~="lead"]'], ]; export const typography = plugin.withOptions(({ className = 'prose', ...styleOptions } = {}) => { return ({ addVariant, addComponents, ...rest }) => { const prefix = rest.prefix; for (const [name, ...values] of SELECTORS) { const selectors = values.length === 0 ? [name] : values; const selector = selectors.join(', '); addVariant(`${className}-${name}`, `& :is(${inWhere(selector, { prefix, className, })})`); } addComponents({ [`.${className}`]: configToCss({ ...styles.DEFAULT, css: [ ...(styles.DEFAULT.css ?? []), styleOptions.disableRoundedTable ? styles.normalTable : styles.roundedTable, ], }, { className, modifier: 'DEFAULT', prefix, }), }); }; }); export default typography;