Безголовая система компонентов · Новый ресурс

Дизайн-система Kobalte.

Kobalte предоставляет headless-компоненты для Solid, включая числовые поля с отдельными исходными и форматированными значениями, составную анатомию флажка и кнопки с состоянием нажатия. В руководстве эти примитивы связаны с сигналами Solid на примере масштабатора рецепта с вычисляемыми количествами и сбрасываемыми флажками ингредиентов.

Руководство OpenDesign9 глав29 справочных материаловАвторское руководство по приложению
01

Предварительный просмотр интерфейса дизайн-системы Kobalte

Измените количество ингредиентов, соберите нужные продукты и локально закрепите оригинальный рецепт.

Реактивная карточка рецептаОткрыть полный пример
Kobalte 0.13.14 и Solid 1.9.15; нативные бессоставные примитивы и полная оригинальная CSS. · Справочник по реализации
02

Справочник по дизайн-системе Kobalte

Kobalte 0.13.14 предоставляет бессоставные примитивы Solid, но не задаёт эталонную визуальную шкалу. Все цвета и размеры recipe-* ниже — полная конфигурация оригинального приложения. Названия API и атрибуты состояний проверены по установленному пакету и официальной документации компонентов.

Исходные семантические цвета

Источник
Название / правилоЗначение / конфигурацияПрименение
--recipe-canvas#f6f2ec Внешний фон
--recipe-surface#fffefa Панели рецепта и порций
--recipe-ink#403a30 Основной текст
--recipe-muted#807465 Вспомогательный текст
--recipe-line#e6ded2 Разделители и границы
--recipe-action#95612f Выбранный флажок и текст действия
--recipe-on-action#fffТекст на заливке действия
--recipe-focus#b88955 Видимый индикатор клавиатурного фокуса
--recipe-soft#f1e4d3 Фон счётчика и нажатого переключателя закрепления

Исходная типографика и размеры

Источник
Название / правилоЗначение / конфигурацияПрименение
Bodysystem-ui, sans-serifБез загрузки шрифтов
Page heading34px / 1.15; 29px narrowИсходная иерархия
Recipe titleGeorgia, serif; 30px / 1.2Оригинальный редакционный акцент
Ingredient copy13px / 1.5; 12px narrowУдобные для чтения переносы меток
--recipe-space-1 / 2 / 34px / 8px / 12pxНебольшие интервалы
--recipe-space-4 / 6 / 816px / 24px / 32pxОтступы панелей и страницы
Panel radius16pxИсходная геометрия поверхностей
Checkbox control21 × 21px; 5px radiusИсходный видимый индикатор
Stepper trigger42px wideНативный триггер с исходной геометрией
Layout breakpoint760pxДве колонки переходят в одну
Focus ring3px; 3px offsetИсходное видимое оформление клавиатурного фокуса

Контракт состояний нативного компонента

Источник
Название / правилоЗначение / конфигурацияПрименение
NumberField.rawValuenumberЧисловое состояние вместо форматированного текста
NumberField.onRawValueChange(value: number) => voidОбновляет количества и сбрасывает устаревшие отметки
minValue / maxValue / step1 / 8 / 1Ограничения приложения, переданные нативному числовому полю
formatOptions.maximumFractionDigits0Отображение целого количества порций
validationStatevalid | invalidВалидность приложения отражена в нативном поле
Checkbox.checked / onChangeboolean / callbackУправляемый выбор ингредиентов
Checkbox data attributesdata-checked; data-focus-visibleСтилизация нативных состояний «отмечен» и «в фокусе»
ToggleButton.pressed / onChangeboolean / callbackУправляемое локальное закрепление
ToggleButton data-pressedpresent when pressedСтиль закреплённого состояния с заливкой
03

Полное руководство по дизайн-системе Kobalte

Практическое руководство по выбору, применению и оценке этой системы для реального интерфейса.

DESIGN-kobalte.md

Подробные рекомендации, отправная точка для реализации и ссылки на источники.

Скачать руководство

Дизайн-система Kobalte — подключение компонентов Kobalte к сигналам Solid

Подключите компоненты Kobalte к сигналам Solid

Компонент использует сигналы Solid для количества порций, собранных ингредиентов и закрепления. createMemo вычисляет валидность на основе числового сигнала, а For отображает стабильную коллекцию ингредиентов. Управляемые свойства Kobalte напрямую считывают эти сигналы; для обновлений не нужны отдельный провайдер формы или адаптер состояния в стиле React.

