feat(admin-ui): shadcn/ui + Tailwind v4 foundation + theme switch

This commit is contained in:
Александр Зимин
2026-05-10 21:46:47 +00:00
parent c11a70128c
commit 3bfa7cae17
10 changed files with 1448 additions and 26 deletions
@@ -0,0 +1,57 @@
import { useTranslation } from 'react-i18next'
import { Monitor, Moon, Sun } from 'lucide-react'
import { useTheme, type ThemePreference } from '@/stores/ThemeProvider'
import { cn } from '@/lib/utils'
/**
* Tri-state theme switch: Light / Dark / System.
*
* <p>Segmented control с тремя иконками. Активный — `--color-accent`
* background + `--color-on-accent` foreground. Click меняет `preference`
* в {@link ThemeProvider}, который пишет в localStorage и переключает
* `data-theme` на html.
*
* <p>System icon показывает «follow OS» — auto-update при OS theme change.
*/
export function ThemeSwitch() {
const { t } = useTranslation()
const { preference, setPreference } = useTheme()
const items: { id: ThemePreference; label: string; icon: typeof Sun }[] = [
{ id: 'light', label: t('theme.light'), icon: Sun },
{ id: 'dark', label: t('theme.dark'), icon: Moon },
{ id: 'system', label: t('theme.system'), icon: Monitor },
]
return (
<div
role="radiogroup"
aria-label={t('theme.label')}
className="inline-flex items-center rounded-md border border-line bg-surface p-0.5"
>
{items.map((item) => {
const Icon = item.icon
const active = preference === item.id
return (
<button
key={item.id}
type="button"
role="radio"
aria-checked={active}
aria-label={item.label}
title={item.label}
onClick={() => setPreference(item.id)}
className={cn(
'inline-flex h-6 w-6 items-center justify-center rounded transition-colors',
active
? 'bg-accent text-on-accent'
: 'text-mute hover:text-ink hover:bg-surface-2',
)}
>
<Icon size={13} strokeWidth={2.25} />
</button>
)
})}
</div>
)
}