dark mode v2

This commit is contained in:
hamid zarghami
2025-08-19 09:57:19 +03:30
parent 916b88731c
commit 8fb54cdd95
8 changed files with 56 additions and 52 deletions
+3 -2
View File
@@ -4,7 +4,8 @@ import persian from 'react-date-object/calendars/persian';
import persian_fa from 'react-date-object/locales/persian_fa';
import DateObject from 'react-date-object';
import { Calendar } from 'iconsax-react';
import { clx } from '../helpers/utils';
import { clx } from '@/helpers/utils';
import { getIconColor } from '@/utils/colorUtils';
type Props = {
onChange: (date: string) => void;
@@ -76,7 +77,7 @@ const DatePickerComponent: FC<Props> = (props: Props) => {
<Calendar
size={20}
color='black'
color={getIconColor('muted')}
className='absolute top-0 bottom-0 my-auto left-2 pointer-events-none'
/>
</div>
+7 -3
View File
@@ -1,7 +1,9 @@
import { FC, useState } from 'react'
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
import EmojiPicker, { EmojiClickData, Theme as EmojiTheme } from 'emoji-picker-react'
import { EmojiHappy } from 'iconsax-react'
import { getIconColor } from '@/utils/colorUtils'
import { useOutsideClick } from '@/hooks/useOutSideClick'
import { useTheme } from '@/contexts/ThemeContext'
interface EmojiReactionProps {
onEmojiSelect: (emoji: string) => void
@@ -10,6 +12,7 @@ interface EmojiReactionProps {
const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }) => {
const [isOpen, setIsOpen] = useState(false)
const { theme } = useTheme()
const containerRef = useOutsideClick(() => {
if (isOpen) {
@@ -30,14 +33,14 @@ const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }
<div ref={containerRef} className={`relative ${className}`}>
<EmojiHappy
size={24}
color='black'
color={getIconColor('primary')}
className='cursor-pointer hover:opacity-70 transition-opacity'
onClick={togglePicker}
/>
{isOpen && (
<div className='absolute bottom-10 right-0 z-50'>
<div className='bg-white rounded-lg shadow-lg border border-gray-200'>
<div className='bg-popover text-popover-foreground rounded-lg shadow-lg border border-border'>
<EmojiPicker
onEmojiClick={handleEmojiClick}
width={300}
@@ -45,6 +48,7 @@ const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }
searchDisabled
skinTonesDisabled
theme={theme === 'dark' ? EmojiTheme.DARK : EmojiTheme.LIGHT}
previewConfig={{
defaultCaption: 'یک ایموجی انتخاب کنید',
defaultEmoji: '1f60a'
+2 -1
View File
@@ -1,6 +1,7 @@
import { FC, useEffect, useState } from 'react'
import { Star1 } from 'iconsax-react'
import { useMoveToFavorite, useUnFavoriteMessage } from '../hooks/useEmailData';
import { getIconColor } from '@/utils/colorUtils'
const Favorite: FC<{ flagged: boolean, id: string, mailbox: string }> = ({ flagged, id, mailbox }) => {
@@ -29,7 +30,7 @@ const Favorite: FC<{ flagged: boolean, id: string, mailbox: string }> = ({ flagg
<Star1
variant={isFavorite ? 'Bold' : 'Outline'}
size={19}
color={isFavorite ? '#FFC107' : '#8C90A3'}
color={isFavorite ? '#FFC107' : getIconColor('muted')}
className='cursor-pointer select-none'
/>
</div>
+2 -2
View File
@@ -144,7 +144,7 @@ const Header: FC<{
onClick={handleDownloadEmail}
>
<div className='flex gap-2 items-center xl:px-5'>
<DocumentDownload size={20} color={getIconColor('primary')} />
<DocumentDownload size={20} color={'#000000'} />
<span className='pt-0.5 xl:block hidden'>{t('mail.download')}</span>
</div>
</Button>
@@ -153,7 +153,7 @@ const Header: FC<{
onClick={handlePrint}
>
<div className='flex gap-2 items-center xl:px-5'>
<Printer size={20} color={getIconColor('primary')} />
<Printer size={20} color={'#000000'} />
<span className='pt-0.5 xl:block hidden'>{t('mail.print')}</span>
</div>
</Button>
@@ -1,4 +1,5 @@
import { Star1 } from 'iconsax-react'
import { getIconColor } from '@/utils/colorUtils'
import { FC, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useMoveToFavorite, useUnFavoriteMessage } from '../hooks/useEmailData'
@@ -25,7 +26,7 @@ const MessageFavorite: FC<{ flagged: boolean }> = ({ flagged }) => {
})
}
setIsFavorite(!isFavorite)
}} className='lg:size-[18px] size-[20px]' variant={isFavorite ? 'Bold' : 'Outline'} color={isFavorite ? '#FFC107' : 'black'} />
}} className='lg:size-[18px] size-[20px]' variant={isFavorite ? 'Bold' : 'Outline'} color={isFavorite ? '#FFC107' : getIconColor('muted')} />
)
}
@@ -2,60 +2,60 @@ import { FC } from 'react'
const SkeletonDetail: FC = () => {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='bg-card rounded-4xl p-8'>
<div className='animate-pulse'>
{/* Header Skeleton */}
<div className='h-6 bg-gray-200 rounded w-48 mb-6'></div>
<div className='h-6 bg-secondary rounded w-48 mb-6'></div>
{/* Subject and Tags Section */}
<div className='mt-6 flex xl:flex-row flex-col justify-between xl:items-center'>
<div className='flex xl:flex-row flex-col-reverse gap-4 xl:items-center'>
<div className='flex gap-1 items-center'>
<div className='h-4 bg-gray-200 rounded w-12 hidden xl:block'></div>
<div className='h-5 bg-gray-200 rounded w-40'></div>
<div className='h-4 bg-secondary rounded w-12 hidden xl:block'></div>
<div className='h-5 bg-secondary rounded w-40'></div>
</div>
<div className='flex gap-2'>
<div className='h-6 xl:h-10 bg-gray-200 rounded-full w-20'></div>
<div className='h-6 xl:h-10 bg-gray-200 rounded-full w-24'></div>
<div className='h-6 xl:h-10 bg-secondary rounded-full w-20'></div>
<div className='h-6 xl:h-10 bg-secondary rounded-full w-24'></div>
</div>
</div>
<div className='h-4 bg-gray-200 rounded w-24'></div>
<div className='h-4 bg-secondary rounded w-24'></div>
</div>
{/* Sender Info */}
<div className='mt-6 flex gap-4 items-center'>
<div className='size-10 bg-gray-200 rounded-full'></div>
<div className='h-4 bg-gray-200 rounded w-32'></div>
<div className='size-10 bg-secondary rounded-full'></div>
<div className='h-4 bg-secondary rounded w-32'></div>
</div>
{/* Email Content */}
<div className='xl:mt-8 mt-3 space-y-3'>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-3/4'></div>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-2/3'></div>
<div className='h-4 bg-secondary rounded w-full'></div>
<div className='h-4 bg-secondary rounded w-full'></div>
<div className='h-4 bg-secondary rounded w-3/4'></div>
<div className='h-4 bg-secondary rounded w-full'></div>
<div className='h-4 bg-secondary rounded w-2/3'></div>
</div>
{/* Attachments Section */}
<div className='mt-10'>
<div className='h-4 bg-gray-200 rounded w-20 mb-4'></div>
<div className='flex border-b border-gray-200 pb-10 mt-4 flex-wrap gap-2.5 items-center'>
<div className='h-10 bg-gray-200 rounded-full w-36'></div>
<div className='h-4 bg-secondary rounded w-20 mb-4'></div>
<div className='flex border-b border-border pb-10 mt-4 flex-wrap gap-2.5 items-center'>
<div className='h-10 bg-secondary rounded-full w-36'></div>
</div>
</div>
{/* Action Buttons */}
<div className='flex mt-9 justify-end'>
<div className='flex gap-5'>
<div className='size-6 bg-gray-200 rounded'></div>
<div className='flex gap-2 items-center border-r border-gray-200 pr-5'>
<div className='w-[17px] h-[17px] bg-gray-200 rounded'></div>
<div className='h-4 bg-gray-200 rounded w-12'></div>
<div className='size-6 bg-secondary rounded'></div>
<div className='flex gap-2 items-center border-r border-border pr-5'>
<div className='w-[17px] h-[17px] bg-secondary rounded'></div>
<div className='h-4 bg-secondary rounded w-12'></div>
</div>
<div className='flex gap-2 items-center border-r border-gray-200 pr-5'>
<div className='w-[17px] h-[17px] bg-gray-200 rounded'></div>
<div className='h-4 bg-gray-200 rounded w-16'></div>
<div className='flex gap-2 items-center border-r border-border pr-5'>
<div className='w-[17px] h-[17px] bg-secondary rounded'></div>
<div className='h-4 bg-secondary rounded w-16'></div>
</div>
</div>
</div>
+9 -8
View File
@@ -16,6 +16,7 @@ import Forward from '@/components/newMessage/Forward'
import EmojiReaction from '@/components/EmojiReaction'
import { useAuthStore } from '@/pages/auth/store/AuthStore'
import { SendEmailDto } from './types/Types'
import { getIconColor } from '@/utils/colorUtils'
const DetailEmail: FC = () => {
@@ -130,8 +131,8 @@ const DetailEmail: FC = () => {
if (error || !messageDetail) {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='text-center text-red-500'>
<div className='bg-card rounded-4xl p-8'>
<div className='text-center text-destructive'>
خطا در بارگذاری پیام
</div>
</div>
@@ -139,7 +140,7 @@ const DetailEmail: FC = () => {
}
return (
<div className='bg-white rounded-4xl p-8'>
<div className='bg-card rounded-4xl p-8'>
<Header
flagged={messageDetail?.flagged || false}
mailBoxName={messageDetail?.mailboxName as MailboxEnum}
@@ -155,7 +156,7 @@ const DetailEmail: FC = () => {
<div className='flex gap-2 justify-between items-center'>
<Button
label={t(`mail.${messageDetail?.mailboxName}`)}
className='bg-[#ECEFF6] text-description w-fit px-6 rounded-full h-6 xl:h-10 text-[10px] xl:text-sm'
className='bg-secondary text-description w-fit px-6 rounded-full h-6 xl:h-10 text-[10px] xl:text-sm'
/>
<div className='lg:hidden flex justify-end text-description xl:text-sm text-[10px]'>
@@ -195,7 +196,7 @@ const DetailEmail: FC = () => {
<div className='xl:mt-8 mt-3 text-[13px] leading-7 font-light'>
{messageDetail.html && messageDetail.html.length > 0 ? (
<div
className='email-content-container border border-gray-200 rounded-lg p-4 overflow-x-auto overflow-y-hidden'
className='email-content-container border border-border rounded-lg p-4 overflow-x-auto overflow-y-hidden'
style={{
direction: detectTextDirection(messageDetail.html.join(''))
}}
@@ -215,11 +216,11 @@ const DetailEmail: FC = () => {
<div className='flex border-b border-border pb-10 mt-4 flex-wrap gap-2.5 items-center'>
{messageDetail.attachments && messageDetail.attachments.length > 0 ? (
messageDetail.attachments.map((attachment, index) => (
<div key={index} className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<div key={index} className='bg-secondary text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<span>{attachment.filename || `attachment-${index + 1}`}</span>
<DocumentDownload
size={20}
color='#0038FF'
color={getIconColor('primary')}
className='cursor-pointer hover:opacity-70 transition-opacity'
onClick={() => handleDownloadAttachment(
attachment.id || `ATT${String(index + 1).padStart(5, '0')}`,
@@ -229,7 +230,7 @@ const DetailEmail: FC = () => {
</div>
))
) : (
<div className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<div className='bg-secondary text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<span>فایل ضمیمهای وجود ندارد</span>
</div>
)}
+7 -11
View File
@@ -1,24 +1,20 @@
/**
* تابع برای دریافت رنگ مناسب آیکون‌ها بر اساس theme
* @param variant - نوع رنگ: 'primary' یا 'muted'
* @returns رنگ مناسب برای theme فعلی
* منبع رنگ آیکون‌ها بر اساس متغیرهای CSS تم
* - primary: از `--primary`
* - muted: از `--muted-foreground`
* استفاده از متغیرهای CSS باعث می‌شود با تغییر تم، رنگ‌ها بدون نیاز به رفرش بروزرسانی شوند.
*/
export const getIconColor = (
variant: "primary" | "muted" = "primary"
): string => {
// تشخیص theme از document
const isDark = document.documentElement.classList.contains("dark");
if (variant === "muted") {
return isDark ? "#9ca3af" : "#6b7280"; // خاکستری متوسط
return "var(--muted-foreground)";
}
// primary color
return isDark ? "#ffffff" : "#000000"; // سفید برای dark، سیاه برای light
return "var(--primary)";
};
/**
* Hook برای استفاده در React components
* Hook ساده برای دسترسی به رنگ‌ها؛ به دلیل استفاده از CSS vars، نیازی به context نیست
*/
export const useIconColor = () => {
return {