Skip to content

Commit 32d17ea

Browse files
committed
stories: implement programmatic pagination detection
1 parent 3827265 commit 32d17ea

File tree

3 files changed

+71
-15
lines changed

3 files changed

+71
-15
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: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,90 @@
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';
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';
610
import {useStory} from 'sentry/stories/view/useStory';
711
import {space} from 'sentry/styles/space';
12+
import {useLocation} from 'sentry/utils/useLocation';
813

914
export function StoryFooter() {
15+
const location = useLocation();
16+
1017
const {story} = useStory();
11-
if (!story.filename.endsWith('.mdx')) return null;
12-
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+
});
29+
1330
return (
1431
<Flex align="center" justify="space-between" gap={space(2)}>
15-
{prev && (
16-
<Card to={prev.link} icon={<IconArrow direction="left" />}>
32+
{pagination?.prev && (
33+
<Card
34+
to={`/stories/?${prevLocationDescriptor}`}
35+
icon={<IconArrow direction="left" />}
36+
>
1737
<CardLabel>Previous</CardLabel>
18-
<CardTitle>{prev.label}</CardTitle>
38+
<CardTitle>{pagination.prev.story.label}</CardTitle>
1939
</Card>
2040
)}
21-
{next && (
22-
<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+
>
2347
<CardLabel>Next</CardLabel>
24-
<CardTitle>{next.label}</CardTitle>
48+
<CardTitle>{pagination.next.story.label}</CardTitle>
2549
</Card>
2650
)}
2751
</Flex>
2852
);
2953
}
3054

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+
let prev: {category: string; story: StoryTreeNode} | undefined;
63+
let next: {category: string; story: StoryTreeNode} | undefined;
64+
65+
const stories: Array<{category: string; story: StoryTreeNode}> = [];
66+
67+
// Flatten into a single list so we don't have to deal with overflowing index
68+
// categories and can simplify the search procedure.
69+
for (const key in categories) {
70+
const category = categories[key as keyof typeof categories];
71+
for (const s of category) {
72+
stories.push({category: key, story: s});
73+
}
74+
}
75+
76+
for (let i = 0; i < stories.length; i++) {
77+
const s = stories[i]!;
78+
if (s.story.filesystemPath === story.filename) {
79+
prev = stories[i - 1];
80+
next = stories[i + 1];
81+
return {prev, next};
82+
}
83+
}
84+
85+
return null;
86+
}
87+
3188
const Card = styled(LinkButton)`
3289
display: flex;
3390
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: [],

0 commit comments

Comments
 (0)