Skip to content

Commit 8f5b647

Browse files
JonasBaandrewshie-sentry
authored andcommitted
stories: implement programmatic pagination detection (#95397)
Implement programmatic pagination detection in the story tree, which won't require manual link maintenance.
1 parent 63c5ccb commit 8f5b647

File tree

4 files changed

+60
-20
lines changed

4 files changed

+60
-20
lines changed

static/app/components/core/button/index.mdx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ resources:
1111
WCAG 2.4.7: https://www.w3.org/TR/WCAG22/#focus-visible
1212
WCAG 2.5.8: https://www.w3.org/TR/WCAG22/#target-size-minimum
1313
WAI-ARIA Button Practices: https://www.w3.org/WAI/ARIA/apg/patterns/button/
14-
next:
15-
link: '/stories/?name=app%2Fcomponents%2Fcore%2Fbutton%2FbuttonBar.stories.tsx'
16-
label: ButtonBar
17-
prev:
18-
link: '?name=app%2Fcomponents%2Fcore%2Fbadge%2Ftag.stories.tsx'
19-
label: Tag
2014
---
2115

2216
import {Button} from 'sentry/components/core/button';

static/app/stories/view/storyFooter.tsx

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,80 @@
11
import styled from '@emotion/styled';
2+
import qs from 'query-string';
23

34
import {LinkButton} from 'sentry/components/core/button/linkButton';
45
import {Flex} from 'sentry/components/core/layout';
56
import {IconArrow} from 'sentry/icons';
6-
import {isMDXStory} from 'sentry/stories/view/useStoriesLoader';
7+
import {useStoryBookFilesByCategory} from 'sentry/stories/view/storySidebar';
8+
import type {StoryTreeNode} from 'sentry/stories/view/storyTree';
9+
import type {StoryDescriptor} from 'sentry/stories/view/useStoriesLoader';
710
import {useStory} from 'sentry/stories/view/useStory';
811
import {space} from 'sentry/styles/space';
12+
import {useLocation} from 'sentry/utils/useLocation';
913

1014
export function StoryFooter() {
15+
const location = useLocation();
16+
1117
const {story} = useStory();
12-
if (!isMDXStory(story)) return null;
13-
const {prev, next} = story.exports.frontmatter ?? {};
18+
const categories = useStoryBookFilesByCategory();
19+
const pagination = findPreviousAndNextStory(story, categories);
20+
21+
const prevLocationDescriptor = qs.stringify({
22+
...location.query,
23+
name: pagination?.prev?.story.filesystemPath,
24+
});
25+
const nextLocationDescriptor = qs.stringify({
26+
...location.query,
27+
name: pagination?.next?.story.filesystemPath,
28+
});
1429

1530
return (
1631
<Flex align="center" justify="space-between" gap={space(2)}>
17-
{typeof prev === 'object' && 'link' in prev && (
18-
<Card to={prev.link} icon={<IconArrow direction="left" />}>
32+
{pagination?.prev && (
33+
<Card
34+
to={`/stories/?${prevLocationDescriptor}`}
35+
icon={<IconArrow direction="left" />}
36+
>
1937
<CardLabel>Previous</CardLabel>
20-
<CardTitle>{prev.label}</CardTitle>
38+
<CardTitle>{pagination.prev.story.label}</CardTitle>
2139
</Card>
2240
)}
23-
{typeof next === 'object' && 'link' in next && (
24-
<Card data-flip to={next.link} icon={<IconArrow direction="right" />}>
41+
{pagination?.next && (
42+
<Card
43+
data-flip
44+
to={`/stories/?${nextLocationDescriptor}`}
45+
icon={<IconArrow direction="right" />}
46+
>
2547
<CardLabel>Next</CardLabel>
26-
<CardTitle>{next.label}</CardTitle>
48+
<CardTitle>{pagination.next.story.label}</CardTitle>
2749
</Card>
2850
)}
2951
</Flex>
3052
);
3153
}
3254

55+
function findPreviousAndNextStory(
56+
story: StoryDescriptor,
57+
categories: ReturnType<typeof useStoryBookFilesByCategory>
58+
): {
59+
next: {category: string; story: StoryTreeNode} | undefined;
60+
prev: {category: string; story: StoryTreeNode} | undefined;
61+
} | null {
62+
const stories = Object.entries(categories).flatMap(([key, category]) =>
63+
category.map(s => ({category: key, story: s}))
64+
);
65+
66+
const currentIndex = stories.findIndex(s => s.story.filesystemPath === story.filename);
67+
68+
if (currentIndex === -1) {
69+
return null;
70+
}
71+
72+
return {
73+
prev: stories[currentIndex - 1] ?? undefined,
74+
next: stories[currentIndex + 1] ?? undefined,
75+
};
76+
}
77+
3378
const Card = styled(LinkButton)`
3479
display: flex;
3580
flex-direction: column;

static/app/stories/view/storySidebar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
33

44
import {space} from 'sentry/styles/space';
55

6+
import type {StoryTreeNode} from './storyTree';
67
import {StoryTree, useStoryTree} from './storyTree';
78
import {useStoryBookFiles} from './useStoriesLoader';
89

@@ -29,9 +30,13 @@ export function StorySidebar() {
2930
);
3031
}
3132

32-
export function useStoryBookFilesByCategory() {
33+
export function useStoryBookFilesByCategory(): Record<
34+
'foundations' | 'core' | 'shared',
35+
StoryTreeNode[]
36+
> {
3337
const files = useStoryBookFiles();
3438
const filesByOwner = useMemo(() => {
39+
// The order of keys here is important and used by the pagination in storyFooter
3540
const map: Record<'foundations' | 'core' | 'shared', string[]> = {
3641
foundations: [],
3742
core: [],

static/app/stories/view/useStoriesLoader.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@ export interface StoryResources {
1212
js?: string;
1313
}
1414

15-
type FrontmatterPagination = boolean | {label: string; link: string};
16-
1715
interface MDXStoryDescriptor {
1816
exports: {
1917
default: React.ComponentType | any;
2018
frontmatter?: {
2119
description: string;
2220
title: string;
23-
next?: FrontmatterPagination;
24-
prev?: FrontmatterPagination;
2521
resources?: StoryResources;
2622
source?: string;
2723
types?: string;

0 commit comments

Comments
 (0)