Из этого руководства OpenDesign

NumberField, Checkbox и ToggleButton импортируются из точек входа соответствующих компонентов. У каждого своя роль: числовое поле предназначено для редактирования числового значения, каждый флажок отмечает один собранный ингредиент, а переключатель закрепляет рецепт. Арифметика и правила сброса остаются в функциях приложения, а не встраиваются в визуальные селекторы.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — храните числовое значение отдельно от форматированного текста

Храните числовое значение отдельно от форматированного текста

В NumberField передаются rawValue={portions()} и onRawValueChange={update}. Числовой сигнал напрямую используется для умножения, а поле отвечает за форматирование и ввод данных пользователем. Если подставлять в расчёты отображаемый отформатированный текст, придётся делать допущения о разделителях и локали, тогда как нативный примитив уже решает эту задачу.

В этом примере допускается от одной до восьми целых порций, шаг равен единице, а maximumFractionDigits задан равным нулю. Отдельный вычисляемый предикат по-прежнему проверяет конечность, целочисленность и допустимые границы: при редактировании поле может временно содержать неполное или недопустимое значение. Пока значение недопустимо, вместо суммы отображается длинное тире, а флажки ингредиентов отключены. Интерфейс не выдаёт правдоподобное, но неверное количество за корректный ввод пользователя.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — составьте поле из нативной метки и сообщений

Составьте поле из нативной метки и сообщений

Числовое поле включает Label, DecrementTrigger, Input, IncrementTrigger, Description и ErrorMessage. Видимое описание поясняет диапазон и последствия изменения количества порций. Триггерам даны понятные названия — «Меньше порций» и «Больше порций», поэтому их условным знакам минуса и плюса не нужно самостоятельно обеспечивать полное доступное имя.

validationState переключается между valid и invalid в зависимости от предиката приложения. ErrorMessage относится к этому нативному полю, а не к отдельному абзацу в другой части страницы. Для отправляемой формы Kobalte также предоставляет HiddenInput для сериализации исходного значения; в этом примере его нет, поскольку здесь не предусмотрены ни конечная точка отправки, ни данные формы.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — храните выбор ингредиентов в неизменяемом множестве

Храните выбор ингредиентов в неизменяемом множестве

Для каждого ингредиента используется полная структура Checkbox: Input, Control, Indicator и Label. Настоящий элемент ввода сохраняет семантику и управление с клавиатуры, а видимый элемент управления получает галочку и оригинальные стили состояний. Метка включает и название ингредиента, и его количество, поэтому пользователь видит, сколько именно продукта нужно собрать.

mark создаёт новое Set на основе предыдущего выбора, прежде чем добавить или удалить ID. Простое изменение существующего Set не обеспечило бы столь же надёжного обновления сигнала. Отображаемый счётчик считывает размер Set, а каждый Checkbox проверяет наличие стабильного ID ингредиента. При изменении количества порций весь выбор сбрасывается: отметка о сборе 320 граммов не подтверждает, что выполнено новое требование в 400 граммов.

  • Связывайте каждое видимое название ингредиента с Checkbox.Label.
  • Сохраняйте Checkbox.Input, даже если видимый квадрат оформлен пользовательскими стилями.
  • Используйте стабильные ID для выбора и создавайте новое Set при каждом обновлении.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — используйте переключатель для сохранения локального выбора

Используйте переключатель для сохранения локального выбора

Для закрепления используется ToggleButton со свойствами pressed и onChange, а не обычная кнопка, которая только меняет подпись. Состояние нажатия отражается в нативной семантике и селекторе data-pressed. Метка меняется на «Закреплено» с галочкой, а расположенный рядом статус поясняет, что это влияет только на локальный предпросмотр.

Закрепление не меняет количество ингредиентов или их выбор. При изменении количества порций отметки ингредиентов сбрасываются, но закрепление сохраняется: рецепт остаётся тем же. Команда сброса рецепта по умолчанию одновременно восстанавливает все три части модели: четыре порции, пустое множество ингредиентов и ненажатый переключатель. Эти границы сброса определяются смыслом состояния, а не правилом сбрасывать всё при каждом взаимодействии.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — задайте воспроизводимую визуальную конфигурацию для бессоставных элементов управления

Задайте воспроизводимую визуальную конфигурацию для бессоставных элементов управления

Таблица стилей рецепта отдельно задаёт поверхность редактирования числового поля, триггеры счётчика и стили выбранных флажков. Приглушённый цвет текста ингредиента применяется только после выбора; количества остаются хорошо различимыми, чтобы пользователь мог сопоставить собранное количество с текущим числом порций.

В отступах используются значения 4, 8, 12, 16, 24 и 32 пикселя. В оригинальной иерархии шрифтов для элементов управления используется системный шрифт без засечек, а для заголовка рецепта — Georgia. Рядом с полной связанной меткой расположен визуальный флажок размером 21 пиксель; метка остаётся удобной областью для нажатия. Все эти точные значения указаны в полном CSS, чтобы воссоздать образец без догадок по скриншоту.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — адаптируйте композицию, не скрывая информацию

Адаптируйте композицию, не скрывая информацию

В широкой версии гибкая карточка рецепта расположена рядом с панелью порций шириной 280 пикселей. При ширине 760 пикселей блоки выстраиваются в одну колонку. Заголовок карточки и метки ингредиентов могут переноситься, а количество остаётся компактным значением без переноса. На самой узкой контрольной точке отступы панели и размер шрифта меток немного уменьшаются, а не скрывают количества.

Иллюстрация салата — оригинальная работа CSS, помеченная как декоративная. Смысл передают название рецепта, число порций и количества ингредиентов, независимо от изображения. При замене иллюстрации или сужении карточки сохраняйте полные названия ингредиентов и их количества: именно по ним пользователь собирает нужные продукты.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte — обрабатывайте изменение количества как обновление зависимого состояния

Обрабатывайте изменение количества как обновление зависимого состояния

На каждую порцию приходится по 250 граммов каждого из пяти ингредиентов. При увеличении числа порций с четырёх до пяти общий вес меняется с 1 000 до 1 250 граммов, а количество апельсинов — с 320 до 400 граммов. Ранее собранные ингредиенты больше не отмечены, поскольку их количество соответствовало прежним значениям; закрепление рецепта сохраняется, так как сам рецепт не изменился.

Функция обновления сначала сравнивает входящее значение с текущим сигналом с помощью Object.is. Если значение не изменилось, обратный вызов не должен очищать список отметок или сообщать об изменении количества. Для недопустимых значений количества не отображаются, а сбор отключается; сообщение об ошибке объясняет допустимый диапазон. Эти правила позволяют отслеживать промежуточные изменения, не допуская, чтобы недопустимое число приводило к неверному итогу.

  1. Выберите 4 порции апельсиновых долек.
  2. Увеличьте количество до пяти и убедитесь, что апельсина нужно 400 г, всего ингредиентов — 1 250 г, а собрано 0 позиций.
  3. Закрепите рецепт, затем восстановите значения по умолчанию и убедитесь, что всё локальное состояние сброшено.

Прочитать вспомогательную спецификацию

Дизайн-система Kobalte: используйте Solid Build и подключайте таблицу стилей целиком

Используйте сборку Solid и подключайте таблицу стилей целиком

Настройка использует шаблон Vite Solid, фиксирует проверенные версии Solid и Kobalte и монтирует приложение в явно заданный корневой элемент. Приведённые выше компонент и таблица стилей — полные файлы. Для этого примера не нужны провайдер темы, пакет иконок, внешний шрифт или серверная часть.

При интеграции в другое приложение согласуйте компилятор фреймворка и генерацию компонентов. Трансформация React не преобразует JSX Solid в корректное приложение Kobalte. Аналогично, перед заменой закреплённой зависимости на будущую мажорную версию Kobalte API следует свериться с её документацией. Количества ингредиентов и палитра — примеры данных приложения, поэтому их адаптация не требует изменения контракта нативных примитивов.

Прочитать вспомогательную спецификацию

Kobalte Примеры компонентов дизайн-системы

Полный реактивный компонент рецепта

Нативное числовое поле, структура флажка и кнопка-переключатель с явной логикой ошибок и сброса.

jsx
import {createSignal,createMemo,For} from 'solid-js';
import {NumberField} from '@kobalte/core/number-field';
import {Checkbox} from '@kobalte/core/checkbox';
import {ToggleButton} from '@kobalte/core/toggle-button';
export default function App(){
 const[portions,setPortions]=createSignal(4),[checked,setChecked]=createSignal(new Set()),[pinned,setPinned]=createSignal(false),[notice,setNotice]=createSignal('');
 const ingredients=[{id:'orange',name:'Orange segments',grams:80},{id:'beans',name:'Cooked white beans',grams:90},{id:'leaves',name:'Salad leaves',grams:25},{id:'fennel',name:'Thinly sliced fennel',grams:40},{id:'dressing',name:'Citrus dressing',grams:15}];
 const valid=createMemo(()=>Number.isFinite(portions())&&Number.isInteger(portions())&&portions()>=1&&portions()<=8);
 function update(value){if(Object.is(value,portions()))return;setPortions(value);setChecked(new Set());setNotice('Portions changed. Ingredient checks were cleared for the new quantities.')}
 function mark(id,value){setChecked(previous=>{const next=new Set(previous);value?next.add(id):next.delete(id);return next})}
 function reset(){setPortions(4);setChecked(new Set());setPinned(false);setNotice('Restored four portions and an unchecked ingredient list.')}
 return <main class="recipe-shell"><header><p class="eyebrow">KITCHEN NOTEBOOK · REACTIVE RECIPE CARD</p><h1>A brighter bowl, sized for your table.</h1><p>Change the portions, check the scaled ingredients and pin the recipe locally. The quantities are original sample data for this interface.</p></header><div class="recipe-layout"><section class="recipe-card"><div class="salad-art" aria-hidden="true"><div class="plate"><i/><b/><em/><span/><strong/></div></div><div class="recipe-heading"><div><p class="eyebrow">CITRUS & WHITE BEAN SALAD</p><h2>Fresh, crisp, generous.</h2></div><ToggleButton class="pin" pressed={pinned()} onChange={value=>{setPinned(value);setNotice(value?'Recipe pinned in this local preview.':'Recipe unpinned in this local preview.')}} aria-label="Pin recipe">{pinned()?'Pinned ✓':'Pin recipe'}</ToggleButton></div><p class="recipe-description">Sweet citrus, tender beans and crisp fennel. Use the checklist to gather the amounts for your chosen portion count.</p><div class="ingredient-title"><h3>Ingredients</h3><span>{checked().size} / 5 gathered</span></div><div class="ingredients"><For each={ingredients}>{ingredient=><Checkbox class="ingredient" checked={checked().has(ingredient.id)} onChange={value=>mark(ingredient.id,value)} disabled={!valid()}><Checkbox.Input/><Checkbox.Control class="check-control"><Checkbox.Indicator>✓</Checkbox.Indicator></Checkbox.Control><Checkbox.Label class="ingredient-label"><span>{ingredient.name}</span><strong>{valid()?ingredient.grams*portions()+' g':'—'}</strong></Checkbox.Label></Checkbox>}</For></div></section><aside class="portion-panel"><h2>Make it yours</h2><NumberField rawValue={portions()} onRawValueChange={update} minValue={1} maxValue={8} step={1} formatOptions={{maximumFractionDigits:0}} validationState={valid()?'valid':'invalid'} class="portion-field"><NumberField.Label class="field-label">Portions</NumberField.Label><div class="stepper"><NumberField.DecrementTrigger aria-label="Fewer portions">−</NumberField.DecrementTrigger><NumberField.Input/><NumberField.IncrementTrigger aria-label="More portions">+</NumberField.IncrementTrigger></div><NumberField.Description class="field-description">Choose 1–8 whole portions. Changing the quantity clears ingredient checks.</NumberField.Description><NumberField.ErrorMessage class="field-error">Enter a whole number from 1 to 8.</NumberField.ErrorMessage></NumberField><div class="portion-total"><span>Total ingredients</span><strong>{valid()?ingredients.reduce((s,x)=>s+x.grams,0)*portions()+' g':'—'}</strong></div><h3>A simple preparation order</h3><ol><li>Gather the scaled amounts and drain the cooked beans.</li><li>Combine the orange, fennel and leaves in a bowl.</li><li>Add the beans and dressing, then toss gently.</li></ol><button class="reset" onClick={reset}>Restore default recipe</button><p role="status" class="notice">{notice()||(pinned()?'Recipe pinned in this local preview.':'No shopping order or account update is created.')}</p></aside></div></main>
}

