Add storybook
This commit is contained in:
parent
4656f5b280
commit
a81a5a2458
13
.storybook/main.js
Normal file
13
.storybook/main.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
stories: ['../src/components/**/*.stories.js'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-knobs/register',
|
||||||
|
'@storybook/addon-actions',
|
||||||
|
'@storybook/addon-links'
|
||||||
|
],
|
||||||
|
webpackFinal: async config => {
|
||||||
|
// do mutation to the config
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
8
.storybook/preview.js
Normal file
8
.storybook/preview.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { addDecorator } from '@storybook/react'
|
||||||
|
import { withInfo } from '@storybook/addon-info'
|
||||||
|
import { withKnobs } from '@storybook/addon-knobs'
|
||||||
|
import { withConsole } from '@storybook/addon-console'
|
||||||
|
|
||||||
|
addDecorator(withInfo({ inline: true }))
|
||||||
|
addDecorator(withKnobs)
|
||||||
|
addDecorator((storyFn, context) => withConsole()(storyFn)(context))
|
23
.storybook/webpack.config.js
Normal file
23
.storybook/webpack.config.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
modules: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
5418
package-lock.json
generated
5418
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -11,7 +11,9 @@
|
|||||||
"test": "tests",
|
"test": "tests",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"format": "prettier --write src/**/*.js",
|
"format": "prettier --write src/**/*.js",
|
||||||
"graph": "mkdir -p build && madge src --exclude '.css$' --image build/dep-graph.svg"
|
"graph": "mkdir -p build && madge src --exclude '.css$' --image build/dep-graph.svg",
|
||||||
|
"storybook": "start-storybook -p 6006 --ci",
|
||||||
|
"build-storybook": "build-storybook"
|
||||||
},
|
},
|
||||||
"author": "Alfred Melch (dev@melch.pro)",
|
"author": "Alfred Melch (dev@melch.pro)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -34,6 +36,13 @@
|
|||||||
"@babel/preset-env": "^7.8.4",
|
"@babel/preset-env": "^7.8.4",
|
||||||
"@babel/preset-react": "^7.8.3",
|
"@babel/preset-react": "^7.8.3",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.1.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.1.3",
|
||||||
|
"@storybook/addon-actions": "^5.3.14",
|
||||||
|
"@storybook/addon-console": "^1.2.1",
|
||||||
|
"@storybook/addon-info": "^5.3.14",
|
||||||
|
"@storybook/addon-knobs": "^5.3.14",
|
||||||
|
"@storybook/addon-links": "^5.3.14",
|
||||||
|
"@storybook/addons": "^5.3.14",
|
||||||
|
"@storybook/react": "^5.3.14",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"css-loader": "^3.4.2",
|
"css-loader": "^3.4.2",
|
||||||
@ -55,6 +64,7 @@
|
|||||||
"postcss-normalize": "^8.0.1",
|
"postcss-normalize": "^8.0.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
"react-refresh": "^0.7.2",
|
"react-refresh": "^0.7.2",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.3",
|
||||||
"webpack": "^4.41.5",
|
"webpack": "^4.41.5",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.wrapper {
|
.indicator {
|
||||||
overflow: hidden;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
41
src/components/Indicator.js
Normal file
41
src/components/Indicator.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import styles from './Indicator.css'
|
||||||
|
|
||||||
|
export const Indicator = ({ type = 'border', children }) => {
|
||||||
|
const IndicatorType = indicatorByType(type)
|
||||||
|
return (
|
||||||
|
<div className={styles.indicator}>
|
||||||
|
<IndicatorType>{children}</IndicatorType>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const indicatorByType = type => {
|
||||||
|
switch (type) {
|
||||||
|
case 'border':
|
||||||
|
return BorderIndicator
|
||||||
|
case 'pipe':
|
||||||
|
return PipeIndicator
|
||||||
|
default:
|
||||||
|
throw Error(`Indicator of type ${type} is not defined`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BorderIndicator = ({ children }) => (
|
||||||
|
<>
|
||||||
|
<div className={styles.border}></div>
|
||||||
|
<div className={styles.marker}></div>
|
||||||
|
{children}
|
||||||
|
<div className={styles.marker}></div>
|
||||||
|
<div className={styles.border}></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const PipeIndicator = ({ children }) => (
|
||||||
|
<>
|
||||||
|
<div>|</div>
|
||||||
|
{children}
|
||||||
|
<div>|</div>
|
||||||
|
</>
|
||||||
|
)
|
25
src/components/Indicator.stories.js
Normal file
25
src/components/Indicator.stories.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Indicator } from './Indicator'
|
||||||
|
import { Segment } from './Segment'
|
||||||
|
import { radios } from '@storybook/addon-knobs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Indicator,
|
||||||
|
title: 'Indicator'
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeKnob = () =>
|
||||||
|
radios('Type', { Border: 'border', Pipe: 'pipe' }, 'border')
|
||||||
|
|
||||||
|
export const HappyPath = () => {
|
||||||
|
return <Indicator type={typeKnob()}>Hello World</Indicator>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithSegment = () => {
|
||||||
|
return (
|
||||||
|
<Indicator type={typeKnob()}>
|
||||||
|
<Segment>Hello World</Segment>
|
||||||
|
</Indicator>
|
||||||
|
)
|
||||||
|
}
|
23
src/components/Offset.js
Normal file
23
src/components/Offset.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper that is of double width while hiding its overflow.
|
||||||
|
* Place children with offset (from -50 to 50, 0 is center)
|
||||||
|
*/
|
||||||
|
export const Offset = ({ offset = 0, children }) => {
|
||||||
|
offset = (offset - 50) / 2
|
||||||
|
return (
|
||||||
|
<div style={{ overflow: 'hidden' }}>
|
||||||
|
<div style={{ width: '200%' }}>
|
||||||
|
<div style={{ position: 'relative', left: `${offset}%` }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset.propTypes = {
|
||||||
|
offset: PropTypes.number
|
||||||
|
}
|
41
src/components/Offset.stories.js
Normal file
41
src/components/Offset.stories.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { number } from '@storybook/addon-knobs'
|
||||||
|
|
||||||
|
import { Offset } from './Offset'
|
||||||
|
import { Indicator } from './Indicator'
|
||||||
|
import { Segment } from './Segment'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Offset,
|
||||||
|
title: 'Offset'
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetKnob = () =>
|
||||||
|
number('Offset', 0, {
|
||||||
|
range: true,
|
||||||
|
min: -50,
|
||||||
|
max: 50,
|
||||||
|
step: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
export const BlockElement = () => (
|
||||||
|
<Offset offset={offsetKnob()}>
|
||||||
|
<div style={{ textAlign: 'center' }}>This text is centered</div>
|
||||||
|
</Offset>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const InlineElement = () => (
|
||||||
|
<Offset offset={offsetKnob()}>
|
||||||
|
This a long text to see that inline elements get pushed far to the left. To
|
||||||
|
be precise they get pushed half the container with to the left. Use
|
||||||
|
text-align: center for better display.
|
||||||
|
</Offset>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const WithIndicator = () => (
|
||||||
|
<Offset offset={offsetKnob()}>
|
||||||
|
<Indicator>
|
||||||
|
<Segment>Hello World</Segment>
|
||||||
|
</Indicator>
|
||||||
|
</Offset>
|
||||||
|
)
|
@ -1,28 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useSelector } from '../store'
|
|
||||||
|
|
||||||
import styles from './PivotMarker.css'
|
|
||||||
import { selectOffset } from '../store/selectors'
|
|
||||||
|
|
||||||
export const PipeMarker = ({ children }) => (
|
|
||||||
<div>
|
|
||||||
<div style={{ textAlign: 'center' }}>|</div>
|
|
||||||
{children}
|
|
||||||
<div style={{ textAlign: 'center' }}>|</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const BorderMarker = ({ children }) => {
|
|
||||||
const offset = useSelector(selectOffset)
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={styles.border}></div>
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<div className={styles.marker} style={{ left: offset + '%' }}></div>
|
|
||||||
{children}
|
|
||||||
<div className={styles.marker} style={{ left: offset + '%' }}></div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.border}></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -7,11 +7,16 @@ import { Options } from './Options'
|
|||||||
import { PlayerControl } from './PlayerControl'
|
import { PlayerControl } from './PlayerControl'
|
||||||
|
|
||||||
import styles from './RsvpReader.css'
|
import styles from './RsvpReader.css'
|
||||||
import { BorderMarker } from './PivotMarker'
|
|
||||||
import { Progress } from './Progress'
|
import { Progress } from './Progress'
|
||||||
import { TotalTime } from './TotalTime'
|
import { TotalTime } from './TotalTime'
|
||||||
|
import { Indicator } from './Indicator'
|
||||||
|
import { Offset } from './Offset'
|
||||||
|
import { useSelector } from '../store'
|
||||||
|
import { selectCurrentSegment, selectOffset } from '../store/selectors'
|
||||||
|
|
||||||
export const RsvpReader = () => {
|
export const RsvpReader = () => {
|
||||||
|
const segment = useSelector(selectCurrentSegment)
|
||||||
|
const offset = useSelector(selectOffset)
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
@ -19,9 +24,11 @@ export const RsvpReader = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.mainItem}>
|
<div className={styles.mainItem}>
|
||||||
<Progress />
|
<Progress />
|
||||||
<BorderMarker>
|
<Offset offset={offset}>
|
||||||
<Segment />
|
<Indicator>
|
||||||
</BorderMarker>
|
<Segment>{segment}</Segment>
|
||||||
|
</Indicator>
|
||||||
|
</Offset>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
<SegmentControl>
|
<SegmentControl>
|
||||||
<PlayerControl />
|
<PlayerControl />
|
||||||
|
@ -16,4 +16,5 @@
|
|||||||
}
|
}
|
||||||
.suffix {
|
.suffix {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store'
|
import PropTypes from 'prop-types'
|
||||||
import { selectPivotizedSegment, selectOffset } from '../store/selectors'
|
|
||||||
|
|
||||||
import styles from './Segment.css'
|
import styles from './Segment.css'
|
||||||
|
import { pivotize } from '../lib/pivotize'
|
||||||
|
|
||||||
export const Segment = () => {
|
export const Segment = ({ children = '' }) => {
|
||||||
const [prefix, pivot, suffix] = useSelector(selectPivotizedSegment)
|
const [prefix, pivot, suffix] = pivotize(children)
|
||||||
const offset = useSelector(selectOffset)
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.container}>
|
||||||
<div className={styles.container} style={{ left: offset + '%' }}>
|
<span className={styles.prefix}>{prefix}</span>
|
||||||
<span className={styles.prefix}>{prefix}</span>
|
<span className={styles.pivot}>{pivot}</span>
|
||||||
<span className={styles.pivot}>{pivot}</span>
|
<span className={styles.suffix}>{suffix}</span>
|
||||||
<span className={styles.suffix}>{suffix}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Segment.propTypes = {
|
||||||
|
children: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
13
src/components/Segment.stories.js
Normal file
13
src/components/Segment.stories.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { text } from '@storybook/addon-knobs'
|
||||||
|
|
||||||
|
import { Segment } from './Segment'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Segment,
|
||||||
|
title: 'Segment'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HappyPath = () => {
|
||||||
|
return <Segment>{text('Text', 'Hello')}</Segment>
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import styles from './Slider.css'
|
import styles from './Slider.css'
|
||||||
|
|
||||||
@ -8,14 +9,15 @@ export const Slider = ({
|
|||||||
value,
|
value,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
name,
|
name,
|
||||||
min,
|
min = 0,
|
||||||
max,
|
max = 100,
|
||||||
step,
|
step = 1,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
|
value = value || defaultValue || (max - min) / 2 + min
|
||||||
// use ref for onChange to not trigger state updates on rerenders of parent
|
// use ref for onChange to not trigger state updates on rerenders of parent
|
||||||
const onChangeRef = useRef(null)
|
const onChangeRef = useRef(null)
|
||||||
const [internalValue, setInternalValue] = useState(value || defaultValue)
|
const [internalValue, setInternalValue] = useState(value)
|
||||||
|
|
||||||
// trigger external update on internal change
|
// trigger external update on internal change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -61,3 +63,15 @@ export const Slider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const calcWidth = num => `${(String(num).length + 1) * 12}px`
|
const calcWidth = num => `${(String(num).length + 1) * 12}px`
|
||||||
|
|
||||||
|
Slider.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
description: PropTypes.string,
|
||||||
|
value: PropTypes.number,
|
||||||
|
defaultValue: PropTypes.number,
|
||||||
|
name: PropTypes.string,
|
||||||
|
min: PropTypes.number,
|
||||||
|
max: PropTypes.number,
|
||||||
|
step: PropTypes.number,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
}
|
||||||
|
15
src/components/Slider.stories.js
Normal file
15
src/components/Slider.stories.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { action } from '@storybook/addon-actions'
|
||||||
|
|
||||||
|
import { Slider } from './Slider'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Slider,
|
||||||
|
title: 'Slider'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default = () => <Slider onChange={action()} />
|
||||||
|
|
||||||
|
export const CustomRange = () => <Slider onChange={action()} min={5} max={15} />
|
||||||
|
|
||||||
|
export const CustomStep = () => <Slider onChange={action()} step={5} />
|
Loading…
Reference in New Issue
Block a user