Skip to content

Commit a6f9f33

Browse files
JonasBaTkDodo
andauthored
storybook: streamline writing stories (#92035)
We want to streamline how we write stories, and a big part of that is focusing on removing the boilerplate that it takes to write them. This PR moves the common storybook primitives and creates separation from the inner workings of storybook that lives in views/storybook, and the public components that can be used to write actual stories Following items that remain: - [ ] ~drop `export default Storybook.story` requirement in favor of simply exporting a function. The benefit here is that this is handled for us by mdx loader, meaning anyone writing mdx can omit the export entirely~ This cannot be done as CJS and ESM exports have to be sorted as per the spec def, which means that we cannot preserve the order of stories. The best solution here is to just migrate these components to MDX ```jsx import story from 'sentry/stories' export default function story((story) => { story('title', () => (){ ... content ... }) }) ``` will just become markdown like ```mdx ## Story ### Title content ``` - [ ] Consolidate components defined in colors.mdx and typography.stories.tsx to components/stories and reuse them - [ ] Enable type checking for MDX components - [ ] Document how to write MDX for stories --------- Co-authored-by: TkDodo <dominik.dorfmeister@sentry.io>
1 parent c5cf170 commit a6f9f33

File tree

125 files changed

+1291
-1380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+1291
-1380
lines changed

knip.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const storyBookEntryPoints = [
3333
// our storybook implementation is here
3434
'static/app/stories/storyBook.tsx',
3535
// custom webpack loaders for stories
36-
'static/app/**/stories/*loader.ts',
36+
'static/app/stories/*loader.ts',
3737
];
3838

3939
const config: KnipConfig = {

rspack.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ const appConfig: Configuration = {
380380
resolveLoader: {
381381
alias: {
382382
'type-loader': STORYBOOK_TYPES
383-
? path.resolve(__dirname, 'static/app/views/stories/type-loader.ts')
384-
: path.resolve(__dirname, 'static/app/views/stories/noop-type-loader.ts'),
383+
? path.resolve(__dirname, 'static/app/stories/type-loader.ts')
384+
: path.resolve(__dirname, 'static/app/stories/noop-type-loader.ts'),
385385
},
386386
},
387387

static/app/components/acl/featureDisabled.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {Fragment} from 'react';
22

33
import FeatureDisabled from 'sentry/components/acl/featureDisabled';
4-
import storyBook from 'sentry/stories/storyBook';
4+
import * as Storybook from 'sentry/stories';
55

6-
export default storyBook('FeatureDisabled', story => {
6+
export default Storybook.story('FeatureDisabled', story => {
77
story('Default', () => {
88
return (
99
<Fragment>

static/app/components/alerts/pageBanner.stories.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import PageBanner from 'sentry/components/alerts/pageBanner';
77
import {Button} from 'sentry/components/core/button';
88
import {LinkButton} from 'sentry/components/core/button/linkButton';
99
import ExternalLink from 'sentry/components/links/externalLink';
10-
import SizingWindow from 'sentry/components/stories/sizingWindow';
1110
import {IconBroadcast} from 'sentry/icons';
12-
import storyBook from 'sentry/stories/storyBook';
11+
import * as Storybook from 'sentry/stories';
1312

14-
export default storyBook('PageBanner', story => {
13+
export default Storybook.story('PageBanner', story => {
1514
const storiesButton = (
1615
<LinkButton
1716
external
@@ -80,7 +79,7 @@ export default storyBook('PageBanner', story => {
8079
flexGrow: <var>{flexGrow ? 1 : 0}</var>
8180
</Button>
8281
</p>
83-
<SizingWindow>
82+
<Storybook.SizingWindow>
8483
<PageBanner
8584
style={{flexGrow: flexGrow ? 1 : 0}}
8685
button={storiesButton}
@@ -90,7 +89,7 @@ export default storyBook('PageBanner', story => {
9089
image={replaysDeadRageBackground}
9190
title="UI Library Available"
9291
/>
93-
</SizingWindow>
92+
</Storybook.SizingWindow>
9493
</Fragment>
9594
);
9695
});

static/app/components/assigneeBadge.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import {Fragment, useState} from 'react';
22
import {uuid4} from '@sentry/core';
33

44
import {AssigneeBadge} from 'sentry/components/assigneeBadge';
5-
import storyBook from 'sentry/stories/storyBook';
5+
import * as Storybook from 'sentry/stories';
66
import type {Actor} from 'sentry/types/core';
77
import type {Team} from 'sentry/types/organization';
88
import {useUser} from 'sentry/utils/useUser';
99
import {useUserTeams} from 'sentry/utils/useUserTeams';
1010

11-
export default storyBook('AssigneeBadge', story => {
11+
export default Storybook.story('AssigneeBadge', story => {
1212
story('User Assignee', () => {
1313
const user = useUser();
1414
const [chevron1Toggle, setChevron1Toggle] = useState<'up' | 'down'>('down');

static/app/components/badge/groupPriority.stories.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ import {
44
GroupPriorityBadge,
55
GroupPriorityDropdown,
66
} from 'sentry/components/badge/groupPriority';
7-
import SideBySide from 'sentry/components/stories/sideBySide';
8-
import storyBook from 'sentry/stories/storyBook';
7+
import * as Storybook from 'sentry/stories';
98
import {PriorityLevel} from 'sentry/types/group';
109

1110
const PRIORITIES = [PriorityLevel.HIGH, PriorityLevel.MEDIUM, PriorityLevel.LOW];
1211

13-
export const Badge = storyBook('GroupPriorityBadge', story => {
12+
export const Badge = Storybook.story('GroupPriorityBadge', story => {
1413
story('Default', () => (
15-
<SideBySide>
14+
<Storybook.SideBySide>
1615
{PRIORITIES.map(priority => (
1716
<GroupPriorityBadge key={priority} priority={priority} />
1817
))}
19-
</SideBySide>
18+
</Storybook.SideBySide>
2019
));
2120
});
2221

23-
export const Dropdown = storyBook('GroupPriorityDropdown', story => {
22+
export const Dropdown = Storybook.story('GroupPriorityDropdown', story => {
2423
story('Default', () => {
2524
const [value, setValue] = useState(PriorityLevel.MEDIUM);
2625

static/app/components/calendar/calendar.stories.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@ import type {Range} from 'react-date-range';
33

44
import {DatePicker, DateRangePicker} from 'sentry/components/calendar';
55
import ExternalLink from 'sentry/components/links/externalLink';
6-
import JSXNode from 'sentry/components/stories/jsxNode';
7-
import JSXProperty from 'sentry/components/stories/jsxProperty';
8-
import storyBook from 'sentry/stories/storyBook';
6+
import * as Storybook from 'sentry/stories';
97

10-
export default storyBook('Calendar', story => {
8+
export default Storybook.story('Calendar', story => {
119
story('DatePicker', () => {
1210
const [selected, setSelected] = useState<Date | undefined>(undefined);
1311
return (
1412
<Fragment>
1513
<p>
16-
The most common props to set are <JSXProperty name="date" value={Date} /> with{' '}
17-
<JSXProperty name="onChange" value={Function} />.
14+
The most common props to set are{' '}
15+
<Storybook.JSXProperty name="date" value={Date} /> with{' '}
16+
<Storybook.JSXProperty name="onChange" value={Function} />.
1817
</p>
1918
<p>
2019
Under the hook we're using{' '}
@@ -34,10 +33,10 @@ export default storyBook('Calendar', story => {
3433
return (
3534
<Fragment>
3635
<p>
37-
<JSXNode name="DateRangePicker" /> accepts{' '}
38-
<JSXProperty name="startDate" value={Date} />,{' '}
39-
<JSXProperty name="endDate" value={Date} /> along with{' '}
40-
<JSXProperty name="onChange" value={Function} /> which accepts a{' '}
36+
<Storybook.JSXNode name="DateRangePicker" /> accepts{' '}
37+
<Storybook.JSXProperty name="startDate" value={Date} />,{' '}
38+
<Storybook.JSXProperty name="endDate" value={Date} /> along with{' '}
39+
<Storybook.JSXProperty name="onChange" value={Function} /> which accepts a{' '}
4140
<code>Range</code>.
4241
</p>
4342
<p>

static/app/components/charts/chartWidgetLoader.stories.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import {Fragment} from 'react';
22

33
import {CodeSnippet} from 'sentry/components/codeSnippet';
4-
import JSXNode from 'sentry/components/stories/jsxNode';
5-
import storyBook from 'sentry/stories/storyBook';
4+
import * as Storybook from 'sentry/stories';
65

76
// eslint-disable-next-line import/no-webpack-loader-syntax
87
import types from '!!type-loader!sentry/components/charts/chartWidgetLoader';
98

10-
export default storyBook('ChartWidgetLoader', (story, APIReference) => {
9+
export default Storybook.story('ChartWidgetLoader', (story, APIReference) => {
1110
APIReference(types.ChartWidgetLoader);
1211

1312
story('Getting Started', () => {
1413
return (
1514
<Fragment>
1615
<p>
17-
<JSXNode name="ChartWidgetLoader" /> is a helper component that dynamically
18-
imports and renders a chart. It currently only supports charts used in Insights,
19-
specifically all chart widgets that are defined in
16+
<Storybook.JSXNode name="ChartWidgetLoader" /> is a helper component that
17+
dynamically imports and renders a chart. It currently only supports charts used
18+
in Insights, specifically all chart widgets that are defined in
2019
<code>sentry/views/insights/common/components/widgets</code>. However, it will
2120
be expanded to support at least one other chart type (
22-
<JSXNode name="EventGraph" />) in the future.
21+
<Storybook.JSXNode name="EventGraph" />) in the future.
2322
</p>
2423

2524
<p>

static/app/components/checkInTimeline/checkInTimeline.stories.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import {ButtonBar} from 'sentry/components/core/button/buttonBar';
66
import {CompactSelect} from 'sentry/components/core/compactSelect';
77
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
88
import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
9-
import JSXNode from 'sentry/components/stories/jsxNode';
10-
import JSXProperty from 'sentry/components/stories/jsxProperty';
11-
import storyBook from 'sentry/stories/storyBook';
9+
import * as Storybook from 'sentry/stories';
1210
import {space} from 'sentry/styles/space';
1311
import {useDimensions} from 'sentry/utils/useDimensions';
1412

@@ -79,7 +77,7 @@ function generateMockTickData(
7977
.filter(([ts, _]) => ts <= timeWindowConfig.end.getTime() / 1000);
8078
}
8179

82-
export default storyBook('CheckInTimeline', story => {
80+
export default Storybook.story('CheckInTimeline', story => {
8381
story('Simple', () => {
8482
const elementRef = useRef<HTMLDivElement>(null);
8583
const {width: timelineWidth} = useDimensions<HTMLDivElement>({elementRef});
@@ -94,8 +92,8 @@ export default storyBook('CheckInTimeline', story => {
9492
return (
9593
<PageFiltersContainer>
9694
<p>
97-
The <JSXNode name="CheckInTimeline" /> component may be used to render a
98-
timeline of 'check-ins'.
95+
The <Storybook.JSXNode name="CheckInTimeline" /> component may be used to render
96+
a timeline of 'check-ins'.
9997
</p>
10098
<p>
10199
The timeline is given a list of "Buckets" where each bucket contains a time
@@ -133,7 +131,8 @@ export default storyBook('CheckInTimeline', story => {
133131
<p>
134132
You may compose various components exposed in the <code>checkInTimeline</code>{' '}
135133
module together to create a more visually useful timeline. See:{' '}
136-
<JSXNode name="GridLineOverlay" /> and <JSXNode name="GridLineLabels" />
134+
<Storybook.JSXNode name="GridLineOverlay" /> and{' '}
135+
<Storybook.JSXNode name="GridLineLabels" />
137136
</p>
138137

139138
<ExampleContainer>
@@ -151,9 +150,10 @@ export default storyBook('CheckInTimeline', story => {
151150
</ExampleContainer>
152151

153152
<p>
154-
Enabling the <JSXProperty name="allowZoom" value /> and{' '}
155-
<JSXProperty name="showCursor" value /> attributes of the{' '}
156-
<JSXNode name="GridLineOverlay" /> will make the timeline more interactive.
153+
Enabling the <Storybook.JSXProperty name="allowZoom" value /> and{' '}
154+
<Storybook.JSXProperty name="showCursor" value /> attributes of the{' '}
155+
<Storybook.JSXNode name="GridLineOverlay" /> will make the timeline more
156+
interactive.
157157
</p>
158158

159159
<ExampleContainer>

static/app/components/clippedBox.stories.tsx

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,28 @@ import styled from '@emotion/styled';
44
import onboardingFrameworkSelectionJavascript from 'sentry-images/spot/replay-dead-rage-changelog.svg';
55

66
import ClippedBox from 'sentry/components/clippedBox';
7-
import JSXNode from 'sentry/components/stories/jsxNode';
8-
import JSXProperty from 'sentry/components/stories/jsxProperty';
9-
import Matrix from 'sentry/components/stories/matrix';
10-
import SideBySide from 'sentry/components/stories/sideBySide';
11-
import SizingWindow from 'sentry/components/stories/sizingWindow';
12-
import storyBook from 'sentry/stories/storyBook';
7+
import * as Storybook from 'sentry/stories';
138
import {space} from 'sentry/styles/space';
149

15-
export default storyBook('ClippedBox', story => {
10+
export default Storybook.story('ClippedBox', story => {
1611
story('Default', () => (
1712
<Fragment>
1813
<p>
19-
By default <JSXNode name="ClippedBox" /> is just a container. Add{' '}
20-
<JSXProperty name="defaultClipped" value /> to occlude the bottom part of the
21-
content if the content height is larger than{' '}
22-
<JSXProperty name="clipHeight" value={Number} />. You should also set{' '}
23-
<JSXProperty name="clipHeight" value={Number} /> (default: 300) to be something
24-
reasonable for the situation.
14+
By default <Storybook.JSXNode name="ClippedBox" /> is just a container. Add{' '}
15+
<Storybook.JSXProperty name="defaultClipped" value /> to occlude the bottom part
16+
of the content if the content height is larger than{' '}
17+
<Storybook.JSXProperty name="clipHeight" value={Number} />. You should also set{' '}
18+
<Storybook.JSXProperty name="clipHeight" value={Number} /> (default: 300) to be
19+
something reasonable for the situation.
2520
</p>
2621
<p>
27-
Once expanded, <JSXNode name="ClippedBox" /> cannot be collapsed again.
22+
Once expanded, <Storybook.JSXNode name="ClippedBox" /> cannot be collapsed again.
2823
</p>
29-
<SizingWindow>
24+
<Storybook.SizingWindow>
3025
<ClippedBox>
3126
<img src={onboardingFrameworkSelectionJavascript} height={300} />
3227
</ClippedBox>
33-
</SizingWindow>
28+
</Storybook.SizingWindow>
3429
</Fragment>
3530
));
3631

@@ -46,15 +41,15 @@ export default storyBook('ClippedBox', story => {
4641
`;
4742

4843
story('Title', () => (
49-
<SizingWindow>
44+
<Storybook.SizingWindow>
5045
<ClippedBox title="This is the title">
5146
<img src={onboardingFrameworkSelectionJavascript} height={300} />
5247
</ClippedBox>
53-
</SizingWindow>
48+
</Storybook.SizingWindow>
5449
));
5550

5651
story('Custom Button & Fade', () => (
57-
<Matrix
52+
<Storybook.PropMatrix
5853
render={ClippedBox}
5954
propMatrix={{
6055
btnText: ['Custom Label'],
@@ -77,10 +72,10 @@ export default storyBook('ClippedBox', story => {
7772
<Fragment>
7873
<p>
7974
Some callbacks are available:{' '}
80-
<JSXProperty name="onSetRenderedHeight" value={Function} />&{' '}
81-
<JSXProperty name="onReveal" value={Function} />.
75+
<Storybook.JSXProperty name="onSetRenderedHeight" value={Function} />&{' '}
76+
<Storybook.JSXProperty name="onReveal" value={Function} />.
8277
</p>
83-
<SideBySide>
78+
<Storybook.SideBySide>
8479
{[50, 100, 150].map(imgHeight => {
8580
const [isRevealed, setIsRevealed] = useState(false);
8681
const [renderedHeight, setRenderedHeight] = useState<number | undefined>(
@@ -89,13 +84,13 @@ export default storyBook('ClippedBox', story => {
8984
return (
9085
<div key={imgHeight}>
9186
<p>
92-
<JSXNode name="ClippedBox" props={{clipHeight: 100}}>
93-
<JSXNode name="img" props={{height: imgHeight}} />
94-
</JSXNode>
87+
<Storybook.JSXNode name="ClippedBox" props={{clipHeight: 100}}>
88+
<Storybook.JSXNode name="img" props={{height: imgHeight}} />
89+
</Storybook.JSXNode>
9590
</p>
9691
<p>isRevealed = {String(isRevealed)}</p>
9792
<p>renderedHeight = {renderedHeight}</p>
98-
<SizingWindow>
93+
<Storybook.SizingWindow>
9994
<ClippedBox
10095
clipHeight={100}
10196
onReveal={() => setIsRevealed(true)}
@@ -106,11 +101,11 @@ export default storyBook('ClippedBox', story => {
106101
height={imgHeight}
107102
/>
108103
</ClippedBox>
109-
</SizingWindow>
104+
</Storybook.SizingWindow>
110105
</div>
111106
);
112107
})}
113-
</SideBySide>
108+
</Storybook.SideBySide>
114109
</Fragment>
115110
);
116111
});

0 commit comments

Comments
 (0)