Skip to content

Commit

Permalink
feat: added arrow scroll component (#608)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsladerman authored Jun 14, 2024
1 parent 847dc90 commit 6cf451b
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 16 deletions.
126 changes: 126 additions & 0 deletions src/components/ArrowScroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
Children,
type ComponentProps,
type ReactElement,
cloneElement,
useEffect,
useRef,
useState,
} from 'react'
import styled from 'styled-components'

import { ArrowLeftIcon, ArrowRightIcon } from '../icons'

const ComponentWrapperSC = styled.div({
position: 'relative',
overflowX: 'auto',
'& > *': {
scrollbarWidth: 'none', // Firefox
msOverflowStyle: 'none', // Edge
'&::-webkit-scrollbar': {
display: 'none', // Chrome, Safari, Opera
},
},
})
const ArrowWrapperSC = styled.div<{
$direction: 'left' | 'right'
}>(({ theme, $direction }) => ({
color: theme.colors['icon-light'],
zIndex: theme.zIndexes.modal,
position: 'absolute',
top: 0,
bottom: 0,
width: '36px',
display: 'flex',
justifyContent: $direction === 'left' ? 'flex-start' : 'flex-end',
padding: `0 ${theme.spacing.xxsmall}px`,
alignItems: 'center',
transition: 'opacity .2s ease',
background: `linear-gradient(${
$direction === 'left' ? 'to left' : 'to right'
}, transparent 0%, #303540 70%, #303540 100%)`,
...($direction === 'left' ? { left: 0 } : { right: 0 }),
'&.visible': {
cursor: 'pointer',
opacity: 1,
},
'&.hidden': {
opacity: 0,
},
}))

function Arrow({
direction,
show,
...props
}: {
direction: 'left' | 'right'
show: boolean
} & ComponentProps<typeof ArrowWrapperSC>) {
const Icon = direction === 'left' ? ArrowLeftIcon : ArrowRightIcon

return (
<ArrowWrapperSC
$direction={direction}
className={show ? 'visible' : 'hidden'}
{...props}
>
<Icon size={8} />
</ArrowWrapperSC>
)
}

const scroll = (
element: HTMLElement | null | undefined,
direction: 'left' | 'right'
) => {
if (element) {
element.scrollBy({
left: direction === 'left' ? -100 : 100,
behavior: 'smooth',
})
}
}

function ArrowScroll({ children, ...props }: { children?: ReactElement }) {
const containerRef = useRef<HTMLElement>(undefined)
const [showLeftArrow, setShowLeftArrow] = useState(false)
const [showRightArrow, setShowRightArrow] = useState(false)

const checkScroll = () => {
if (containerRef.current) {
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current

setShowLeftArrow(scrollLeft > 0)
setShowRightArrow(Math.ceil(scrollLeft + clientWidth) < scrollWidth)
}
}

useEffect(() => {
checkScroll()
window.addEventListener('resize', checkScroll)

return () => window.removeEventListener('resize', checkScroll)
}, [])

return (
<ComponentWrapperSC {...props}>
<Arrow
onClick={() => scroll(containerRef.current, 'left')}
direction="left"
show={showLeftArrow}
/>
<Arrow
onClick={() => scroll(containerRef.current, 'right')}
direction="right"
show={showRightArrow}
/>
{cloneElement(Children.only(children), {
onScroll: checkScroll,
ref: containerRef,
})}
</ComponentWrapperSC>
)
}

export default ArrowScroll
39 changes: 23 additions & 16 deletions src/components/TabList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
import styled, { useTheme } from 'styled-components'

import { useItemWrappedChildren } from './ListBox'
import ArrowScroll from './ArrowScroll'
import WrapWithIf from './WrapWithIf'

export type MakeOptional<Type, Key extends keyof Type> = Omit<Type, Key> &
Partial<Pick<Type, Key>>
Expand Down Expand Up @@ -135,23 +137,28 @@ function TabListRef(
}

return (
<Flex
{...tabListProps}
{...props}
flexDirection={stateProps.orientation === 'vertical' ? 'column' : 'row'}
alignItems={
stateProps.orientation === 'vertical' ? 'flex-start' : 'flex-end'
}
css={{
...(scrollable && {
overflow: 'auto',
whiteSpace: 'nowrap',
}),
}}
ref={mergedRef as any}
<WrapWithIf
condition={scrollable}
wrapper={<ArrowScroll />}
>
{tabChildren}
</Flex>
<Flex
{...tabListProps}
{...props}
flexDirection={stateProps.orientation === 'vertical' ? 'column' : 'row'}
alignItems={
stateProps.orientation === 'vertical' ? 'flex-start' : 'flex-end'
}
css={{
...(scrollable && {
overflow: 'auto',
whiteSpace: 'nowrap',
}),
}}
ref={mergedRef as any}
>
{tabChildren}
</Flex>
</WrapWithIf>
)
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './plural-logos'

// Components
export { default as Accordion } from './components/Accordion'
export { default as ArrowScroll } from './components/ArrowScroll'
export { default as Banner } from './components/Banner'
export { default as Button } from './components/Button'
export type { CardProps } from './components/Card'
Expand Down

0 comments on commit 6cf451b

Please sign in to comment.