Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

View File

@@ -0,0 +1,338 @@
.categoriesContainer {
display: flex;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
position: sticky;
top: 0;
background: var(--ant-bg-container);
z-index: 10;
border-bottom: 1px solid var(--ant-color-border);
overflow: hidden;
text-align: center;
height: 96px;
overflow-x: auto;
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
will-change: position, transform, opacity, filter;
transform-origin: top center;
gap: 16px;
}
/* Enhanced responsive categories container */
@media (min-width: 769px) and (max-width: 1024px) {
.categoriesContainer {
height: 160px;
padding: 16px;
gap: 16px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.categoriesSticky {
padding: 12px 16px !important;
height: 30px !important;
}
}
@media (min-width: 1025px) {
.categoriesContainer {
height: 180px;
padding: 24px;
max-width: 1200px;
margin: 0 auto;
gap: 16px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.categoriesSticky {
padding: 16px 24px !important;
height: 70px !important;
max-width: 100vw !important;
margin: 0 !important;
}
}
/* Menu Sections and Grid Layout */
/* .menuSections {
margin-bottom: 105px !important;
} */
.menuSection:first-child h3 {
margin-top: 0px !important;
}
.menuSection h3 {
margin: 30px 0px 15px 0px;
transition: color 0.3s ease;
}
/* Enhanced responsive menu section headers */
@media (min-width: 769px) and (max-width: 1024px) {
.menuSection h3 {
margin: 40px 0px 20px 0px;
font-size: 24px;
}
}
@media (min-width: 1025px) {
.menuSection h3 {
margin: 50px 0px 25px 0px;
font-size: 28px;
}
}
.menuItemsGrid {
display: grid;
gap: 12px;
grid-template-columns: 1fr;
margin-bottom: 10px;
}
.menuItemsGridMobile {
gap: 12px;
grid-template-columns: 1fr;
}
.menuItemsGridTablet {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 0 12px;
margin-bottom: 20px;
}
.menuItemsGridDesktop {
grid-template-columns: repeat(3, 1fr);
gap: 20px;
padding: 0 16px;
margin-bottom: 24px;
}
/* Enhanced responsive menu items grid */
@media (min-width: 1280px) {
.menuItemsGridDesktop {
grid-template-columns: repeat(3, 1fr);
gap: 24px;
padding: 0 20px;
margin-bottom: 28px;
}
}
/* Restaurant Header Skeleton */
.restaurantHeader {
position: relative;
margin-bottom: 24px;
}
.leftShape {
position: absolute;
top: 133px;
width: 47px;
height: 50px;
left: -11px;
background-color: var(--background);
clip-path: path("M 0 53 Q 50 50, 50 0 Q 50 50, 100 100 L 0 100 Z");
}
:global(.darkApp) .leftShape {
background-color: var(--background);
}
.rightShape {
position: absolute;
top: 133px;
width: 47px;
height: 50px;
left: 102px;
background-color: var(--background);
clip-path: path("M 0 53 Q 50 50, 50 0 Q 50 50, 100 100 L 0 100 Z");
transform: scale(-1, 1);
}
:global(.darkApp) .rightShape {
background-color: var(--background);
}
/* Restaurant Info Skeleton */
.restaurantInfoSkeleton {
text-align: center;
padding: 0 16px;
}
/* Loyalty Card Skeleton */
.loyaltySkeleton {
margin: 16px 0;
padding: 0 16px;
}
.loyaltyCardSkeleton {
height: 115px;
border-radius: 16px;
}
.loyaltyCardSkeleton :global(.ant-card-body) {
height: 115px;
padding: 12px 16px !important;
}
/* Page Container */
.pageContainer {
transition: all 0.3s ease;
display: flex;
flex-direction: column;
gap: 12px;
}
/* Enhanced responsive page container */
@media (min-width: 769px) and (max-width: 1024px) {
.pageContainer {
padding: 24px;
}
.restaurantInfoSkeleton {
padding: 0 24px;
margin: 32px 0;
}
.loyaltySkeleton {
padding: 0 24px;
}
.restaurantHeader {
margin-bottom: 32px;
}
}
@media (min-width: 1025px) {
.pageContainer {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
.restaurantInfoSkeleton {
padding: 0 32px;
margin: 40px 0;
}
.loyaltySkeleton {
padding: 0 32px;
}
.restaurantHeader {
margin-bottom: 40px;
}
}
/* Skeleton Loading Styles */
.skeletonContainer {
animation: skeletonPulse 1.5s ease-in-out infinite;
}
.skeletonContainer .ant-skeleton {
transition: all 0.3s ease;
}
.skeletonContainer .ant-skeleton-image {
border-radius: 8px;
overflow: hidden;
}
.skeletonContainer .ant-skeleton-input {
border-radius: 6px;
}
.skeletonContainer .ant-skeleton-button {
border-radius: 20px;
}
@keyframes skeletonPulse {
0% {
opacity: 1;
}
50% {
opacity: 0.7;
}
100% {
opacity: 1;
}
}
@keyframes skeletonShimmer {
0% {
background-position: -200px 0;
}
100% {
background-position: calc(200px + 100%) 0;
}
}
.skeletonContainer .ant-skeleton-active .ant-skeleton-input,
.skeletonContainer .ant-skeleton-active .ant-skeleton-image,
.skeletonContainer .ant-skeleton-active .ant-skeleton-button {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200px 100%;
animation: skeletonShimmer 1.5s infinite;
}
/* Mobile-specific skeleton styles */
@media (max-width: 768px) {
.skeletonContainer .ant-skeleton {
margin-bottom: 12px;
}
.skeletonContainer .ant-skeleton-image {
border-radius: 4px;
}
.skeletonContainer .ant-skeleton-input {
border-radius: 4px;
}
.skeletonContainer .ant-skeleton-button {
border-radius: 16px;
}
}
/* Dark theme skeleton styles */
:global(.darkApp) .skeletonContainer .ant-skeleton-active .ant-skeleton-input,
:global(.darkApp) .skeletonContainer .ant-skeleton-active .ant-skeleton-image,
:global(.darkApp) .skeletonContainer .ant-skeleton-active .ant-skeleton-button {
background: linear-gradient(90deg, #181818 25%, #363636 50%, #181818 75%);
background-size: 200px 100%;
animation: skeletonShimmer 1.5s infinite;
}
:global(.darkApp) .skeletonContainer .ant-skeleton-input,
:global(.darkApp) .skeletonContainer .ant-skeleton-image,
:global(.darkApp) .skeletonContainer .ant-skeleton-button {
background-color: #181818;
border-color: #363636;
}
/* Tablet-specific skeleton styles */
@media (min-width: 769px) and (max-width: 1024px) {
.skeletonContainer {
padding: 24px;
}
.skeletonContainer .ant-skeleton {
margin-bottom: 16px;
}
.skeletonContainer .ant-skeleton-image {
border-radius: 6px;
}
.skeletonContainer .ant-skeleton-input {
border-radius: 5px;
}
.skeletonContainer .ant-skeleton-button {
border-radius: 18px;
}
}

View File

@@ -0,0 +1,438 @@
import { Card, Grid, Skeleton } from "antd";
import styles from "./MenuSkeleton.module.css";
interface MenuSkeletonProps {
categoryCount?: number;
itemCount?: number;
variant?:
| "default"
| "minimal"
| "detailed"
| "categories-only"
| "menu-only";
}
const { useBreakpoint } = Grid;
const MenuSkeleton = ({
categoryCount = 6,
itemCount = 8,
variant = "default",
}: MenuSkeletonProps) => {
const { xs, sm, md } = useBreakpoint();
const isMobile = xs;
const isTablet = sm && !md;
const isDesktop = md;
const getCategoryCardStyle = () => {
if (isMobile) {
return {
width: 90,
height: 95,
};
} else if (isTablet) {
return {
width: 120,
height: 140,
};
} else {
return {
width: 140,
height: 160,
};
}
};
const getMenuItemCardStyle = () => {
if (isMobile) {
return {
height: 140,
padding: "12px 12px 12px 16px",
};
} else if (isTablet) {
return {
height: 160,
padding: "16px",
};
} else {
return {
height: 180,
padding: "20px",
};
}
};
const getCategoryImageStyle = () => {
if (isMobile) {
return {
width: 90,
height: 60,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
};
} else if (isTablet) {
return {
width: 120,
height: 90,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
};
} else {
return {
width: 120,
height: 100,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
};
}
};
const getMenuItemImageStyle = () => {
if (isMobile) {
return {
width: 90,
height: 95,
};
} else if (isTablet) {
return {
width: 120,
height: 120,
};
} else {
return {
width: 140,
height: 140,
};
}
};
const getGridClass = () => {
if (isMobile) {
return styles.menuItemsGridMobile;
} else if (isTablet) {
return styles.menuItemsGridTablet;
} else {
return styles.menuItemsGridDesktop;
}
};
const getPageContainerStyle = () => {
if (isDesktop) {
return {
maxWidth: "1200px",
margin: "0 auto",
padding: "32px",
};
} else if (isTablet) {
return {
padding: "24px",
};
}
};
return (
<div
className={`${styles.pageContainer} ${styles.skeletonContainer}`}
style={getPageContainerStyle()}
>
{/* Restaurant Header Skeleton */}
<div className={styles.restaurantHeader}>
{/* Cover Image Skeleton */}
<Skeleton.Image
active
style={{
width: "100vw",
height: isMobile ? 182 : isTablet ? 200 : 220,
borderRadius: 0,
}}
/>
{/* Logo Skeleton */}
<Skeleton.Image
active
style={{
position: "absolute",
left: isMobile ? "33px" : "40px",
top: isMobile ? "133px" : "-70px",
borderRadius: "50%",
width: isMobile ? "72px" : "80px",
height: isMobile ? "72px" : "80px",
border: "3px solid var(--background)",
zIndex: 10,
overflow: "hidden",
}}
/>
{/* Decorative Shapes Skeleton */}
<div className={styles.leftShape}></div>
<div className={styles.rightShape}></div>
</div>
{/* Restaurant Info Skeleton */}
<div className={styles.restaurantInfoSkeleton}>
<div
className={
styles.restaurantDescriptionSkeleton +
" " +
"restaurant-description-skeleton"
}
>
<Skeleton
active
paragraph={{
rows: 1,
width: ["100%"],
}}
/>
</div>
</div>
{/* Loyalty Card Skeleton */}
<div className={styles.loyaltySkeleton}>
<Card className={styles.loyaltyCardSkeleton}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "0.5rem",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Skeleton.Image
active
style={{
width: "24px",
height: "24px",
borderRadius: "4px",
}}
/>
<Skeleton.Input
active
style={{
width: "120px",
height: "16px",
}}
/>
</div>
<Skeleton.Image
active
style={{
width: "24px",
height: "24px",
borderRadius: "4px",
}}
/>
</div>
<br />
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div style={{ display: "flex", gap: "4px" }}>
{Array.from({ length: 5 }).map((_, index) => (
<Skeleton.Image
key={index}
active
style={{
width: "32px",
height: "32px",
borderRadius: "50%",
}}
/>
))}
</div>
<Skeleton.Button
active
style={{
width: "80px",
height: "32px",
borderRadius: "16px",
}}
/>
</div>
</Card>
</div>
{/* Categories Skeleton */}
{(variant === "default" ||
variant === "minimal" ||
variant === "detailed" ||
variant === "categories-only") && (
<div style={{ padding: "0 1rem", display: "flex", gap: 8, overflow: "hidden" }}>
{Array.from({
length:
variant === "minimal"
? Math.min(categoryCount, 4)
: categoryCount,
}).map((_, index) => (
<div
key={index}
style={{
marginRight: isMobile ? "3px" : "8px",
}}
>
<Card
style={{ borderRadius: 8 }}
styles={{
body: {
...getCategoryCardStyle(),
padding: 0,
},
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "space-between",
gap: "0px",
width: "100%",
height: "100%",
}}
>
<Skeleton.Image
active
style={{
...getCategoryImageStyle(),
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}}
/>
<div
style={{
flex: 1,
padding: isMobile ? "4px" : "8px",
textAlign: "center",
width: "100%",
}}
>
<Skeleton
active
paragraph={{ rows: 1 }}
style={{
margin: 0,
padding: isMobile ? 3 : 8,
}}
/>
</div>
</div>
</Card>
</div>
))}
</div>
)}
{/* Menu Items Skeleton */}
{(variant === "default" ||
variant === "minimal" ||
variant === "detailed" ||
variant === "menu-only") && (
<div className={styles.menuSections} style={{ padding: "0 1rem" }}>
{Array.from({ length: variant === "minimal" ? 1 : 3 }).map(
(_, sectionIndex) => (
<div key={sectionIndex} className={styles.menuSection}>
{/* Section Header Skeleton */}
{/* <div
style={{
margin: isMobile ? "20px 0 15px 0" : isTablet ? "30px 0 20px 0" : "40px 0 25px 0",
padding: isTablet ? "0 12px" : isDesktop ? "0 16px" : "0"
}}
>
<Skeleton.Input
active
size="large"
style={{
width: isMobile ? 120 : isTablet ? 160 : 200,
height: isMobile ? 24 : isTablet ? 28 : 32,
}}
/>
</div> */}
<div className={`${styles.menuItemsGrid} ${getGridClass()}`}>
{Array.from({
length:
variant === "minimal"
? Math.min(itemCount, 4)
: itemCount,
}).map((_, itemIndex) => (
<Card
key={itemIndex}
className="responsive-card"
style={{ borderRadius: 8 }}
styles={{
body: {
...getMenuItemCardStyle(),
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
},
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-around",
gap: isMobile ? 12 : 16,
position: "relative",
}}
>
<div
style={{
lineHeight: 1,
height: "100%",
flex: 1,
}}
>
{/* Item Name Skeleton */}
<Skeleton.Input
active
style={{
marginBottom: isMobile ? 8 : isTablet ? 16 : 20,
height: isMobile ? 16 : isTablet ? 18 : 20,
width: "80%",
}}
/>
{/* Item Description Skeleton */}
<Skeleton
active
paragraph={{
rows: isMobile ? 1 : 2,
width: ["100%", "90%", "70%"],
}}
style={{
marginBottom: isMobile ? 8 : isTablet ? 16 : 20,
}}
/>
{/* Action Icons Skeleton */}
</div>
<div style={{ position: "relative" }}>
{/* Item Image Skeleton */}
<Skeleton.Image
active
style={{
...getMenuItemImageStyle(),
}}
/>{" "}
</div>
</div>
</Card>
))}
</div>
</div>
)
)}
</div>
)}
</div>
);
};
export default MenuSkeleton;