src/lib/components/organisms/ticker-v2/index.jsx (94 lines of code) (raw):
import { toChildArray } from "preact"
import { mergeStyles } from "$styles/helpers/mergeStyles"
import defaultStyles from "./style.module.scss"
import { Gradient } from "./Gradient.jsx"
import { ArrowButton } from "$particles"
import { useRef, useState, useEffect } from "preact/hooks"
export function Ticker({ buttonScrollDistance = 250, styles, children }) {
styles = mergeStyles({ ...defaultStyles }, styles)
const [isScrolledToStart, setIsScrolledToStart] = useState(true)
const [isScrolledToEnd, setIsScrolledToEnd] = useState(false)
const [isOverflow, setIsOverflow] = useState(false)
const childArray = toChildArray(children)
const scrollContainerRef = useRef(null)
function scrubLeft() {
const scrollContainer = scrollContainerRef.current
if (!scrollContainer) return
let newScrollLeft = scrollContainer.scrollLeft - buttonScrollDistance
if (newScrollLeft < 100) {
newScrollLeft = 0
}
scrollContainer.scrollTo({
left: newScrollLeft,
behavior: "smooth",
})
}
function scrubRight() {
const scrollContainer = scrollContainerRef.current
if (!scrollContainer) return
let scrollSpace = scrollContainer.scrollWidth - scrollContainer.clientWidth
let newScrollLeft = scrollContainer.scrollLeft + buttonScrollDistance
if (newScrollLeft > scrollSpace - 100) {
// Plus one here to account for any fractional numbers
newScrollLeft = scrollSpace + 1
}
scrollContainer.scrollTo({
left: newScrollLeft,
behavior: "smooth",
})
}
useEffect(() => {
const scrollContainer = scrollContainerRef.current
if (!scrollContainer) return
let scrollDebounceTimeout
const handleScroll = () => {
clearTimeout(scrollDebounceTimeout)
scrollDebounceTimeout = setTimeout(() => {
const currentScroll = scrollContainer.scrollLeft
const scrollSpace =
scrollContainer.scrollWidth - scrollContainer.clientWidth - 4
setIsScrolledToEnd(currentScroll > scrollSpace)
setIsScrolledToStart(currentScroll === 0)
}, 20)
}
scrollContainer.addEventListener("scroll", handleScroll)
return () => {
clearTimeout(scrollDebounceTimeout)
scrollContainer.removeEventListener("scroll", handleScroll)
}
}, [])
useEffect(() => {
const scrollContainer = scrollContainerRef.current
if (!scrollContainer) return
if (scrollContainer.scrollWidth > scrollContainer.clientWidth) {
setIsOverflow(true)
}
}, [children])
return (
<div className={styles.ticker}>
<div ref={scrollContainerRef} className={styles.scrollContainer}>
{childArray.map((child, index) => (
<div className={styles.tickerItem} key={child.props?.id ?? index}>
{child}
</div>
))}
</div>
<div
className={`${styles.scrubControls} ${isOverflow ? styles.showControls : ""}`}
>
<Gradient position="right" />
<ArrowButton
styles={{ button: styles.arrowButton }}
onClick={scrubRight}
disabled={isScrolledToEnd}
/>
<ArrowButton
styles={{ button: styles.arrowButton }}
direction="left"
onClick={scrubLeft}
disabled={isScrolledToStart}
/>
</div>
</div>
)
}