feat(admin): add orange rgb(241,119,50) as an exact brand-color option

Revert the earlier mis-scoped orange on the personalization panel and
instead expose rgb(241,119,50) as a selectable 'رنگ اصلی' swatch. When
picked it overrides --primary/-600/-700/-soft/-soft2/-ring inline with
exact hex, so every var(--primary) consumer across the whole admin
(buttons, active nav, rings) becomes that orange. Non-fixed hues keep
the oklch hue pipeline (inline overrides removed on switch).

- uiStore: BrandHue interface + optional fixed hex; shared applyBrand()
  helper handles fixed vs hue, dark-aware soft tints
- AdminLayout: reuse applyBrand() (no divergence with store)
- Topbar: swatch shows the exact fixed color
- tests: fixed-color apply, dark soft, and override-cleanup on switch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-15 17:06:24 +03:30
co-authored by Claude Opus 4.8
parent 89ebeb8fce
commit 62e0cdb518
5 changed files with 83 additions and 13 deletions
+49 -6
View File
@@ -1,15 +1,33 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const HUES = [
/**
* رنگ‌های ثابت (hex) برای گزینه‌هایی که باید دقیقاً یک رنگ مشخص باشند و نه
* محاسبه‌شده از hue (فرمول oklch با L ثابت نمی‌تواند رنگ روشن دقیق بدهد).
*/
export interface BrandFixed {
primary: string; p600: string; p700: string;
soft: string; soft2: string; softDark: string; soft2Dark: string; ring: string;
}
export interface BrandHue { name: string; h: number; c: number; fixed?: BrandFixed; }
export const HUES: BrandHue[] = [
{ name: 'بنفش', h: 277, c: 0.177 },
{ name: 'آبی پزشکی', h: 256, c: 0.15 },
{ name: 'فیروزه‌ای', h: 205, c: 0.12 },
{ name: 'سبز سلامت', h: 162, c: 0.13 },
{ name: 'نیلی', h: 272, c: 0.16 },
] as const;
export type BrandHue = (typeof HUES)[number];
{
name: 'نارنجی', h: 45, c: 0.16,
// رنگ اختصاصی rgb(241,119,50) — با انتخابش رنگ اصلی کل سایت دقیقاً همین می‌شود
fixed: {
primary: 'rgb(241, 119, 50)', p600: '#db5a1f', p700: '#b74d16',
soft: '#fdeee4', soft2: '#fbd9c4',
softDark: '#3a2b23', soft2Dark: '#4a3527',
ring: 'rgba(241, 119, 50, 0.32)',
},
},
];
interface UiState {
sidebarOpen: boolean;
@@ -25,13 +43,38 @@ interface UiState {
toggleSettingsPanel: () => void;
}
const FIXED_VAR_KEYS = [
'--primary', '--primary-600', '--primary-700',
'--primary-soft', '--primary-soft2', '--ring',
];
/**
* اعمال رنگ برند روی ریشه. اگر hue رنگ ثابت داشته باشد، مقادیر hex را مستقیماً
* inline می‌گذارد (روی oklch/base غالب می‌شود)؛ وگرنه override را پاک می‌کند تا
* فرمول hue‌محورِ stylesheet دوباره فعال شود. hue/chroma همیشه ست می‌شوند.
*/
export function applyBrand(root: HTMLElement, brandHue: BrandHue, darkMode: boolean) {
root.style.setProperty('--brand-h', String(brandHue.h));
root.style.setProperty('--brand-c', String(brandHue.c));
const f = brandHue.fixed;
if (f) {
root.style.setProperty('--primary', f.primary);
root.style.setProperty('--primary-600', f.p600);
root.style.setProperty('--primary-700', f.p700);
root.style.setProperty('--primary-soft', darkMode ? f.softDark : f.soft);
root.style.setProperty('--primary-soft2', darkMode ? f.soft2Dark : f.soft2);
root.style.setProperty('--ring', f.ring);
} else {
FIXED_VAR_KEYS.forEach((k) => root.style.removeProperty(k));
}
}
function applyTheme(state: Pick<UiState, 'darkMode' | 'density' | 'brandHue'>) {
const root = document.documentElement;
root.classList.toggle('dark', state.darkMode);
root.setAttribute('data-theme', state.darkMode ? 'dark' : 'light');
root.setAttribute('data-density', state.density);
root.style.setProperty('--brand-h', String(state.brandHue.h));
root.style.setProperty('--brand-c', String(state.brandHue.c));
applyBrand(root, state.brandHue, state.darkMode);
}
export const useUiStore = create<UiState>()(