add primary color and splash screen
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -92,7 +92,7 @@ function FoodPage({ }: Props) {
|
||||
{categoryName || '-'}
|
||||
</div>
|
||||
<button onClick={() => router.back()} className='p-2 rounded-full bg-container/40'>
|
||||
<ArrowLeft size={18} className='stroke-black' />
|
||||
<ArrowLeft size={18} className='stroke-primary dark:stroke-foreground' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ function FoodPage({ }: Props) {
|
||||
{foodName}
|
||||
</h5>
|
||||
<button className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'>
|
||||
<Heart variant='Bold' size={24} className='fill-black dark:fill-foreground' />
|
||||
<Heart variant='Bold' size={24} className='fill-primary dark:fill-foreground' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ function RatingOrderIndex({ }: Props) {
|
||||
title: 'text-sm2! font-normal mt-1',
|
||||
header: 'rounded-lg h-7! w-full',
|
||||
headerActive: 'bg-white',
|
||||
titleActive: 'text-black'
|
||||
titleActive: 'text-primary'
|
||||
}}>
|
||||
<TabHeader title={t("Tabs.Weeknesses.Title")} viewRenderer={badPoitns()}></TabHeader>
|
||||
<TabHeader title={t("Tabs.Strengths.Title")} viewRenderer={goodPoitns()}></TabHeader>
|
||||
|
||||
@@ -60,10 +60,7 @@ function TransactionsIndex() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='max-w-36 place-self-end col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal'
|
||||
style={{
|
||||
background: 'linear-gradient(165.51deg, #000000 -8.44%, #C3C7DD 100%)',
|
||||
}}
|
||||
className='max-w-36 place-self-end col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal bg-linear-to-br from-primary to-menu-header'
|
||||
>
|
||||
<p className='text-sm2 text-white leading-tight'>
|
||||
<span className='font-normal'>Customer</span><br />
|
||||
|
||||
@@ -50,10 +50,7 @@ function TransactionsIndex() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal'
|
||||
style={{
|
||||
background: 'linear-gradient(165.51deg, #000000 -8.44%, #C3C7DD 100%)',
|
||||
}}
|
||||
className='col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal bg-linear-to-br from-primary to-menu-header'
|
||||
>
|
||||
<p className='text-sm2 text-white leading-tight'>
|
||||
<span className='font-medium'>Your</span><br />
|
||||
|
||||
@@ -42,6 +42,48 @@ export default async function RootLayout({
|
||||
dir='rtl'
|
||||
className="h-svh overflow-hidden"
|
||||
data-theme="light" >
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function() {
|
||||
try {
|
||||
const savedColor = localStorage.getItem('theme-primary-color');
|
||||
if (savedColor) {
|
||||
const hex = savedColor.replace('#', '');
|
||||
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
||||
|
||||
const lightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const chroma = (max - min) * 0.15;
|
||||
|
||||
let hue = 0;
|
||||
if (chroma !== 0) {
|
||||
if (max === r) {
|
||||
hue = ((g - b) / (max - min) + (g < b ? 6 : 0)) * 60;
|
||||
} else if (max === g) {
|
||||
hue = ((b - r) / (max - min) + 2) * 60;
|
||||
} else {
|
||||
hue = ((r - g) / (max - min) + 4) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
const oklchColor = 'oklch(' + lightness.toFixed(3) + ' ' + chroma.toFixed(3) + ' ' + hue.toFixed(1) + ')';
|
||||
document.documentElement.style.setProperty('--primary', oklchColor);
|
||||
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
const foregroundOklch = brightness > 0.5 ? 'oklch(0 0 0)' : 'oklch(1 0 0)';
|
||||
document.documentElement.style.setProperty('--primary-foreground', foregroundOklch);
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
|
||||
className={`antialiased bg-background h-svh overflow-hidden`}
|
||||
|
||||
@@ -109,7 +109,7 @@ function ToggleButton({
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<Refresh stroke="#333333" size={24} />
|
||||
<Refresh className="stroke-primary" size={24} />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -330,7 +330,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
|
||||
{tInstallPwaModal('AlreadyInstalled')}
|
||||
</p>
|
||||
) : isIOSDevice ? (
|
||||
<div className="mt-5 rounded-3xl bg-black/5 px-4 py-5 backdrop-blur-sm dark:bg-white/5">
|
||||
<div className="mt-5 rounded-3xl bg-primary/5 px-4 py-5 backdrop-blur-sm dark:bg-white/5">
|
||||
<p className="text-xs font-medium leading-relaxed text-center text-foreground/80 dark:text-neutral-200">
|
||||
{tInstallPwaModal('IOSDescription')}
|
||||
</p>
|
||||
|
||||
@@ -16,13 +16,13 @@ export default function SideMenuItem({ href, title, icon, className, children, .
|
||||
className={`
|
||||
inline-flex gap-3 ps-6 xl:ps-9 h-full w-full items-center overflow-hidden text-nowrap
|
||||
text-xs font-light text-disabled-text border-transparent
|
||||
data-[active]:border-s-6 data-[active]:border-black data-[active]:text-black
|
||||
dark:data-[active]:border-foreground dark:data-[active]:text-foreground
|
||||
data-active:border-s-6 data-active:border-primary data-active:text-primary
|
||||
dark:data-active:border-foreground dark:data-active:text-foreground
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
<span className="flex-shrink-0">{icon}</span>
|
||||
<span className="shrink-0">{icon}</span>
|
||||
<span className='mt-0.5'>{title}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface SplashScreenProps {
|
||||
logo?: string;
|
||||
restaurantName?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export default function SplashScreen({
|
||||
logo,
|
||||
restaurantName,
|
||||
duration = 2000
|
||||
}: SplashScreenProps) {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
// setIsVisible(false);
|
||||
}, duration);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [duration]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="fixed inset-0 z-[9999] flex flex-col items-center justify-center"
|
||||
>
|
||||
{/* دایرههای تزئینی پسزمینه */}
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.1, 0.2, 0.1]
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
className="absolute -top-20 -right-20 w-80 h-80 rounded-full bg-primary/20 blur-3xl"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1.2, 1, 1.2],
|
||||
opacity: [0.1, 0.2, 0.1]
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
delay: 0.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
className="absolute -bottom-20 -left-20 w-80 h-80 rounded-full bg-primary/20 blur-3xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: "easeOut"
|
||||
}}
|
||||
className="flex flex-col items-center gap-8 px-8 w-full max-w-md relative z-10"
|
||||
>
|
||||
{logo && (
|
||||
<motion.div
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.2, duration: 0.5 }}
|
||||
className="relative"
|
||||
>
|
||||
<div>
|
||||
<Image
|
||||
src={logo}
|
||||
alt={restaurantName || 'Restaurant Logo'}
|
||||
width={200}
|
||||
height={100}
|
||||
priority
|
||||
className="object-contain"
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
style={{ opacity: imageLoaded ? 1 : 0, transition: 'opacity 0.3s' }}
|
||||
/>
|
||||
{!imageLoaded && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* حلقه درخشان دور لوگو */}
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.05, 1],
|
||||
opacity: [0.5, 0.8, 0.5]
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="absolute -inset-2 rounded-3xl bg-gradient-to-r from-primary/20 via-primary/40 to-primary/20 -z-10 blur-xl"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{/*
|
||||
{restaurantName && (
|
||||
<motion.h1
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.3, duration: 0.5 }}
|
||||
className="text-3xl font-bold text-foreground text-center"
|
||||
>
|
||||
{restaurantName}
|
||||
</motion.h1>
|
||||
)} */}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5, duration: 0.5 }}
|
||||
className="flex gap-3 mt-2"
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: 0.2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: 0.4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -42,10 +42,10 @@ export const TabContainerClassNames: TabContainerClassName = {
|
||||
headerActive: '',
|
||||
icon: 'text-xs transition-all duration-150',
|
||||
iconDeactive: 'stroke-disabled-text',
|
||||
iconActive: 'stroke-black dark:stroke-foreground',
|
||||
iconActive: 'stroke-primary dark:stroke-foreground',
|
||||
title: 'text-xs transition-all duration-150',
|
||||
titleDeactive: 'text-disabled-text',
|
||||
titleActive: 'text-black dark:text-foreground'
|
||||
titleActive: 'text-primary dark:text-foreground'
|
||||
}
|
||||
|
||||
function TabContainer({ className = TabContainerClassNames, changeType = TabContainerRenderType.RENDERER, defaultIndex = 0, itemRenderer, children, ...restProps }: Props) {
|
||||
|
||||
@@ -44,7 +44,7 @@ function TopBar({ profileDropState, toggleProfileDropState, toggleMenuState }: P
|
||||
S
|
||||
</div> */}
|
||||
{/* <div
|
||||
className={`z-50 top-0 left-0 w-full h-full px-5 py-15 md:px-15 xl:p-0 fixed xl:relative bg-[#00000044] xl:bg-transparent backdrop-blur-sm xl:backdrop-blur-none justify-center xl:block ${searchModalState ? 'flex' : 'hidden'}`}>
|
||||
className={`z-50 top-0 left-0 w-full h-full px-5 py-15 md:px-15 xl:p-0 fixed xl:relative bg-primary/25 xl:bg-transparent backdrop-blur-sm xl:backdrop-blur-none justify-center xl:block ${searchModalState ? 'flex' : 'hidden'}`}>
|
||||
<div
|
||||
className={`w-full h-full absolute`}
|
||||
onClick={toggleSearchModalState}>
|
||||
|
||||
@@ -38,7 +38,7 @@ function DialogOverlay({
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-primary/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -21,7 +21,7 @@ function RateBar({ percentage, content, className = '' }: Props) {
|
||||
>
|
||||
</motion.span>
|
||||
</span>
|
||||
<Star1 className='fill-black dark:fill-foreground me-1 mb-1' size={12} />
|
||||
<Star1 className='fill-primary dark:fill-foreground me-1 mb-1' size={12} />
|
||||
<span className='text-xs font-medium'>{content}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -13,11 +13,11 @@ type Props = {
|
||||
}
|
||||
|
||||
const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
|
||||
{ value: '5', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '4', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '3', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '2', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '1', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '5', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '4', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '3', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '2', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '1', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
]
|
||||
|
||||
function RateSelectionBar({ name, content = defaultContent, className = '', onValueChange, defaultValue = [5] }: Props) {
|
||||
@@ -36,7 +36,7 @@ function RateSelectionBar({ name, content = defaultContent, className = '', onVa
|
||||
{content?.map((value, index) => {
|
||||
return (
|
||||
<span key={index} className='flex items-center gap-1'>
|
||||
<Star1 className='fill-black dark:fill-neutral-200 me-0.5 mb-1' size={12} />
|
||||
<Star1 className='fill-primary dark:fill-neutral-200 me-0.5 mb-1' size={12} />
|
||||
<span className='text-xs font-medium'>{value.value}</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,128 @@
|
||||
'use client';
|
||||
|
||||
import usePreference from '@/hooks/helpers/usePreference';
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||
import SplashScreen from '@/components/overlays/SplashScreen';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
// تابع برای اعمال رنگ primary
|
||||
function applyPrimaryColor(colorHex: string) {
|
||||
const root = document.documentElement;
|
||||
const hex = colorHex.replace('#', '');
|
||||
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
||||
|
||||
const lightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const chroma = (max - min) * 0.15;
|
||||
|
||||
let hue = 0;
|
||||
if (chroma !== 0) {
|
||||
if (max === r) {
|
||||
hue = ((g - b) / (max - min) + (g < b ? 6 : 0)) * 60;
|
||||
} else if (max === g) {
|
||||
hue = ((b - r) / (max - min) + 2) * 60;
|
||||
} else {
|
||||
hue = ((r - g) / (max - min) + 4) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
const oklchColor = `oklch(${lightness.toFixed(3)} ${chroma.toFixed(3)} ${hue.toFixed(1)})`;
|
||||
root.style.setProperty('--primary', oklchColor);
|
||||
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
const foregroundOklch = brightness > 0.5 ? 'oklch(0 0 0)' : 'oklch(1 0 0)';
|
||||
root.style.setProperty('--primary-foreground', foregroundOklch);
|
||||
}
|
||||
|
||||
function PreferenceWrapper({ children }: Props) {
|
||||
const { state: nightMode } = usePreference('night-mode', false);
|
||||
const { data: aboutData, isSuccess } = useGetAbout();
|
||||
|
||||
// بررسی اینکه آیا باید اسپلش نمایش داده بشه
|
||||
const [showSplash, setShowSplash] = useState(() => {
|
||||
if (typeof window === 'undefined') return true;
|
||||
const navigationEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
|
||||
const isReload = navigationEntries.length > 0 && navigationEntries[0].type === 'reload';
|
||||
return !sessionStorage.getItem('app-loaded') || isReload;
|
||||
});
|
||||
|
||||
const [cachedLogo, setCachedLogo] = useState<string | null>(null);
|
||||
const [cachedName, setCachedName] = useState<string | null>(null);
|
||||
|
||||
// بارگذاری رنگ و اطلاعات از localStorage در اولین رندر
|
||||
useEffect(() => {
|
||||
const theme = nightMode //|| window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
// بارگذاری رنگ
|
||||
const savedColor = localStorage.getItem('theme-primary-color');
|
||||
if (savedColor) {
|
||||
applyPrimaryColor(savedColor);
|
||||
}
|
||||
|
||||
// بارگذاری لوگو و نام
|
||||
const savedLogo = localStorage.getItem('restaurant-logo');
|
||||
const savedName = localStorage.getItem('restaurant-name');
|
||||
if (savedLogo) setCachedLogo(savedLogo);
|
||||
if (savedName) setCachedName(savedName);
|
||||
|
||||
// ثبت اینکه app لود شده
|
||||
sessionStorage.setItem('app-loaded', 'true');
|
||||
}, []);
|
||||
|
||||
// اعمال رنگ primary از about (با ذخیره در localStorage)
|
||||
useEffect(() => {
|
||||
if (aboutData?.data?.menuColor) {
|
||||
// ذخیره رنگ و اطلاعات در localStorage
|
||||
localStorage.setItem('theme-primary-color', aboutData.data.menuColor);
|
||||
|
||||
// ذخیره لوگو و نام برای اسپلش
|
||||
if (aboutData.data.logo) {
|
||||
localStorage.setItem('restaurant-logo', aboutData.data.logo);
|
||||
}
|
||||
if (aboutData.data.name) {
|
||||
localStorage.setItem('restaurant-name', aboutData.data.name);
|
||||
}
|
||||
|
||||
applyPrimaryColor(aboutData.data.menuColor);
|
||||
}
|
||||
}, [aboutData?.data?.menuColor]);
|
||||
|
||||
// اعمال تم تاریک/روشن
|
||||
useEffect(() => {
|
||||
const theme = nightMode;
|
||||
document.documentElement.setAttribute("data-theme", theme ? "dark" : "light");
|
||||
}, [nightMode]);
|
||||
|
||||
// بستن اسپلش بعد از مدت زمان مشخص
|
||||
useEffect(() => {
|
||||
if (showSplash) {
|
||||
const timer = setTimeout(() => {
|
||||
setShowSplash(false);
|
||||
}, 2500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [showSplash]);
|
||||
|
||||
// استفاده از دادههای cache شده برای اسپلش
|
||||
const splashLogo = aboutData?.data?.logo || cachedLogo || undefined;
|
||||
const splashName = aboutData?.data?.name || cachedName || undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{showSplash && (
|
||||
<SplashScreen
|
||||
logo={splashLogo}
|
||||
restaurantName={splashName}
|
||||
duration={2500}
|
||||
/>
|
||||
)}
|
||||
{!showSplash && children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface UseThemeColorProps {
|
||||
primaryColor?: string | null;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* تبدیل hex به RGB normalized (0-1)
|
||||
*/
|
||||
function hexToRgbNormalized(
|
||||
hex: string
|
||||
): { r: number; g: number; b: number } | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16) / 255,
|
||||
g: parseInt(result[2], 16) / 255,
|
||||
b: parseInt(result[3], 16) / 255,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* تبدیل RGB به OKLCH (تقریبی)
|
||||
*/
|
||||
function rgbToOklch(r: number, g: number, b: number): string {
|
||||
// محاسبه Lightness تقریبی
|
||||
const lightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
|
||||
// محاسبه Chroma و Hue (ساده شده)
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const chroma = (max - min) * 0.15; // مقدار تقریبی
|
||||
|
||||
let hue = 0;
|
||||
if (chroma !== 0) {
|
||||
if (max === r) {
|
||||
hue = ((g - b) / (max - min) + (g < b ? 6 : 0)) * 60;
|
||||
} else if (max === g) {
|
||||
hue = ((b - r) / (max - min) + 2) * 60;
|
||||
} else {
|
||||
hue = ((r - g) / (max - min) + 4) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
return `oklch(${lightness.toFixed(3)} ${chroma.toFixed(3)} ${hue.toFixed(
|
||||
1
|
||||
)})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* هوک برای تغییر دینامیک رنگ primary بر اساس رنگ دریافتی از about
|
||||
*/
|
||||
export function useThemeColor({
|
||||
primaryColor,
|
||||
enabled = true,
|
||||
}: UseThemeColorProps) {
|
||||
useEffect(() => {
|
||||
if (!enabled || !primaryColor) return;
|
||||
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
|
||||
// اگر رنگ hex هست، آن را به oklch تبدیل میکنیم
|
||||
if (primaryColor.startsWith("#")) {
|
||||
const rgb = hexToRgbNormalized(primaryColor);
|
||||
if (rgb) {
|
||||
const oklchColor = rgbToOklch(rgb.r, rgb.g, rgb.b);
|
||||
|
||||
// اعمال رنگ primary
|
||||
root.style.setProperty("--primary", oklchColor);
|
||||
|
||||
// محاسبه رنگ foreground متناسب
|
||||
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||||
const foregroundOklch =
|
||||
brightness > 0.5 ? "oklch(0 0 0)" : "oklch(1 0 0)";
|
||||
root.style.setProperty("--primary-foreground", foregroundOklch);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error setting theme color:", error);
|
||||
}
|
||||
}, [primaryColor, enabled]);
|
||||
}
|
||||
Reference in New Issue
Block a user