-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add .stories suffix to Playground files
- Loading branch information
1 parent
68441b7
commit ab753e7
Showing
2 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import * as React from 'react'; | ||
import { Checkbox, Dropdown, IDropdownOption, Stack, TextField } from '@fluentui/react'; | ||
import { Text } from '@fluentui/react-text'; | ||
import { PlaygroundProps } from './Playground.types.stories'; | ||
|
||
const tableStyle: React.CSSProperties = { | ||
border: '1px solid black', | ||
}; | ||
const cellStyle: React.CSSProperties = { | ||
border: '1px solid black', | ||
padding: '5px', | ||
}; | ||
|
||
export const Playground = function <TType>(props: PlaygroundProps<TType>): JSX.Element { | ||
const { children, sections } = props; | ||
|
||
const [componentProps, setComponentProps] = React.useState<{ [key in string]: boolean | string } | null>(null); | ||
const newProps: { [key in string]: boolean | string } = {}; | ||
|
||
const playgroundSections: JSX.Element[] = []; | ||
|
||
let booleanValueChanged = false; | ||
|
||
for (const section of sections) { | ||
const sectionList: JSX.Element[] = []; | ||
for (const prop of section.propList) { | ||
const propName = prop.propName as string; | ||
const propType = prop.propType; | ||
let isPropEnabled = true; | ||
|
||
if (componentProps && prop.dependsOnProps) { | ||
for (const dependentProp of prop.dependsOnProps as string[]) { | ||
isPropEnabled = | ||
isPropEnabled && | ||
(dependentProp[0] === '~' ? !componentProps[dependentProp.substr(1)] : !!componentProps[dependentProp]); | ||
} | ||
} | ||
|
||
if (propType === 'boolean') { | ||
newProps[propName + 'Default'] = prop.defaultValue || false; | ||
const propDefaultValueChanged = | ||
componentProps && | ||
prop.defaultValue !== undefined && | ||
prop.defaultValue !== componentProps[propName + 'Default']; | ||
const propEnabledValueChanged = | ||
componentProps && componentProps[propName] !== (componentProps[propName] && isPropEnabled); | ||
newProps[propName] = | ||
componentProps && componentProps[propName] !== 'undefined' && !propDefaultValueChanged | ||
? componentProps[propName] && isPropEnabled | ||
: newProps[propName + 'Default']; | ||
|
||
if (propDefaultValueChanged || propEnabledValueChanged) { | ||
prop.setDefaultValue?.(newProps[propName] as boolean); | ||
booleanValueChanged = true; | ||
} | ||
|
||
const onBooleanPropChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => { | ||
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps }; | ||
newComponentProps[propName] = checked || false; | ||
setComponentProps(newComponentProps); | ||
prop.setDefaultValue?.(checked || false); | ||
}; | ||
|
||
sectionList.push( | ||
<tr key={section.sectionName + '_' + propName}> | ||
<td style={cellStyle}>{propName}:</td> | ||
<td style={cellStyle}> | ||
<Checkbox | ||
checked={ | ||
componentProps && componentProps[propName] !== undefined && !propDefaultValueChanged | ||
? (componentProps[propName] as boolean) | ||
: (prop.defaultValue as boolean) | ||
} | ||
disabled={!isPropEnabled} | ||
// eslint-disable-next-line react/jsx-no-bind | ||
onChange={onBooleanPropChange} | ||
/> | ||
</td> | ||
</tr>, | ||
); | ||
} else if (propType === 'string') { | ||
newProps[propName] = (componentProps && componentProps[propName]) || prop.defaultValue || ''; | ||
|
||
const onStringPropChange = ( | ||
ev?: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, | ||
newValue?: string, | ||
) => { | ||
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps }; | ||
newComponentProps[propName] = newValue || ''; | ||
setComponentProps(newComponentProps); | ||
}; | ||
|
||
sectionList.push( | ||
<tr key={section.sectionName + '_' + propName}> | ||
<td style={cellStyle}>{propName}:</td> | ||
<td style={cellStyle}> | ||
<TextField | ||
value={ | ||
componentProps && componentProps[propName] | ||
? (componentProps[propName] as string) | ||
: (prop.defaultValue as string) | ||
} | ||
disabled={!isPropEnabled} | ||
// eslint-disable-next-line react/jsx-no-bind | ||
onChange={onStringPropChange} | ||
/> | ||
</td> | ||
</tr>, | ||
); | ||
} else { | ||
const defaultSelectedKey = prop.defaultValue || propType[0]; | ||
newProps[propName] = (componentProps && componentProps[propName]) || prop.defaultValue || propType[0]; | ||
|
||
const onOptionsPropChange = ( | ||
ev?: React.FormEvent<HTMLDivElement>, | ||
option?: IDropdownOption<any>, | ||
index?: number, | ||
) => { | ||
const newComponentProps: { [key in string]: boolean | string } = { ...componentProps }; | ||
if (option) { | ||
newComponentProps[propName] = (option.key as string) || ''; | ||
setComponentProps(newComponentProps); | ||
} | ||
}; | ||
|
||
sectionList.push( | ||
<tr key={section.sectionName + '_' + propName}> | ||
<td style={cellStyle}>{propName}:</td> | ||
<td style={cellStyle}> | ||
<Dropdown | ||
disabled={!isPropEnabled} | ||
selectedKey={ | ||
componentProps && componentProps[propName] | ||
? (componentProps[propName] as string) | ||
: (defaultSelectedKey as string) | ||
} | ||
options={propType.map(value => ({ key: value, text: value }))} | ||
// eslint-disable-next-line react/jsx-no-bind | ||
onChange={onOptionsPropChange} | ||
/> | ||
</td> | ||
</tr>, | ||
); | ||
} | ||
} | ||
playgroundSections.push( | ||
<React.Fragment key={section.sectionName}> | ||
<tr> | ||
<td style={cellStyle} colSpan={2}> | ||
<Text variant="title3">{section.sectionName}</Text> | ||
</td> | ||
</tr> | ||
{sectionList} | ||
</React.Fragment>, | ||
); | ||
} | ||
|
||
if (componentProps === null || booleanValueChanged) { | ||
setComponentProps(newProps); | ||
} | ||
|
||
const elementProps = { | ||
...componentProps, | ||
children: componentProps && !componentProps.iconOnly && !componentProps.children && componentProps.content, | ||
icon: componentProps && componentProps.icon ? 'x' : undefined, | ||
}; | ||
|
||
return ( | ||
<> | ||
<Stack horizontalAlign="center">{React.cloneElement(children, elementProps || {})}</Stack> | ||
<table style={tableStyle} cellSpacing={0}> | ||
<tbody>{playgroundSections}</tbody> | ||
</table> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import * as React from 'react'; | ||
|
||
/* eslint-disable @typescript-eslint/naming-convention */ | ||
|
||
/** Type to get only the string keys of T. */ | ||
type StringKeyOf<T> = { [K in keyof T]: K extends string ? K : never }[keyof T]; | ||
|
||
/** Definition for a prop that is to be controlled by the Playground. */ | ||
export interface PropDefinition<TType> { | ||
/** Name of the prop. */ | ||
propName: keyof TType | 'content'; | ||
|
||
/** Type of the prop, it can be boolean, string or an array of defined string values. */ | ||
propType: 'boolean' | 'string' | string[]; | ||
|
||
/** Default value for the prop. */ | ||
defaultValue?: boolean | string; | ||
|
||
/** Callback to set the default value of the prop if it is boolean and controlled behavior is wanted. */ | ||
setDefaultValue?: (value: boolean) => void; | ||
|
||
/** | ||
* An array of prop names that this prop requires to be truthy or falsy (prop name preceded by '~') in order to enable | ||
* this prop. | ||
*/ | ||
dependsOnProps?: (keyof TType | `~${StringKeyOf<TType>}` | 'content' | '~content')[]; | ||
} | ||
|
||
/** Props received by the Playground component. */ | ||
export interface PlaygroundProps<TType> { | ||
/** Single children to clone with the playground props. */ | ||
children: React.ReactElement; | ||
|
||
/** Sections of props for the playground, where each section has a name and an array of prop definitions. */ | ||
sections: Array<{ | ||
sectionName: string; | ||
propList: PropDefinition<TType>[]; | ||
}>; | ||
} |