Помощници за предпочитания

Седем малки пакета на фреймуърк, всеки от които притежава една потребителска задача от начало до край, плюс лента, която събира пет от тях. Вече се запознахте с theme-picker; ето locale-picker, text-size-picker и motion-picker (предпочитания), share-picker и search-picker (действия), date-time-picker (стойност на форма) и picker-bar.

Какво ще изградите

Панел с настройки, в който потребителите избират език и размер на текста — избори, които важат за целия документ, запазват се между посещенията и ви струват два компонента и няколко реда CSS. Около 15 минути.

Преди да започнете

Първо завършете ръководството за фреймуърка за вашия стек. Примерите по-долу са на Svelte; помощниците за React, Vue, Angular, HTML, Nunjucks и Blazor съответстват договор по договор.

Формата, която споделят

  • Headless бутон-икона, който отваря WAI-ARIA APG listbox (или, за date-time-picker, диалог за избор на дата) — без роден <select>, така че всеки фреймуърк получава еднакво поведение на клавиатурата и стилови куки.
  • Една класова кука на пакет (.locale-picker, .text-size-picker, .share-picker, .date-time-picker), нула доставен CSS.
  • Безопасно за SSR: записите в DOM стават само в жизнения цикъл mount/effect.
  • Чисто за i18n: всеки низ, видим за потребителя, е prop.
  • Налично за Svelte (каноничен), React, Vue, Angular, персонализиран HTML елемент, Nunjucks и Blazor — един и същ договор във всеки.

theme-picker, locale-picker и text-size-picker притежават всеки по едно потребителско предпочитание от начало до край (избор, прилагане към DOM, незадължително localStorage запазване чрез prop-а storageKey ). share-picker и date-time-picker са различни по вид: share-picker притежава действие — не прилага нищо към документа и не запазва нищо — а date-time-picker притежава стойност на форма, защото датата във форма са данни, а не предпочитание.

locale-picker — език и посока

Позволява на потребителя да избере локал, после записва lang и dir върху корена на документа, така че вашата i18n библиотека, вашият CSS и помощните технологии да го следват. RTL се открива автоматично за арабски, иврит и други писмености отдясно наляво. Занимава се само със сигнализиране — преводът е работа на вашата 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"
/>

Опциите се означават от вградена таблица с имена на локали (презаписва се чрез localeLabels), всяка опция носи собствен атрибут lang , за да произнасят екранните четци „Français“ на френски, а кодовете с долна черта като en_US се преобразуват без загуби към BCP 47 и обратно en-US. Свържете onChange (или обвързаната стойност) с i18next, Paraglide, vue-i18n или каквото използвате.

text-size-picker — размер, контролиран от читателя

Победа за достъпността за минути: помощникът задава data-text-size="{slug}" върху корена на документа, а вашият CSS съпоставя всяка стойност с размер.

<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%; }

Оразмерете оформлението си в rem и цялото приложение се мащабира според избора на потребителя. Етикетите се образуват от слъговете с главна буква („X Large“) или се презаписват с sizeLabels.

Проверете работата си: изберете „Large“ от селектора и текстът на страницата расте. Презаредете — още е голям: изборът е запазен в localStorage. Превключете селектора на език на арабски и dir="rtl" се задава автоматично върху документа.

share-picker — изпратете тази страница

Бутон с един глиф, който отваря родния лист за споделяне на устройството, където има такъв, а иначе се връща към списък с дестинации, които вие доставяте, плюс действие за копиране на URL. Не доставя URL адреси на социални мрежи — кои мрежи принадлежат на вашия продукт е ваше решение, не на дизайн системата.

<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 по подразбиране е location.href, така че обичайният случай не изисква свързване. Наложете път с strategy="list" или strategy="native" когато ви трябва последователно поведение между платформите за тестове или снимки на екрана.

date-time-picker — дата, час или и двете

Текстово поле, в което можете да пишете, плюс бутон-икона, който отваря диалог за избор на дата по WAI-ARIA APG. Правилен за локала по конструкция — имената на месеците, имената на дните, първият ден от седмицата и 12- или 24-часовият часовник идват от Intl — а стойността винаги е сортируем ISO низ без часова зона: "2026-03-15" за mode="date", "09:30" за mode="time", "2026-03-15T09:30" за 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",
  }}
/>

ISO стойността е идентична с това, което <input type="date"> изпраща, така че можете да смените родния контрол вътре или вън, без да пипате бекенда.

motion-picker — уважавайте намаленото движение

Помощникът задава data-motion="{slug}" върху корена на документа. Първата му стойност идва от настройката prefers-reduced-motion на операционната система на посетителя — винаги, без флаг за включване, защото движението е реална нужда от достъпност (WCAG 2.3.3). Вашият CSS и скриптове решават какво да се потисне.

<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 — поле за търсене, което не пречи

Бутон-икона, който отваря поле за търсене. Натискането на Return или на бутона ⏎ до полето навигира с обикновена GET заявка към /?<text> (текстът се подрязва и се кодира като URI; празна заявка не води никъде). Трите му достъпни имена са задължителни, защото помощникът не доставя английски.

<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 променя пътя (/search?foo), а navigate задържа навигацията вътре в приложението; без него помощникът извиква location.assign.

picker-bar — целият ред на заглавката наведнъж

picker-bar рендира търсене, тема, локал, размер на текста и споделяне в един ред, с пълния набор референтни теми и седемстепенната скала за текст предварително свързани. Всяко достъпно име идва в един задължителен 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}
/>

Достигнете всеки обвит селектор с searchProps, themeProps, localeProps, textSizeProps и shareProps.

Подсказките са вградени

Всеки селектор показва етикета си в подсказка, когато показалецът почива върху бутона му или бутонът има фокус от клавиатурата. Подсказката остава, докато показалецът се премества върху нея, Escape я затваря, където и да е фокусът, и никога не се показва, докато изскачащият прозорец на селектора е отворен. Носи същия текст като достъпното име на бутона, така че няма какво да се конфигурира и няма какво да се превежда два пъти; референтните теми я стилизират.

Панел с настройки на едно място

Четирите помощника за предпочитания се комбинират естествено:

<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>

Проверено: фрагментите за locale-picker, text-size-picker, share-picker, date-time-picker и комбинирания панел бяха компилирани поотделно спрямо публикуваните 0.1.1 пакети, чрез svelte/server (2026-08-29); фрагментите за motion-picker, search-picker и picker-bar бяха рендирани спрямо собствените компоненти на пакетите на 2026-10-06.

Откъде да ги вземете

# 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-

Всеки пакет има собствен spec, тестове, съпоставени към клаузите на spec-а, изпълними примери (включително модели с бисквитки за SSR) и конвейер за изграждане и публикуване. Пакетите за Svelte са каноничните; при съмнение техните договори печелят.

Следващи стъпки

Въпроси по пътя? Страницата за помощ е пълна с отговори — а ако някоя стъпка може да е по-ясна, кажете ни и с радост ще подобрим ръководството.