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",
|
||||
"lint": "eslint src",
|
||||
"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)",
|
||||
"license": "ISC",
|
||||
@ -34,6 +36,13 @@
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/preset-react": "^7.8.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-loader": "^8.0.6",
|
||||
"css-loader": "^3.4.2",
|
||||
@ -55,6 +64,7 @@
|
||||
"postcss-normalize": "^8.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^1.19.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-refresh": "^0.7.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"webpack": "^4.41.5",
|
||||
|
@ -1,5 +1,5 @@
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
.indicator {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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 styles from './RsvpReader.css'
|
||||
import { BorderMarker } from './PivotMarker'
|
||||
import { Progress } from './Progress'
|
||||
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 = () => {
|
||||
const segment = useSelector(selectCurrentSegment)
|
||||
const offset = useSelector(selectOffset)
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.item}>
|
||||
@ -19,9 +24,11 @@ export const RsvpReader = () => {
|
||||
</div>
|
||||
<div className={styles.mainItem}>
|
||||
<Progress />
|
||||
<BorderMarker>
|
||||
<Segment />
|
||||
</BorderMarker>
|
||||
<Offset offset={offset}>
|
||||
<Indicator>
|
||||
<Segment>{segment}</Segment>
|
||||
</Indicator>
|
||||
</Offset>
|
||||
<div className={styles.controls}>
|
||||
<SegmentControl>
|
||||
<PlayerControl />
|
||||
|
@ -16,4 +16,5 @@
|
||||
}
|
||||
.suffix {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from '../store'
|
||||
import { selectPivotizedSegment, selectOffset } from '../store/selectors'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import styles from './Segment.css'
|
||||
import { pivotize } from '../lib/pivotize'
|
||||
|
||||
export const Segment = () => {
|
||||
const [prefix, pivot, suffix] = useSelector(selectPivotizedSegment)
|
||||
const offset = useSelector(selectOffset)
|
||||
export const Segment = ({ children = '' }) => {
|
||||
const [prefix, pivot, suffix] = pivotize(children)
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.container} style={{ left: offset + '%' }}>
|
||||
<span className={styles.prefix}>{prefix}</span>
|
||||
<span className={styles.pivot}>{pivot}</span>
|
||||
<span className={styles.suffix}>{suffix}</span>
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<span className={styles.prefix}>{prefix}</span>
|
||||
<span className={styles.pivot}>{pivot}</span>
|
||||
<span className={styles.suffix}>{suffix}</span>
|
||||
</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 PropTypes from 'prop-types'
|
||||
|
||||
import styles from './Slider.css'
|
||||
|
||||
@ -8,14 +9,15 @@ export const Slider = ({
|
||||
value,
|
||||
defaultValue,
|
||||
name,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
onChange
|
||||
}) => {
|
||||
value = value || defaultValue || (max - min) / 2 + min
|
||||
// use ref for onChange to not trigger state updates on rerenders of parent
|
||||
const onChangeRef = useRef(null)
|
||||
const [internalValue, setInternalValue] = useState(value || defaultValue)
|
||||
const [internalValue, setInternalValue] = useState(value)
|
||||
|
||||
// trigger external update on internal change
|
||||
useEffect(() => {
|
||||
@ -61,3 +63,15 @@ export const Slider = ({
|
||||
}
|
||||
|
||||
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