Last active
November 17, 2021 21:51
-
-
Save elliottsj/91b904e12cc11d4a8148d882fe3017a2 to your computer and use it in GitHub Desktop.
Basic stories in Next.js 11 (put `[...slug].tsx` and `index.tsx` in `pages/`, stories in `stories/`)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import dynamic from 'next/dynamic'; | |
import { useRouter } from 'next/router'; | |
import React from 'react'; | |
import { css, Global } from '@emotion/core'; | |
interface StoryLocation { | |
modulePath: string; | |
storyName: string; | |
} | |
/** | |
* Given a slug parameter, determine the module path and story name. | |
* | |
* @example | |
* '/Avatar.stories/playground' | |
* -> ['Avatar.stories', 'playground'] | |
* -> { modulePath: 'Avatar.stories', storyName: 'playground' } | |
* | |
* @example | |
* '/Dropdowns/AsyncMultiDropdown.stories/asyncOptions' | |
* -> ['Dropdowns', 'AsyncMultiDropdown.stories', 'asyncOptions'] | |
* -> { modulePath: 'Dropdowns/AsyncMultiDropdown.stories', storyName: 'asyncOptions' } | |
*/ | |
const getStoryLocation = (slug?: string | string[]): StoryLocation | null => { | |
if (!Array.isArray(slug) || slug.length < 2) { | |
return null; | |
} | |
const modulePath = slug.slice(0, -1).join('/'); | |
const storyName = slug[slug.length - 1]; | |
return { modulePath, storyName }; | |
}; | |
/** | |
* Render a single story. | |
*/ | |
const StoryPage: React.FC = () => { | |
const router = useRouter(); | |
const storyLocation = getStoryLocation(router.query.slug); | |
if (!storyLocation) { | |
return <div>Story not found.</div>; | |
} | |
const Story = dynamic(async () => { | |
const storyModule = await import(`../stories/${storyLocation.modulePath}.tsx`); | |
const stories = { ...storyModule }; | |
delete stories.default; | |
return stories[storyLocation.storyName]; | |
}); | |
return ( | |
<> | |
<Global | |
styles={css` | |
@font-face { | |
font-family: 'Sofia Pro'; | |
src: url('/static/fonts/SofiaPro-Regular.woff2') format('woff2'), | |
url('/static/fonts/SofiaPro-Regular.woff') format('woff'); | |
font-weight: normal; | |
font-style: normal; | |
} | |
`} | |
/> | |
<Story /> | |
</> | |
); | |
}; | |
export default StoryPage; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { ActionList, ActionItemButton } from '../components'; | |
import { css } from '@emotion/core'; | |
import { Example, Title } from './Utilities'; | |
export default { | |
title: 'ActionList', | |
}; | |
export const Buttons = () => ( | |
<Example> | |
<Title>Buttons</Title> | |
<div | |
css={css` | |
padding: 1rem; | |
`} | |
> | |
<ActionList> | |
<ActionItemButton>One</ActionItemButton> | |
<ActionItemButton>Two</ActionItemButton> | |
<ActionItemButton>Three</ActionItemButton> | |
</ActionList> | |
</div> | |
</Example> | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GetServerSideProps } from 'next'; | |
import Link from 'next/link'; | |
import path from 'path'; | |
import React from 'react'; | |
type RequireContextType = ReturnType<typeof require.context>; | |
interface StoryModule { | |
title: string; | |
path: string; | |
stories: Story[]; | |
} | |
interface Story { | |
name: string; | |
href: string; | |
} | |
interface StoriesPageProps { | |
stories: StoryModule[]; | |
} | |
/** | |
* An index of every story in the project. | |
*/ | |
const StoriesPage: React.FC<StoriesPageProps> = (props) => { | |
return ( | |
<ul className="stories" data-stories={JSON.stringify(props.stories)}> | |
{props.stories.map((storyModule) => ( | |
<li key={storyModule.path}> | |
<span>{storyModule.title}</span> | |
<ul> | |
{storyModule.stories.map((story) => ( | |
<li key={story.href}> | |
<Link href={story.href}>{story.name}</Link> | |
</li> | |
))} | |
</ul> | |
</li> | |
))} | |
</ul> | |
); | |
}; | |
export default StoriesPage; | |
/** | |
* Given a story module name from a webpack context, compute the path to the story module. | |
* | |
* @example | |
* > getPath('Button.stories.tsx') | |
* 'Button.stories' | |
* | |
* @example | |
* > getPath('DropdownButton/DropdownButton.stories.tsx') | |
* 'DropdownButton/DropdownButton.stories' | |
*/ | |
const getPath = (moduleName: string) => moduleName.match(/^(?<slug>.+)\.tsx$/)?.groups?.slug; | |
const getStoryModules = async (storyModulesReq: RequireContextType): Promise<StoryModule[]> => { | |
const storyModules: StoryModule[] = storyModulesReq | |
.keys() | |
.map((moduleName) => { | |
const modulePath = getPath(moduleName); | |
if (!modulePath) throw new Error(`Could not compute path for "${moduleName}"`); | |
return { moduleName, path: modulePath }; | |
}) | |
.map(({ moduleName, path }) => { | |
const mod = storyModulesReq(moduleName); | |
const { default: meta, ...stories } = mod; | |
if (!meta) { | |
throw new Error( | |
`Story module "${moduleName}" is missing a default meta export. ` + | |
`Add \`export default { title: 'Your title' };\``, | |
); | |
} | |
if (!meta.title) { | |
throw new Error( | |
`Story module "${moduleName}" is missing a title from its default meta export. ` + | |
`Add \`export default { title: 'Your title' };\``, | |
); | |
} | |
return { | |
title: meta.title, | |
path, | |
stories: Object.keys(stories).map((storyName) => ({ | |
name: storyName, | |
href: `/${path}/${storyName}`, | |
})), | |
}; | |
}); | |
return storyModules; | |
}; | |
export const getServerSideProps: GetServerSideProps = async () => ({ | |
props: { | |
stories: await getStoryModules(require.context('../stories', true, /\.stories\.tsx$/)), | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment