Move the completed material-icons-ui change to openspec/changes/archive/2026-10-10-material-icons-ui/ and create the ui-iconography main spec from its ADDED requirements.
7.3 KiB
Design
Context
Фронтенд — SvelteKit 2 + Svelte 5, adapter-static в режиме SPA (ssr=false),
собирается в статику и вшивается в Go-бинарник через go:embed. Стили — Tailwind v4,
семантические токены через CSS-переменные и data-theme на <html> (темы light/dark/neutral
- system). Внешние CDN-запросы нежелательны: приложение self-hosted и должно работать полностью автономно. Сейчас все «иконки» — эмодзи и юникод-глифы, рендерящиеся системным шрифтом (мотивация — в proposal.md).
Goals / Non-Goals
Goals:
- Единый набор векторных иконок, самодостаточный (без внешних запросов и без иконочных шрифтов).
- Иконки наследуют цвет текущей темы и не влияют на размеры/раскладку при переключении тем.
- Переиспользуемый компонентный API, спрайт для дедупликации повторяющихся иконок.
- Доступность: корректное поведение для декоративных и смысловых иконок и для тумблеров.
Non-Goals:
- Не менять доменную логику, API, БД и backend.
- Не вводить анимации/переходы иконок.
- Не добавлять внешние зависимости (иконочные пакеты, Vite-плагины).
- Не делать полный редизайн макета — только замена иконок и ведущие иконки контролов.
Decisions
1. Inline SVG через спрайт <symbol> + <use>
В +layout.svelte монтируется один скрытый <svg> со всеми <symbol id="i-...">;
места использования рисуют <svg><use href="#i-..."/></svg>.
- Почему: нет внешних запросов, работает внутри статичного бандла,
currentColorнаследуется через<use>, а данные пути хранятся один раз в DOM (важно для сеток, гдеfavorite/mode_commentповторяются на каждой плитке). - Альтернативы (отклонены): иконочный шрифт через CDN — внешний запрос, FOUT, ломает
автономность; внешний файл-спрайт
<use href="/icons.svg#id">— межфайловые<use>имеют ограничения в ряде браузеров; «иконка = компонент с полным inline-путём» — дублирование path-данных в DOM при массовом повторении.
2. Вендорим данные иконок в registry.ts, без зависимостей
Реестр — объект имя -> { path }, где path — сырая inner-разметка (<path d="..." .../>,
иногда с fill-rule/clip-rule). Источник — Google Material Symbols (через Iconify,
prefix material-symbols); базовые имена дают стиль filled, поэтому набор вендорится в
filled-стиле, а для состояний-пар берутся отдельные ключи (favorite = filled, лайкнут;
favorite-outline = контурный, не лайкнут). Примечание: контурного варианта для всех
иконок в источнике нет, поэтому единый filled-набор согласованнее, чем смесь стилей.
- Почему: ноль зависимостей и supply-chain, малый бандл, полный контроль, согласуется с философией «один статичный бинарник».
- Альтернативы (отклонены):
@mdi/js— готовые path-строки, но новая зависимость и другой визуальный набор; Iconify +unplugin-icons— мощный DX, но тяжёлый тулчейн ради ~25 иконок. - Задел: при росте набора переход на
@mdi/jsне меняет компонентный API — только источник данных реестра.
3. Компонентный API
Icon.svelte: пропсыname, необязательныйlabel; размер и цвет задаются классами (size-4/5/6/16,text-*). По умолчаниюaria-hidden="true" focusable="false"; при заданномlabel—role="img"+aria-labelиaria-hiddenснимается.IconSprite.svelte: рендерит все<symbol>из реестра один раз, монтируется в+layout.svelte.IconButton.svelte: иконочная кнопка с обязательнымlabel(попадает вaria-label).- Неизвестное имя: в dev —
console.warnи пустой бокс (не падать); в prod — пусто.
4. Стиль и метрики
fill="currentColor", шкала размеров:size-4(в тексте/счётчиках),size-5(кнопки/навигация),size-6(панель лайтбокса),size-16(плейсхолдеры).- Пара состояний лайка:
favorite-outline(не лайкнут) /favorite(залитая, лайкнут).
5. Маппинг иконок тем
light_mode, dark_mode, palette (нейтральная), brightness_auto (системная).
6. Доступность
- Декоративные иконки рядом с текстом/внутри кнопки с текстом —
aria-hidden. - Одиночные иконочные кнопки (тумблеры тем) —
aria-labelна<button>+aria-pressed. - Счётчики лайков/комментариев: иконка декоративная, число несёт доступное имя
(
aria-label="12 лайков").
Risks / Trade-offs
- [Спрайт должен оставаться в DOM] →
<use>ссылается на#idв том же документе;IconSpriteживёт в+layout.svelteи не размонтируется. - [
{@html}в<symbol>] → данные статичны и вендорятся на этапе сборки, не приходят от пользователя; риск XSS отсутствует. - [Рост DOM при больших сетках] → митигируется спрайтом (одна копия пути).
- [Визуальная регрессия/раскладка] → размеры задаём классами, иконки в квадратном
viewBox 0 0 24 24; проверяем на трёх темах. - [Лицензия/атрибуция] → Material Symbols под Apache 2.0; добавляем уведомление об источнике рядом с реестром.