@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
159 lines • 7.12 kB
JavaScript
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