Skip to content

Commit 5002bd8

Browse files
Merge pull request #259 from mongodb/code-example
2 parents 81dd5b8 + 6f875bd commit 5002bd8

19 files changed

+297
-159
lines changed

components/pages/example/Knob/types.ts renamed to components/pages/example/KnobsTable/Knob/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { HTMLElementProps } from '@leafygreen-ui/lib';
22

3-
import { KnobOptionType, TypeString } from '../types';
3+
import { KnobOptionType, TypeString } from '../../types';
44

55
export interface KnobProps extends HTMLElementProps<'input'> {
66
propName: string;

components/pages/example/KnobRow/KnobRow.tsx renamed to components/pages/example/KnobsTable/KnobRow.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { kebabCase } from 'lodash';
2+
import { isRequired } from 'utils/tsdoc.utils';
23

34
import { css } from '@leafygreen-ui/emotion';
45
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
56
import { HTMLElementProps } from '@leafygreen-ui/lib';
67
import { palette } from '@leafygreen-ui/palette';
78
import { spacing } from '@leafygreen-ui/tokens';
89
import Tooltip from '@leafygreen-ui/tooltip';
9-
import { Body } from '@leafygreen-ui/typography';
10+
import { Body, Disclaimer } from '@leafygreen-ui/typography';
1011

11-
import { Knob } from '../Knob/Knob';
1212
import { KnobType } from '../types';
1313

14+
import { Knob } from './Knob/Knob';
15+
1416
const knobRowWrapperStyle = (darkMode: boolean) => css`
1517
display: flex;
1618
width: 100%;
@@ -31,6 +33,13 @@ const knobControlStyle = css`
3133
justify-content: end;
3234
`;
3335

36+
const requiredFlagStyle = css`
37+
display: inline;
38+
padding-left: 1ch;
39+
color: ${palette.red.base};
40+
text-transform: uppercase;
41+
`;
42+
3443
interface KnobRowProps extends HTMLElementProps<'div'> {
3544
knob: KnobType;
3645
knobValue?: any;
@@ -66,6 +75,9 @@ export const KnobRow = ({ knob, knobValue, setKnobValue }: KnobRowProps) => {
6675
id={`${kebabCase()}-knob-${name}`}
6776
>
6877
<strong>{name}</strong>
78+
{isRequired(knob) && (
79+
<Disclaimer className={requiredFlagStyle}>(required)</Disclaimer>
80+
)}
6981
</Body>
7082
</div>
7183
{args?.disabled && args?.description ? (
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { css } from '@leafygreen-ui/emotion';
2+
3+
export const exampleCodeButtonRowStyle = css`
4+
text-align: right;
5+
padding: 16px 24px;
6+
`;
7+
8+
export const exampleCodeButtonStyle = css`
9+
white-space: nowrap;
10+
`;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { MouseEventHandler } from 'react';
2+
3+
import Button from '@leafygreen-ui/button';
4+
import Icon from '@leafygreen-ui/icon';
5+
6+
import {
7+
LiveExampleContext,
8+
LiveExampleStateReturnValue,
9+
} from '../useLiveExampleState';
10+
11+
import { KnobRow } from './KnobRow';
12+
import {
13+
exampleCodeButtonRowStyle,
14+
exampleCodeButtonStyle,
15+
} from './KnobsTable.styles';
16+
17+
interface KnobsTableProps {
18+
showCode: boolean;
19+
codeExampleEnabled: boolean;
20+
handleShowCodeClick: MouseEventHandler;
21+
knobsArray: Required<LiveExampleContext>['knobsArray'];
22+
knobValues: Required<LiveExampleContext>['knobValues'];
23+
updateKnobValue: LiveExampleStateReturnValue['updateKnobValue'];
24+
}
25+
26+
export const KnobsTable = ({
27+
showCode,
28+
codeExampleEnabled,
29+
handleShowCodeClick,
30+
knobsArray,
31+
knobValues,
32+
updateKnobValue,
33+
}: KnobsTableProps) => {
34+
return (
35+
<div id="knobs">
36+
{codeExampleEnabled && (
37+
<div className={exampleCodeButtonRowStyle}>
38+
<Button
39+
className={exampleCodeButtonStyle}
40+
variant="default"
41+
size="xsmall"
42+
onClick={handleShowCodeClick}
43+
leftGlyph={
44+
<Icon glyph={showCode ? 'VisibilityOff' : 'Visibility'} />
45+
}
46+
>
47+
{showCode ? 'Hide' : 'Show'} Code
48+
</Button>
49+
</div>
50+
)}
51+
{knobsArray.map(knob => (
52+
<KnobRow
53+
key={knob.name}
54+
knob={knob}
55+
knobValue={knobValues?.[knob.name]}
56+
setKnobValue={updateKnobValue}
57+
/>
58+
))}
59+
</div>
60+
);
61+
};

components/pages/example/LiveExample.styles.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,3 @@ export const codeStyle = css`
8585
height: 100%;
8686
overflow: auto;
8787
`;
88-
89-
export const exampleCodeButtonRowStyle = css`
90-
text-align: right;
91-
padding: 16px 24px;
92-
`;
93-
94-
export const exampleCodeButtonStyle = css`
95-
white-space: nowrap;
96-
`;

components/pages/example/LiveExample.tsx

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
import { useEffect, useRef, useState } from 'react';
22
import { CustomComponentDoc } from 'utils/tsdoc.utils';
33

4-
import Button from '@leafygreen-ui/button';
54
import Card from '@leafygreen-ui/card';
65
import { css, cx } from '@leafygreen-ui/emotion';
76
import { usePrevious } from '@leafygreen-ui/hooks';
87
import LeafyGreenProvider, {
98
useDarkMode,
109
} from '@leafygreen-ui/leafygreen-provider';
1110

12-
import { KnobRow } from './KnobRow/KnobRow';
13-
import {
14-
assertCompleteContext,
15-
isStateReady,
16-
} from './useLiveExampleState/utils';
11+
import { KnobsTable } from './KnobsTable/KnobsTable';
12+
import { isStateReady } from './useLiveExampleState/utils';
1713
import { CodeExample } from './CodeExample';
1814
import {
1915
blockContainerStyle,
20-
exampleCodeButtonRowStyle,
21-
exampleCodeButtonStyle,
2216
liveExampleWrapperStyle,
2317
storyContainerStyle,
2418
} from './LiveExample.styles';
@@ -28,8 +22,8 @@ import {
2822
LiveExampleLoading,
2923
LiveExampleNotFound,
3024
} from './LiveExampleStateComponents';
31-
import { LiveExampleContext, useLiveExampleState } from './useLiveExampleState';
32-
import { getStoryCode } from './utils';
25+
import { useLiveExampleState } from './useLiveExampleState';
26+
import { useStoryCode } from './useStoryCode';
3327

3428
// Use standard block flow for these packages
3529
const useBlockWrapperFor = [
@@ -50,7 +44,6 @@ export const LiveExample = ({
5044
}) => {
5145
const prevComponentName = usePrevious(componentName);
5246
const [showCode, setShowCode] = useState(false);
53-
const [storyCode, setCode] = useState('No code found');
5447
const storyContainerRef = useRef<HTMLDivElement>(null);
5548
const storyWrapperRef = useRef<HTMLDivElement>(null);
5649

@@ -72,23 +65,11 @@ export const LiveExample = ({
7265
}
7366
}, [componentName, prevComponentName, tsDoc, resetContext, setErrorState]);
7467

75-
/** Re-generates story example code from context */
76-
const regenerateStoryCode = (context: Partial<LiveExampleContext>) => {
77-
const code = assertCompleteContext(context)
78-
? getStoryCode(context) ?? 'No code found'
79-
: 'No code found';
80-
setCode(code);
81-
};
82-
83-
/** re-generate example code when the context changes */
84-
useEffect(() => {
85-
regenerateStoryCode(context);
86-
}, [context]);
68+
const storyCode = useStoryCode(context, showCode);
8769

8870
/** Triggered on button click */
89-
const handleShowCodeClick = () => {
71+
const toggleShowCode = () => {
9072
setShowCode(sc => !sc);
91-
regenerateStoryCode(context);
9273
};
9374

9475
const storyWrapperStyle = context.meta?.parameters?.wrapperStyle;
@@ -104,6 +85,8 @@ export const LiveExample = ({
10485
// should match the total height of the story container
10586
const exampleCodeHeight = storyContainerHeight + 48;
10687

88+
const codeExampleEnabled = !disableCodeExampleFor.includes(componentName);
89+
10790
return (
10891
<LeafyGreenProvider darkMode={darkMode}>
10992
<Card
@@ -141,37 +124,24 @@ export const LiveExample = ({
141124
<LiveExampleError message={context.errorMessage} />
142125
)}
143126
</div>
144-
{!disableCodeExampleFor.includes(componentName) && (
127+
{codeExampleEnabled && (
145128
<CodeExample
146129
showCode={showCode}
147130
code={storyCode}
148131
height={exampleCodeHeight}
149132
/>
150133
)}
151134
</div>
152-
<div id="knobs">
153-
{!disableCodeExampleFor.includes(componentName) && (
154-
<div className={exampleCodeButtonRowStyle}>
155-
<Button
156-
className={exampleCodeButtonStyle}
157-
variant="default"
158-
size="xsmall"
159-
onClick={handleShowCodeClick}
160-
>
161-
{showCode ? 'Hide' : 'Show'} Code
162-
</Button>
163-
</div>
164-
)}
165-
{isStateReady(context) &&
166-
context.knobsArray.map(knob => (
167-
<KnobRow
168-
key={knob.name}
169-
knob={knob}
170-
knobValue={context.knobValues?.[knob.name]}
171-
setKnobValue={updateKnobValue}
172-
/>
173-
))}
174-
</div>
135+
{isStateReady(context) && (
136+
<KnobsTable
137+
showCode={showCode}
138+
codeExampleEnabled={codeExampleEnabled}
139+
handleShowCodeClick={toggleShowCode}
140+
knobsArray={context.knobsArray}
141+
knobValues={context.knobValues}
142+
updateKnobValue={updateKnobValue}
143+
/>
144+
)}
175145
</Card>
176146
</LeafyGreenProvider>
177147
);

components/pages/example/useLiveExampleState/LiveExampleState.types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface LiveExampleContext {
1919
componentName?: string;
2020
tsDoc?: Array<CustomComponentDoc> | null;
2121
meta?: Meta<any>;
22-
StoryFn?: ComponentStoryFn<any>;
22+
StoryFn?: ComponentStoryFn<any> & React.FunctionComponent<any>;
2323
knobValues?: { [arg: string]: any };
2424
knobsArray?: Array<KnobType>;
2525
errorMessage?: string;
@@ -51,3 +51,11 @@ export type LiveExampleAction =
5151
type: LiveExampleActionType.NOT_FOUND;
5252
componentName: string;
5353
};
54+
55+
export interface LiveExampleStateReturnValue {
56+
context: LiveExampleContext;
57+
updateKnobValue: (prop: string, val: any) => void;
58+
resetContext: (name: string, tsDoc: Array<CustomComponentDoc>) => void;
59+
setErrorState: (msg: string) => void;
60+
isState: (state: LiveExampleState) => boolean;
61+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
export type { LiveExampleContext } from './LiveExampleState.types';
1+
export type {
2+
LiveExampleContext,
3+
LiveExampleStateReturnValue,
4+
} from './LiveExampleState.types';
25
export { useLiveExampleState } from './useLiveExampleState';

components/pages/example/useLiveExampleState/useLiveExampleState.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useReducer } from 'react';
22
import { kebabCase, merge } from 'lodash';
3+
import pascalcase from 'pascalcase';
34
import { getComponentStories, ModuleType } from 'utils/getComponentStories';
45
import { CustomComponentDoc } from 'utils/tsdoc.utils';
56

@@ -14,14 +15,15 @@ import {
1415
import {
1516
LiveExampleActionType,
1617
LiveExampleState,
18+
LiveExampleStateReturnValue,
1719
} from './LiveExampleState.types';
1820
import { liveExampleStateReducer } from './LiveExampleStateReducer';
1921
import { assertContext, defaultLiveExampleContext } from './utils';
2022

2123
export function useLiveExampleState(
2224
componentName: string,
2325
tsDoc?: Array<CustomComponentDoc> | null,
24-
) {
26+
): LiveExampleStateReturnValue {
2527
const initialState = merge(
2628
{ componentName, tsDoc },
2729
defaultLiveExampleContext,
@@ -51,6 +53,7 @@ export function useLiveExampleState(
5153
});
5254
}
5355

56+
/** Log an error */
5457
function setErrorState(message: string) {
5558
dispatch({
5659
type: LiveExampleActionType.ERROR,
@@ -70,6 +73,7 @@ export function useLiveExampleState(
7073
function parse(module: ModuleType) {
7174
const { default: meta, ...stories } = module;
7275
const StoryFn = getDefaultStoryFn(meta, stories);
76+
StoryFn.displayName = pascalcase(componentName) + 'Story';
7377

7478
if (assertContext(context, ['state', 'componentName', 'tsDoc'])) {
7579
const knobsArray = getKnobsArray({
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { isStateReady } from './useLiveExampleState/utils';
4+
import { getStoryCode } from './utils/getStoryCode';
5+
import { LiveExampleContext } from './useLiveExampleState';
6+
7+
const emptyStateCode = 'No code found';
8+
9+
export const useStoryCode = (
10+
context: LiveExampleContext,
11+
showCode: boolean,
12+
) => {
13+
const [storyCode, setCode] = useState(emptyStateCode);
14+
15+
useEffect(() => {
16+
if (showCode && isStateReady(context)) {
17+
const code = getStoryCode(context) ?? emptyStateCode;
18+
setCode(code);
19+
}
20+
}, [context, showCode]);
21+
22+
return storyCode;
23+
};

0 commit comments

Comments
 (0)