UNPKG

@hypothesis/frontend-shared

Version:

Shared components, styles and utilities for Hypothesis projects

738 lines (728 loc) 20.9 kB
var _jsxFileName = "/home/runner/work/frontend-shared/frontend-shared/src/pattern-library/components/Library.tsx"; import classnames from 'classnames'; import { toChildArray, createContext } from 'preact'; import { useState, useContext, useEffect } from 'preact/hooks'; import { Link as RouteLink } from 'wouter-preact'; import { CautionIcon, CodeIcon, Link as UILink, Scroll, ScrollContainer } from '../../'; import { highlightCode, jsxToHTML } from '../util/jsx-to-string'; /** * Components for rendering component documentation, examples and demos in the * pattern-library page. */ import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "preact/jsx-dev-runtime"; /** * Render content for a pattern-library page */ function Page({ children, intro, title }) { return _jsxDEV("main", { className: "styled-text text-stone-600", children: _jsxDEV("div", { className: "px-8 py-4", children: [_jsxDEV("h1", { className: "text-3xl text-slate-600 font-bold", id: "page-header", children: title }, void 0, false, { fileName: _jsxFileName, lineNumber: 36, columnNumber: 9 }, this), intro && _jsxDEV("div", { className: "my-8 pb-8 border-b space-y-4 font-light text-xl leading-relaxed", children: intro }, void 0, false, { fileName: _jsxFileName, lineNumber: 40, columnNumber: 11 }, this), children] }, void 0, true, { fileName: _jsxFileName, lineNumber: 35, columnNumber: 7 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 34, columnNumber: 5 }, this); } function Heading({ children, level = 2, ...htmlAttributes }) { if (level <= 2) { return _jsxDEV("h2", { className: "text-2xl text-slate-600 font-medium mb-4", ...htmlAttributes, children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 62, columnNumber: 7 }, this); } else if (level === 3) { return _jsxDEV("h3", { className: "text-xl text-slate-600 font-medium mb-4", ...htmlAttributes, children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 71, columnNumber: 7 }, this); } else { return _jsxDEV("h4", { className: "text-lg border-b border-stone-300 text-slate-600 font-normal mb-4", ...htmlAttributes, children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 80, columnNumber: 7 }, this); } } // Keep track of <Section> nested depth const SectionDepthContext = createContext(0); /** * Render a primary section of a page. Each component documented on a pattern- * library page gets its own section. */ function Section({ children, intro, level, title, ...htmlAttributes }) { const sectionDepth = useContext(SectionDepthContext); const depth = level !== null && level !== void 0 ? level : sectionDepth + 1; return _jsxDEV(SectionDepthContext.Provider, { value: depth, children: _jsxDEV("section", { "data-depth": depth, className: classnames('leading-relaxed', { 'mt-8 mb-16': depth <= 1, 'mt-8 mb-8': depth === 2, 'mt-6 mb-8': depth >= 3 }), ...htmlAttributes, children: [title && // Heading level is one larger than section level because heading // level 1 is reserved for the main heading of the page. _jsxDEV(Heading, { level: depth + 1, children: title }, void 0, false, { fileName: _jsxFileName, lineNumber: 128, columnNumber: 11 }, this), intro && _jsxDEV("div", { className: "text-base space-y-3 leading-relaxed", children: intro }, void 0, false, { fileName: _jsxFileName, lineNumber: 131, columnNumber: 11 }, this), children] }, void 0, true, { fileName: _jsxFileName, lineNumber: 116, columnNumber: 7 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 115, columnNumber: 5 }, this); } /** * Render a second-level section. e.g. Usage, Props, Status */ function SectionL2({ children, title, ...htmlAttributes }) { return _jsxDEV(Section, { level: 2, title: title, ...htmlAttributes, children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 153, columnNumber: 5 }, this); } /** * Render content in a third-level section, e.g. documentation * about a specific prop or examples of usage. */ function SectionL3({ children, title, ...htmlAttributes }) { return _jsxDEV(Section, { level: 3, title: title, ...htmlAttributes, children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 174, columnNumber: 5 }, this); } function SimpleError({ message }) { return _jsxDEV("div", { className: "w-full text-red-600 p-2 border rounded border-red-600", children: message }, void 0, false, { fileName: _jsxFileName, lineNumber: 182, columnNumber: 5 }, this); } /** * Fetches provided example file and returns its contents as text, excluding * the import statements. * An error is thrown if the file cannot be fetched for any reason. */ async function fetchCodeExample(exampleFile, signal) { const res = await fetch(`/examples/${exampleFile}.tsx`, { signal }); if (res.status >= 400) { throw new Error(`Failed loading ${exampleFile} example file`); } const text = await res.text(); // Remove import statements and trim trailing empty lines return text.replace(/^import .*;\n/gm, '').replace(/^\s*\n*/, ''); } function isStaticDemoContent(contentResult) { return 'children' in contentResult; } /** * Determines what are the contents to be used for a Demo, which can be either * an explicitly provided set of children, or the contents of an example file * which is dynamically imported. */ function useDemoContents(props) { const [code, setCode] = useState(); const [example, setExample] = useState(); const [codeError, setCodeError] = useState(); const [exampleError, setExampleError] = useState(); useEffect(() => { if (!props.exampleFile) { return () => {}; } import(`../examples/${props.exampleFile}.tsx`).then(({ default: Example }) => setExample(_jsxDEV(Example, {}, void 0, false, { fileName: _jsxFileName, lineNumber: 270, columnNumber: 50 }, this))).catch(() => setExampleError(`Failed loading ../examples/${props.exampleFile}.tsx module`)); const controller = new AbortController(); fetchCodeExample(props.exampleFile, controller.signal).then(setCode).catch(e => setCodeError(e.message)); return () => controller.abort(); }, [props.exampleFile, props.children]); if (props.children) { return { children: props.children }; } return { code, example, codeError, exampleError }; } /** * Render a "Demo", with optional source. This will render the children as * provided in a tabbed container. If `withSource` is `true`, the JSX source * of the children will be provided in a separate "Source" tab from the * rendered Demo content. */ function Demo({ classes, withSource = false, style = {}, title, ...rest }) { const [visibleTab, setVisibleTab] = useState('demo'); const demoContents = useDemoContents(rest); const isStaticContent = isStaticDemoContent(demoContents); return _jsxDEV("div", { className: "my-8 p-2 space-y-1", children: [_jsxDEV("div", { className: "flex items-center px-2", children: [_jsxDEV("div", { className: "py-1 grow", children: _jsxDEV("h5", { className: "text-base text-base leading-none font-semibold text-slate-600", children: title }, void 0, false, { fileName: _jsxFileName, lineNumber: 313, columnNumber: 11 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 312, columnNumber: 9 }, this), _jsxDEV("div", { className: "flex flex-row items-center justify-end gap-x-4", children: withSource && _jsxDEV("button", { className: classnames('flex items-center gap-x-1.5 py-1 px-2 rounded-md', 'text-sm text-slate-500', 'hover:bg-slate-50 border hover:border-slate-300', { 'bg-slate-100 border border-slate-400 shadow-inner': visibleTab === 'source', 'border-slate-100 shadow': visibleTab !== 'source' }), "aria-pressed": visibleTab === 'source', onClick: () => setVisibleTab(prevState => prevState === 'source' ? 'demo' : 'source'), title: "Toggle view-source panel", children: _jsxDEV(CodeIcon, { className: "w-[18px] h-[18px]" }, void 0, false, { fileName: _jsxFileName, lineNumber: 338, columnNumber: 15 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 319, columnNumber: 13 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 317, columnNumber: 9 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 311, columnNumber: 7 }, this), _jsxDEV("div", { className: "p-2 unstyled-text", children: [visibleTab === 'demo' && _jsxDEV("div", { className: "w-full p-8 rounded-md border border-stone-300 bg-stone-50 rounded-md", style: style, children: _jsxDEV("div", { className: classnames('h-full flex flex-row items-center justify-center gap-2', classes), children: [!isStaticContent ? demoContents.example : demoContents.children, !isStaticContent && demoContents.exampleError && _jsxDEV(SimpleError, { message: demoContents.exampleError }, void 0, false, { fileName: _jsxFileName, lineNumber: 357, columnNumber: 17 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 349, columnNumber: 13 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 345, columnNumber: 11 }, this), visibleTab === 'source' && _jsxDEV(_Fragment, { children: [!isStaticContent ? demoContents.code && _jsxDEV(Code, { content: demoContents.code }, void 0, false, { fileName: _jsxFileName, lineNumber: 365, columnNumber: 36 }, this) : _jsxDEV("div", { className: "border w-full rounded-md bg-slate-7 text-color-text-inverted p-4", children: _jsxDEV("ul", { children: toChildArray(demoContents.children).map((child, idx) => { return _jsxDEV("li", { children: _jsxDEV("code", { children: _jsxDEV("pre", { className: "font-pre whitespace-pre-wrap break-words text-sm", dangerouslySetInnerHTML: { __html: jsxToHTML(child) } }, void 0, false, { fileName: _jsxFileName, lineNumber: 373, columnNumber: 27 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 372, columnNumber: 25 }, this) }, idx, false, { fileName: _jsxFileName, lineNumber: 371, columnNumber: 23 }, this); }) }, void 0, false, { fileName: _jsxFileName, lineNumber: 368, columnNumber: 17 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 367, columnNumber: 15 }, this), !isStaticContent && demoContents.codeError && _jsxDEV(SimpleError, { message: demoContents.codeError }, void 0, false, { fileName: _jsxFileName, lineNumber: 387, columnNumber: 15 }, this)] }, void 0, true)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 343, columnNumber: 7 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 310, columnNumber: 5 }, this); } /** * Render a pill to highlight a change */ function StatusChip({ status }) { return _jsxDEV("span", { className: classnames('rounded-md py-1 px-2 mr-1.5 text-sm font-semibold', { 'bg-red-error text-color-text-inverted': status === 'breaking', 'bg-yellow-notice': status === 'deprecated' || status === 'changed', 'bg-green-success text-color-text-inverted': status === 'added' }), children: [status === 'breaking' && _jsxDEV("span", { children: "Breaking" }, void 0, false, { fileName: _jsxFileName, lineNumber: 417, columnNumber: 33 }, this), status === 'deprecated' && _jsxDEV("span", { children: "Deprecated" }, void 0, false, { fileName: _jsxFileName, lineNumber: 418, columnNumber: 35 }, this), status === 'added' && _jsxDEV("span", { children: "Added" }, void 0, false, { fileName: _jsxFileName, lineNumber: 419, columnNumber: 30 }, this), status === 'changed' && _jsxDEV("span", { children: "Changed" }, void 0, false, { fileName: _jsxFileName, lineNumber: 420, columnNumber: 32 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 407, columnNumber: 5 }, this); } /** * Wrapper around a list of changelog items */ function Changelog({ children }) { return _jsxDEV("ul", { children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 433, columnNumber: 10 }, this); } /** * Single changelog item */ function ChangelogItem({ status, children }) { return _jsxDEV("li", { className: classnames('flex gap-x-2', // "Outdent": <ul>s are indented (for readability) by 2rem. In the // case of this particular <ul>, we want to regain left-hand space // because the status chips are aligned to the right. '-ml-8 '), children: [_jsxDEV("div", { className: "mt-2 min-w-[7rem] text-right", children: _jsxDEV(StatusChip, { status: status }, void 0, false, { fileName: _jsxFileName, lineNumber: 456, columnNumber: 9 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 455, columnNumber: 7 }, this), _jsxDEV("div", { className: "grow", children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 458, columnNumber: 7 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 446, columnNumber: 5 }, this); } function isCodeWithContent(props) { return 'content' in props; } /** * Dynamically resolves the content based on provided props. * An error is optionally returned in case loading the content failed. */ function useCodeContent(props) { const hasStaticContent = isCodeWithContent(props); const [codeMarkup, setCodeMarkup] = useState(hasStaticContent ? jsxToHTML(props.content) : undefined); const [error, setError] = useState(); useEffect(() => { if (hasStaticContent) { return () => {}; } const controller = new AbortController(); fetchCodeExample(`/examples/${props.exampleFile}.tsx`, controller.signal).then(code => setCodeMarkup(highlightCode(code))).catch(setError); return () => controller.abort(); }, [hasStaticContent, props]); return [codeMarkup, error]; } /** * Render provided `content` as a "code block" example. * * Long code content will scroll if <Code /> is rendered inside a parent * element with constrained dimensions. */ function Code({ size, title, ...rest }) { const [codeMarkup, error] = useCodeContent(rest); return _jsxDEV("figure", { className: "space-y-2 min-h-0 h-full", children: [codeMarkup && _jsxDEV(ScrollContainer, { borderless: true, children: [_jsxDEV("div", { className: classnames('unstyled-text bg-slate-7 text-color-text-inverted p-4 rounded-md min-h-0 h-full', { 'text-sm': size === 'sm' }), children: _jsxDEV(Scroll, { variant: "flat", children: _jsxDEV("code", { className: "text-color-text-inverted", children: _jsxDEV("pre", { className: "whitespace-pre-wrap", dangerouslySetInnerHTML: { __html: codeMarkup } }, void 0, false, { fileName: _jsxFileName, lineNumber: 539, columnNumber: 17 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 538, columnNumber: 15 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 537, columnNumber: 13 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 531, columnNumber: 11 }, this), title && _jsxDEV("figcaption", { className: "flex justify-end", children: _jsxDEV("span", { className: "italic", children: title }, void 0, false, { fileName: _jsxFileName, lineNumber: 548, columnNumber: 15 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 547, columnNumber: 13 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 530, columnNumber: 9 }, this), error && _jsxDEV(SimpleError, { message: error.message }, void 0, false, { fileName: _jsxFileName, lineNumber: 553, columnNumber: 17 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 528, columnNumber: 5 }, this); } /** * Render import "usage" of a given `componentName` */ function Usage({ componentName, symbolName = componentName, size = 'md' }) { const importPath = '@hypothesis/frontend-shared'; return _jsxDEV(Code, { content: `import { ${symbolName} } from '${importPath}'; `, size: size }, void 0, false, { fileName: _jsxFileName, lineNumber: 575, columnNumber: 5 }, this); } /** * Render an internal link to another pattern-library page. * TODO: Support external links */ function Link({ children, href }) { return _jsxDEV(RouteLink, { href: href, asChild: true, children: _jsxDEV(UILink, { underline: "always", children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 595, columnNumber: 7 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 594, columnNumber: 5 }, this); } function Callout({ children }) { return _jsxDEV("div", { className: "flex gap-x-2 p-3 rounded-md bg-slate-50 border shadow-inner", children: [_jsxDEV("div", { className: "w-6 h-6", children: _jsxDEV(CautionIcon, { className: "text-yellow-notice w-6 h-6 mt-0.5" }, void 0, false, { fileName: _jsxFileName, lineNumber: 604, columnNumber: 9 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 603, columnNumber: 7 }, this), _jsxDEV("div", { children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 606, columnNumber: 7 }, this)] }, void 0, true, { fileName: _jsxFileName, lineNumber: 602, columnNumber: 5 }, this); } /** * Render a two-column grid for label-description pairs */ function Info({ children }) { return _jsxDEV("div", { className: "grid grid-cols-[6rem_1fr] gap-x-4 gap-y-2 m-4", children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 616, columnNumber: 5 }, this); } /** * Render a "row" in an Info layout with a label and description (children) */ function InfoItem({ children, label }) { return _jsxDEV(_Fragment, { children: [_jsxDEV("div", { className: "pt-1 text-right font-medium text-stone-600 text-sm", children: label }, void 0, false, { fileName: _jsxFileName, lineNumber: 634, columnNumber: 7 }, this), _jsxDEV("div", { children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 637, columnNumber: 7 }, this)] }, void 0, true); } export default { Callout, Changelog, ChangelogItem, Code, Demo, Info, InfoItem, Link, Page, Section, SectionL2, SectionL3, StatusChip, Usage }; //# sourceMappingURL=Library.js.map