refactor(frontend): move repeated Tailwind chains into BEM classes
This commit is contained in:
@@ -13,22 +13,20 @@ export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1 rounded-md border border-border bg-surface/90 p-1 text-xs shadow-card',
|
||||
'language-switcher',
|
||||
className,
|
||||
)}
|
||||
aria-label={t('language.switch')}
|
||||
>
|
||||
<Languages className="ml-1 h-3.5 w-3.5 text-muted" aria-hidden />
|
||||
<Languages className="language-switcher__icon" aria-hidden />
|
||||
{languages.map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => setLanguage(item.value)}
|
||||
className={cn(
|
||||
'rounded px-2 py-1 font-semibold transition-colors',
|
||||
language === item.value
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted hover:bg-surface-muted hover:text-ink',
|
||||
'language-switcher__button',
|
||||
language === item.value && 'language-switcher__button--active',
|
||||
)}
|
||||
aria-pressed={language === item.value}
|
||||
title={item.value === 'ru' ? t('language.ru') : t('language.en')}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Footer } from './Footer';
|
||||
|
||||
export function AppShell() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<div className="app-shell">
|
||||
<Header />
|
||||
<main className="container-page flex-1 py-6 sm:py-10">
|
||||
<main className="app-shell__main">
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@@ -17,15 +17,15 @@ export function Footer() {
|
||||
});
|
||||
|
||||
return (
|
||||
<footer className="container-page mt-10 py-6 text-xs text-muted">
|
||||
<div className="flex flex-col items-center justify-between gap-2 sm:flex-row">
|
||||
<div className="flex items-center gap-2">
|
||||
<footer className="app-footer">
|
||||
<div className="app-footer__inner">
|
||||
<div className="app-footer__brand">
|
||||
<Gift className="h-4 w-4" aria-hidden />
|
||||
<span className="font-display text-sm">{t('app.name')}</span>
|
||||
<span className="app-footer__brand-name">{t('app.name')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="app-footer__meta">
|
||||
<span>{t('footer.frontend', { version: FRONTEND_VERSION })}</span>
|
||||
<span className="opacity-50">·</span>
|
||||
<span className="app-footer__separator">·</span>
|
||||
<span>{t('footer.backend', { version: data?.backend ?? '...' })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,21 +44,21 @@ export function Header() {
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<header className="container-page pt-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-surface/80 px-4 py-3 backdrop-blur">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<span className="inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground shadow-card">
|
||||
<header className="app-header">
|
||||
<div className="app-header__inner">
|
||||
<Link to="/" className="app-header__brand">
|
||||
<span className="app-header__brand-mark">
|
||||
<Gift className="h-4 w-4" />
|
||||
</span>
|
||||
<div>
|
||||
<div className="font-display text-lg leading-tight">{t('app.name')}</div>
|
||||
<div className="text-xs text-muted">
|
||||
<div className="app-header__brand-title">{t('app.name')}</div>
|
||||
<div className="app-header__brand-subtitle">
|
||||
{t('header.signedInAs', { name: user.displayName })}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<nav className="flex flex-wrap items-center gap-1">
|
||||
<nav className="app-header__nav">
|
||||
{links.map((l) => (
|
||||
<NavLink
|
||||
key={l.to}
|
||||
@@ -66,8 +66,8 @@ export function Header() {
|
||||
end={l.end}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
|
||||
isActive ? 'bg-primary text-primary-foreground shadow-card' : 'text-ink hover:bg-ink/5',
|
||||
'app-header__nav-link',
|
||||
isActive && 'app-header__nav-link--active',
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -80,8 +80,8 @@ export function Header() {
|
||||
to={`/u/${friend.data.slug}`}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
|
||||
isActive ? 'bg-primary text-primary-foreground shadow-card' : 'text-ink hover:bg-ink/5',
|
||||
'app-header__nav-link',
|
||||
isActive && 'app-header__nav-link--active',
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -91,7 +91,7 @@ export function Header() {
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="app-header__actions">
|
||||
<LanguageSwitcher />
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -40,9 +40,9 @@ export function Modal({
|
||||
if (!open) return null;
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-6">
|
||||
<div className="modal">
|
||||
<div
|
||||
className="absolute inset-0 bg-ink/40 backdrop-blur-sm animate-fade-in-up"
|
||||
className="modal__backdrop"
|
||||
onClick={onClose}
|
||||
aria-hidden
|
||||
/>
|
||||
@@ -50,27 +50,21 @@ export function Modal({
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
className={cn(
|
||||
'relative w-full bg-surface shadow-pop animate-fade-in-up',
|
||||
'rounded-t-xl sm:rounded-xl',
|
||||
size === 'md' ? 'sm:max-w-lg' : 'sm:max-w-2xl',
|
||||
'max-h-[90vh] overflow-hidden flex flex-col',
|
||||
'modal__panel',
|
||||
size === 'md' ? 'modal__panel--md' : 'modal__panel--lg',
|
||||
)}
|
||||
>
|
||||
<header className="flex items-start justify-between gap-4 border-b border-border px-5 py-4">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-lg font-semibold text-ink">{title}</h2>
|
||||
{description && <p className="mt-1 text-sm text-muted">{description}</p>}
|
||||
<header className="modal__header">
|
||||
<div className="modal__title-wrap">
|
||||
<h2 className="modal__title">{title}</h2>
|
||||
{description && <p className="modal__description">{description}</p>}
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} aria-label="Close">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</header>
|
||||
<div className="overflow-y-auto px-5 py-5">{children}</div>
|
||||
{footer && (
|
||||
<footer className="flex items-center justify-end gap-2 border-t border-border px-5 py-4">
|
||||
{footer}
|
||||
</footer>
|
||||
)}
|
||||
<div className="modal__body">{children}</div>
|
||||
{footer && <footer className="modal__footer">{footer}</footer>}
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
|
||||
Reference in New Issue
Block a user