UNPKG

@workday/canvas-kit-docs

Version:

Documentation components of Canvas Kit components

218 lines (217 loc) • 10.8 kB
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 })); };