-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(website): create simple grid carousel
- Loading branch information
Media Center
committed
May 9, 2024
1 parent
0424a36
commit 11872b4
Showing
2 changed files
with
140 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
apps/dynamic-grid-website/src/components/carousel/carousel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { | ||
FC, | ||
MouseEvent, | ||
ReactNode, | ||
useCallback, | ||
useLayoutEffect, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
import styled from '@emotion/styled'; | ||
import { DynamicGrid, DynamicGridProps } from '@mordech/dynamic-grid-react'; | ||
|
||
import { Button } from '../button'; | ||
import { Icon } from '../icon'; | ||
|
||
type CarouselProps = { | ||
children?: ReactNode; | ||
} & DynamicGridProps; | ||
|
||
export const CarouselWrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.75rem; | ||
`; | ||
|
||
const ButtonContainer = styled.div` | ||
display: flex; | ||
gap: 0.5rem; | ||
align-self: flex-end; | ||
`; | ||
|
||
export const Carousel: FC<CarouselProps> = ({ children, ...rest }) => { | ||
const [leftArrowDisabled, setLeftArrowDisabled] = useState<boolean>(true); | ||
const [rightArrowDisabled, setRightArrowDisabled] = useState<boolean>(false); | ||
|
||
const gridRef = useRef<HTMLDivElement>(null); | ||
|
||
const handleArrowsState = useCallback(() => { | ||
const buffer = 30; | ||
const grid = gridRef.current; | ||
|
||
if (!grid) return; | ||
|
||
const isAtEnd = | ||
grid.scrollLeft + grid.offsetWidth >= grid.scrollWidth - buffer; | ||
|
||
const isAtStart = grid.scrollLeft < buffer; | ||
|
||
isAtStart ? setLeftArrowDisabled(true) : setLeftArrowDisabled(false); | ||
isAtEnd ? setRightArrowDisabled(true) : setRightArrowDisabled(false); | ||
}, []); | ||
|
||
const handleScroll = useCallback((mouseEvent: MouseEvent) => { | ||
const grid = gridRef.current; | ||
if (!grid) return; | ||
|
||
const firstGridChildWidth = grid.firstElementChild?.clientWidth || 200; | ||
|
||
if (!(mouseEvent.target instanceof HTMLElement)) return; | ||
mouseEvent.target.ariaLabel === 'Next' | ||
? grid.scrollBy({ left: firstGridChildWidth }) | ||
: grid.scrollBy({ left: -1 * firstGridChildWidth }); | ||
}, []); | ||
|
||
useLayoutEffect(() => { | ||
const resizeObserver = new ResizeObserver(handleArrowsState); | ||
resizeObserver.observe(gridRef.current!); | ||
|
||
return resizeObserver.disconnect; | ||
}, [handleArrowsState]); | ||
|
||
return ( | ||
<CarouselWrapper> | ||
<ButtonContainer> | ||
<Button | ||
aria-label="Previous" | ||
radius="round" | ||
variant="tonal" | ||
size="compact" | ||
disabled={leftArrowDisabled} | ||
onClick={handleScroll} | ||
> | ||
<Icon icon="arrow_back" /> | ||
</Button> | ||
|
||
<Button | ||
aria-label="Next" | ||
radius="round" | ||
variant="tonal" | ||
size="compact" | ||
disabled={rightArrowDisabled} | ||
onClick={handleScroll} | ||
> | ||
<Icon icon="arrow_forward" /> | ||
</Button> | ||
</ButtonContainer> | ||
|
||
<DynamicGrid | ||
ref={gridRef} | ||
onScroll={handleArrowsState} | ||
scrollOptions={{ hideScrollbar: true }} | ||
{...rest} | ||
isScroll | ||
> | ||
{children} | ||
</DynamicGrid> | ||
|
||
<ButtonContainer> | ||
<Button radius="pill" variant="text" size="compact"> | ||
Inspect | ||
<Icon slot="icon-end" icon="code" /> | ||
</Button> | ||
</ButtonContainer> | ||
</CarouselWrapper> | ||
); | ||
}; |