Иконки
На этой странице
icon() возвращает встроенный <svg>. Все знаки нарисованы на одной сетке 24×24 незалитыми штрихами в currentColor, поэтому наследуют цвет и кегль того, в чём стоят, и не требуют собственного оформления.
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check'),
icon('search'),
icon('trash'),
icon('settings'),
)Внутри компонента
Иконка — такой же потомок, как любой другой. Поскольку она задаётся в em, она подходит к подписи рядом, и говорить ей размер этой подписи не нужно:
stack({ direction: 'row', gap: 'sm', align: 'center', wrap: true },
button({ color: 'primary' }, icon('download'), 'Скачать'),
button({ variant: 'outline' }, icon('external-link'), 'Открыть'),
button({ size: 'sm', variant: 'soft', color: 'danger' }, icon('trash'), 'Удалить'),
iconButton({ label: 'Поиск', variant: 'soft', icon: icon('search') }),
)Размер
По умолчанию 1em — размер окружающего текста. size принимает токен или любую CSS-длину, когда нужно от этого отойти:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('star', { size: 'sm' }),
icon('star'),
icon('star', { size: 'lg' }),
icon('star', { size: '2rem' }),
icon('star', { size: '3rem' }),
)Цвет
Пропа цвета нет. Иконка рисуется в currentColor, поэтому берёт цвет своего окружения — именно это позволяет одному набору работать внутри пяти палитр:
stack({ direction: 'row', gap: 'md', align: 'center' },
text({ style: 'color: var(--su-primary)' }, icon('heart', { size: 'lg' })),
text({ style: 'color: var(--su-success)' }, icon('check-circle', { size: 'lg' })),
text({ style: 'color: var(--su-warning)' }, icon('alert-triangle', { size: 'lg' })),
text({ style: 'color: var(--su-danger)' }, icon('x-circle', { size: 'lg' })),
text({ tone: 'muted' }, icon('info', { size: 'lg' })),
)Доступные имена
Иконка по умолчанию имеет aria-hidden, и это верно гораздо чаще, чем наоборот: иконку рядом со словом «Удалить» не нужно объявлять во второй раз. Давайте ей label только тогда, когда весь смысл несёт сама иконка, — и она станет role="img" с этим именем.
icon('trash') // декоративная — скрыта
button(icon('trash'), 'Удалить') // говорит слово
icon('trash', { label: 'Удалить' }) // объявляется как изображение
// Кнопка с одной иконкой подписывает кнопку, а не знак внутри неё
iconButton({ label: 'Удалить', icon: icon('trash') })Заливка
filled закрашивает знак вместо обводки. Контур в обоих случаях один и тот же — меняется только атрибут fill —, поэтому обе формы делят ровно одну внешнюю кромку и не могут разойтись.
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('bell', { size: 'lg' }),
icon('bookmark', { size: 'lg' }),
icon('folder', { size: 'lg' }),
icon('heart', { size: 'lg' }),
icon('star', { size: 'lg' }),
)И те же имена, но с заливкой:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('bell', { filled: true, size: 'lg' }),
icon('bookmark', { filled: true, size: 'lg' }),
icon('folder', { filled: true, size: 'lg' }),
icon('heart', { filled: true, size: 'lg' }),
icon('star', { filled: true, size: 'lg' }),
)Это проп, а не второй набор имён, потому что залитость почти всегда — состояние: сохранено, понравилось, оценено, — а значит ей нужен булев флаг, а не другая строка:
icon('heart', { filled: liked })
icon('bookmark', { filled: saved, label: saved ? 'Сохранено' : 'Сохранить' })
// вместо
icon(liked ? 'heart-filled' : 'heart')Знаки состояния заливаются иначе, потому что их метка сидит внутри формы. Закраска круга проглотила бы галочку, поэтому метку из него, наоборот, вырезают:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check-circle', { filled: true, size: 'lg' }),
icon('x-circle', { filled: true, size: 'lg' }),
icon('info', { filled: true, size: 'lg' }),
icon('help', { filled: true, size: 'lg' }),
icon('alert-triangle', { filled: true, size: 'lg' }),
)У них есть второй рисунок — сплошная форма с меткой, вырезанной через fill-rule: evenodd, — потому что такую вырезку нельзя получить из контурного пути сменой одного атрибута. Внешняя форма рисуется по внешней кромке обводки, так что обе версии заканчиваются одним силуэтом. Проп в обоих случаях один и тот же; каким механизмом пользуется знак — его личное дело.
У шеврона внутренности нет вовсе — это открытая линия, — поэтому он заливается до треугольника, который описывают его три точки, сохраняя обводку, скругляющую углы:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('chevron-up', { filled: true, size: 'lg' }),
icon('chevron-down', { filled: true, size: 'lg' }),
icon('chevron-left', { filled: true, size: 'lg' }),
icon('chevron-right', { filled: true, size: 'lg' }),
)fillableIcons() перечисляет всё, что откликается на filled. Знак без залитой формы его игнорирует и остаётся контурным: залить eye значило бы потерять зрачок, а tag — его отверстие, так что ни один из них не притворяется.
Вращение
spin вращает знак — задумано для spinner, хотя ничто не мешает крутить refresh, пока что-то перезагружается. При prefers-reduced-motion он замедляется до еле заметного, а не останавливается: остановившийся спиннер выглядит сломанным.
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('spinner', { spin: true, size: 'lg' }),
icon('refresh', { spin: true, size: 'lg' }),
button({ variant: 'soft' }, icon('spinner', { spin: true }), 'Сохраняем…'),
)Весь набор
Имена описывают рисунок, а не работу, которую он делает: x-circle, а не error — потому что один и тот же рисунок идёт на несвязанные задачи, и имя, описывающее картинку, при этом остаётся верным. Алиасы ниже покрывают привычные намерения.
Бренды
С набором идут восемь фирменных знаков: facebook, google, instagram, linkedin, tiktok, whatsapp, x-twitter и youtube. Они по-прежнему принимают size и label и по-прежнему рисуются в currentColor:
stack({ direction: 'row', gap: 'md', align: 'center', wrap: true },
icon('facebook', { size: 'lg' }),
icon('instagram', { size: 'lg' }),
icon('x-twitter', { size: 'lg' }),
icon('youtube', { size: 'lg' }),
icon('whatsapp', { size: 'lg' }),
button({ variant: 'soft', color: 'neutral' }, icon('linkedin'), 'Поделиться'),
)Это воспроизведения чужих знаков, а не рисунки в стиле этой библиотеки, поэтому они намеренно нарушают два её правила: они сплошные фигуры, а не штрихи, — именно таков логотип, — и пропорции у них фирменные, а не этой сетки. filled для них ничего не значит: они уже залиты.
Графика взята из Simple Icons, публикуемого под CC0. Это покрывает рисунок, но не товарный знак: используйте их, чтобы указать на то, что они называют, — ссылку на профиль, кнопку «Поделиться», — но не на собственном продукте.
Это x-twitter, а не x, потому что x уже алиас для close, и кнопка закрытия, превращающаяся в логотип, была бы неприятным сюрпризом. twitter тоже ведёт сюда.
Алиасы
Каждый из них рисует знак из списка выше — под тем именем, к которому вы, скорее всего, потянетесь:
success → check-circle
warning → alert-triangle
danger, error → x-circle
x, cross → close
question → help
loading → spinner
cog, gears → gear
delete, trash-can → trash
pencil → edit
notification → bell
dots → more-horizontal
bolt, lightning → zap
arrow-back → arrow-left
arrow-forward → arrow-right
cart → shopping-cart
bag → shopping-bag
card → credit-card
cash, money → banknote
delivery, shipping → truck
shop → store
discount, sale → percent
login, sign-in → log-in
logout, sign-out → log-out
map-pin, marker → location
mobile → smartphone
like → thumbs-up
dislike → thumbs-down
comment, message, chat → comment-bubble
ai, magic → sparkles
printer → print
accessibility, a11y → universal-access
twitter → x-twitter
Свои иконки
registerIcons() добавляет знак или заменяет встроенный. Разметка — это содержимое <svg>: фигуры на той же сетке 24×24, оставленные незалитыми, чтобы до них дошёл currentColor. Вызовите её один раз из модуля, который импортируют ваши страницы:
import { registerIcons } from 'sitelo/ui'
registerIcons({
logo: '<path d="M4 20 12 4l8 16z"/>',
// Уже существующее имя заменяет знак повсюду — так и переоформляют
// встроенный, не форкая библиотеку.
check: '<path d="m5 13 4 4 10-11"/>',
// Одна замкнутая фигура, чтобы знак откликался на `filled`, как встроенные.
pin: { markup: '<path d="M12 21s7-6.3 7-11a7 7 0 1 0-14 0c0 4.7 7 11 7 11z"/>', fillable: true },
})import { icon } from 'sitelo/ui'
icon('logo') // ваш знак
icon('check') // теперь тоже ваш
registerIcons({ check: null }) // и обратно к встроенномуПочему встроенный SVG, а не спрайт
Иконки рисуются прямо в странице, а не вытягиваются из icons.svg через <use>. Спрайт экономит порядка сотни гзипованных байт HTML на страницу и стоит за это одного похода по сети: повторяющаяся разметка — как раз тот случай, где gzip хорош, так что почти всё, ради чего спрайт и существует, уже сдедуплицировано. Встроенный вариант вдобавок означает, что нет файла, который нужно выдавать, нет базового пути, который нужно настраивать, и нечему пропасть из dist — та же сделка, что и у styles({ inline: true }).
Пропсы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
name | string | — | Какой знак. Можно передать и первым аргументом. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Токен или любая CSS-длина. По умолчанию 1em. |
label | string | — | Объявлять как изображение с этим именем, а не скрывать. |
spin | boolean | false | Непрерывно вращать. |
filled | boolean | false | Закрасить знак вместо обводки. Незаливаемые знаки это игнорируют. |
Неизвестное имя не рисует ничего вместо того, чтобы бросать ошибку: косметический проп не должен уметь уронить сборку. hasIcon(name) говорит, существует ли такой знак, iconNames() перечисляет все, а fillableIcons() — те, что принимают filled.