From 916b88731c0f16c6b06ec970fa76dc544772622a Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 18 Aug 2025 15:23:31 +0330 Subject: [PATCH] dark mode v1 --- src/App.tsx | 37 +-- src/components/Button.tsx | 37 +-- src/components/DefaultTableSkeleton.tsx | 9 +- src/components/EmailNotifications.tsx | 23 +- src/components/Input.tsx | 11 +- src/components/RowActionsDropdown.tsx | 82 +++---- src/components/Select.tsx | 9 +- src/components/Table.tsx | 42 ++-- src/components/Tabs.tsx | 4 +- src/components/ThemeToggle.tsx | 45 ++++ src/components/UploadBox.tsx | 10 +- src/components/UploadBoxDraggble.tsx | 21 +- src/components/UploadButton.tsx | 7 +- src/components/newMessage/EmailActions.tsx | 3 +- src/components/newMessage/EmailEditor.tsx | 2 +- .../newMessage/EmailFormActions.tsx | 4 +- src/components/newMessage/EmailInput.tsx | 10 +- src/components/newMessage/NewMessage.tsx | 5 +- src/contexts/ThemeContext.tsx | 70 ++++++ src/index.css | 229 +++++++++++++----- src/pages/Trash/List.tsx | 9 +- src/pages/archive/List.tsx | 9 +- src/pages/auth/Login.tsx | 14 +- src/pages/auth/components/LoginStep1.tsx | 9 +- src/pages/auth/components/LoginStep2.tsx | 12 +- src/pages/auth/components/LoginStep3.tsx | 8 +- src/pages/draft/List.tsx | 13 +- src/pages/favorite/List.tsx | 9 +- src/pages/notification/Notification.tsx | 21 +- src/pages/profile/Profile.tsx | 8 +- src/pages/profile/components/Email.tsx | 2 +- src/pages/profile/components/Phone.tsx | 4 +- src/pages/profile/components/Username.tsx | 2 +- src/pages/received/Components/Header.tsx | 7 +- .../received/Components/MessageArchive.tsx | 3 +- src/pages/received/Components/MessageSpam.tsx | 3 +- .../received/Components/MessageUnread.tsx | 3 +- src/pages/received/List.tsx | 13 +- src/pages/search/List.tsx | 15 +- src/pages/sent/List.tsx | 13 +- src/pages/setting/Setting.tsx | 9 +- .../setting/components/ChangePassword.tsx | 4 +- src/pages/setting/components/Enabled.tsx | 6 +- src/pages/setting/components/TwoFactor.tsx | 32 +-- src/pages/spam/List.tsx | 11 +- src/shared/Header.tsx | 58 ++--- src/shared/SideBar.tsx | 35 +-- src/shared/SideBarItem.tsx | 6 +- src/utils/colorUtils.ts | 28 +++ 49 files changed, 655 insertions(+), 371 deletions(-) create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/contexts/ThemeContext.tsx create mode 100644 src/utils/colorUtils.ts diff --git a/src/App.tsx b/src/App.tsx index 1cda332..e4aebcd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import Login from './pages/auth/Login'; import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext'; import SocketManagment from './lib/SocketManagment'; import NajvaToken from './components/NajvaToken'; +import { ThemeProvider } from './contexts/ThemeContext'; declare global { interface Window { @@ -121,23 +122,25 @@ const App: FC = () => { }, []); return ( - - - - {isLogin === 'isLogin' && userToken ? ( - -
- - - - ) : isLogin === 'isNotLogin' ? ( - - ) : null} - - - - - + + + + + {isLogin === 'isLogin' && userToken ? ( + +
+ + + + ) : isLogin === 'isNotLogin' ? ( + + ) : null} + + + + + + ) } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 4dd4cf6..b000aee 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,39 +1,40 @@ -import { FC, ReactNode } from 'react' +import { FC, ReactNode, ButtonHTMLAttributes } from 'react' import { clx } from '../helpers/utils' import MoonLoader from "react-spinners/ClipLoader" -interface ButtonProps { - label?: string; - children?: ReactNode; - className?: string; - loading?: boolean; - onClick?: () => void; - type?: 'button' | 'submit' | 'reset'; - disabled?: boolean; +type ButtonBaseProps = Omit, 'className' | 'children'> + +interface ButtonProps extends ButtonBaseProps { + label?: string + children?: ReactNode + className?: string + loading?: boolean variant?: 'primary' | 'secondary' } const Button: FC = (props) => { + const { label, children, className, loading, variant, type, disabled, ...rest } = props + const buttonClass = clx( - 'w-full bg-primary text-white cursor-pointer rounded-xl font-normal text-xs md:text-sm h-10 px-3 md:px-4 transition-all duration-200 hover:bg-opacity-90 disabled:opacity-50 disabled:cursor-not-allowed', - props.variant === 'secondary' && 'bg-[#ECEEF5] text-black', - props.className + 'w-full bg-primary text-primary-foreground cursor-pointer rounded-xl font-normal text-xs md:text-sm h-10 px-3 md:px-4 transition-all duration-200 hover:bg-opacity-90 disabled:opacity-50 disabled:cursor-not-allowed', + variant === 'secondary' && 'bg-[#ECEEF5] text-black', + className ) return ( ) diff --git a/src/components/DefaultTableSkeleton.tsx b/src/components/DefaultTableSkeleton.tsx index 9e7257a..19776c5 100644 --- a/src/components/DefaultTableSkeleton.tsx +++ b/src/components/DefaultTableSkeleton.tsx @@ -1,5 +1,5 @@ import { FC, Fragment } from 'react' -import Td from './Td' +import Td from '@/components/Td' import Skeleton from 'react-loading-skeleton' type Props = { @@ -8,15 +8,18 @@ type Props = { } const DefaultTableSkeleton: FC = ({ tdCount, trCount = 5 }) => { + const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark') + const baseColor = isDark ? '#2d2d30' : '#e5e7eb' + const highlightColor = isDark ? '#3c3c3c' : '#f3f4f6' return ( { Array.from({ length: trCount }).map((_, rowIndex) => ( - + { Array.from({ length: tdCount }).map((_, colIndex) => ( - + )) } diff --git a/src/components/EmailNotifications.tsx b/src/components/EmailNotifications.tsx index 08b7672..ad26eb6 100644 --- a/src/components/EmailNotifications.tsx +++ b/src/components/EmailNotifications.tsx @@ -3,6 +3,7 @@ import { useEmailWebSocket } from '@/hooks/useEmailWebSocket'; import { useEmailEvents, EmailPayload, UnreadCountPayload, EmailStatusPayload } from '@/hooks/useEmailEvents'; import { useNotificationSound } from '@/hooks/useNotificationSound'; import { Notification as NotificationIcon, VolumeHigh } from 'iconsax-react'; +import { getIconColor } from '@/utils/colorUtils'; interface EmailNotificationsProps { userToken: string; @@ -94,17 +95,17 @@ export const EmailNotifications: React.FC = ({ onClick={() => { playSound(); }} - className="p-2 rounded-full hover:bg-gray-100 transition-colors" + className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" title="تست صدا" > - +
setIsVisible(!isVisible)} > - + {unreadCount > 0 && ( {unreadCount > 99 ? '99+' : unreadCount} @@ -113,20 +114,20 @@ export const EmailNotifications: React.FC = ({
{isVisible && ( -
-
+
+

📧 نوتیفیکیشن‌های ایمیل

- {unreadCount} خوانده نشده + {unreadCount} خوانده نشده
{notifications.length === 0 ? ( -
ایمیل جدیدی وجود ندارد
+
ایمیل جدیدی وجود ندارد
) : ( notifications.map((email) => (
onEmailClick?.(email)} >
@@ -136,8 +137,8 @@ export const EmailNotifications: React.FC = ({ {email.hasAttachments && 📎}
{email.subject}
-
{email.preview}
-
+
{email.preview}
+
{new Date(email.timestamp).toLocaleString('fa-IR')}
diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 6ef0b9d..3bdb577 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -1,6 +1,7 @@ import { FC, InputHTMLAttributes, useEffect, useState } from 'react' import { clx } from '../helpers/utils'; import { Eye, SearchNormal } from 'iconsax-react'; +import { getIconColor } from '@/utils/colorUtils'; import Error from './Error'; type Variant = "floating_outlined" | "primary" | "search"; @@ -34,8 +35,8 @@ const Input: FC = (props: Props) => { const [search, setSearch] = useState('') const inputClass = clx( - 'w-full bg-white h-10 text-black block px-4 text-xs rounded-xl border border-border', - props.readOnly && 'bg-gray-100 border-0 text-description', + 'w-full input-surface h-10 block px-4 text-xs rounded-xl transition-colors', + props.readOnly && 'bg-muted border-0 text-muted-foreground', props.variant === 'search' && 'ps-10', props.className ); @@ -79,7 +80,7 @@ const Input: FC = (props: Props) => { return (
-