Skip to content

Commit 74f1403

Browse files
authored
Storybook iframe update to match website design (microsoft#21270)
* add script to head * unpkg link to iframe resizer * update preview head * create custom preview head template * style updates * Change files * fix style imports and add iframe resize script * update right rail * logic tweak * remove comment * change to inline iframe code * updated docs page layout styles * fix code size bug * fix code overflow * add hosted checking * fix typings * move styles and code into root instance, cleanup suggestions from PR * remove unused css file * remove story max width
1 parent 0f3e58e commit 74f1403

10 files changed

+1193
-415
lines changed

.storybook/docs-root.css

+410
Large diffs are not rendered by default.

.storybook/preview-head-template.html

+657-311
Large diffs are not rendered by default.

.storybook/preview.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { withFluentProvider, withStrictMode } from '@fluentui/react-storybook';
22
import 'cypress-storybook/react';
33
import * as dedent from 'dedent';
4+
import './docs-root.css';
45

56
/** @type {NonNullable<import('@storybook/react').Story['decorators']>} */
67
export const decorators = [withFluentProvider, withStrictMode];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Custom styles for storybook to better align with public website",
4+
"packageName": "@fluentui/react-components",
5+
"email": "mgodbolt@microsoft.com",
6+
"dependentChangeType": "none"
7+
}

packages/react-components/.storybook/preview.js

-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import * as rootPreview from '../../../.storybook/preview';
22
import { FluentDocsContainer } from '../src/DocsComponents/FluentDocsContainer.stories';
33
import { FluentDocsPage } from '../src/DocsComponents/FluentDocsPage.stories';
44

