@workday/canvas-kit-docs
Version:
Documentation components of Canvas Kit components
218 lines (217 loc) • 10.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React from 'react';
import styled from '@emotion/styled';
import { createComponent } from '@workday/canvas-kit-react/common';
import { useDialogModel, Dialog } from '@workday/canvas-kit-react/dialog';
import { colors, space, type } from '@workday/canvas-kit-react/tokens';
import { Breadcrumbs } from '@workday/canvas-kit-react/breadcrumbs';
import { MDX, MdxJSToJSX } from './MDXElements';
import { Hyperlink } from '@workday/canvas-kit-react/button';
import { docs } from './docs';
import { Value } from './Value';
/**
* This context allows us to keep track if we're within a nested stack of dialog
*/
const NestedContext = React.createContext(false);
/**
* Context to help keep track of breadcrumbs and update them
*/
const SymbolDocBreadcrumbsContext = React.createContext(undefined);
/** React context to track the current rendering context to avoid tables inside tables */
export const RenderContext = React.createContext('table');
/**
* React context to track the current heading level. This is useful to relatively change heading
* levels of a subtree
*/
export const HeadingLevelContext = React.createContext(3);
/**
* React context to keep track of the current indent level
*/
export const IndentLevelContext = React.createContext(0);
/**
* Utility function for getting non-breaking space for code formatting. It will print a string of
* non-breaking spaces according to the level provided. These characters are safe for a React render
* function.
*/
export function indent(level) {
return [...Array(level * 2)].map(v => '\u00A0').join('');
}
export function capitalize(input) {
return input.charAt(0).toUpperCase() + input.slice(1);
}
/**
* Special heading component that uses @mdx-js/react heading components, but is also aware of
* relative heading levels.
*/
export const Heading = createComponent('h4')({
displayName: 'MDXHeading',
Component({ headingOffset = 0, ...elemProps }) {
const headingLevel = React.useContext(HeadingLevelContext);
const as = `h${headingLevel + headingOffset}`; // Make Typescript happy
return _jsx(MDX, { as: as, ...elemProps });
},
});
export function renderTypeParameters(typeParameters) {
return typeParameters && typeParameters.length ? (_jsxs(RenderContext.Provider, { value: "inline", children: [_jsx("span", { children: "<" }), typeParameters.map((p, index) => {
return (_jsxs(React.Fragment, { children: [index !== 0 && _jsx("span", { className: "token punctuation", children: ", " }), _jsx(Value, { value: p })] }, index));
}), _jsx("span", { children: ">" })] })) : ('');
}
const ButtonHyperLink = Hyperlink.as('button');
export const SymbolDialog = ({ value }) => {
const [symbol, setSymbol] = React.useState(undefined);
const { breadcrumbsList, updateBreadcrumbs } = React.useContext(SymbolDocBreadcrumbsContext);
const nestedContext = React.useContext(NestedContext);
const model = useDialogModel({
onShow() {
setSymbol(docs.find(d => value.name === d.name) || undefined);
},
onHide() {
// Reset breadcrumbs when the dialog is closed
updateBreadcrumbs([]);
},
});
const handleTargetClick = (e) => {
e.preventDefault();
updateBreadcrumbs(breadcrumbsList.concat(value.name));
};
const handleBreadcrumbClick = (e, index) => {
e.preventDefault();
if (e.currentTarget.textContent &&
breadcrumbsList.length &&
breadcrumbsList.indexOf(e.currentTarget.textContent) > -1) {
// Slice breadcrumbs list up to but not including the current item clicked
updateBreadcrumbs(breadcrumbsList.slice(0, index + 1));
}
};
// If we're inside a nested context, we render a Hyperlink. This button will update the dialog instead of rendering a new one.
if (nestedContext) {
return (_jsx(ButtonHyperLink, { style: {
border: 'none',
background: 'transparent',
fontSize: 'inherit',
fontFamily: 'inherit',
}, onClick: handleTargetClick, children: value.displayName || value.name }));
}
return (_jsx(NestedContext.Provider, { value: true, children: _jsxs(Dialog, { model: model, children: [_jsx(Dialog.Target, { as: ButtonHyperLink, className: "token symbol", onClick: handleTargetClick, style: {
border: 'none',
background: 'transparent',
fontSize: 'inherit',
fontFamily: 'inherit',
}, "aria-haspopup": "true", children: value.displayName || value.name }), renderTypeParameters(value.typeParameters), _jsx(Dialog.Popper, { children: _jsxs(Dialog.Card, { maxHeight: "50vh", maxWidth: "90vh", minWidth: '600px', children: [_jsx(Dialog.CloseIcon, {}), _jsxs(Dialog.Heading, { children: [value.name, " "] }), breadcrumbsList.length > 1 && (_jsx(Breadcrumbs, { "aria-label": "Breadcrumbs", children: _jsx(Breadcrumbs.List, { paddingX: "xxs", children: breadcrumbsList.map((item, index) => {
return (_jsx(_Fragment, { children: index === breadcrumbsList.length - 1 ? (_jsx(Breadcrumbs.CurrentItem, { children: item }, index)) : (_jsx(Breadcrumbs.Item, { children: _jsx(Breadcrumbs.Link, { onClick: e => handleBreadcrumbClick(e, index), href: '#', children: item }) }, index)) }));
}) }) })), _jsx(Dialog.Body, { children: _jsx(RenderContext.Provider, { value: "table", children: _jsx(IndentLevelContext.Provider, { value: 0, children: symbol ? (_jsx(SymbolDoc, { name: breadcrumbsList.length >= 1
? breadcrumbsList[breadcrumbsList.length - 1]
: value.name, headingStart: 3, hideHeading: true })) : (_jsxs(_Fragment, { children: [_jsx("p", { children: "Basic type information:" }), _jsx("pre", { children: _jsx("code", { children: value.name }) })] })) }) }) })] }) })] }) }));
};
function createColor(color) {
return {
color: colors[color],
};
}
const StyledSymbolDoc = styled('div')({
marginBottom: space.m,
'button[data-symbol]': {
border: 'none',
background: 'transparent',
fontSize: 'inherit',
fontFamily: 'inherit',
},
code: {
fontSize: type.properties.fontSizes[14],
lineHeight: 1.5,
fontFamily: type.properties.fontFamilies.monospace,
whiteSpace: 'nowrap',
'.token': {
'&.string': createColor('juicyPear600'),
'&.symbol': createColor('grapeSoda600'),
'&.property': createColor('berrySmoothie600'),
'&.primitive': createColor('pomegranate600'),
'&.number': createColor('pomegranate600'),
'&.boolean': createColor('pomegranate600'),
'&.variable': createColor('pomegranate600'),
'&.keyword': createColor('pomegranate600'),
'&.punctuation': createColor('blackPepper600'),
'&.operator': createColor('blackPepper600'),
'&.bold': { fontWeight: 700 },
},
},
});
function getSymbolDocChildren(doc, meta, name) {
if (!doc) {
// We're within a symbol doc context, but there's no information, so we still want to render the basic type information
return (_jsxs(_Fragment, { children: [_jsx("p", { children: "Basic type information:" }), _jsx("pre", { children: _jsx("code", { children: name }) })] }));
}
if (doc && doc.type) {
return _jsx(Value, { value: doc.type, doc: doc, meta: meta });
}
return _jsx("div", { children: "Not found" });
}
function findDoc({ name, fileName }) {
const doc = (docs || []).find(d => {
return d.name === name && (fileName ? d.fileName.includes(fileName) : true);
});
if ((doc === null || doc === void 0 ? void 0 : doc.type.kind) === 'alias') {
return findDoc({ name: doc.type.name });
}
return doc;
}
export const useDoc = (criteria) => {
// Listen to criteria.name and criteria.fileName so that we can re-fetch docs in the dialog
const doc = React.useMemo(() => findDoc(criteria), [criteria.name, criteria.fileName || '']);
return doc;
};
/**
* Renders the `<Value>` of a exported symbol without any wrapper. This should be used when nesting
* inside a `<SymbolDoc>` component.
*/
export const SymbolValue = (props) => {
const doc = useDoc(props);
return getSymbolDocChildren(doc);
};
/**
* Renders just the description of an exported symbol.
*/
export const SymbolDescription = (props) => {
const doc = useDoc(props);
return doc ? _jsx(MdxJSToJSX, { children: doc.description }) : null;
};
/**
* Renders an exported symbol as a doc object. This will render JSDoc tags, description and setup
* rendering contexts for headers.
*/
export const SymbolDoc = ({ name, fileName, headingStart = 3, hideDescription = false, hideHeading = false, descriptionOverride, meta, ...elemProps }) => {
const doc = useDoc({ name, fileName });
const symbolDocBreadcrumb = React.useContext(SymbolDocBreadcrumbsContext);
const children = getSymbolDocChildren(doc, meta, name);
const requiresCodeWrapper = [
'symbol',
'generic',
'array',
'indexedAccess',
'qualifiedName',
'parenthesis',
'typeParameter',
'keyof',
'type',
'conditional',
'indexSignature',
'tuple',
'external',
'number',
'string',
'boolean',
'primitive',
'unknown',
'union',
'intersection',
'function',
'callExpression',
].includes((doc === null || doc === void 0 ? void 0 : doc.type.kind) || 'notFound');
const contents = requiresCodeWrapper ? (_jsx("code", { style: { display: 'block' }, children: children })) : (children);
const symbolDocContents = (_jsx(StyledSymbolDoc, { ...elemProps, children: _jsxs(HeadingLevelContext.Provider, { value: headingStart, children: [!hideHeading && _jsx(Heading, { id: `${name.toLowerCase()}-api`, children: name }), !hideDescription && doc && (_jsx(MdxJSToJSX, { children: descriptionOverride || doc.description })), contents] }) }));
return symbolDocBreadcrumb === undefined ? (_jsx(SymbolDocWrapper, { children: symbolDocContents })) : (symbolDocContents);
};
const SymbolDocWrapper = ({ children }) => {
const [breadcrumbsList, updateBreadcrumbs] = React.useState([]);
return (_jsx(SymbolDocBreadcrumbsContext.Provider, { value: { breadcrumbsList, updateBreadcrumbs }, children: children }));
};