Pomocniki preferencji

Siedem małych pakietów na framework, z których każdy posiada jedno zadanie użytkownika od początku do końca, plus pasek łączący pięć z nich. Znasz już theme-picker; oto locale-picker, text-size-picker i motion-picker (preferencje), share-picker i search-picker (akcje), date-time-picker (wartość formularza) oraz picker-bar.

Co zbudujesz

Panel ustawień, w którym użytkownicy wybierają język i rozmiar tekstu — wybory, które obowiązują w całym dokumencie, utrzymują się między wizytami i kosztują cię dwa komponenty oraz kilka linijek CSS. Około 15 minut.

Zanim zaczniesz

Najpierw ukończ samouczek frameworka dla swojego stosu. Poniższe przykłady są w Svelte; pomocniki dla Reacta, Vue, Angulara, HTML, Nunjucks i Blazora odpowiadają im kontrakt po kontrakcie.

Kształt, który współdzielą

  • Bezstylowy przycisk-ikona otwierający listbox WAI-ARIA APG (albo, dla date-time-picker, okno wyboru daty) — bez natywnego <select>, więc każdy framework dostaje to samo działanie klawiatury i te same haczyki stylów.
  • Jeden haczyk klasy na pakiet (.locale-picker, .text-size-picker, .share-picker, .date-time-picker), zero dostarczanego CSS.
  • Bezpieczny dla SSR: zapisy do DOM zachodzą tylko w cyklu życia mount/effect.
  • Czysty pod i18n: każdy widoczny dla użytkownika napis jest propsem.
  • Dostępne dla Svelte (kanoniczny), Reacta, Vue, Angulara, elementu niestandardowego HTML, Nunjucks i Blazora — ten sam kontrakt w każdym.

theme-picker, locale-picker i text-size-picker posiadają każdy jedną preferencję użytkownika od początku do końca (wybór, zastosowanie do DOM, opcjonalna localStorage trwałość przez props storageKey ). share-picker i date-time-picker są innego rodzaju: share-picker posiada akcję — nie stosuje niczego do dokumentu i niczego nie zapisuje — a date-time-picker posiada wartość formularza, ponieważ data w formularzu to dane, a nie preferencja.

locale-picker — język i kierunek

Pozwala użytkownikowi wybrać ustawienia regionalne, a następnie zapisuje lang i dir w korzeniu dokumentu, aby twoja biblioteka i18n, CSS i technologie wspomagające podążały za nim. RTL jest wykrywany automatycznie dla arabskiego, hebrajskiego i innych pism pisanych od prawej do lewej. Zajmuje się wyłącznie sygnalizowaniem — tłumaczenie obsługuje twoja biblioteka i18n.

<script>
  import LocalePicker from "@lilydesignsystem/svelte-locale-picker";

  let locale = $state("en-US");
</script>

<LocalePicker
  label="Language"
  locales={["en-US", "fr", "ar", "he", "ja"]}
  bind:value={locale}
  storageKey="my-app-locale"
/>

Opcje są podpisywane z wbudowanej tabeli nazw ustawień regionalnych (można nadpisać przez localeLabels), każda opcja ma własny atrybut lang , aby czytniki ekranu wymawiały „Français” po francusku, a kody z podkreśleniem, takie jak en_US są bezstratnie konwertowane do BCP 47 i z powrotem en-US. Podłącz onChange (lub związaną wartość) do i18next, Paraglide, vue-i18n lub czegokolwiek używasz.

text-size-picker — rozmiar kontrolowany przez czytelnika

Zysk dla dostępności w kilka minut: pomocnik ustawia data-text-size="{slug}" na korzeniu dokumentu, a twój CSS mapuje każdą wartość na rozmiar.

<script>
  import TextSizePicker from "@lilydesignsystem/svelte-text-size-picker";
</script>

<TextSizePicker
  label="Text size"
  sizes={["small", "medium", "large", "x-large"]}
  storageKey="my-app-text-size"
