add arrows for youMayLike component

This commit is contained in:
2025-10-09 00:21:01 +03:00
parent 373a1497d4
commit ca54ce37cb
2 changed files with 331 additions and 142 deletions

View File

@@ -1,67 +1,124 @@
.youMightAlsoLikeContainer { .youMightAlsoLikeContainer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: flex-start;
overflow-y: scroll; overflow-x: hidden;
padding: 0;
scroll-behavior: smooth;
} }
:global(.darkApp) .youMightAlsoLikeContainer path { :global(.darkApp) .youMightAlsoLikeContainer path {
fill: var(--primary); fill: var(--primary);
} }
.popularMenuItemImage { .popularMenuItemImage {
width: 73px; width: 73px;
height: 73px; height: 73px;
object-fit: cover; object-fit: cover;
border-radius: 8px; border-radius: 8px;
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
.popularMenuItemImage:hover { .popularMenuItemImage:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.popularMenuItemImageMobile { .popularMenuItemImageMobile {
width: 73px; width: 73px;
height: 73px; height: 73px;
min-height: 73px; min-height: 73px;
object-fit: cover; object-fit: cover;
} }
.popularMenuItemImageTablet { .popularMenuItemImageTablet {
width: 90px; width: 90px;
height: 90px; height: 90px;
border-radius: 12px; border-radius: 12px;
} }
.popularMenuItemImageDesktop { .popularMenuItemImageDesktop {
width: 110px; width: 110px;
height: 110px; height: 110px;
border-radius: 16px; border-radius: 16px;
} }
[data-theme="dark"] .popularMenuItemImage {
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
@media (min-width: 768px) {
.popularMenuItemImage:focus {
outline-offset: 4px;
}
[data-theme="dark"] .popularMenuItemImage {
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
@media (min-width: 768px) {
.popularMenuItemImage:focus {
outline-offset: 4px;
}
} }
/* Hover effects for devices that support hover */ /* Hover effects for devices that support hover */
@media (hover: hover) { @media (hover: hover) {
.popularMenuItemImage:hover { .popularMenuItemImage:hover {
transform: scale(1.05); transform: scale(1.05);
} }
} }
/* Focus states for accessibility */ /* Focus states for accessibility */
.popularMenuItemImage:focus { .popularMenuItemImage:focus {
outline: 2px solid var(--primary); outline: 2px solid var(--primary);
outline-offset: 2px; outline-offset: 2px;
} }
.itemDescriptionIcons svg { .itemDescriptionIcons svg {
width: 18px; width: 18px;
height: 18px; height: 18px;
}
.arrowButton {
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(8px);
}
.arrowButton:hover {
background: rgba(255, 255, 255, 1);
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.arrowButton:active {
transform: scale(0.95);
}
.arrowButton:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.arrowIcon {
width: 24px;
height: 24px;
transition: transform 0.2s ease;
}
.arrowButton:hover .arrowIcon {
transform: scale(1.1);
}
/* Dark theme styles */
[data-theme="dark"] .arrowButton {
background: rgba(30, 30, 30, 0.95);
border-color: rgba(255, 255, 255, 0.1);
color: white;
}
[data-theme="dark"] .arrowButton:hover {
background: rgba(40, 40, 40, 1);
border-color: rgba(255, 255, 255, 0.2);
} }

View File

@@ -1,11 +1,15 @@
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
import { Grid, Space } from "antd"; import { Space } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import ImageWithFallback from "components/ImageWithFallback"; import ImageWithFallback from "components/ImageWithFallback";
import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons.tsx"; import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons.tsx";
import ProText from "components/ProText.tsx"; import ProText from "components/ProText.tsx";
import { menuItems } from "data/menuItems.ts"; import { menuItems } from "data/menuItems.ts";
import { addItem } from "features/order/orderSlice.ts"; import { addItem } from "features/order/orderSlice.ts";
import useBreakPoint from "hooks/useBreakPoint";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import { colors } from "ThemeConstants.ts"; import { colors } from "ThemeConstants.ts";
@@ -13,15 +17,88 @@ import { default_image } from "utils/constants.ts";
import { Product } from "utils/types/appTypes.ts"; import { Product } from "utils/types/appTypes.ts";
import styles from "./YouMayAlsoLike.module.css"; import styles from "./YouMayAlsoLike.module.css";
const { useBreakpoint } = Grid;
export default function YouMightAlsoLike() { export default function YouMightAlsoLike() {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { sm, md } = useBreakpoint(); const { isMobile, isTablet, isDesktop } = useBreakPoint();
const isMobile = !sm;
const isTablet = sm && !md; const [currentIndex, setCurrentIndex] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
// Check if scrolling is needed based on container width
const [containerWidth, setContainerWidth] = useState(0);
useEffect(() => {
const updateContainerWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth);
}
};
updateContainerWidth();
window.addEventListener("resize", updateContainerWidth);
return () => window.removeEventListener("resize", updateContainerWidth);
}, []);
// Calculate actual visible items based on container width
const itemWidth = isMobile ? 95 + 16 : isTablet ? 120 + 16 : 140 + 16;
const totalItemsWidth = menuItems.length * itemWidth;
const needsScrolling = totalItemsWidth > containerWidth;
const canNavigateLeft = currentIndex > 0;
const canNavigateRight =
needsScrolling &&
currentIndex * itemWidth + containerWidth < totalItemsWidth;
const scrollToIndex = (index: number) => {
if (containerRef.current) {
const scrollLeft = isRTL ? -(index * itemWidth) : index * itemWidth;
containerRef.current.scrollTo({
left: scrollLeft,
behavior: "smooth",
});
}
};
const handlePrevious = () => {
if (canNavigateLeft) {
const newIndex = Math.max(0, currentIndex - 1);
setCurrentIndex(newIndex);
scrollToIndex(newIndex);
}
};
const handleNext = () => {
if (canNavigateRight) {
const maxPossibleIndex = Math.floor(
(totalItemsWidth - containerWidth) / itemWidth,
);
const newIndex = Math.min(maxPossibleIndex, currentIndex + 1);
setCurrentIndex(newIndex);
scrollToIndex(newIndex);
}
};
// RTL-specific handlers
const handleRTLPrevious = () => {
if (canNavigateRight) {
const maxPossibleIndex = Math.floor(
(totalItemsWidth - containerWidth) / itemWidth,
);
const newIndex = Math.min(maxPossibleIndex, currentIndex + 1);
setCurrentIndex(newIndex);
scrollToIndex(newIndex);
}
};
const handleRTLNext = () => {
if (canNavigateLeft) {
const newIndex = Math.max(0, currentIndex - 1);
setCurrentIndex(newIndex);
scrollToIndex(newIndex);
}
};
const handleQuickAdd = (item: Product) => { const handleQuickAdd = (item: Product) => {
dispatch( dispatch(
addItem({ addItem({
@@ -51,119 +128,174 @@ export default function YouMightAlsoLike() {
</ProText> </ProText>
</div> </div>
<div className={styles.youMightAlsoLikeContainer}> <div
{menuItems.map((item: Product) => ( style={{
<div key={item.id}> position: "relative",
<div padding: isTablet || isDesktop ? "0 16px" : "0",
style={{ }}
display: "flex", >
flexDirection: "column", {/* Left Arrow - Tablet and Desktop only */}
alignItems: "center", {(isTablet || isDesktop) && (
justifyContent: "space-between", <button
width: isMobile ? "95px" : isTablet ? "120px" : "140px", className={styles.arrowButton}
position: "relative", style={{
height: 200, position: "absolute",
overflow: "hidden", left: isRTL ? "auto" : -25,
}} right: isRTL ? -25 : "auto",
> top: "25%",
transform: "translateY(-50%)",
zIndex: 10,
}}
onClick={isRTL ? handleRTLPrevious : handlePrevious}
aria-label="Previous items"
>
{isRTL ? (
<NextIcon className={styles.arrowIcon} />
) : (
<BackIcon className={styles.arrowIcon} />
)}
</button>
)}
{/* Right Arrow - Tablet and Desktop only */}
{(isTablet || isDesktop) && (
<button
className={styles.arrowButton}
style={{
position: "absolute",
left: isRTL ? -10 : "auto",
right: isRTL ? "auto" : -10,
top: "25%",
transform: "translateY(-50%)",
zIndex: 10,
}}
onClick={isRTL ? handleRTLNext : handleNext}
aria-label="Next items"
>
{isRTL ? (
<BackIcon className={styles.arrowIcon} />
) : (
<NextIcon className={styles.arrowIcon} />
)}
</button>
)}
<div ref={containerRef} className={styles.youMightAlsoLikeContainer}>
{menuItems.map((item: Product) => (
<div key={item.id}>
<div <div
style={{ style={{
width: isMobile ? 18 : 24,
height: isMobile ? 18 : 24,
borderRadius: "50%",
top: isMobile ? 50 : 80,
position: "absolute",
right: isMobile ? 15 : 20,
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "column",
justifyContent: "center", alignItems: "center",
cursor: "pointer", justifyContent: "space-between",
backgroundColor: "white", width: isMobile ? "95px" : isTablet ? "120px" : "140px",
zIndex: 999, position: "relative",
height: 200,
overflow: "hidden",
}} }}
> >
<PlusOutlined <div
onClick={(e) => {
e.stopPropagation();
handleQuickAdd(item);
}}
style={{ style={{
color: colors.primary, width: isMobile ? 18 : 24,
fontSize: isMobile ? 14 : 16, height: isMobile ? 18 : 24,
borderRadius: "50%",
top: isMobile ? 50 : 80,
position: "absolute",
[isRTL ? "left" : "right"]: isMobile ? 15 : 20,
display: "flex",
flexDirection: "row",
justifyContent: "center",
cursor: "pointer",
backgroundColor: "white",
zIndex: 999,
}} }}
/> >
</div> <PlusOutlined
onClick={(e) => {
<ImageWithFallback e.stopPropagation();
src={item.image} handleQuickAdd(item);
alt={item.name} }}
className={`${styles.popularMenuItemImage} ${
isMobile
? styles.popularMenuItemImageMobile
: isTablet
? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop
}`}
width={isMobile ? 73 : isTablet ? 90 : 110}
height={isMobile ? 73 : isTablet ? 90 : 110}
fallbackSrc={default_image}
/>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "start",
marginTop: 5,
...(isRTL ? { marginRight: -20 } : { marginLeft: -10 }),
}}
>
<ItemDescriptionIcons className={styles.itemDescriptionIcons} />
</div>
<Space
direction="vertical"
size="small"
style={{
flex: 1,
rowGap: 0,
height: 40,
}}
>
<div style={{ height: 25 }}>
<ProText
style={{ style={{
whiteSpace: "nowrap", color: colors.primary,
fontSize: isMobile ? 14 : 16,
}}
/>
</div>
<ImageWithFallback
src={item.image}
alt={item.name}
className={`${styles.popularMenuItemImage} ${
isMobile
? styles.popularMenuItemImageMobile
: isTablet
? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop
}`}
width={isMobile ? 73 : isTablet ? 90 : 110}
height={isMobile ? 73 : isTablet ? 90 : 110}
fallbackSrc={default_image}
/>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "start",
marginTop: 5,
...(isRTL ? { marginRight: -20 } : { marginLeft: -10 }),
}}
>
<ItemDescriptionIcons
className={styles.itemDescriptionIcons}
/>
</div>
<Space
direction="vertical"
size="small"
style={{
flex: 1,
rowGap: 0,
height: 40,
}}
>
<div style={{ height: 25 }}>
<ProText
style={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
padding: 3,
fontSize: isMobile ? 12 : isTablet ? 14 : 16,
width: isMobile ? 80 : isTablet ? 100 : 120,
display: "inline-block",
fontWeight: 600,
}}
>
{item.name}
</ProText>
</div>
<ArabicPrice
price={item.price}
style={{
margin: 0,
WebkitLineClamp: 1,
WebkitBoxOrient: "vertical",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
padding: 3, padding: 3,
fontSize: isMobile ? 12 : isTablet ? 14 : 16, fontSize: isMobile ? 12 : isTablet ? 14 : 16,
width: isMobile ? 80 : isTablet ? 100 : 120, fontWeight: 500,
display: "inline-block",
fontWeight: 600,
}} }}
> />
{item.name} </Space>
</ProText> </div>
</div>
<ArabicPrice
price={item.price}
style={{
margin: 0,
WebkitLineClamp: 1,
WebkitBoxOrient: "vertical",
overflow: "hidden",
textOverflow: "ellipsis",
padding: 3,
fontSize: isMobile ? 12 : isTablet ? 14 : 16,
fontWeight: 500,
}}
/>
</Space>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</> </>
); );