Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code example generator [LG-2954, LG-2950, LG-2951] #259

Merged
merged 31 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7f99f04
Updates component name parsing
TheSonOfThomp Apr 20, 2023
6a4904e
Update LiveExample.tsx
TheSonOfThomp Apr 20, 2023
bfd472b
Parse React Tree to generate code example
TheSonOfThomp Apr 21, 2023
111f37f
update package json & config
TheSonOfThomp Apr 21, 2023
9079c89
Merge branch 'staging' into code-example
TheSonOfThomp Apr 21, 2023
e510252
Merge branch 'live-example-decorators' into code-example
TheSonOfThomp Apr 21, 2023
c613d00
Update LiveExampleError.tsx
TheSonOfThomp Apr 21, 2023
5873644
adds pascalcase
TheSonOfThomp Apr 21, 2023
eae3f0e
update comment with issue reference
TheSonOfThomp Apr 21, 2023
ffc57a3
Merge branch 'live-example-decorators' into code-example
TheSonOfThomp Apr 24, 2023
e4a15ed
ignore default props
TheSonOfThomp Apr 24, 2023
a439b06
Merge branch 'staging' into code-example
TheSonOfThomp Apr 24, 2023
d0f3d20
refactor knob table component
TheSonOfThomp Apr 24, 2023
2649d0e
ensure table is sorted
TheSonOfThomp Apr 24, 2023
f355bb3
mv knobrow & knob
TheSonOfThomp Apr 24, 2023
6ca3477
Sort knobs same as tsdocs
TheSonOfThomp Apr 24, 2023
4f90e8c
Adds required flag
TheSonOfThomp Apr 24, 2023
46b9667
Update tsdoc.utils.ts
TheSonOfThomp Apr 24, 2023
2cab227
use isRequired in knobs table
TheSonOfThomp Apr 24, 2023
b888986
rm var
TheSonOfThomp Apr 24, 2023
a666ce4
bumps versions
TheSonOfThomp Apr 24, 2023
e038f0e
Merge branch 'code-example' into knobs-table
TheSonOfThomp Apr 24, 2023
e8ea489
Merge branch 'staging' into code-example
TheSonOfThomp Apr 24, 2023
f976fe2
Merge branch 'staging' into knobs-table
TheSonOfThomp Apr 24, 2023
dc081d5
makes story code parsing more efficient
TheSonOfThomp Apr 25, 2023
63a2d8e
exclude key prop
TheSonOfThomp Apr 25, 2023
d4bd19b
adds key to ignore props
TheSonOfThomp Apr 25, 2023
9678b1b
Merge branch 'staging' into code-example
TheSonOfThomp Apr 25, 2023
8780cd4
Merge branch 'staging' into code-example
TheSonOfThomp Apr 25, 2023
10f9bd7
Merge branch 'code-example' into knobs-table
TheSonOfThomp Apr 25, 2023
6f875bd
Merge pull request #260 from mongodb/knobs-table
TheSonOfThomp Apr 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HTMLElementProps } from '@leafygreen-ui/lib';

import { KnobOptionType, TypeString } from '../types';
import { KnobOptionType, TypeString } from '../../types';

