تمبندی
نحوه سفارشیسازی و ایجاد تمهای سفارشی در دیزاین سیستم پرتو
سیستم تمبندی پرتو به شما امکان میدهد به راحتی بین تمهای مختلف جابجا شوید و حتی تمهای سفارشی خود را ایجاد کنید. سیستم ما بر اساس CSS Variables و Tailwind CSS ساخته شده است که انعطافپذیری کامل را فراهم میکند.
تغییر تم
برای تست تمهای مختلف، از سوئیچ زیر استفاده کنید:
تمهای پیشفرض
دیزاین سیستم پرتو با 3 تم پیشفرض عرضه میشود:
تم روشن (Light)
تم پیشفرض برای محیطهای روشن با پالت رنگی سفید و خاکستری روشن.
/* theme-light.css */
.light {
--background-default: 0deg 0% 100%;
--foreground-default: 222.2deg 47.4% 11.2%;
--foreground-light: 215.4deg 16.3% 46.9%;
--foreground-lighter: 216deg 12.2% 83.9%;
--foreground-muted: 220deg 8.9% 46.1%;
/* ... */
}بهترین برای:
- وبسایتهای عمومی
- اپلیکیشنهای اداری
- محیطهای با نور زیاد
تم تاریک (Dark)
تم مدرن برای محیطهای کمنور با پالت رنگی تیره و خاکستری تاریک.
/* theme-dark.css */
.dark {
--background-default: 222.2deg 84% 4.9%;
--foreground-default: 210deg 40% 98%;
--foreground-light: 217.9deg 10.6% 64.9%;
--foreground-lighter: 215deg 20.2% 65.1%;
--foreground-muted: 215.4deg 16.3% 46.9%;
/* ... */
}بهترین برای:
- کار طولانیمدت
- محیطهای کمنور
- کاربران حساس به نور
تم کلاسیک تاریک (Classic Dark)
نسخه کلاسیکتر تم تاریک با کنتراست بیشتر.
/* theme-classic-dark.css */
.classic-dark {
--background-default: 0deg 0% 8.6%;
--foreground-default: 0deg 0% 92.9%;
/* ... */
}بهترین برای:
- کاربران علاقهمند به ظاهر کلاسیک
- محیطهای حرفهای
نحوه پیادهسازی تمبندی
استفاده پایه
برای فعالسازی تم، کلاس مربوطه را به تگ html یا body اضافه کنید:
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="fa" dir="rtl" className="light">
<body>{children}</body>
</html>
)
}تغییر دینامیک تم
برای تغییر دینامیک تم، میتوانید از یک hook ساده استفاده کنید:
// hooks/use-theme.ts
'use client'
import { useEffect, useState } from 'react'
type Theme = 'light' | 'dark' | 'classic-dark'
export function useTheme() {
const [theme, setTheme] = useState<Theme>('light')
useEffect(() => {
// بارگذاری تم از localStorage
const savedTheme = localStorage.getItem('theme') as Theme
if (savedTheme) {
setTheme(savedTheme)
document.documentElement.className = savedTheme
}
}, [])
const changeTheme = (newTheme: Theme) => {
setTheme(newTheme)
localStorage.setItem('theme', newTheme)
document.documentElement.className = newTheme
}
return { theme, changeTheme }
}کامپوننت سوئیچ تم
// components/theme-switcher.tsx
'use client'
import { useTheme } from '@/hooks/use-theme'
import { Button } from '@parto/ui'
export function ThemeSwitcher() {
const { theme, changeTheme } = useTheme()
return (
<div className="flex gap-2">
<Button
variant={theme === 'light' ? 'default' : 'outline'}
onClick={() => changeTheme('light')}
>
روشن
</Button>
<Button
variant={theme === 'dark' ? 'default' : 'outline'}
onClick={() => changeTheme('dark')}
>
تاریک
</Button>
<Button
variant={theme === 'classic-dark' ? 'default' : 'outline'}
onClick={() => changeTheme('classic-dark')}
>
کلاسیک
</Button>
</div>
)
}تشخیص تم سیستم
برای تشخیص خودکار تم بر اساس تنظیمات سیستم:
'use client'
import { useEffect, useState } from 'react'
export function useSystemTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
// تشخیص تم سیستم
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
setTheme(mediaQuery.matches ? 'dark' : 'light')
// گوش دادن به تغییرات
const handler = (e: MediaQueryListEvent) => {
setTheme(e.matches ? 'dark' : 'light')
}
mediaQuery.addEventListener('change', handler)
return () => mediaQuery.removeEventListener('change', handler)
}, [])
return theme
}ساخت تم سفارشی
ایجاد فایل تم
برای ایجاد تم سفارشی، یک فایل CSS جدید بسازید:
/* theme-custom.css */
.custom-theme {
/* رنگهای پسزمینه */
--background-default: 280deg 40% 10%;
--background-200: 280deg 40% 12%;
--background-alternative-default: 260deg 40% 15%;
--background-alternative-200: 260deg 40% 18%;
/* سطوح */
--background-surface-75: 280deg 35% 15%;
--background-surface-100: 280deg 35% 18%;
--background-surface-200: 280deg 35% 22%;
--background-surface-300: 280deg 35% 26%;
--background-surface-400: 280deg 35% 30%;
/* رنگهای متن */
--foreground-default: 280deg 20% 95%;
--foreground-light: 280deg 15% 75%;
--foreground-lighter: 280deg 10% 60%;
--foreground-muted: 280deg 8% 50%;
--foreground-contrast: 0deg 0% 100%;
/* حاشیهها */
--border-default: 280deg 30% 25%;
--border-strong: 280deg 30% 35%;
--border-stronger: 280deg 30% 45%;
--border-overlay: 280deg 25% 30%;
--border-control: 280deg 25% 28%;
/* رنگ برند */
--brand-default: 280deg 80% 60%;
--brand-200: 280deg 80% 70%;
--brand-300: 280deg 80% 65%;
--brand-400: 280deg 80% 55%;
--brand-500: 280deg 80% 50%;
--brand-600: 280deg 80% 45%;
/* هشدار */
--warning-default: 45deg 95% 60%;
--warning-200: 45deg 95% 75%;
--warning-300: 45deg 95% 70%;
--warning-400: 45deg 95% 55%;
--warning-500: 45deg 95% 50%;
--warning-600: 45deg 95% 45%;
/* خطر */
--destructive-default: 0deg 85% 60%;
--destructive-200: 0deg 85% 75%;
--destructive-300: 0deg 85% 70%;
--destructive-400: 0deg 85% 55%;
--destructive-500: 0deg 85% 50%;
--destructive-600: 0deg 85% 45%;
}وارد کردن تم سفارشی
فایل تم را در globals.css وارد کنید:
/* app/globals.css */
@import "./supabase/global.css";
@import "./supabase/theme-light.css";
@import "./supabase/theme-dark.css";
@import "./supabase/theme-classic-dark.css";
@import "./theme-custom.css"; /* تم سفارشی شما */استفاده از تم سفارشی
<html lang="fa" dir="rtl" className="custom-theme">
<body>{children}</body>
</html>متغیرهای رنگی اصلی
تمام تمها باید این متغیرهای اصلی را تعریف کنند:
پسزمینهها
--background-default
--background-200
--background-alternative-default
--background-alternative-200
--background-selection
--background-control
--background-surface-75
--background-surface-100
--background-surface-200
--background-surface-300
--background-surface-400
--background-overlay-default
--background-overlay-hover
--background-button-default
--background-dialog-default
--background-dash-sidebar
--background-dash-canvasمتن (Foreground)
--foreground-default
--foreground-light
--foreground-lighter
--foreground-muted
--foreground-contrastحاشیهها
--border-default
--border-strong
--border-stronger
--border-overlay
--border-control
--border-alternative
--border-secondary
--border-button-default
--border-button-hoverرنگهای سمانتیک
--brand-default
--brand-200
--brand-300
--brand-400
--brand-500
--brand-600
--warning-default
--warning-200
--warning-300
--warning-400
--warning-500
--warning-600
--destructive-default
--destructive-200
--destructive-300
--destructive-400
--destructive-500
--destructive-600نکات مهم در طراحی تم
کنتراست مناسب
همیشه اطمینان حاصل کنید که رنگهای متن و پسزمینه کنتراست کافی دارند (WCAG AA حداقل 4.5:1):
// استفاده از ابزار محاسبه کنتراست
function calculateContrast(color1: string, color2: string): number {
// محاسبه contrast ratio
// باید حداقل 4.5:1 باشد
}سازگاری با Brand Colors
رنگهای برند باید در تمام تمها قابل تشخیص باشند:
/* تم روشن */
.light {
--brand-default: 153deg 60% 53%;
}
/* تم تاریک */
.dark {
--brand-default: 155deg 78% 40%;
}تست در شرایط واقعی
قبل از نهایی کردن تم:
- ✅ تست در دستگاههای مختلف
- ✅ تست با محتوای واقعی
- ✅ تست accessibility
- ✅ تست در نورهای مختلف
مستندسازی تم
برای هر تم سفارشی، موارد زیر را مستند کنید:
# تم سفارشی من
**هدف:** برای داشبورد مدیریتی
**پالت اصلی:** بنفش و آبی
**بهترین برای:** کار شبانه
## رنگهای اصلی
- Primary: #7C3AED (بنفش)
- Secondary: #3B82F6 (آبی)
- Background: #1A1B2E (تیره)
## نکات استفاده
- بهتر است در محیطهای کمنور استفاده شود
- برای متنهای طولانی مناسب نیستمثال کامل: سیستم تمبندی
// app/providers.tsx
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
type Theme = 'light' | 'dark' | 'classic-dark' | 'system'
type ResolvedTheme = 'light' | 'dark' | 'classic-dark'
interface ThemeContextType {
theme: Theme
resolvedTheme: ResolvedTheme
setTheme: (theme: Theme) => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>('system')
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light')
useEffect(() => {
// بارگذاری تم از localStorage
const savedTheme = localStorage.getItem('theme') as Theme
if (savedTheme) {
setThemeState(savedTheme)
}
}, [])
useEffect(() => {
const root = document.documentElement
if (theme === 'system') {
// تشخیص تم سیستم
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const systemTheme = mediaQuery.matches ? 'dark' : 'light'
setResolvedTheme(systemTheme)
root.className = systemTheme
// گوش دادن به تغییرات
const handler = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? 'dark' : 'light'
setResolvedTheme(newTheme)
root.className = newTheme
}
mediaQuery.addEventListener('change', handler)
return () => mediaQuery.removeEventListener('change', handler)
} else {
setResolvedTheme(theme)
root.className = theme
localStorage.setItem('theme', theme)
}
}, [theme])
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme)
}
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme must be used within ThemeProvider')
}
return context
}// app/layout.tsx
import { ThemeProvider } from './providers'
export default function RootLayout({ children }) {
return (
<html lang="fa" dir="rtl">
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}// components/theme-switcher.tsx
'use client'
import { useTheme } from '@/app/providers'
import { Button } from '@parto/ui'
export function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
return (
<div className="flex gap-2">
<Button
variant={theme === 'light' ? 'default' : 'outline'}
size="sm"
onClick={() => setTheme('light')}
>
☀️ روشن
</Button>
<Button
variant={theme === 'dark' ? 'default' : 'outline'}
size="sm"
onClick={() => setTheme('dark')}
>
🌙 تاریک
</Button>
<Button
variant={theme === 'classic-dark' ? 'default' : 'outline'}
size="sm"
onClick={() => setTheme('classic-dark')}
>
🌑 کلاسیک
</Button>
<Button
variant={theme === 'system' ? 'default' : 'outline'}
size="sm"
onClick={() => setTheme('system')}
>
💻 سیستم
</Button>
</div>
)
}Troubleshooting
مشکل: تم تغییر نمیکند
// بررسی کنید که کلاس روی html تنظیم شده است
useEffect(() => {
console.log('Current class:', document.documentElement.className)
}, [])مشکل: تم در refresh پاک میشود
// اطمینان حاصل کنید که localStorage را چک میکنید
useEffect(() => {
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
setTheme(savedTheme as Theme)
}
}, [])مشکل: Flash of wrong theme در بارگذاری
// در head قبل از React اجرا شود
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="fa" dir="rtl">
<head>
<script dangerouslySetInnerHTML={{
__html: `
(function() {
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme;
})();
`
}} />
</head>
<body>{children}</body>
</html>
)
}منابع مرتبط
- پالت رنگی - پالت کامل رنگی
- استفاده از رنگ - راهنمای استفاده از رنگها
- کلاسهای Tailwind - لیست کامل کلاسها