Skip to content

Commit

Permalink
[DM] core/components/button.javascript-api (#882)
Browse files Browse the repository at this point in the history
* nav layout code moves to MarkdownPlugin
but must configure all plugins manually now
* support IHeadingTag with single "heading" renderer
TagRenderer receives ITag as first argument to support this polymorphism.
use type in renderer definition to avoid redeclaring each argument.
deconstruct and rename `value` as needed for minimal churn and maximal clarity.
* refactor to support `route` property
* IBlock field rename
* documentalist@0.0.5
  • Loading branch information
giladgray authored Mar 28, 2017
1 parent f98300e commit 9deb01c
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 97 deletions.
43 changes: 8 additions & 35 deletions gulp/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,16 @@ module.exports = (blueprint, gulp, plugins) => {
};

gulp.task("docs-json", () => {
return dm.Documentalist.create({ renderer: text.renderer })
return new dm.Documentalist([], { renderer: text.renderer })
.use(".md", new dm.MarkdownPlugin({ navPage: config.navPage }))
.use(/\.tsx?$/, new dm.TypescriptPlugin())
.use(".scss", new dm.KssPlugin())
.documentGlobs("packages/*/src/**/*")
.then((contents) => {
function nestChildPage(child, parent) {
const originalRef = child.reference;

// update entry reference to include parent reference
const nestedRef = dm.slugify(parent.reference, originalRef);
child.reference = nestedRef;

if (dm.isPageNode(child)) {
// rename nested pages to be <parent>.<child> and remove old <child> entry
contents.docs[nestedRef] = contents.docs[originalRef];
contents.docs[nestedRef].reference = nestedRef;
delete contents.docs[originalRef];
// recurse through page children
child.children.forEach((innerchild) => nestChildPage(innerchild, child));
}
}

// navPage is used to construct the sidebar menu
const roots = dm.createNavigableTree(contents.docs, contents.docs[config.navPage]).children;
// nav page is not a real docs page so we can remove it from output
delete contents.docs[config.navPage];
roots.forEach((page) => {
if (dm.isPageNode(page)) {
page.children.forEach((child) => nestChildPage(child, page));
}
});

// add a new field to data file with pre-processed layout tree
contents.layout = roots;

return text.fileStream(filenames.data, JSON.stringify(contents, null, 2))
.pipe(gulp.dest(config.data));
});
.then((docs) => JSON.stringify(docs, null, 2))
.then((content) => (
text.fileStream(filenames.data, content)
.pipe(gulp.dest(config.data))
));
});

// create a JSON file containing latest released version of each project
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"better-handlebars": "github:wmeldon/better-handlebars",
"chai": "3.5.0",
"del": "2.2.2",
"documentalist": "0.0.4",
"documentalist": "0.0.5",
"enzyme": "2.6.0",
"gulp": "3.9.1",
"gulp-concat": "2.6.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/docs/src/common/resolveDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import * as React from "react";

// this is the default map, containing docs components defined locally.
import { TagRenderer } from "../components/page";
import * as ReactDocs from "../components/reactDocs";

export interface IDocsMap {
Expand All @@ -19,7 +20,7 @@ export interface IDocsMap {
* it to an actual component class in the given map, or in the default map which contains
* valid docs components from this package. Provide a custom map to inject your own components.
*/
export function resolveDocs(componentName: string, key: React.Key) {
export const resolveDocs: TagRenderer = ({ value: componentName }, key) => {
if (componentName == null) {
return undefined;
}
Expand All @@ -29,4 +30,4 @@ export function resolveDocs(componentName: string, key: React.Key) {
throw new Error(`Unknown @reactDocs component: ${componentName}`);
}
return React.createElement(docsComponent, { key });
}
};
5 changes: 3 additions & 2 deletions packages/docs/src/common/resolveExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as CoreExamples from "@blueprintjs/core/examples";
import * as DateExamples from "@blueprintjs/datetime/examples";
import * as TableExamples from "@blueprintjs/table/examples";

import { TagRenderer } from "src/components/page";
import { getTheme } from "./theme";

// tslint:disable-next-line no-empty-interface
Expand Down Expand Up @@ -67,7 +68,7 @@ const Example: React.SFC<IExampleProps> = (props) => (
* it to an actual example component exported by one of the packages. Also returns
* the URL of the source code on GitHub.
*/
export function resolveExample(exampleName: string, key: React.Key) {
export const resolveExample: TagRenderer = ({ value: exampleName }, key) => {
if (exampleName == null) {
return undefined;
}
Expand All @@ -83,4 +84,4 @@ export function resolveExample(exampleName: string, key: React.Key) {
name={exampleName}
sourceUrl={[SRC_HREF_BASE, packageName, "examples", fileName].join("/")}
/>;
}
};
14 changes: 7 additions & 7 deletions packages/docs/src/components/navMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ export const NavMenuItem: React.SFC<INavMenuItemProps & { children?: React.React
const classes = classNames(
"docs-menu-item",
`docs-menu-item-${isPageNode(item) ? "page" : "heading"}`,
`depth-${item.depth}`,
`depth-${item.level}`,
props.className,
);
const itemClasses = classNames(Classes.MENU_ITEM, {
[Classes.ACTIVE]: props.isActive,
[Classes.INTENT_PRIMARY]: props.isActive,
});
const handleClick = () => props.onClick(item.reference);
const handleClick = () => props.onClick(item.route);
const title = props.children ? <strong>{item.title}</strong> : item.title;
return (
<li className={classes} key={item.reference}>
<a className={itemClasses} href={"#" + item.reference} onClick={handleClick}>
<li className={classes} key={item.route}>
<a className={itemClasses} href={"#" + item.route} onClick={handleClick}>
{title}
</a>
{props.children}
Expand All @@ -52,16 +52,16 @@ NavMenuItem.displayName = "Docs.NavMenuItem";

export const NavMenu: React.SFC<INavMenuProps> = (props) => {
const menu = props.items.map((section) => {
const isActive = props.activeSectionId === section.reference;
const isExpanded = isActive || props.activePageId === section.reference;
const isActive = props.activeSectionId === section.route;
const isExpanded = isActive || props.activePageId === (section as IPageNode).reference;
// active section gets selected styles, expanded section shows its children
const menuClasses = classNames({ "docs-nav-expanded": isExpanded });
const childrenMenu = isPageNode(section)
? <NavMenu {...props} className={menuClasses} items={section.children} />
: undefined;
return (
<NavMenuItem
key={section.reference}
key={section.route}
item={section}
isActive={isActive}
onClick={props.onItemClick}
Expand Down
12 changes: 6 additions & 6 deletions packages/docs/src/components/navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { handleStringChange } from "@blueprintjs/core/examples/common/baseExample";

import * as classNames from "classnames";
import { IHeadingNode, IPageNode, isPageNode } from "documentalist/dist/client";
import { IHeadingNode, IPageNode } from "documentalist/dist/client";
import { filter } from "fuzzaldrin-plus";
import * as PureRender from "pure-render-decorator";
import * as React from "react";
Expand All @@ -41,7 +41,7 @@ export interface INavigatorState {
interface INavigationSection {
filterKey: string;
path: string[];
reference: string;
route: string;
title: string;
}

Expand Down Expand Up @@ -114,10 +114,10 @@ export class Navigator extends React.Component<INavigatorProps, INavigatorState>
public componentDidMount() {
this.sections = [];
eachLayoutNode(this.props.items, (node, parents) => {
const { reference, title } = node;
const { route, title } = node;
const path = parents.map((p) => p.title).reverse();
const filterKey = [...path, title].join("/");
this.sections.push({ filterKey, path, reference, title });
this.sections.push({ filterKey, path, route, title });
});
}

Expand All @@ -142,8 +142,8 @@ export class Navigator extends React.Component<INavigatorProps, INavigatorState>
return (
<a
className={classes}
href={"#" + section.reference}
key={section.reference}
href={"#" + section.route}
key={section.route}
onMouseEnter={this.handleResultHover}
>
<small className="docs-result-path pt-text-muted" dangerouslySetInnerHTML={pathHtml} />
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/src/components/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from "react";

import { IPageData } from "documentalist/dist/client";
import { IPageData, ITag } from "documentalist/dist/client";

export type TagRenderer = (value: string, key: React.Key, page: IPageData) => JSX.Element | undefined;
export type TagRenderer = (tag: ITag, key: React.Key, page: IPageData) => JSX.Element | undefined;

export interface IPageProps {
page: IPageData;
Expand All @@ -21,7 +21,7 @@ export const Page: React.SFC<IPageProps> = ({ tagRenderers, page }) => {
if (renderer === undefined) {
throw new Error(`Unknown @tag: ${node.tag}`);
}
return renderer(node.value, i, page);
return renderer(node, i, page);
} catch (ex) {
console.error(ex.message);
return <h3><code>{ex.message}</code></h3>;
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/components/propsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const renderPropRow = (prop: IInheritedPropertyEntry) => {

// TODO: this ignores tags in prop docs, but that's kind of OK cuz they all get processed
// into prop.tags by the TS compiler.
const html = documentation.renderedContent.reduce<string>((a, b) => typeof b === "string" ? a + b : a, "");
const html = documentation.contents.reduce<string>((a, b) => typeof b === "string" ? a + b : a, "");

return (
<tr key={name}>
Expand Down
23 changes: 12 additions & 11 deletions packages/docs/src/components/styleguide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ export interface IStyleguideState {
@HotkeysTarget
@PureRender
export class Styleguide extends React.Component<IStyleguideProps, IStyleguideState> {
/** Map of section reference to containing page reference. */
private referenceToPage: { [reference: string]: string };
/** Map of section route to containing page reference. */
private routeToPage: { [route: string]: string };

private contentElement: HTMLElement;
private navElement: HTMLElement;
Expand All @@ -95,10 +95,10 @@ export class Styleguide extends React.Component<IStyleguideProps, IStyleguideSta
};

// build up static map of all references to their page, for navigation / routing
this.referenceToPage = {};
eachLayoutNode(this.props.layout, (node, [parent]) => {
const { reference } = isPageNode(node) ? node : parent;
this.referenceToPage[node.reference] = reference;
this.routeToPage = {};
eachLayoutNode(this.props.layout, (node, parents) => {
const { reference } = isPageNode(node) ? node : parents[0];
this.routeToPage[node.route] = reference;
});
}

Expand Down Expand Up @@ -187,7 +187,7 @@ export class Styleguide extends React.Component<IStyleguideProps, IStyleguideSta

private handleNavigation = (activeSectionId: string) => {
// only update state if this section reference is valid
const activePageId = this.referenceToPage[activeSectionId];
const activePageId = this.routeToPage[activeSectionId];
if (activeSectionId !== undefined && activePageId !== undefined) {
this.setState({ activePageId, activeSectionId });
}
Expand All @@ -210,10 +210,11 @@ export class Styleguide extends React.Component<IStyleguideProps, IStyleguideSta
}

private maybeScrollToActivePageMenuItem() {
const { activePageId } = this.state;
const { activeSectionId } = this.state;
// only scroll nav menu if active item is not visible in viewport.
// using activePageId so you can see the page title in nav (may not be visible in document).
const navMenuElement = this.navElement.query(`a[href="#${activePageId}"]`);
// using activeSectionId so you can see the page title in nav (may not be visible in document).
const navMenuElement = this.navElement
.query(`a[href="#${activeSectionId}"]`).closest(".docs-menu-item-page");
const innerBounds = navMenuElement.getBoundingClientRect();
const outerBounds = this.navElement.getBoundingClientRect();
if (innerBounds.top < outerBounds.top || innerBounds.bottom > outerBounds.bottom) {
Expand All @@ -230,7 +231,7 @@ export class Styleguide extends React.Component<IStyleguideProps, IStyleguideSta
// active section cannot actually be selected in the nav (often a short one at the end).
const currentSectionId = location.hash.slice(1);
// this map is built by an in-order traversal so the keys are actually sorted correctly!
const sections = Object.keys(this.referenceToPage);
const sections = Object.keys(this.routeToPage);
const index = sections.indexOf(currentSectionId);
const newIndex = index === -1 ? 0 : (index + direction + sections.length) % sections.length;
// updating hash triggers event listener which sets new state.
Expand Down
50 changes: 21 additions & 29 deletions packages/docs/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import { CssExample } from "./common/cssExample";
import { PropsStore } from "./common/propsStore";
import { resolveDocs } from "./common/resolveDocs";
import { resolveExample } from "./common/resolveExample";
import { TagRenderer } from "./components/page";
import { PropsTable } from "./components/propsTable";
import { IPackageInfo, Styleguide } from "./components/styleguide";

import { IPageData, IPageNode, slugify } from "documentalist/dist/client";
import { IHeadingTag, IPageNode } from "documentalist/dist/client";
import { IKssPluginData, IMarkdownPluginData, ITypescriptPluginData } from "documentalist/dist/plugins";

interface IDocsData extends IKssPluginData, IMarkdownPluginData, ITypescriptPluginData {
Expand All @@ -40,46 +41,37 @@ const versions = require<string[]>("./generated/versions.json")
} as IPackageInfo));
/* tslint:enable:no-var-requires */

function resolveCssExample(reference: string, key: React.Key) {
const resolveCssExample: TagRenderer = ({ value: reference }, key) => {
const example = docs.css[reference];
if (example === undefined || example.reference === undefined) {
throw new Error(`Unknown @css reference: ${reference}`);
}
return <CssExample {...example} key={key} />;
}
};

const propsStore = new PropsStore(docs.ts);
function resolveInterface(name: string, key: React.Key) {
const resolveInterface: TagRenderer = ({ value: name }, key) => {
const props = propsStore.getProps(name);
return <PropsTable key={key} name={name} props={props} />;
}

const Heading: React.SFC<{ depth: number, header: string, reference: string }> =
({ depth, header, reference }) => (
// use createElement so we can dynamically choose tag based on depth
React.createElement(`h${depth}`, { className: "docs-title" },
<a className="docs-anchor" key="anchor" name={reference} />,
<a className="docs-anchor-link" href={"#" + reference} key="link">
<span className="pt-icon-standard pt-icon-link" />
</a>,
header,
)
);
};

function renderHeading(depth: number) {
return (heading: string, key: React.Key, page: IPageData): JSX.Element => {
const ref = (depth === 1 ? page.reference : slugify(page.reference, heading));
return <Heading depth={depth} header={heading} key={key} reference={ref} />;
};
}
const Heading: React.SFC<IHeadingTag> = ({ level, route, value }) => (
// use createElement so we can dynamically choose tag based on depth
React.createElement(`h${level}`, { className: "docs-title" },
<a className="docs-anchor" key="anchor" name={route} />,
<a className="docs-anchor-link" href={"#" + route} key="link">
<span className="pt-icon-standard pt-icon-link" />
</a>,
value,
)
);
Heading.displayName = "Docs.Heading";
const renderHeading: TagRenderer = (heading: IHeadingTag, key) => <Heading key={key} {...heading} />;

// tslint:disable:object-literal-key-quotes
const TAGS = {
"#": renderHeading(1),
"##": renderHeading(2),
"###": renderHeading(3),
"####": renderHeading(4),
css: resolveCssExample,
heading: renderHeading,
interface: resolveInterface,
page: () => undefined as JSX.Element,
reactDocs: resolveDocs,
Expand All @@ -98,9 +90,9 @@ const updateExamples = () => {
ReactDOM.render(
<Styleguide
defaultPageId="styleguide"
layout={docs.layout}
layout={docs.nav}
onUpdate={updateExamples}
pages={docs.docs}
pages={docs.pages}
releases={releases}
tagRenderers={TAGS}
versions={versions}
Expand Down

0 comments on commit 9deb01c

Please sign in to comment.