6882516340
Фаза 4 (AGENT.md 7.4): интерфейс для иконки, баннера, splash и акцентного цвета. - секция «Оформление» в настройках сервера: загрузка и удаление иконки, баннера и splash с превью, проверка типа и размера на клиенте, пресеты акцентного цвета; - иконка сервера показывается в рейке (заглушка-буквы остаётся, если файла нет), акцентный цвет переопределяет `--color-accent` внутри приложения и снимается при значении 0; - предпросмотр приглашения показывает splash фоном и иконку сервера; - ответ сервера обновляет карточку и список серверов — оформление видно без перезагрузки; - тесты: превью всех трёх видов, multipart-загрузка, удаление, применение и снятие акцента, отказ для не-изображения, скрытие секции без MANAGE_GUILD.
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { NavLink } from 'react-router';
|
|
|
|
import { Avatar } from '@/components/ui/Avatar';
|
|
|
|
interface GuildRailProps {
|
|
guilds: { id: string; name: string; icon_file_id?: string | undefined }[];
|
|
selectedGuildId: string | null;
|
|
canCreate: boolean;
|
|
onCreate: () => void;
|
|
}
|
|
|
|
/**
|
|
* Левая рейка: первой иконкой — «Друзья», ниже — серверы и кнопка создания.
|
|
* Личные беседы в рейку не попадают: они живут внутри раздела друзей.
|
|
*/
|
|
export function GuildRail({ guilds, selectedGuildId, canCreate, onCreate }: GuildRailProps) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<nav
|
|
aria-label={t('guilds.railLabel')}
|
|
className="flex w-[72px] shrink-0 flex-col items-center gap-2 overflow-y-auto border-r border-border/40 bg-surface-2/60 py-3"
|
|
>
|
|
<NavLink
|
|
to="/app/friends"
|
|
title={t('friends.rail')}
|
|
aria-label={t('friends.rail')}
|
|
data-testid="friends-rail-link"
|
|
className={({ isActive }) =>
|
|
`relative flex h-14 w-14 items-center justify-center rounded-[var(--radius-md)] border text-2xl transition-[border-radius,border-color,background-color,color,transform] duration-[var(--duration-fast)] ${
|
|
isActive
|
|
? 'gl-ring rounded-[var(--radius-lg)] border-accent bg-surface-1 text-accent'
|
|
: 'border-border bg-surface-1 text-fg-muted hover:-translate-y-0.5 hover:rounded-[var(--radius-lg)] hover:text-fg active:translate-y-0'
|
|
}`
|
|
}
|
|
>
|
|
{/* Облачко с двумя собеседниками: узнаваемая иконка личных бесед. */}
|
|
<svg
|
|
aria-hidden="true"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.8"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="h-7 w-7"
|
|
>
|
|
<path d="M4 5.5h16v10H9l-5 4z" />
|
|
<circle cx="9.5" cy="10.5" r="1.4" fill="currentColor" stroke="none" />
|
|
<circle cx="14.5" cy="10.5" r="1.4" fill="currentColor" stroke="none" />
|
|
</svg>
|
|
</NavLink>
|
|
|
|
<div role="separator" className="my-0.5 h-px w-8 bg-border/60" />
|
|
|
|
<ul className="gl-stagger flex flex-col items-center gap-2" data-testid="guild-rail">
|
|
{guilds.map((guild) => {
|
|
const active = guild.id === selectedGuildId;
|
|
return (
|
|
<li key={guild.id}>
|
|
<NavLink
|
|
to={`/app/${guild.id}`}
|
|
title={guild.name}
|
|
aria-label={t('guilds.switchTo', { name: guild.name })}
|
|
aria-current={active ? 'page' : undefined}
|
|
className={`relative block rounded-[var(--radius-md)] transition-[border-radius,background-color,transform] duration-[var(--duration-fast)] ${
|
|
active
|
|
? 'gl-ring rounded-[var(--radius-lg)]'
|
|
: 'hover:-translate-y-0.5 hover:rounded-[var(--radius-lg)] active:translate-y-0'
|
|
}`}
|
|
>
|
|
<Avatar
|
|
name={guild.name}
|
|
seed={guild.id}
|
|
fileId={guild.icon_file_id}
|
|
size="lg"
|
|
shape="square"
|
|
/>
|
|
</NavLink>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
|
|
{canCreate ? (
|
|
<button
|
|
type="button"
|
|
onClick={onCreate}
|
|
aria-label={t('guilds.add')}
|
|
title={t('guilds.add')}
|
|
className="flex h-14 w-14 items-center justify-center rounded-[var(--radius-md)] border border-dashed border-border bg-surface-1 text-2xl text-success transition-[border-radius,border-color,transform] duration-[var(--duration-fast)] hover:-translate-y-0.5 hover:rounded-[var(--radius-lg)] hover:border-success active:translate-y-0"
|
|
>
|
|
+
|
|
</button>
|
|
) : null}
|
|
</nav>
|
|
);
|
|
}
|