Skip to content

Commit

Permalink
refactor: use hooks in Timeline, add useBoolean
Browse files Browse the repository at this point in the history
  • Loading branch information
ambar committed Sep 22, 2021
1 parent 51fb6f9 commit af1df3b
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 108 deletions.
176 changes: 70 additions & 106 deletions packages/griffith/src/components/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,122 +1,86 @@
import React, {Component} from 'react'
import React, {useCallback} from 'react'
import {css} from 'aphrodite/no-important'
import Slider, {SliderProps} from './Slider'
import styles, {
slider as sliderStyles,
hoveredSlider as hoveredSliderStyles,
dotHoveredSlider as dotHoveredSliderStyles,
} from './Timeline.styles'
import useBoolean from '../hooks/useBoolean'
import useHandler from '../hooks/useHandler'
import noop from 'lodash/noop'

export type TimelineProps = {
onDragStart?: (...args: any[]) => any
onDragEnd?: (...args: any[]) => any
onChange?: (...args: any[]) => any
onSeek?: (...args: any[]) => any
onProgressDotHover?: (...args: any[]) => any
onProgressDotLeave?: (...args: any[]) => any
onSeek?: (value: number) => void
} & Omit<SliderProps, 'styles'>

export type TimelineState = {
isHovered: boolean
isFocused: boolean
isDragging: boolean
progressDotHovered: boolean
}

class Timeline extends Component<TimelineProps, TimelineState> {
state = {
isHovered: false,
isFocused: false,
isDragging: false,
progressDotHovered: false,
}

// refs
root = null

handlePointerEnter = () => {
this.setState({isHovered: true})
}

handlePointerLeave = () => {
this.setState({isHovered: false, isFocused: false})
}

handleFocus = () => {
this.setState({isFocused: true})
}

handleBlur = () => {
this.setState({isFocused: false})
}

handleDragStart = () => {
this.setState({isDragging: true})

const {onDragStart} = this.props
if (onDragStart) {
onDragStart()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const useConstCallbacks = (...fns: ((...args: any[]) => void)[]) =>
// eslint-disable-next-line react-hooks/exhaustive-deps
useCallback((...args) => fns.forEach((f) => f(...args)), [])

const Timeline: React.FC<TimelineProps> = ({
value,
onDragStart,
onDragEnd,
onSeek,
onChange,
onProgressDotHover,
onProgressDotLeave,
...props
}) => {
const [isFocused, isFocusedSwitch] = useBoolean()
const [isHovered, isHoveredSwitch] = useBoolean()
const [isDragging, isDraggingSwitch] = useBoolean()
const [progressDotHovered, progressDotHoveredSwitch] = useBoolean()

const handleChange = useHandler((newValue: number) => {
if (newValue !== value) {
onChange?.(newValue)
}
}

handleDragEnd = () => {
this.setState({isDragging: false})

const {onDragEnd} = this.props
if (onDragEnd) {
onDragEnd()
}
}

handleChange = (value: any) => {
const {onSeek, onChange} = this.props
if (onSeek && value !== (this.props as any).value) {
onChange?.(value)
if (!isDragging) {
onSeek?.(newValue)
}
if (this.state.isDragging) return
if (onSeek) {
onSeek(value)
}
}

handleProgressDotHover = (...args: any[]) => {
this.setState({progressDotHovered: true})
this.props.onProgressDotHover?.(...args)
}

handleProgressDotLeave = (...args: any[]) => {
this.setState({progressDotHovered: false})
this.props.onProgressDotLeave?.(...args)
}

render() {
const {isHovered, isFocused, isDragging, progressDotHovered} = this.state
return (
<div
className={css(styles.root)}
onMouseEnter={this.handlePointerEnter}
onMouseLeave={this.handlePointerLeave}
>
<Slider
{...this.props}
styles={
[
sliderStyles,
(isHovered || isFocused || isDragging) && hoveredSliderStyles,
progressDotHovered && dotHoveredSliderStyles,
].filter(Boolean) as Record<string, unknown>[]
}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onDragStart={this.handleDragStart}
onDragEnd={this.handleDragEnd}
onChange={this.handleChange}
onProgressDotHover={this.handleProgressDotHover}
onProgressDotLeave={this.handleProgressDotLeave}
/>
</div>
)
}
})

return (
<div
className={css(styles.root)}
onMouseEnter={isHoveredSwitch.on}
onMouseLeave={useConstCallbacks(isHoveredSwitch.off, isFocusedSwitch.off)}
>
<Slider
{...props}
styles={
[
sliderStyles,
(isHovered || isFocused || isDragging) && hoveredSliderStyles,
progressDotHovered && dotHoveredSliderStyles,
].filter(Boolean) as Record<string, unknown>[]
}
value={value}
onChange={handleChange}
onFocus={isFocusedSwitch.on}
onBlur={isFocusedSwitch.off}
onDragStart={useConstCallbacks(
isDraggingSwitch.on,
useHandler(onDragStart || noop)
)}
onDragEnd={useConstCallbacks(
isDraggingSwitch.off,
useHandler(onDragEnd || noop)
)}
onProgressDotHover={useConstCallbacks(
progressDotHoveredSwitch.on,
useHandler(onProgressDotHover || noop)
)}
onProgressDotLeave={useConstCallbacks(
progressDotHoveredSwitch.off,
useHandler(onProgressDotLeave || noop)
)}
/>
</div>
)
}

export default Timeline
4 changes: 2 additions & 2 deletions packages/griffith/src/components/items/TimelineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react'
import {css} from 'aphrodite/no-important'
import styles from '../Controller.styles'
import Timeline from '../Timeline'
import Timeline, {TimelineProps} from '../Timeline'

const TimelineItem = (props: any) => {
const TimelineItem = (props: TimelineProps) => {
return (
<div className={css(styles.timeline)}>
<Timeline {...props} />
Expand Down
24 changes: 24 additions & 0 deletions packages/griffith/src/hooks/useBoolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {useCallback, useState} from 'react'

/**
* Create a boolean value and switches for changing it
*
* ```js
* const [isOpen, isOpenSwitch] = useBoolean()
*
* <Button onClick={isOpenSwitch.on} label="Open" />
* <Button onClick={isOpenSwitch.off} label="Close" />
* <Button onClick={isOpenSwitch.toggle} label="Toggle" />
* ```
*/
const useBoolean = (initialState: boolean | (() => boolean) = false) => {
const [value, setValue] = useState(initialState)
const valueSwitch = {
on: useCallback(() => setValue(true), []),
off: useCallback(() => setValue(false), []),
toggle: useCallback(() => setValue((v) => !v), []),
} as const
return [value, valueSwitch] as const
}

export default useBoolean

0 comments on commit af1df3b

Please sign in to comment.