5-
// load global styles
6-
import '../public/intro.css';
7-
85
/** @type {NonNullable<typeof rootPreview.parameters['options']>} */
96
const options = {
107
storySort: {

packages/react-components/public/intro.css

-74
This file was deleted.

packages/react-components/src/DocsComponents/FluentDocsContainer.stories.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DocsContainer, DocsContextProps } from '@storybook/addon-docs';
33
import { FluentStoryContext, THEME_ID, themes } from '@fluentui/react-storybook-addon';
44
import { FluentDocsHeader } from './FluentDocsHeader.stories';
55
import { FluentProvider, webLightTheme } from '../index';
6+
import { isHosted } from './isHosted';
67

78
interface FluentDocsContainerProps {
89
context: FluentStoryContext & DocsContextProps;
@@ -13,12 +14,15 @@ interface FluentDocsContainerProps {
1314
*/
1415
export const FluentDocsContainer: React.FC<FluentDocsContainerProps> = ({ children, context }) => {
1516
const selectedTheme = themes.find(theme => theme.id === context.globals[THEME_ID]);
16-
17+
const hosted = isHosted();
1718
return (
1819
<>
19-
<FluentProvider theme={selectedTheme?.theme ?? webLightTheme}>
20-
<FluentDocsHeader storybookGlobals={context.globals} />
21-
</FluentProvider>
20+
{!hosted && (
21+
<FluentProvider theme={selectedTheme?.theme ?? webLightTheme}>
22+
<FluentDocsHeader storybookGlobals={context.globals} />
23+
</FluentProvider>
24+
)}
25+
2226
{/** TODO add table of contents */}
2327
<DocsContainer context={context}>{children}</DocsContainer>
2428
</>

packages/react-components/src/DocsComponents/FluentDocsPage.stories.tsx

+23-10
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,43 @@ import {
1111
Stories,
1212
} from '@storybook/addon-docs';
1313
import { makeStyles, shorthands } from '@griffel/react';
14-
1514
import { Toc, nameToHash } from './Toc.stories';
15+
import { isHosted } from './isHosted';
1616

1717
const useStyles = makeStyles({
18+
divider: {
19+
height: '1px',
20+
backgroundColor: '#e1dfdd',
21+
...shorthands.border('0px', 'none'),
22+
...shorthands.margin('48px', '0px'),
23+
},
1824
wrapper: {
1925
display: 'flex',
20-
flexWrap: 'wrap',
21-
flexDirection: 'row-reverse',
22-
justifyContent: 'flex-end',
2326
...shorthands.gap('16px'),
2427
},
2528
toc: {
2629
flexBasis: '200px',
2730
flexShrink: 0,
31+
[`@media screen and (max-width: 1000px)`]: {
32+
display: 'none',
33+
},
2834
},
2935
container: {
30-
flexBasis: '700px',
3136
flexGrow: 1,
3237
},
38+
// style overrides for when hosted in website
39+
hosted: {
40+
'& h1': {
41+
marginTop: '-12px !important',
42+
},
43+
},
3344
});
3445

3546
export const FluentDocsPage = () => {
3647
const context = React.useContext(DocsContext);
3748
const stories = context.storyStore.getStoriesForKind(context.kind);
3849
const primaryStory = stories[0];
50+
const hosted = isHosted();
3951
const styles = useStyles();
4052
// DEBUG
4153
// console.log('FluentDocsPage', context);
@@ -50,24 +62,25 @@ export const FluentDocsPage = () => {
5062
// );
5163

5264
return (
53-
<>
65+
<div className={hosted ? styles.hosted : ''}>
5466
<Title />
5567

5668
<div className={styles.wrapper}>
57-
<div className={styles.toc}>
58-
<Toc stories={stories} />
59-
</div>
6069
<div className={styles.container}>
6170
<Subtitle />
6271
<Description />
72+
<hr className={styles.divider} />
6373
<HeaderMdx as="h3" id={nameToHash(primaryStory.name)}>
6474
{primaryStory.name}
6575
</HeaderMdx>
6676
<Primary />
6777
<ArgsTable story={PRIMARY_STORY} />
6878
<Stories />
6979
</div>
80+
<div className={styles.toc}>
81+
<Toc stories={stories} />
82+
</div>
7083
</div>
71-
</>
84+
</div>
7285
);
7386
};

packages/react-components/src/DocsComponents/Toc.stories.tsx

+77-13
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,65 @@ import * as React from 'react';
22
import { PublishedStoreItem } from '@storybook/client-api';
33
import { addons } from '@storybook/addons';
44
import { NAVIGATE_URL } from '@storybook/core-events';
5-
import { makeStyles } from '../index';
5+
import { makeStyles, shorthands } from '../index';
66

77
const useTocStyles = makeStyles({
88
root: {
99
top: '64px',
1010
position: 'sticky',
11+
marginLeft: '40px',
1112
},
1213
heading: {
13-
fontSize: '16px',
14+
fontSize: '11px',
15+
fontWeight: 'bold',
16+
textTransform: 'uppercase',
17+
marginBottom: '20px',
1418
},
1519
ol: {
20+
position: 'relative',
1621
listStyleType: 'none',
1722
marginLeft: 0,
18-
paddingInlineStart: '8px',
19-
23+
marginTop: 0,
24+
paddingInlineStart: '20px',
25+
'& li': {
26+
marginBottom: '15px',
27+
lineHeight: '16px',
28+
},
2029
'& a': {
2130
textDecorationLine: 'none',
22-
color: '#0078d4',
31+
color: '#201F1E',
2332
fontSize: '14px',
24-
lineHeight: '24px',
2533
':hover': {
26-
color: '#106EBE',
34+
color: '#201F1E',
2735
},
2836
},
37+
'&:before': {
38+
content: '""',
39+
position: 'absolute',
40+
left: 0,
41+
height: '100%',
42+
width: '3px',
43+
backgroundColor: '#EDEBE9',
44+
...shorthands.borderRadius('4px'),
45+
},
46+
},
47+
selected: {
48+
position: 'relative',
49+
'&:after': {
50+
content: '""',
51+
position: 'absolute',
52+
left: '-20px',
53+
top: 0,
54+
bottom: 0,
55+
width: '3px',
56+
backgroundColor: '#436DCD',
57+
...shorthands.borderRadius('4px'),
58+
},
2959
},
3060
});
3161

62+
type TocItem = PublishedStoreItem & { selected?: boolean };
63+
3264
// // Alternative approach to navigate - rerenders the iframe
3365
// // Usage: selectStory({ story: s.name, kind: s.kind });
3466
// const selectStory = (story: { kind: string; story: string }) => {
@@ -37,26 +69,58 @@ const useTocStyles = makeStyles({
3769
// };
3870

3971
const navigate = (url: string) => {
40-
console.log('Navigate', url);
4172
addons.getChannel().emit(NAVIGATE_URL, url);
4273
};
4374

4475
export const nameToHash = (id: string): string => id.toLowerCase().replace(/[^a-z0-9]/gi, '-');
4576

46-
export const Toc = ({ stories }: { stories: PublishedStoreItem[] }) => {
77+
export const Toc = ({ stories }: { stories: TocItem[] }) => {
78+
const [selected, setSelected] = React.useState('');
79+
80+
React.useEffect(() => {
81+
const observer = new IntersectionObserver(
82+
(entries: IntersectionObserverEntry[]) => {
83+
for (const entry of entries) {
84+
const { intersectionRatio, target } = entry;
85+
if (intersectionRatio > 0.5) {
86+
setSelected(target.id);
87+
return;
88+
}
89+
}
90+
},
91+
{
92+
threshold: [0.5],
93+
},
94+
);
95+
96+
stories.forEach(link => {
97+
const element = document.getElementById(nameToHash(link.name));
98+
if (element) {
99+
observer.observe(element);
100+
}
101+
});
102+
103+
return () => observer.disconnect();
104+
}, [stories]);
105+
106+
const tocItems = stories.map(item => {
107+
return { ...item, selected: nameToHash(item.name) === selected };
108+
});
47109
const tocClasses = useTocStyles();
48110
return (
49111
<nav className={tocClasses.root}>
50112
<h3 className={tocClasses.heading}>On this page</h3>
51113
<ol className={tocClasses.ol}>
52-
{stories.map(s => {
114+
{tocItems.map(s => {
115+
const name = nameToHash(s.name);
53116
return (
54-
<li key={s.id}>
117+
<li className={s.selected ? tocClasses.selected : ''} key={s.id}>
55118
<a
56-
href={`#${nameToHash(s.name)}`}
119+
href={`#${name}`}
57120
target="_self"
58121
onClick={e => {
59-
navigate(`#${nameToHash(s.name)}`);
122+
navigate(`#${name}`);
123+
setSelected(name);
60124
}}
61125
>
62126
{s.name}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// We are doing some conditional styling for when the storybook iframes are "hosted"
2+
// in the Fluent UI website. This utility checkes for a &hosted=true in the url Param
3+
4+
const getURLParameter = (p: string) => {
5+
const url = window.location.search.substring(1);
6+
const searchParams = new URLSearchParams(url);
7+
return searchParams.get(p);
8+
};
9+
10+
export const isHosted = () => getURLParameter('hosted') === 'true';

0 commit comments

Comments
 (0)