/>
:root[data-text-size="small"]   { font-size: 87.5%; }
:root[data-text-size="medium"]  { font-size: 100%; }
:root[data-text-size="large"]   { font-size: 112.5%; }
:root[data-text-size="x-large"] { font-size: 125%; }

Zwymiaruj układ w rem a cała aplikacja skaluje się wraz z wyborem użytkownika. Etykiety powstają ze slugów z wielkiej litery („X Large”) lub są nadpisywane przez sizeLabels.

Sprawdź swoją pracę: wybierz „Large” w selektorze, a tekst strony rośnie. Odśwież — nadal jest duży: wybór został zapisany w localStorage. Przełącz selektor języka na arabski, a dir="rtl" zostanie ustawione na dokumencie automatycznie.

share-picker — wyślij tę stronę

Przycisk z jednym glifem, który otwiera natywny arkusz udostępniania urządzenia, jeśli taki istnieje, a w przeciwnym razie przechodzi na listę celów, które podasz, plus akcję kopiowania adresu URL. Nie dostarcza żadnych adresów URL sieci społecznościowych — które sieci pasują do twojego produktu, to twoja decyzja, a nie systemu projektowego.

<script>
  import SharePicker from "@lilydesignsystem/svelte-share-picker";

  const targets = [
    { id: "mastodon", label: "Mastodon",
      href: (url, title) => `https://mastodon.social/share?text=${encodeURIComponent(title)}%20${encodeURIComponent(url)}` },
    { id: "email", label: "Email",
      href: (url, title) => `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`, newTab: false },
  ];
</script>

<SharePicker
  label="Share this page"
  title="An article worth reading"
  {targets}
  copyLabel="Copy link"
  copiedLabel="Link copied"
/>

url domyślnie ma wartość location.href, więc typowy przypadek nie wymaga okablowania. Wymuś ścieżkę przez strategy="list" lub strategy="native" gdy potrzebujesz spójnego działania na różnych platformach do testów lub zrzutów ekranu.

date-time-picker — data, godzina lub oba

Pole tekstowe, w którym można pisać, plus przycisk-ikona otwierający okno wyboru daty WAI-ARIA APG. Poprawny regionalnie z założenia — nazwy miesięcy, nazwy dni tygodnia, pierwszy dzień tygodnia oraz zegar 12- lub 24-godzinny pochodzą z Intl — a wartość jest zawsze sortowalnym ciągiem ISO bez strefy czasowej: "2026-03-15" dla mode="date", "09:30" dla mode="time", "2026-03-15T09:30" dla mode="datetime".

<script>
  import DateTimePicker from "@lilydesignsystem/svelte-date-time-picker";

  let appointment = $state("");
</script>

<DateTimePicker
  inputId="appointment"
  name="appointment"
  label="Choose an appointment date"
  locale="en-GB"
  bind:value={appointment}
  labels={{
    previousYear: "Previous year",
    previousMonth: "Previous month",
    nextMonth: "Next month",
    nextYear: "Next year",
    confirm: "OK",
    cancel: "Cancel",
  }}
/>

Wartość ISO jest identyczna z tą, którą <input type="date"> wysyła, więc możesz wymienić natywną kontrolkę, nie dotykając backendu.

motion-picker — szanuj ograniczony ruch

Pomocnik ustawia data-motion="{slug}" na korzeniu dokumentu. Jego pierwsza wartość pochodzi z ustawienia prefers-reduced-motion systemu operacyjnego odwiedzającego — zawsze, bez flagi włączającej, ponieważ ruch jest realną potrzebą dostępności (WCAG 2.3.3). Twój CSS i skrypty decydują, co zostanie stłumione.

<script>
  import MotionPicker from "@lilydesignsystem/svelte-motion-picker";
</script>

<MotionPicker
  label="Motion"
  motions={["no-preference", "reduce"]}
  storageKey="my-app-motion"
/>
:root[data-motion="reduce"] *,
:root[data-motion="reduce"] *::before,
:root[data-motion="reduce"] *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}

search-picker — wyszukiwarka, która nie przeszkadza

