Ikony
Na tej stronie
icon() zwraca liniowy <svg>. Każdy glif narysowany jest na tej samej siatce 24×24 jako niewypełnione kreski w currentColor, więc dziedziczy kolor i rozmiar czcionki tego, w czym siedzi, i nie potrzebuje własnych stylów.
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check'),
icon('search'),
icon('trash'),
icon('settings'),
)W komponencie
Ikona jest dzieckiem jak każde inne. Ponieważ wymiaruje się w em, pasuje do etykiety obok bez informowania jej, jak duża ta etykieta jest:
stack({ direction: 'row', gap: 'sm', align: 'center', wrap: true },
button({ color: 'primary' }, icon('download'), 'Pobierz'),
button({ variant: 'outline' }, icon('external-link'), 'Otwórz'),
button({ size: 'sm', variant: 'soft', color: 'danger' }, icon('trash'), 'Usuń'),
iconButton({ label: 'Szukaj', variant: 'soft', icon: icon('search') }),
)Rozmiar
Domyślnie 1em — rozmiar otaczającego tekstu. size przyjmuje token albo dowolną długość CSS, gdy chcesz się od niego oderwać:
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' }),
)Kolor
Nie ma propsa koloru. Ikona rysowana jest w currentColor, więc bierze kolor swojego kontekstu — i to właśnie sprawia, że jeden zestaw działa w pięciu paletach:
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' })),
)Dostępne nazwy
Ikona domyślnie ma aria-hidden, co jest słuszne znacznie częściej niż nie: ikona obok słowa „Usuń” nie powinna być zapowiadana po raz drugi. Daj jej label tylko wtedy, gdy to ikona niesie całe znaczenie — wtedy staje się role="img" z tą nazwą.
icon('trash') // ozdobna — ukryta
button(icon('trash'), 'Usuń') // to słowo mówi
icon('trash', { label: 'Usuń' }) // zapowiadana jako obraz
// Przycisk z samą ikoną nazywa przycisk, a nie glif w środku
iconButton({ label: 'Usuń', icon: icon('trash') })Wypełnione
filled zamalowuje glif, zamiast go obrysowywać. To ta sama ścieżka w obu przypadkach — zmienia się tylko atrybut fill — więc obie postaci dzielą dokładnie tę samą krawędź zewnętrzną i nie mogą się rozjechać.
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' }),
)I te same nazwy wypełnione:
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' }),
)To props, a nie drugi zestaw nazw, bo stan wypełnienia jest niemal zawsze stanem — zapisane, polubione, ocenione — więc chce wartości logicznej, a nie innego ciągu znaków:
icon('heart', { filled: liked })
icon('bookmark', { filled: saved, label: saved ? 'Zapisane' : 'Zapisz' })
// zamiast
icon(liked ? 'heart-filled' : 'heart')Glify statusu wypełniają się inaczej, bo ich znak siedzi wewnątrz kształtu. Zamalowanie koła połknęłoby ptaszka, więc znak jest z niego zamiast tego wycinany:
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' }),
)Te niosą drugi rysunek — kształt pełny ze znakiem wyciętym przez fill-rule: evenodd — bo wycięcia nie da się uzyskać ze ścieżki obrysu przez zmianę atrybutu. Zewnętrzny kształt rysowany jest na zewnętrznej krawędzi obrysu, więc obie postaci i tak kończą na tej samej sylwetce. Props jest ten sam w obu przypadkach; którego mechanizmu używa glif, to już jego sprawa.
Szewron nie ma żadnego wnętrza do zamalowania — to linia otwarta — więc wypełnia się do trójkąta opisanego jego trzema punktami, zachowując kreskę zaokrąglającą rogi:
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() wypisuje wszystko, co odpowiada na filled. Glif bez postaci wypełnionej ignoruje go i zostaje obrysowany — wypełnienie eye straciłoby źrenicę, a tag swój otwór, więc żaden z nich nie udaje.
Obrót
spin obraca glif — pomyślany dla spinner, choć nic nie stoi na przeszkodzie, żeby obracać refresh, gdy coś się przeładowuje. Pod prefers-reduced-motion zwalnia do pełzania, zamiast się zatrzymać, bo zatrzymany wskaźnik wygląda na zepsuty.
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 }), 'Zapisywanie…'),
)Zestaw
Nazwy opisują rysunek, a nie zadanie, które wykonuje — x-circle, a nie error — bo ten sam rysunek bywa używany do niepowiązanych zadań, a nazwa opisująca obrazek zostaje wtedy prawdziwa. Aliasy poniżej pokrywają typowe intencje.
Marki
Z zestawem przychodzi osiem znaków marek — facebook, google, instagram, linkedin, tiktok, whatsapp, x-twitter i youtube. Nadal przyjmują size i label i nadal rysują się w 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'), 'Udostępnij'),
)To reprodukcje cudzych znaków, a nie rysunki w stylu tej biblioteki, więc celowo łamią dwie jej zasady: są pełnymi kształtami, a nie kreskami, czym logo jest, i mają proporcje marki, a nie tej siatki. filled nic dla nich nie znaczy — już takie są.
Rysunki pochodzą z Simple Icons, które udostępnia je na CC0. To obejmuje rysunek, a nie znak towarowy: używaj ich, żeby wskazać rzecz, którą nazywają — odnośnik do profilu, przycisk udostępniania — a nie na własnym produkcie.
Jest to x-twitter, a nie x, bo x jest już aliasem close, a przycisk zamykania zmieniający się w logo byłby paskudną niespodzianką. twitter też się na nie rozwiązuje.
Aliasy
Każdy z nich renderuje glif wypisany powyżej, pod nazwą, po którą prędzej sięgniesz:
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
Własne ikony
registerIcons() dodaje glif albo zastępuje wbudowany. Znacznikami jest zawartość <svg> — kształty na tej samej siatce 24×24, zostawione niewypełnione, żeby sięgnął do nich currentColor. Wywołaj to raz z modułu, który importują Twoje strony:
import { registerIcons } from 'sitelo/ui'
registerIcons({
logo: '<path d="M4 20 12 4l8 16z"/>',
// Nazwa, która już istnieje, zastępuje ją wszędzie — tak przerysowuje
// się wbudowany glif bez forkowania biblioteki.
check: '<path d="m5 13 4 4 10-11"/>',
// Jeden zamknięty kształt, żeby mógł odpowiadać na `filled` jak wbudowane.
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') // Twój glif
icon('check') // teraz też Twój
registerIcons({ check: null }) // i z powrotem do wbudowanegoDlaczego w treści, a nie sprite
Ikony renderują się do strony, zamiast być pobierane z icons.svg przez <use>. Sprite oszczędza rzędu setki bajtów HTML-a po gzipie na stronę i kosztuje za to jedną podróż do serwera — powtarzające się znaczniki to dokładnie ten przypadek, w którym gzip jest najlepszy, więc większość tego, co sprite miałby deduplikować, została już zdeduplikowana. W treści znaczy też, że nie ma pliku do wypuszczenia, ścieżki bazowej do skonfigurowania ani niczego, co mogłoby zginąć z dist — ten sam kompromis, który robi styles({ inline: true }).
Propsy
| Prop | Typ | Domyślnie | Opis |
|---|---|---|---|
name | string | — | Który glif. Można podać jako pierwszy argument zamiast propsa. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Token albo dowolna długość CSS. Domyślnie 1em. |
label | string | — | Zapowiadaj ją jako obraz o tej nazwie, zamiast ją ukrywać. |
spin | boolean | false | Obracaj ją bez przerwy. |
filled | boolean | false | Zamaluj glif, zamiast go obrysowywać. Ignorowane przez glify, których nie da się wypełnić. |
Nieznana nazwa nie renderuje zupełnie nic, zamiast zgłaszać błąd — kosmetyczny props nie powinien móc przerwać buildu. hasIcon(name) mówi, czy dany istnieje, iconNames() wypisuje wszystkie, a fillableIcons() te, które przyjmują filled.