Skip to content

Commit

Permalink
feat: dynamic esm stories
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Oct 9, 2020
1 parent 212f55a commit b1c9c00
Show file tree
Hide file tree
Showing 22 changed files with 588 additions and 147 deletions.
2 changes: 2 additions & 0 deletions core/config/test/__snapshots__/stories.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Array [
"/Users/atanasster/component-controls/ui/blocks/src/Title/Title.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors-starter.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors.stories.jsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/dynamic-stories.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/inherit-from-doc.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/smart-prop-type.stories.js",
"/Users/atanasster/component-controls/examples/stories/src/stories/smart-typescript.stories.js",
Expand Down Expand Up @@ -137,6 +138,7 @@ Array [
"/Users/atanasster/component-controls/ui/blocks/src/Title/Title.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors-starter.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors.stories.jsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/dynamic-stories.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/inherit-from-doc.stories.tsx",
"/Users/atanasster/component-controls/examples/stories/src/stories/smart-prop-type.stories.js",
"/Users/atanasster/component-controls/examples/stories/src/stories/smart-typescript.stories.js",
Expand Down
133 changes: 78 additions & 55 deletions core/core/README.md

Large diffs are not rendered by default.

56 changes: 42 additions & 14 deletions core/core/src/document-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
storyNameFromExport as csfStoryNameFromExport,
} from '@storybook/csf';
import { PagesOnlyRoutes, DocType, PageConfiguration } from './configuration';
import { Document, defDocType } from './document';
import { Document, Story, defDocType, Store } from './document';

export const storyNameFromExport = csfStoryNameFromExport;
export const strToId = (str: string) => str.replace(/\W/g, '-').toLowerCase();
Expand All @@ -20,53 +20,62 @@ export const removeTrailingSlash = (route: string) =>
export const getDocPath = (
docType: DocType,
doc?: Document,
pagesConfig?: PagesOnlyRoutes,
store?: Store,
name: string = '',
tab?: string,
): string => {
const pagesConfig: PagesOnlyRoutes | undefined = store
? (store.config.pages as PagesOnlyRoutes)
: undefined;
const { basePath = '', sideNav = {} } = pagesConfig?.[docType] || {};
const { storyPaths } = sideNav;
const activeTab = doc?.MDXPage ? undefined : tab;
if (storyPaths && doc && doc.stories && doc.stories.length > 0) {
return getStoryPath(doc.stories[0], doc, pagesConfig, activeTab);
if (storyPaths && doc && doc.stories && doc.stories.length > 0 && store) {
return getStoryPath(doc.stories[0], doc, store, activeTab);
}
const route = doc
? doc.route ||
`${ensureStartingSlash(
ensureTrailingSlash(basePath),
)}${ensureTrailingSlash(strToId(doc.title))}${
activeTab ? `${ensureTrailingSlash(activeTab)}` : ''
activeTab ? ensureTrailingSlash(activeTab) : ''
}`
: `${ensureStartingSlash(
ensureTrailingSlash(basePath),
)}${ensureTrailingSlash(strToId(name))}${
activeTab ? `${ensureTrailingSlash(activeTab)}` : ''
activeTab ? ensureTrailingSlash(activeTab) : ''
}`;
return removeTrailingSlash(route);
};

export const getStoryPath = (
storyId?: string,
doc?: Document,
pagesConfig?: PagesOnlyRoutes,
store?: Store,
tab?: string,
): string => {
const pagesConfig: PagesOnlyRoutes | undefined = store
? (store.config.pages as PagesOnlyRoutes)
: undefined;

const docType = doc?.type || defDocType;
const activeTab = doc?.MDXPage ? undefined : tab;
if (!storyId) {
return getDocPath(docType, doc, pagesConfig, undefined, activeTab);
return getDocPath(docType, doc, store, undefined, activeTab);
}

const { basePath = '' } = pagesConfig?.[docType] || {};
const docRoute = `${
doc?.route
? ensureStartingSlash(ensureTrailingSlash(doc?.route))
: `${ensureStartingSlash(ensureTrailingSlash(basePath))}`
}`;
const route = `${docRoute}${storyId ? ensureTrailingSlash(storyId) : ''}${
activeTab ? `${ensureTrailingSlash(activeTab)}` : ''
: ensureStartingSlash(ensureTrailingSlash(basePath))
}`;
return removeTrailingSlash(route);
const story = store?.stories[storyId];
const { factoryId, name } = story || {};
const id = factoryId || storyId;
const route = `${docRoute}${id ? ensureTrailingSlash(id) : ''}${
activeTab ? ensureTrailingSlash(activeTab) : ''
}${factoryId ? `?story=${name}` : ''}`;
return encodeURI(removeTrailingSlash(route));
};

export const getDocTypePath = (type: PageConfiguration) =>
Expand All @@ -76,3 +85,22 @@ export const getDocTypePath = (type: PageConfiguration) =>

export const docStoryToId = (docId: string, storyId: string) =>
toId(docId, storyNameFromExport(storyId));

/**
* maps an exported story to an array of stories. Used for dynamically created stories.
*/
export const mapDynamicStories = (story: Story, doc: Document): Story[] => {
if (story.factory && typeof story.renderFn === 'function') {
const stories = story.renderFn(doc);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, name, ...storyProps } = story;
return Array.isArray(stories)
? stories.map(s => ({
...storyProps,
factoryId: docStoryToId(doc.title, id || name),
...s,
}))
: [story];
}
return [story];
};
16 changes: 16 additions & 0 deletions core/core/src/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,24 @@ export type Story = {
* optional story subtitle property
*/
subtitle?: string;
/**
* if set to true, the function is a stories factory, returns a list of Story objects
*/
factory?: boolean;

/**
* if the story was created by a dynacmi storiers factory, this is the original 'parent' factory id.
* it is set internally and will be used to create a story URL
*/
factoryId?: string;
} & StoryProps;

/**
* dynamic story factory function type.
* returns an array of dynamically loaded stories
*/
export type StoryFactoryFn = (doc: Document) => Story[];

export const defDocType: DocType = 'story';
/**
* A documentation file's metadata.
Expand Down
4 changes: 2 additions & 2 deletions core/store/src/create-pages/pages-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export const getDocPages = (store: Store): DocPagesPath[] => {
? doc.stories
: [undefined];
stories.forEach((storyId?: string) => {
const path = getStoryPath(storyId, doc, pages, route);
const path = getStoryPath(storyId, doc, store, route);
docPaths.push({
path,
type: docType,
Expand All @@ -172,7 +172,7 @@ export const getDocPages = (store: Store): DocPagesPath[] => {
const path = getDocPath(
type as DocType,
{ title: tag, componentsLookup: {} },
pages,
store,
);
docPaths.push({
path,
Expand Down
35 changes: 20 additions & 15 deletions core/store/src/serialization/load-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
PageConfiguration,
Pages,
PageLayoutProps,
mapDynamicStories,
} from '@component-controls/core';
import { LoadingStore } from '@component-controls/loader';
import { render as reactRender } from '@component-controls/render/react';
Expand Down Expand Up @@ -70,22 +71,26 @@ export const loadStore = (store: LoadingStore): Store => {
};
globalStore.docs[doc.title] = doc;
Object.keys(storeStories).forEach((storyName: string) => {
const story: Story = storeStories[storyName];
Object.assign(story, deepMerge(docStoryProps, story));
story.controls = transformControls(story, doc, loadedComponents);
if (doc.title && story.id) {
const id = docStoryToId(doc.title, story.id);
if (!doc.stories) {
doc.stories = [];
const exportedStory: Story = storeStories[storyName];
let stories: Story[] = mapDynamicStories(exportedStory, doc);
stories.forEach(story => {
story.id = story.id || story.name;
Object.assign(story, deepMerge(docStoryProps, story));
story.controls = transformControls(story, doc, loadedComponents);
if (doc.title && story.id) {
const id = docStoryToId(doc.title, story.id);
if (!doc.stories) {
doc.stories = [];
}
doc.stories.push(id);
globalStore.stories[id] = {
...story,
name: storyNameFromExport(story.name),
id,
doc: doc.title,
};
}
doc.stories.push(id);
globalStore.stories[id] = {
...story,
name: storyNameFromExport(story.name),
id,
doc: doc.title,
};
}
});
});
}
});
Expand Down
15 changes: 7 additions & 8 deletions core/store/src/state/context/document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
getDocPath,
getComponentName,
} from '@component-controls/core';
import { useStore, useConfig, useActiveTab } from './store';
import { useStore, useActiveTab } from './store';

const DocumentContext = createContext<Document | undefined>(undefined);

Expand Down Expand Up @@ -190,7 +190,7 @@ const useNavigationLinks = (doc: Document): NavigationResult => {
const docId = doc.title;
const type = doc.type || defDocType;
const docs = useDocByType(type);
const config = useConfig();
const store = useStore();
const activeTab = useActiveTab();
const result: NavigationResult = {};
//next page
Expand All @@ -202,7 +202,7 @@ const useNavigationLinks = (doc: Document): NavigationResult => {
link: getDocPath(
nextDoc.type || defDocType,
nextDoc,
config.pages,
store,
nextDoc.title,
activeTab,
),
Expand All @@ -217,7 +217,7 @@ const useNavigationLinks = (doc: Document): NavigationResult => {
link: getDocPath(
prevDoc.type || defDocType,
prevDoc,
config.pages,
store,
prevDoc.title,
activeTab,
),
Expand Down Expand Up @@ -252,16 +252,15 @@ export const useDocumentPath: UseGetDocumentPath = (
activeTab,
) => {
const doc = useDocument(docId);
const config = useConfig();
return getDocPath(type, doc, config.pages, docId, activeTab);
const store = useStore();
return getDocPath(type, doc, store, docId, activeTab);
};

export const useGetDocumentPath = (): UseGetDocumentPath => {
const store = useStore();
const config = useConfig();
return (type = defDocType, docId, activeTab) => {
const doc = store.docs[docId];
return getDocPath(type, doc, config.pages, docId, activeTab);
return getDocPath(type, doc, store, docId, activeTab);
};
};

Expand Down
8 changes: 3 additions & 5 deletions core/store/src/state/context/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getComponentName,
Component,
} from '@component-controls/core';
import { useStore, StoreContext, useActiveTab, useConfig } from './store';
import { useStore, StoreContext, useActiveTab } from './store';

interface StoryContextProps {
story?: Story;
Expand Down Expand Up @@ -160,21 +160,19 @@ export const useStoryComponent = (
export const useStoryPath = (storyId: string): string => {
const store = useStore();
const activeTab = useActiveTab();
const config = useConfig();
const story = store.stories[storyId];
if (!story) {
return '';
}
const doc = store.docs[story?.doc || ''];
return getStoryPath(story.id, doc, config.pages, activeTab);
return getStoryPath(story.id, doc, store, activeTab);
};

export const useGetStoryPath = () => {
const store = useStore();
const config = useConfig();
return (storyId: string, activeTab?: string): string => {
const story = store.stories[storyId];
const doc = story && story.doc ? store.docs[story.doc] : undefined;
return getStoryPath(storyId, doc, config.pages, activeTab);
return getStoryPath(storyId, doc, store, activeTab);
};
};
22 changes: 8 additions & 14 deletions core/store/src/state/recoil/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ import {
getDocPath,
getComponentName,
} from '@component-controls/core';
import {
storeState,
useStore,
useConfig,
configState,
activeTabState,
} from './store';
import { storeState, useStore, activeTabState } from './store';

export const documentIdState = atom<string | undefined>({
key: 'document_id',
Expand Down Expand Up @@ -213,7 +207,7 @@ const navigationState = selector<NavigationResult>({
key: 'navigation_selector',
get: ({ get }) => {
const doc = get(currentDocumentState);
const config = get(configState);
const store = get(storeState);
const activeTab = get(activeTabState);

const result: NavigationResult = {};
Expand All @@ -230,7 +224,7 @@ const navigationState = selector<NavigationResult>({
link: getDocPath(
nextDoc.type || defDocType,
nextDoc,
config.pages,
store,
nextDoc.title,
activeTab,
),
Expand All @@ -246,7 +240,7 @@ const navigationState = selector<NavigationResult>({
link: getDocPath(
prevDoc.type || defDocType,
prevDoc,
config.pages,
store,
prevDoc.title,
activeTab,
),
Expand Down Expand Up @@ -280,16 +274,16 @@ export const useDocumentPath: UseGetDocumentPath = (
activeTab,
) => {
const doc = useDocument(docId);
const config = useConfig();
return getDocPath(type, doc, config.pages, name, activeTab);
const store = useStore();
return getDocPath(type, doc, store, name, activeTab);
};

export const useGetDocumentPath = (): UseGetDocumentPath => {
const getDoc = useGetDocument();
const config = useConfig();
const store = useStore();
return (type = defDocType, docId, activeTab) => {
const doc = getDoc(docId);
return getDocPath(type, doc, config.pages, docId, activeTab);
return getDocPath(type, doc, store, docId, activeTab);
};
};

Expand Down
6 changes: 2 additions & 4 deletions core/store/src/state/recoil/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,19 @@ export const useStoryComponent = (
export const useStoryPath = (storyId: string): string => {
const store = useStore();
const activeTab = useActiveTab();
const config = useConfig();
const story = store.stories[storyId];
if (!story) {
return '';
}
const doc = store.docs[story?.doc || ''];
return getStoryPath(story.id, doc, config.pages, activeTab);
return getStoryPath(story.id, doc, store, activeTab);
};

export const useGetStoryPath = () => {
const store = useStore();
const config = useConfig();
return (storyId: string, activeTab?: string): string => {
const story = store.stories[storyId];
const doc = story && story.doc ? store.docs[story.doc] : undefined;
return getStoryPath(storyId, doc, config.pages, activeTab);
return getStoryPath(storyId, doc, store, activeTab);
};
};
Loading

0 comments on commit b1c9c00

Please sign in to comment.