Справочник по коду

  • Используйте шаблон Solid Vite, а не компилятор JSX для React.
  • rawValue хранит числовое состояние; все количества ингредиентов рассчитываются на основе этого сигнала.
  • При изменении количества порций очищается Set с отметками, поскольку прежние количества больше не актуальны.

Полная оригинальная визуальная конфигурация

Семантические пользовательские свойства, иерархия типографики, отступы, оформление состояний и оригинальная иллюстрация CSS.

css
:root{--recipe-canvas:#f6f2ec;--recipe-surface:#fffefa;--recipe-ink:#403a30;--recipe-muted:#807465;--recipe-line:#e6ded2;--recipe-action:#95612f;--recipe-on-action:#fff;--recipe-focus:#b88955;--recipe-soft:#f1e4d3;--recipe-space-1:4px;--recipe-space-2:8px;--recipe-space-3:12px;--recipe-space-4:16px;--recipe-space-6:24px;--recipe-space-8:32px}html,body,#app{height:auto;min-height:0;margin:0}*{box-sizing:border-box}body{font-family:system-ui,sans-serif;color:var(--recipe-ink);background:var(--recipe-canvas)}button,input{font:inherit}button{cursor:pointer}button:disabled{opacity:.4;cursor:default}button:focus-visible,input:focus-visible{outline:3px solid var(--recipe-focus);outline-offset:3px}.recipe-shell{max-width:1090px;margin:auto;padding:32px}.eyebrow{font-size:10px;font-weight:700;letter-spacing:.13em;color:var(--recipe-muted)}h1{font-size:34px;line-height:1.15;max-width:700px;margin:12px 0}header>p:last-child{font-size:14px;line-height:1.7;color:var(--recipe-muted);max-width:630px}.recipe-layout{display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:24px;margin-top:28px}.recipe-card,.portion-panel{background:var(--recipe-surface);border:1px solid var(--recipe-line);border-radius:16px;padding:24px;min-width:0}.portion-panel{align-self:start}.salad-art{height:180px;background:#e8e4d5;border-radius:10px;position:relative;overflow:hidden;margin-bottom:24px}.plate{position:absolute;width:210px;height:210px;left:50%;top:18px;transform:translateX(-50%);border:18px solid #faf8f0;border-radius:50%;background:#dedcbf;box-shadow:0 10px 25px #5e584b1c}.plate i,.plate b,.plate em,.plate span,.plate strong{position:absolute;display:block}.plate i{width:85px;height:42px;border-radius:0 0 85px 85px;background:#dc9658;border:6px solid #f7c888;left:14px;top:18px;transform:rotate(-23deg)}.plate b{width:72px;height:36px;border-radius:0 0 72px 72px;background:#dc9658;border:6px solid #f7c888;left:75px;top:83px;transform:rotate(28deg)}.plate em{width:50px;height:77px;background:#83976a;border-radius:80% 0 80% 0;left:108px;top:4px;transform:rotate(20deg)}.plate span{width:30px;height:50px;background:#667f56;border-radius:80% 0 80% 0;left:25px;top:88px;transform:rotate(-23deg)}.plate strong{width:24px;height:34px;background:#fff3d7;border-radius:50%;left:80px;top:46px;box-shadow:-34px 14px 0 #fff3d7,25px 28px 0 #fff3d7}.recipe-heading{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}.recipe-heading h2{font:400 30px/1.2 Georgia,serif;margin:8px 0}.pin{border:1px solid var(--recipe-line);background:white;border-radius:999px;padding:9px 13px;font-size:11px;color:var(--recipe-action)}.pin[data-pressed]{background:var(--recipe-soft);border-color:var(--recipe-action)}.recipe-description{font-size:13px;line-height:1.7;color:var(--recipe-muted)}.ingredient-title{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:24px}.ingredient-title h3{font-size:16px;margin:0}.ingredient-title span{font-size:11px;color:var(--recipe-muted)}.ingredients{margin-top:14px}.ingredient{display:flex;gap:12px;align-items:center;border-top:1px solid var(--recipe-line);padding:16px 0;cursor:pointer}.check-control{height:21px;width:21px;border:1px solid #b9ab98;border-radius:5px;flex-shrink:0;display:flex;justify-content:center;align-items:center;font-size:13px}.check-control[data-checked]{background:var(--recipe-action);color:white;border-color:var(--recipe-action)}.check-control[data-focus-visible]{outline:3px solid var(--recipe-focus);outline-offset:3px}.ingredient-label{display:flex;justify-content:space-between;gap:12px;width:100%;font-size:13px;line-height:1.5;cursor:pointer}.ingredient-label strong{white-space:nowrap;font-size:12px}.ingredient[data-checked] .ingredient-label>span{text-decoration:line-through;color:var(--recipe-muted)}.portion-panel>h2{font-size:20px;margin:0 0 24px}.field-label{font-size:13px;font-weight:600}.stepper{display:flex;margin-top:10px;border:1px solid var(--recipe-line);border-radius:8px;overflow:hidden}.stepper button{width:42px;flex-shrink:0;background:var(--recipe-soft);border:0;font-size:21px}.stepper input{width:100%;min-width:0;text-align:center;border:0;padding:12px 0;background:white;color:var(--recipe-ink)}.field-description{font-size:11px;line-height:1.7;color:var(--recipe-muted);margin:10px 0 0}.field-error{font-size:12px;color:#a73324}.portion-total{display:flex;justify-content:space-between;gap:12px;margin:24px 0;padding:18px 0;border-top:1px solid var(--recipe-line);border-bottom:1px solid var(--recipe-line);font-size:12px}.portion-total strong{font-size:19px;white-space:nowrap}.portion-panel h3{font-size:13px;line-height:1.6}.portion-panel ol{padding-left:18px;font-size:12px;line-height:1.7;color:var(--recipe-muted)}.portion-panel li{margin-bottom:12px}.reset{width:100%;padding:12px;border:1px solid var(--recipe-line);border-radius:8px;background:white;color:var(--recipe-action);font-size:12px;margin-top:16px}.notice{font-size:11px;line-height:1.7;color:var(--recipe-muted)}@media(max-width:760px){.recipe-layout{grid-template-columns:minmax(0,1fr)}.recipe-shell{padding:20px}h1{font-size:29px}}@media(max-width:360px){.recipe-shell{padding:14px}.recipe-card,.portion-panel{padding:16px}.ingredient-label{gap:8px;font-size:12px}.ingredient{gap:8px}.ingredient-label strong{font-size:11px}.recipe-heading h2{font-size:27px}}

Справочник по коду

  • Эти значения recipe-* — оригинальные решения приложения, а не официальные токены Kobalte.
  • Полная таблица стилей включает адаптивную компоновку и заметное оформление фокуса.
04

Kobalte Настройка агента дизайн-системы

Отправная точка для реализации

Шаблон Solid Vite предоставляет необходимый компилятор JSX для Solid. Примеры App.jsx и style.css выше содержат весь код приложения и стили; провайдер или внешний сервис не требуются.

javascript
// npm create vite@latest kobalte-recipe -- --template solid
// cd kobalte-recipe && npm install
// npm install [email protected] @kobalte/[email protected]
// Save the complete examples as src/App.jsx and src/style.css.
// src/index.jsx:
import {render} from 'solid-js/web';
import App from './App.jsx';
import './style.css';
render(() => <App />, document.getElementById('app'));
// index.html: <div id="app"></div><script type="module" src="/src/index.jsx"></script>
// npm run dev

Запрос для вашего проекта

Промпт для AI-агента
Используйте Kobalte 0.13.14 с Solid 1.9.15, чтобы воспроизвести реактивную карточку рецепта. Сохраните NumberField rawValue/onRawValueChange, диапазон от одного до восьми, проверку целого числа порций и полную структуру Label/Description/ErrorMessage. Рассчитывайте граммы ингредиентов и итоговые значения на основе сигнала количества порций; при фактическом изменении количества очищайте неизменяемый Set идентификаторов отмеченных позиций, но сохраняйте закрепление рецепта. Используйте нативные части Checkbox и ToggleButton с pressed/onChange, отключайте сбор при недопустимом количестве и при сбросе возвращайте четыре порции, отсутствие отметок и отсутствие закрепления. Добавьте полный оригинальный рецепт CSS; всё состояние остаётся локальным.
05

Kobalte Источники и область применения дизайн-системы

Проверено с Kobalte 0.13.14 и Solid 1.9.15. Все токены recipe-*, иллюстрация CSS и количества ингредиентов — оригинальные данные примера. Закрепление и сбор хранятся в локальном состоянии; приложение не подключено к учётной записи, сервису покупок или постоянному хранилищу рецептов.