export interface KnobProps extends HTMLElementProps<'input'> {
propName: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { kebabCase } from 'lodash';
import { isRequired } from 'utils/tsdoc.utils';

import { css } from '@leafygreen-ui/emotion';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import { HTMLElementProps } from '@leafygreen-ui/lib';
import { palette } from '@leafygreen-ui/palette';
import { spacing } from '@leafygreen-ui/tokens';
import Tooltip from '@leafygreen-ui/tooltip';
import { Body } from '@leafygreen-ui/typography';
import { Body, Disclaimer } from '@leafygreen-ui/typography';

import { Knob } from '../Knob/Knob';
import { KnobType } from '../types';

import { Knob } from './Knob/Knob';

const knobRowWrapperStyle = (darkMode: boolean) => css`
display: flex;
width: 100%;
Expand All @@ -31,6 +33,13 @@ const knobControlStyle = css`
justify-content: end;
`;

const requiredFlagStyle = css`
display: inline;
padding-left: 1ch;
color: ${palette.red.base};
text-transform: uppercase;
`;

interface KnobRowProps extends HTMLElementProps<'div'> {
knob: KnobType;
knobValue?: any;
Expand Down Expand Up @@ -66,6 +75,9 @@ export const KnobRow = ({ knob, knobValue, setKnobValue }: KnobRowProps) => {
id={`${kebabCase()}-knob-${name}`}
>
<strong>{name}</strong>
{isRequired(knob) && (
<Disclaimer className={requiredFlagStyle}>(required)</Disclaimer>
)}
</Body>
</div>
{args?.disabled && args?.description ? (
Expand Down
10 changes: 10 additions & 0 deletions components/pages/example/KnobsTable/KnobsTable.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { css } from '@leafygreen-ui/emotion';

export const exampleCodeButtonRowStyle = css`
text-align: right;
padding: 16px 24px;
`;

export const exampleCodeButtonStyle = css`
white-space: nowrap;
`;
61 changes: 61 additions & 0 deletions components/pages/example/KnobsTable/KnobsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { MouseEventHandler } from 'react';

import Button from '@leafygreen-ui/button';
import Icon from '@leafygreen-ui/icon';

import {
LiveExampleContext,
LiveExampleStateReturnValue,
} from '../useLiveExampleState';

import { KnobRow } from './KnobRow';
import {
exampleCodeButtonRowStyle,
exampleCodeButtonStyle,
} from './KnobsTable.styles';

interface KnobsTableProps {
showCode: boolean;
codeExampleEnabled: boolean;
handleShowCodeClick: MouseEventHandler;
knobsArray: Required<LiveExampleContext>['knobsArray'];
knobValues: Required<LiveExampleContext>['knobValues'];
updateKnobValue: LiveExampleStateReturnValue['updateKnobValue'];
}

export const KnobsTable = ({
showCode,
codeExampleEnabled,
handleShowCodeClick,
knobsArray,
knobValues,
updateKnobValue,
}: KnobsTableProps) => {
return (
<div id="knobs">
{codeExampleEnabled && (
<div className={exampleCodeButtonRowStyle}>
<Button
className={exampleCodeButtonStyle}
variant="default"
size="xsmall"
onClick={handleShowCodeClick}
leftGlyph={
<Icon glyph={showCode ? 'VisibilityOff' : 'Visibility'} />
}
>
{showCode ? 'Hide' : 'Show'} Code
</Button>
</div>
)}
{knobsArray.map(knob => (
<KnobRow
key={knob.name}
knob={knob}
knobValue={knobValues?.[knob.name]}
setKnobValue={updateKnobValue}
/>
))}
</div>
);
};
9 changes: 0 additions & 9 deletions components/pages/example/LiveExample.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,3 @@ export const codeStyle = css`
height: 100%;
overflow: auto;
`;

export const exampleCodeButtonRowStyle = css`
text-align: right;
padding: 16px 24px;
`;

export const exampleCodeButtonStyle = css`
white-space: nowrap;
`;
68 changes: 19 additions & 49 deletions components/pages/example/LiveExample.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { useEffect, useRef, useState } from 'react';
import { CustomComponentDoc } from 'utils/tsdoc.utils';

import Button from '@leafygreen-ui/button';
import Card from '@leafygreen-ui/card';
import { css, cx } from '@leafygreen-ui/emotion';
import { usePrevious } from '@leafygreen-ui/hooks';
import LeafyGreenProvider, {
useDarkMode,
} from '@leafygreen-ui/leafygreen-provider';

import { KnobRow } from './KnobRow/KnobRow';
import {
assertCompleteContext,
isStateReady,
} from './useLiveExampleState/utils';
import { KnobsTable } from './KnobsTable/KnobsTable';
import { isStateReady } from './useLiveExampleState/utils';
import { CodeExample } from './CodeExample';
import {
blockContainerStyle,
exampleCodeButtonRowStyle,
exampleCodeButtonStyle,
liveExampleWrapperStyle,
storyContainerStyle,
} from './LiveExample.styles';
Expand All @@ -28,8 +22,8 @@ import {
LiveExampleLoading,
LiveExampleNotFound,
} from './LiveExampleStateComponents';
import { LiveExampleContext, useLiveExampleState } from './useLiveExampleState';
import { getStoryCode } from './utils';
import { useLiveExampleState } from './useLiveExampleState';
import { useStoryCode } from './useStoryCode';

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

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

/** Re-generates story example code from context */
const regenerateStoryCode = (context: Partial<LiveExampleContext>) => {
const code = assertCompleteContext(context)
? getStoryCode(context) ?? 'No code found'
: 'No code found';
setCode(code);
};

/** re-generate example code when the context changes */
useEffect(() => {
regenerateStoryCode(context);
}, [context]);
const storyCode = useStoryCode(context, showCode);

/** Triggered on button click */
const handleShowCodeClick = () => {
const toggleShowCode = () => {
setShowCode(sc => !sc);
regenerateStoryCode(context);
};

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

const codeExampleEnabled = !disableCodeExampleFor.includes(componentName);

return (
<LeafyGreenProvider darkMode={darkMode}>
<Card
Expand Down Expand Up @@ -141,37 +124,24 @@ export const LiveExample = ({
<LiveExampleError message={context.errorMessage} />
)}
</div>
{!disableCodeExampleFor.includes(componentName) && (
{codeExampleEnabled && (
<CodeExample
showCode={showCode}
code={storyCode}
height={exampleCodeHeight}
/>
)}
</div>
<div id="knobs">
{!disableCodeExampleFor.includes(componentName) && (
<div className={exampleCodeButtonRowStyle}>
<Button
className={exampleCodeButtonStyle}
variant="default"
size="xsmall"
onClick={handleShowCodeClick}
>
{showCode ? 'Hide' : 'Show'} Code
</Button>
</div>
)}
{isStateReady(context) &&
context.knobsArray.map(knob => (
<KnobRow
key={knob.name}
knob={knob}
knobValue={context.knobValues?.[knob.name]}
setKnobValue={updateKnobValue}
/>
))}
</div>
{isStateReady(context) && (
<KnobsTable
showCode={showCode}
codeExampleEnabled={codeExampleEnabled}
handleShowCodeClick={toggleShowCode}
knobsArray={context.knobsArray}
knobValues={context.knobValues}
updateKnobValue={updateKnobValue}
/>
)}
</Card>
</LeafyGreenProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface LiveExampleContext {
componentName?: string;
tsDoc?: Array<CustomComponentDoc> | null;
meta?: Meta<any>;
StoryFn?: ComponentStoryFn<any>;
StoryFn?: ComponentStoryFn<any> & React.FunctionComponent<any>;
knobValues?: { [arg: string]: any };
knobsArray?: Array<KnobType>;
errorMessage?: string;
Expand Down Expand Up @@ -51,3 +51,11 @@ export type LiveExampleAction =
type: LiveExampleActionType.NOT_FOUND;
componentName: string;
};

export interface LiveExampleStateReturnValue {
context: LiveExampleContext;
updateKnobValue: (prop: string, val: any) => void;
resetContext: (name: string, tsDoc: Array<CustomComponentDoc>) => void;
setErrorState: (msg: string) => void;
isState: (state: LiveExampleState) => boolean;
}
5 changes: 4 additions & 1 deletion components/pages/example/useLiveExampleState/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export type { LiveExampleContext } from './LiveExampleState.types';
export type {
LiveExampleContext,
LiveExampleStateReturnValue,
} from './LiveExampleState.types';
export { useLiveExampleState } from './useLiveExampleState';
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useReducer } from 'react';
import { kebabCase, merge } from 'lodash';
import pascalcase from 'pascalcase';
TheSonOfThomp marked this conversation as resolved.
Show resolved Hide resolved
import { getComponentStories, ModuleType } from 'utils/getComponentStories';
import { CustomComponentDoc } from 'utils/tsdoc.utils';

Expand All @@ -14,14 +15,15 @@ import {
import {
LiveExampleActionType,
LiveExampleState,
LiveExampleStateReturnValue,
} from './LiveExampleState.types';
import { liveExampleStateReducer } from './LiveExampleStateReducer';
import { assertContext, defaultLiveExampleContext } from './utils';

export function useLiveExampleState(
componentName: string,
tsDoc?: Array<CustomComponentDoc> | null,
) {
): LiveExampleStateReturnValue {
const initialState = merge(
{ componentName, tsDoc },
defaultLiveExampleContext,
Expand Down Expand Up @@ -51,6 +53,7 @@ export function useLiveExampleState(
});
}

/** Log an error */
TheSonOfThomp marked this conversation as resolved.
Show resolved Hide resolved
function setErrorState(message: string) {
dispatch({
type: LiveExampleActionType.ERROR,
Expand All @@ -70,6 +73,7 @@ export function useLiveExampleState(
function parse(module: ModuleType) {
const { default: meta, ...stories } = module;
const StoryFn = getDefaultStoryFn(meta, stories);
StoryFn.displayName = pascalcase(componentName) + 'Story';

if (assertContext(context, ['state', 'componentName', 'tsDoc'])) {
const knobsArray = getKnobsArray({
Expand Down
23 changes: 23 additions & 0 deletions components/pages/example/useStoryCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';

import { isStateReady } from './useLiveExampleState/utils';
import { getStoryCode } from './utils/getStoryCode';
import { LiveExampleContext } from './useLiveExampleState';

const emptyStateCode = 'No code found';

export const useStoryCode = (
context: LiveExampleContext,
showCode: boolean,
) => {
const [storyCode, setCode] = useState(emptyStateCode);

useEffect(() => {
if (showCode && isStateReady(context)) {
const code = getStoryCode(context) ?? emptyStateCode;
setCode(code);
}
}, [context, showCode]);

return storyCode;
};
Loading