Przycisk-ikona otwierający pole wyszukiwania. Naciśnięcie Return lub przycisku ⏎ obok pola nawiguje zwykłym żądaniem GET do /?<text> (tekst jest przycinany i kodowany jako URI; puste zapytanie donikąd nie prowadzi). Jego trzy dostępne nazwy są wymagane, ponieważ pomocnik nie dostarcza angielskiego.

<script>
  import SearchPicker from "@lilydesignsystem/svelte-search-picker";
  import { goto } from "$app/navigation";
</script>

<SearchPicker
  label="Search this site"
  inputLabel="Search terms"
  submitLabel="Search"
  placeholder="Search…"
  action="/search"
  navigate={(href) => goto(href)}
/>

action zmienia ścieżkę (/search?foo), a navigate zatrzymuje nawigację wewnątrz aplikacji; bez niego pomocnik wywołuje location.assign.

picker-bar — cały wiersz nagłówka naraz

picker-bar renderuje wyszukiwanie, motyw, język, rozmiar tekstu i udostępnianie w jednym wierszu, z pełnym zestawem motywów referencyjnych i siedmiostopniową skalą tekstu podłączonymi z góry. Każda dostępna nazwa przychodzi w jednym wymaganym obiekcie labels .

<script>
  import PickerBar from "@lilydesignsystem/svelte-picker-bar";
</script>

<PickerBar
  labels={{
    search: "Search", searchInput: "Search terms", searchSubmit: "Search",
    theme: "Theme", locale: "Language", textSize: "Text size", share: "Share",
  }}
  themesUrl="/themes/"
  locales={["en", "fr", "es"]}
  shareTargets={targets}
/>

Dotrzyj do dowolnego opakowanego selektora przez searchProps, themeProps, localeProps, textSizeProps i shareProps.

Podpowiedzi są wbudowane

Każdy selektor pokazuje swoją etykietę w podpowiedzi, gdy wskaźnik spoczywa na jego przycisku lub przycisk ma fokus klawiatury. Podpowiedź pozostaje, gdy wskaźnik przesuwa się na nią, Escape zamyka ją niezależnie od tego, gdzie jest fokus, i nigdy nie pojawia się, gdy okno podręczne selektora jest otwarte. Niesie ten sam tekst co dostępna nazwa przycisku, więc nie ma nic do konfigurowania ani do dwukrotnego tłumaczenia; stylizują ją motywy referencyjne.

Panel ustawień w jednym miejscu

Cztery pomocniki preferencji składają się naturalnie:

<fieldset class="fieldset">
  <legend>Preferences</legend>
  <ThemePicker    label="Theme"     themesUrl="/themes/" themes={themes} storageKey="pref-theme" />
  <LocalePicker   label="Language"  locales={locales}    storageKey="pref-locale" />
  <TextSizePicker label="Text size" sizes={sizes}        storageKey="pref-text-size" />
  <MotionPicker   label="Motion"    motions={motions}    storageKey="pref-motion" />
</fieldset>

Zweryfikowano: fragmenty locale-picker, text-size-picker, share-picker, date-time-picker i połączonego panelu zostały skompilowane osobno względem opublikowanych 0.1.1 pakietów, przez svelte/server (2026-08-29); fragmenty motion-picker, search-picker i picker-bar zostały wyrenderowane względem własnych komponentów pakietów 2026-10-06.

Gdzie je zdobyć

# one directory per package in the monorepo, e.g.
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-svelte-theme-picker
# or lily-design-system-react-theme-picker, -vue- / -angular- / -html- / -web-components- / -nunjucks- / -blazor-

Każdy pakiet ma własny spec, testy odwzorowane na klauzule specu, uruchamialne przykłady (w tym wzorce ciasteczek SSR) oraz potok budowania i publikacji. Pakiety Svelte są kanoniczne; w razie wątpliwości ich kontrakty wygrywają.

Następne kroki

Pytania po drodze? Strona pomocy jest pełna odpowiedzi — a jeśli jakiś krok mógłby być jaśniejszy, daj nam znać a chętnie ulepszymy samouczek.