@hypothesis/frontend-shared
Version:
Shared components, styles and utilities for Hypothesis projects
738 lines (728 loc) • 20.9 kB
JavaScript
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