UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

159 lines 7.12 kB
import { Shade, createComponent } from '@furystack/shades'; import { cssVariableTheme } from '../../services/css-variable-theme.js'; import { Checkbox } from '../inputs/checkbox.js'; import { Typography } from '../typography.js'; import { parseMarkdown, toggleCheckbox } from './markdown-parser.js'; const renderInline = (nodes) => { return (createComponent(createComponent, null, nodes.map((node) => { switch (node.type) { case 'text': return createComponent(createComponent, null, node.content); case 'bold': return createComponent("strong", null, renderInline(node.children)); case 'italic': return createComponent("em", null, renderInline(node.children)); case 'code': return createComponent("code", { className: "md-inline-code" }, node.content); case 'link': return (createComponent("a", { className: "md-link", href: node.href, target: "_blank", rel: "noopener noreferrer" }, renderInline(node.children))); case 'image': return createComponent("img", { className: "md-image", src: node.src, alt: node.alt }); default: return createComponent(createComponent, null); } }))); }; const variantForLevel = (level) => { const map = { 1: 'h1', 2: 'h2', 3: 'h3', 4: 'h4', 5: 'h5', 6: 'h6' }; return map[level]; }; const renderBlock = (node, _index, options) => { switch (node.type) { case 'heading': return createComponent(Typography, { variant: variantForLevel(node.level) }, renderInline(node.children)); case 'paragraph': return createComponent(Typography, { variant: "body1" }, renderInline(node.children)); case 'codeBlock': return (createComponent("pre", { className: "md-code-block", "data-language": node.language || undefined, tabIndex: 0 }, createComponent("code", null, node.content))); case 'blockquote': return (createComponent("blockquote", { className: "md-blockquote" }, node.children.map((child, i) => renderBlock(child, i, options)))); case 'horizontalRule': return createComponent("hr", { className: "md-hr" }); case 'list': { const listItems = node.items.map((item) => { if (item.checkbox !== undefined) { const handleChange = () => { if (!options.readOnly && options.onChange) { options.onChange(toggleCheckbox(options.content, item.sourceLineIndex)); } }; return (createComponent("li", { className: "md-list-item md-checkbox-item", "data-source-line": String(item.sourceLineIndex) }, createComponent(Checkbox, { checked: item.checkbox === 'checked', disabled: options.readOnly, onchange: handleChange }), createComponent("span", { className: "md-checkbox-label" }, renderInline(item.children)))); } return createComponent("li", { className: "md-list-item" }, renderInline(item.children)); }); if (node.ordered) { return createComponent("ol", { className: "md-list" }, listItems); } return createComponent("ul", { className: "md-list" }, listItems); } default: return createComponent(createComponent, null); } }; /** * Renders a Markdown string using FuryStack Shades components. * Supports headings, paragraphs, lists, checkboxes, code blocks, * blockquotes, images, links, and horizontal rules. */ export const MarkdownDisplay = Shade({ customElementName: 'shade-markdown-display', css: { display: 'block', fontFamily: cssVariableTheme.typography.fontFamily, color: cssVariableTheme.text.primary, lineHeight: cssVariableTheme.typography.lineHeight.relaxed, '& .md-inline-code': { fontFamily: 'monospace', backgroundColor: cssVariableTheme.action.hoverBackground, padding: '2px 6px', borderRadius: cssVariableTheme.shape.borderRadius.xs, fontSize: '0.9em', }, '& .md-code-block': { fontFamily: 'monospace', background: cssVariableTheme.background.default, border: `1px solid ${cssVariableTheme.action.subtleBorder}`, borderRadius: cssVariableTheme.shape.borderRadius.md, padding: cssVariableTheme.spacing.md, overflow: 'auto', fontSize: cssVariableTheme.typography.fontSize.sm, margin: `${cssVariableTheme.spacing.sm} 0`, }, '& .md-code-block code': { font: 'inherit', whiteSpace: 'pre', }, '& .md-code-block:focus-visible': { outline: cssVariableTheme.action.focusOutline, outlineOffset: '-2px', }, '& .md-blockquote': { borderLeft: `4px solid ${cssVariableTheme.palette.primary.main}`, margin: `${cssVariableTheme.spacing.sm} 0`, padding: `${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.md}`, color: cssVariableTheme.text.secondary, }, '& .md-link': { color: cssVariableTheme.palette.primary.main, textDecoration: 'none', }, '& .md-link:hover': { textDecoration: 'underline', }, '& .md-link:focus-visible': { textDecoration: 'underline', outline: cssVariableTheme.action.focusOutline, outlineOffset: '2px', borderRadius: cssVariableTheme.shape.borderRadius.xs, }, '& .md-image': { maxWidth: '100%', borderRadius: cssVariableTheme.shape.borderRadius.md, }, '& .md-hr': { border: 'none', borderTop: `1px solid ${cssVariableTheme.divider}`, margin: `${cssVariableTheme.spacing.md} 0`, }, '& .md-list': { paddingLeft: cssVariableTheme.spacing.xl, margin: `${cssVariableTheme.spacing.sm} 0`, }, '& .md-list-item': { marginBottom: cssVariableTheme.spacing.xs, fontSize: cssVariableTheme.typography.fontSize.md, }, '& .md-checkbox-item': { listStyle: 'none', display: 'flex', alignItems: 'center', gap: cssVariableTheme.spacing.sm, }, '& .md-checkbox-label': { fontSize: cssVariableTheme.typography.fontSize.md, }, }, render: ({ props }) => { const readOnly = props.readOnly !== false; const ast = parseMarkdown(props.content); return (createComponent("div", { className: "md-root" }, ast.map((node, i) => renderBlock(node, i, { content: props.content, readOnly, onChange: props.onChange, })))); }, }); //# sourceMappingURL=markdown-display.js.map