first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
import React, {Fragment} from 'react';
import {ChannelContentProps} from '@app/channels/channel-content';
import {ChannelHeader} from '@app/channels/channel-header/channel-header';
import {ChannelContentGridItem} from '@app/channels/content-grid/channel-content-grid-item';
import {useCarousel} from '@app/channels/carousel/use-carousel';
import clsx from 'clsx';
import {ContentGridProps} from '@app/channels/content-grid/content-grid-layout';
import {IconButton} from '@common/ui/buttons/icon-button';
import {KeyboardArrowLeftIcon} from '@common/icons/material/KeyboardArrowLeft';
import {KeyboardArrowRightIcon} from '@common/icons/material/KeyboardArrowRight';
interface Props extends ChannelContentProps {
variant?: ContentGridProps['variant'];
}
export function ChannelContentCarousel(props: Props) {
const {channel, variant} = props;
const {
scrollContainerRef,
canScrollForward,
canScrollBackward,
scrollToPreviousPage,
scrollToNextPage,
containerClassName,
itemClassName,
} = useCarousel();
const gridClassName =
variant === 'landscape'
? 'content-grid-landscape'
: 'content-grid-portrait';
return (
<div>
<ChannelHeader
{...props}
actions={
<Fragment>
<IconButton
disabled={!canScrollBackward}
onClick={() => scrollToPreviousPage()}
aria-label="Previous page"
>
<KeyboardArrowLeftIcon />
</IconButton>
<IconButton
disabled={!canScrollForward}
onClick={() => scrollToNextPage()}
aria-label="Next page"
>
<KeyboardArrowRightIcon />
</IconButton>
</Fragment>
}
/>
<div
ref={scrollContainerRef}
className={clsx(containerClassName, gridClassName)}
>
{channel.content?.data.map(item => (
<div className={itemClassName} key={`${item.id}-${item.model_type}`}>
<ChannelContentGridItem item={item} variant={variant} />
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,112 @@
import {useCallback, useEffect, useRef, useState} from 'react';
import debounce from 'just-debounce-it';
import {useLayoutEffect} from '@react-aria/utils';
interface Options {
rotate?: boolean;
}
const containerClassName =
'content-carousel content-grid relative w-full grid grid-flow-col grid-rows-[auto] overflow-x-auto overflow-y-hidden gap-24 snap-always snap-x snap-mandatory hidden-scrollbar scroll-smooth';
const itemClassName = 'snap-start snap-normal';
export function useCarousel({rotate = false}: Options = {}) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const itemWidth = useRef<number>(0);
const perPage = useRef<number>(5);
const [canScrollBackward, setCanScrollBackward] = useState(rotate);
const [canScrollForward, setCanScrollForward] = useState(true);
const [activePage, setActivePage] = useState(0);
const updateNavStatus = useCallback(() => {
const el = scrollContainerRef.current;
if (el && itemWidth.current) {
if (!rotate) {
setCanScrollForward(
el.scrollWidth - 1 > el.scrollLeft + el.clientWidth
);
setCanScrollBackward(el.scrollLeft > 0);
}
const pageWidth = el.clientWidth;
const activePage = Math.round(el.scrollLeft / pageWidth);
setActivePage(activePage);
}
}, [rotate]);
// enable/disable navigation buttons based on element scroll offset
useEffect(() => {
const el = scrollContainerRef.current;
const handleScroll = debounce(() => updateNavStatus(), 100);
if (el) {
el.addEventListener('scroll', handleScroll);
}
return () => el?.removeEventListener('scroll', handleScroll);
}, [updateNavStatus]);
// get width for first grid item
useLayoutEffect(() => {
const el = scrollContainerRef.current;
if (el) {
perPage.current = Number(
getComputedStyle(el).getPropertyValue('--nVisibleItems')
);
const firstGridItem = el.children.item(0);
const observer = new ResizeObserver(entries => {
itemWidth.current = entries[0].contentRect.width;
updateNavStatus();
});
if (firstGridItem) {
observer.observe(firstGridItem);
}
return () => observer.unobserve(el);
}
}, [updateNavStatus]);
const scrollToIndex = useCallback((index: number) => {
if (scrollContainerRef.current) {
setActivePage(index);
const amount = itemWidth.current * index;
scrollContainerRef.current.scrollTo({left: amount});
}
}, []);
const scrollToPreviousPage = useCallback(() => {
if (scrollContainerRef.current) {
const pageWidth = scrollContainerRef.current.clientWidth;
const currentScroll = scrollContainerRef.current.scrollLeft;
const scrollLeft =
!currentScroll && rotate
? scrollContainerRef.current.scrollWidth - pageWidth
: currentScroll - pageWidth;
scrollContainerRef.current.scrollTo({
left: scrollLeft,
});
}
}, [rotate]);
const scrollToNextPage = useCallback(() => {
if (scrollContainerRef.current) {
const pageWidth = scrollContainerRef.current.clientWidth;
const currentScroll = scrollContainerRef.current.scrollLeft;
const scrollLeft =
rotate &&
currentScroll + pageWidth >= scrollContainerRef.current.scrollWidth
? 0
: (activePage + 1) * pageWidth;
scrollContainerRef.current.scrollTo({left: scrollLeft});
}
}, [activePage, rotate]);
return {
scrollContainerRef,
scrollToIndex,
scrollToPreviousPage,
scrollToNextPage,
canScrollForward,
canScrollBackward,
activePage,
containerClassName,
itemClassName,
};
}