fix theme and language buttons
This commit is contained in:
@@ -93,7 +93,7 @@ export default function AdminDashboardLayout({ children }: { children: React.Rea
|
|||||||
</svg>
|
</svg>
|
||||||
{dict.logout}
|
{dict.logout}
|
||||||
</button>
|
</button>
|
||||||
<div className="mt-4 flex flex-col items-center gap-3 border-t border-stone-200/80 pt-4 dark:border-blue-900">
|
<div className="mt-4 flex items-center gap-2 border-t border-stone-200/80 pt-4 dark:border-blue-900">
|
||||||
<AdminLanguageSwitcher />
|
<AdminLanguageSwitcher />
|
||||||
<AdminThemeSwitcher />
|
<AdminThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,68 +160,146 @@ export function useAdminI18n() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AdminLanguageSwitcher() {
|
export function AdminLanguageSwitcher() {
|
||||||
const { language, setLanguage, dict } = useAdminI18n()
|
const { language, setLanguage } = useAdminI18n()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [embedded, setEmbedded] = useState(false)
|
const [embedded, setEmbedded] = useState(false)
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEmbedded(window.self !== window.top)
|
setEmbedded(window.self !== window.top)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
function onMouseDown(e: MouseEvent) {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', onMouseDown)
|
||||||
|
return () => document.removeEventListener('mousedown', onMouseDown)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
if (embedded) return null
|
if (embedded) return null
|
||||||
|
|
||||||
|
const current = languageMeta[language]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2 py-1 shadow-sm transition-colors dark:border-blue-800 dark:bg-[#0d1b38]/85">
|
<div ref={ref} className="relative flex-1">
|
||||||
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-stone-500 dark:text-stone-400">{dict.language}</span>
|
<button
|
||||||
{(['en', 'fr', 'ar'] as AdminLanguage[]).map((value) => {
|
type="button"
|
||||||
const active = value === language
|
onClick={() => setOpen((o) => !o)}
|
||||||
const meta = languageMeta[value]
|
className="flex w-full items-center justify-between gap-1.5 rounded-xl border border-stone-200/80 bg-white/95 px-3 py-2 text-xs font-semibold text-stone-700 shadow-sm transition hover:bg-stone-50 dark:border-blue-800 dark:bg-[#0d1b38]/85 dark:text-stone-200 dark:hover:bg-[#162038]"
|
||||||
return (
|
>
|
||||||
<button
|
<span className="flex items-center gap-1.5">
|
||||||
key={value}
|
<span aria-hidden="true">{current.flag}</span>
|
||||||
type="button"
|
<span>{current.shortLabel}</span>
|
||||||
onClick={() => setLanguage(value)}
|
</span>
|
||||||
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
|
<svg className={`h-3 w-3 text-stone-400 transition-transform dark:text-stone-500 ${open ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
active ? 'bg-blue-900 text-white dark:bg-orange-400 dark:text-white' : 'text-stone-600 hover:bg-stone-100 dark:text-stone-300 dark:hover:bg-[#162038]'
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||||
}`}
|
</svg>
|
||||||
>
|
</button>
|
||||||
<span className="inline-flex items-center gap-1.5">
|
|
||||||
<span aria-hidden="true">{meta.flag}</span>
|
{open && (
|
||||||
<span>{meta.shortLabel}</span>
|
<div className="absolute bottom-full left-0 mb-1.5 w-full overflow-hidden rounded-xl border border-stone-200/80 bg-white shadow-lg dark:border-blue-800 dark:bg-[#0d1b38]">
|
||||||
</span>
|
{(['en', 'fr', 'ar'] as AdminLanguage[]).map((value) => {
|
||||||
</button>
|
const meta = languageMeta[value]
|
||||||
)
|
const active = value === language
|
||||||
})}
|
return (
|
||||||
|
<button
|
||||||
|
key={value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => { setLanguage(value); setOpen(false) }}
|
||||||
|
className={`flex w-full items-center gap-2 px-3 py-2 text-xs font-semibold transition-colors ${
|
||||||
|
active
|
||||||
|
? 'bg-blue-900 text-white dark:bg-orange-500 dark:text-white'
|
||||||
|
: 'text-stone-700 hover:bg-stone-100 dark:text-stone-200 dark:hover:bg-[#162038]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">{meta.flag}</span>
|
||||||
|
<span>{meta.shortLabel}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdminThemeSwitcher() {
|
export function AdminThemeSwitcher() {
|
||||||
const { theme, setTheme, dict } = useAdminI18n()
|
const { theme, setTheme, dict } = useAdminI18n()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [embedded, setEmbedded] = useState(false)
|
const [embedded, setEmbedded] = useState(false)
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEmbedded(window.self !== window.top)
|
setEmbedded(window.self !== window.top)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
function onMouseDown(e: MouseEvent) {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', onMouseDown)
|
||||||
|
return () => document.removeEventListener('mousedown', onMouseDown)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
if (embedded) return null
|
if (embedded) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2 py-1 shadow-sm transition-colors dark:border-blue-800 dark:bg-[#0d1b38]/85">
|
<div ref={ref} className="relative flex-1">
|
||||||
{(['light', 'dark'] as AdminTheme[]).map((value) => {
|
<button
|
||||||
const active = value === theme
|
type="button"
|
||||||
return (
|
onClick={() => setOpen((o) => !o)}
|
||||||
<button
|
className="flex w-full items-center justify-between gap-1.5 rounded-xl border border-stone-200/80 bg-white/95 px-3 py-2 text-xs font-semibold text-stone-700 shadow-sm transition hover:bg-stone-50 dark:border-blue-800 dark:bg-[#0d1b38]/85 dark:text-stone-200 dark:hover:bg-[#162038]"
|
||||||
key={value}
|
>
|
||||||
type="button"
|
<span className="flex items-center gap-1.5">
|
||||||
onClick={() => setTheme(value)}
|
{theme === 'light' ? (
|
||||||
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
|
<svg className="h-3.5 w-3.5 text-orange-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
active ? 'bg-blue-900 text-white dark:bg-orange-400 dark:text-white' : 'text-stone-600 hover:bg-stone-100 dark:text-stone-300 dark:hover:bg-[#162038]'
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
||||||
}`}
|
</svg>
|
||||||
>
|
) : (
|
||||||
{value === 'light' ? dict.light : dict.dark}
|
<svg className="h-3.5 w-3.5 text-blue-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
</button>
|
<path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||||
)
|
</svg>
|
||||||
})}
|
)}
|
||||||
|
<span>{theme === 'light' ? dict.light : dict.dark}</span>
|
||||||
|
</span>
|
||||||
|
<svg className={`h-3 w-3 text-stone-400 transition-transform dark:text-stone-500 ${open ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="absolute bottom-full left-0 mb-1.5 w-full overflow-hidden rounded-xl border border-stone-200/80 bg-white shadow-lg dark:border-blue-800 dark:bg-[#0d1b38]">
|
||||||
|
{(['light', 'dark'] as AdminTheme[]).map((value) => {
|
||||||
|
const active = value === theme
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => { setTheme(value); setOpen(false) }}
|
||||||
|
className={`flex w-full items-center gap-2 px-3 py-2 text-xs font-semibold transition-colors ${
|
||||||
|
active
|
||||||
|
? 'bg-blue-900 text-white dark:bg-orange-500 dark:text-white'
|
||||||
|
: 'text-stone-700 hover:bg-stone-100 dark:text-stone-200 dark:hover:bg-[#162038]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{value === 'light' ? (
|
||||||
|
<svg className="h-3.5 w-3.5 text-orange-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="h-3.5 w-3.5 text-blue-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<span className={active ? 'text-inherit' : ''}>{value === 'light' ? dict.light : dict.dark}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user