Skip to content

Commit

Permalink
[#459] Feature: SingleSlider 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
giwan-dev committed Feb 7, 2020
1 parent 3fe6ac3 commit 8f6d67c
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 134 deletions.
34 changes: 28 additions & 6 deletions docs/stories/slider.stories.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React, { useState } from 'react'
import { storiesOf } from '@storybook/react'

import { RangeSlider } from '@titicaca/slider'
import { SingleSlider, RangeSlider } from '@titicaca/slider'
import { Text } from '@titicaca/core-elements'

function Label({ fromValue, toValue }) {
function SingleLabel({ value }) {
return <Text size="large">컴포넌트 내부: {value}</Text>
}

function RangeLabel({ fromValue, toValue }) {
return (
<Text size="large">
컴포넌트 내부: {fromValue} ~ {toValue}
Expand All @@ -14,31 +18,49 @@ function Label({ fromValue, toValue }) {

storiesOf('Slider', module)
.add('기본', () => {
const [values, setValues] = useState([])
const [value, setValue] = useState(500000)

return (
<div style={{ height: '4000px', padding: '500px 20px 0 20px' }}>
컴포넌트 외부: {value}
<SingleSlider
initialValue={value}
min={0}
max={500000}
onChange={setValue}
labelComponent={SingleLabel}
/>
</div>
)
})
.add('범위', () => {
const [values, setValues] = useState([0, 500000])

return (
<div style={{ height: '4000px', padding: '500px 20px 0 20px' }}>
컴포넌트 외부: {values.join(', ')}
<RangeSlider
initialValues={values}
min={0}
max={500000}
onChange={setValues}
labelComponent={Label}
labelComponent={RangeLabel}
/>
</div>
)
})
.add('Non linear', () => {
const [values, setValues] = useState([])
const [values, setValues] = useState([0, 500000])

return (
<div style={{ height: '4000px', padding: '500px 20px 0 20px' }}>
컴포넌트 외부: {values.join(', ')}
<RangeSlider
initialValues={values}
min={0}
max={500000}
onChange={setValues}
labelComponent={Label}
labelComponent={RangeLabel}
nonLinear
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/slider/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as SingleSlider } from './single-slider'
export { default as RangeSlider } from './range-slider'
166 changes: 38 additions & 128 deletions packages/slider/src/range-slider.tsx
Original file line number Diff line number Diff line change
@@ -1,143 +1,53 @@
import React, { useState, useEffect, useCallback, ComponentType } from 'react'
import styled from 'styled-components'
import {
Rail,
Slider as OriginalSlider,
Handles,
Tracks,
} from 'react-compound-slider'
import { debounce } from '@titicaca/view-utilities'
import { Container } from '@titicaca/core-elements'
import Track from './track'
import Handle from './handle'
import React, { ComponentType } from 'react'
import { Tracks } from 'react-compound-slider'

type SliderValue = readonly number[]
type ValueTransformer = (x: number) => number
import Track from './track'
import SliderBase, { SliderBaseProps } from './slider-base'
import { SliderValue } from './types'

interface RangeSliderProps {
initialValues?: SliderValue
step?: number
min: number
max: number
interface RangeSliderProps extends Omit<SliderBaseProps, 'labelComponent'> {
labelComponent?: ComponentType<{
fromValue: SliderValue[0]
toValue: SliderValue[1]
}>
onChange: (values: SliderValue) => void
nonLinear?: boolean
}

const SliderContainer = styled.div`
margin: 24px 0 21px 0;
padding: 0 9px;
height: 18px;
touch-action: pan-x;
`

const RailBase = styled.div`
position: absolute;
width: 100%;
border-radius: 4px;
background-color: #efefef;
height: 3px;
transform: translate(0, -50%);
`

const IDENTICAL_SCALE: ValueTransformer = (x) => x

const LINEAR_FN_SET: ValueTransformer[] = [IDENTICAL_SCALE, IDENTICAL_SCALE]

const EXPONENT = 1 / Math.E
const NON_LINEAR_FN_SET: ValueTransformer[] = [
(x) => Math.round(Math.pow(x, EXPONENT)),
(x) => Math.round(Math.pow(x, 1 / EXPONENT)),
]

export default function RangeSlider({
step = 1,
initialValues,
min,
max,
onChange,
labelComponent: LabelComponent,
nonLinear,
...restProps
}: RangeSliderProps) {
const [values, setValues] = useState<SliderValue>(initialValues || [min, max])

const [scaleFn, scaleFnInverse] = nonLinear
? NON_LINEAR_FN_SET
: LINEAR_FN_SET

const limiter: ValueTransformer = (value) => {
if (value < min) {
return min
}
if (value > max) {
return max
}
return value
}

const debouncedChangeHandler = useCallback(debounce(onChange, 500), [
onChange,
])

useEffect(() => {
debouncedChangeHandler(values)
}, [debouncedChangeHandler, values])

return (
<Container>
{LabelComponent ? (
<LabelComponent fromValue={values[0]} toValue={values[1]} />
) : null}

<SliderContainer>
<OriginalSlider
values={values.map(scaleFn)}
mode={2}
step={scaleFn(step)}
domain={[min, max].map(scaleFn)}
rootStyle={{ position: 'relative' }}
onUpdate={(newValues) =>
setValues(newValues.map(scaleFnInverse).map(limiter))
}
>
<Rail>{() => <RailBase />}</Rail>

<Handles>
{({ handles, getHandleProps }) => (
<>
{handles.map(({ id, percent }, i) => (
<Handle key={i} percent={percent} {...getHandleProps(id)} />
))}
</>
)}
</Handles>

<Tracks>
{({ tracks, getTrackProps }) => (
<>
{tracks.map(
({
id,
source: { id: sourceId, percent: sourcePercent },
target: { id: targetId, percent: targetPercent },
}) => (
<Track
key={id}
left={sourcePercent}
right={targetPercent}
{...getTrackProps()}
active={sourceId !== '$' && targetId !== '$'}
/>
),
)}
</>
<SliderBase
{...restProps}
labelComponent={
LabelComponent
? ({ values }) => (
<LabelComponent fromValue={values[0]} toValue={values[1]} />
)
: undefined
}
>
<Tracks>
{({ tracks, getTrackProps }) => (
<>
{tracks.map(
({
id,
source: { id: sourceId, percent: sourcePercent },
target: { id: targetId, percent: targetPercent },
}) => (
<Track
key={id}
left={sourcePercent}
right={targetPercent}
{...getTrackProps()}
active={sourceId !== '$' && targetId !== '$'}
/>
),
)}
</Tracks>
</OriginalSlider>
</SliderContainer>
</Container>
</>
)}
</Tracks>
</SliderBase>
)
}
57 changes: 57 additions & 0 deletions packages/slider/src/single-slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { ComponentType } from 'react'
import { Tracks } from 'react-compound-slider'

import Track from './track'
import SliderBase, { SliderBaseProps } from './slider-base'

interface SingleSliderProps
extends Omit<
SliderBaseProps,
'initialValues' | 'labelComponent' | 'onChange'
> {
initialValue?: number
labelComponent?: ComponentType<{ value: number }>
onChange: (value: number) => void
}

export default function SingleSlider({
initialValue,
labelComponent: LabelComponent,
onChange,
...restProps
}: SingleSliderProps) {
return (
<SliderBase
{...restProps}
initialValues={initialValue ? [initialValue] : undefined}
onChange={(values) => onChange(values[0])}
labelComponent={
LabelComponent
? ({ values }) => <LabelComponent value={values[0]} />
: undefined
}
>
<Tracks>
{({ tracks, getTrackProps }) => (
<>
{tracks.map(
({
id,
source: { percent: sourcePercent },
target: { id: targetId, percent: targetPercent },
}) => (
<Track
key={id}
left={sourcePercent}
right={targetPercent}
{...getTrackProps()}
active={targetId !== '$'}
/>
),
)}
</>
)}
</Tracks>
</SliderBase>
)
}
Loading

0 comments on commit 8f6d67c

Please sign in to comment.