oscript-ui
oscript-ui

Сегменты

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

Когда использовать#

  • Вариантов два–четыре, подписи короткие, и человеку полезно видеть их все сразу: «Светлая / Тёмная / Авто».
  • От выбора зависит, какие поля заполнять дальше: «Ключ / Пароль» — у каждого способа свои поля.
  • Выбор в строке списка, который сохраняется сам: роль участника, режим уведомлений.

Вариантов больше пяти или подписи длинные — это поле выбора (Кит.Поле(…).Вариант(…)). Два состояния «включено / выключено» — тумблер. Соседние виды одного объекта со своими адресами — вкладки: сегменты ничего не открывают, они выбирают значение.

Выбор#

Сегменты — Кит.Сегменты(Имя, …) из Кит.Сегмент(Значение, Подпись). Имя — имя поля формы: под ним уходит значение выбранного сегмента. Отмечен ровно один: сегмент со словом Выбран(), иначе тот, чьё значение назвало слово Выбрано(Значение), иначе первый. Подпись — имя группы для скринридера.

Тема интерфейса

HTML = Кит.Форма("/настройки/тема", Кит.Сегменты("theme", Кит.Сегмент("light", "Светлая"), Кит.Сегмент("dark", "Тёмная"), Кит.Сегмент("auto", "Авто")).Подпись("Тема").Выбрано("auto"), Кит.Кнопка("Сохранить").Основная()).Токен("т0кен-сессии").Рендер();

Значение, которого нет среди сегментов, отмечает первый — так же браузер показывает выпадающий список, чьё значение не нашлось. Два сегмента с одним значением сборка не пускает: сервер получил бы одно и то же, какой ни выбери.

Значок у сегмента#

Слово сегмента Иконка(Имя) ставит значок перед подписью — глиф набора или логотип бренда (бренд:telegram): канал уведомлений, способ входа (дев-сборка 10). Значок декоративен — что выбрано, говорит подпись; галка выбранного встаёт перед ним. На телефоне подпись переносится, а значок остаётся.

Канал подписки — сегменты со значками

HTML = Кит.Сегменты("канал", Кит.Сегмент("webhook", "Вебхук").Иконка("вебхук"), Кит.Сегмент("telegram", "Telegram").Иконка("бренд:telegram"), Кит.Сегмент("email", "Почта").Иконка("конверт")).Подпись("Канал").Выбрано("telegram").Рендер();

Наборы полей#

Дети сегмента — его набор полей: под группой виден набор отмеченного сегмента, остальные спрятаны. Переключает их лист кита (:has(:checked)), без скрипта и без запроса на сервер. Сегмент без детей — без набора: под ним пусто.

Способ входа — у каждого свои поля

HTML = Кит.Форма("/подключения", Кит.Сегменты("auth", Кит.Сегмент("key", "Ключ", Кит.Поле("token", "Ключ доступа").Подсказка("выдаёт владелец проекта")), Кит.Сегмент("password", "Пароль", Кит.Поле("login", "Логин"), Кит.Поле("secret", "Пароль").Секрет()), Кит.Сегмент("none", "Без входа")).Подпись("Способ входа"), Кит.Кнопка("Подключить").Основная()).Токен("т0кен-сессии").Рендер();

Как это связано. Сегменты печатают под группой по панели на каждый сегмент, в том же порядке, — <div class="segments__panel" data-segment="значение"> с его набором внутри. Лист показывает панель номер N, пока отмечен сегмент номер N; правила перечислены до восьми сегментов, и девятый набор сборка не пускает. Признак data-segment несёт значение сегмента: по нему свой CSS или скрипт страницы находит набор, не считая номеров.

Спрятанный набор не отключён: его поля уходят с формой вместе с выбранным, и сервер берёт поля того способа, который пришёл в имени сегментов. Движок без :has() показывает все наборы сразу — поля не теряются.

Панель вне набора#

Набор полей стоит прямо под группой. Когда область должна появиться в другом месте той же формы — в следующем шаге мастера, в другой группе, — её печатают отдельно: Кит.ПанельСегмента(ИмяСегментов, Номер, д…) или короче словом самих сегментов Сегменты.Панель(Номер, д…). Панель видна, пока в той же форме отмечен сегмент номер Номер (с 1) этих сегментов; вместо номера можно дать значение сегмента строкой. Переключает её лист, без скрипта и без запроса, тем же приёмом, что и наборы.

Канал выбирают в первом шаге, а текст сообщения — во втором, только у канала с текстом

Куда
Текст

Вебхук получает событие целиком, текст ему не нужен.

Канал = Кит.Сегменты("channel", Кит.Сегмент("chat", "Чат"), Кит.Сегмент("mail", "Почта"), Кит.Сегмент("hook", "Вебхук")).Подпись("Канал").Выбрано("chat"); HTML = Кит.Форма("/подписки", Кит.Шаги( Кит.Шаг("Куда", Канал, Кит.Поле("target", "Адрес").Обязательное()), Кит.Шаг("Текст", Канал.Панель(1, Кит.Поле("chat_text", "Текст в чат").Многострочное() .Значение("Новая версия {проект} {версия}")), Кит.ПанельСегмента("channel", "mail", Кит.Поле("mail_subject", "Тема письма").Значение("Вышла {версия}"), Кит.Поле("mail_text", "Текст письма").Многострочное()), Кит.ПанельСегмента("channel", 3, Кит.Примечание("Вебхук получает событие целиком, текст ему не нужен.")))) .Кнопка("Создать подписку")).Токен("т0кен-сессии").Рендер();

Как это связано. Панель печатается <div class="segments-panel" data-segments-slot="1" data-segments-number="N" data-segment="значение">, а группа сегментов, на которую ссылаются панели, получает тот же data-segments-slot. Это место группы среди сегментов формы, у которых есть панели: лист не сравнит имя поля панели с именем радиокнопок, поэтому сервер нумерует такие группы, а правила перечислены по месту — до четырёх групп с панелями в форме — и по номеру сегмента, до восьми. Без панелей разметка сегментов прежняя.

Панель и её сегменты лежат в одной форме — Кит.Форма, окно или секция со словом Форма: панель вне формы, панель без своих сегментов в форме и номер больше числа сегментов останавливают сборку. Как и у наборов, спрятанная панель не отключена: её поля уходят с формой, лишние отбрасывает сервер, и обязательное поле в панели, которая может быть спрятана, браузер не пропустит — делайте такие поля обязательными на сервере. Движок без :has() показывает все панели.

Сохранять сразу#

Слово СохранятьСразу — как у тумблера: выбор сам отправляет форму, а с БезПерезагрузки() у формы — в фоне, без перезагрузки страницы. Кнопку сохранения форме всё равно оставьте: без скрипта отправить выбор больше нечем.

Режим уведомлений сохраняется сам

HTML = Кит.Форма("/уведомления", Кит.Сегменты("notify", Кит.Сегмент("all", "Все"), Кит.Сегмент("mentions", "Упоминания"), Кит.Сегмент("none", "Никаких")).Подпись("Уведомления").Выбрано("mentions").СохранятьСразу(), Кит.Кнопка("Сохранить").Вторичная()).БезПерезагрузки().Токен("т0кен-сессии").Рендер();

Источник значения#

Слово Источник(Текст) — как у поля и тумблера: откуда взялось значение — «по умолчанию · ui.theme», «из окружения». Источник стоит мелко и приглушённо справа на строке подписи группы: подпись группы слева, источник справа, пилюля под ними. Не помещается рядом — уходит под подпись, у правого края. Без группы вокруг источник встаёт своей строкой над пилюлей, справа.

Тема и откуда она взялась

Темапо умолчанию · ui.theme
HTML = Кит.Форма("/настройки/тема", Кит.Группа("Тема", Кит.Сегменты("theme", Кит.Сегмент("light", "Светлая"), Кит.Сегмент("dark", "Тёмная")).Подпись("Тема").Выбрано("dark") .Источник("по умолчанию · ui.theme")).Вид("простая"), Кит.Кнопка("Сохранить").Основная()).Токен("т0кен-сессии").Рендер();

Группа сегментов ссылается на источник aria-describedby: скринридер дочитает его после имени группы. Без слова разметка сегментов прежняя.

Отказ#

Слово Ошибка(Текст) — как у поля: отказ сервера словами строкой под пилюлей (дев-сборка 10). Чужое значение — старая вкладка, подделанный запрос, вариант, которого больше нет, — сервер отбивает под самими сегментами, а не тостом. Рамка пилюли — цветом ошибки, группа помечена aria-invalid="true" и ссылается на строку отказа aria-describedby; строка — та же field__error с ролью alert, что под полем. У сегментов с наборами полей строка стоит под пилюлей, над набором. Со скриптом после тихой отправки фокус встаёт на отмеченный сегмент отбитой группы. Без слова разметка прежняя.

Сервер отбил чужое значение

Пресет темы
Пресета «sunset» нет — выберите один из трёх.
HTML = Кит.Форма("/настройки/тема", Кит.Группа("Пресет темы", Кит.Сегменты("пресет", Кит.Сегмент("classic", "Как было"), Кит.Сегмент("material", "Новая"), Кит.Сегмент("oscript", "oscript")).Подпись("Пресет темы").Выбрано("material") .Ошибка("Пресета «sunset» нет — выберите один из трёх.")).Вид("простая"), Кит.Кнопка("Сохранить").Основная()).Токен("т0кен-сессии").Рендер();

Малые#

Малые() — та же пилюля ниже и плотнее: для строки списка, записи реестра, ячейки таблицы, где обычные сегменты выше соседей.

Роль участника в строке таблицы

УчастникРоль
anna
boris
Участники = Новый Массив(); Для Каждого Пара Из СтрРазделить("anna:write,boris:read", ",") Цикл Имя = СтрРазделить(Пара, ":")[0]; Участники.Добавить(Новый Структура("name, role", Имя, Кит.Форма("/участники/" + Имя, Кит.Сегменты("role", Кит.Сегмент("read", "Чтение"), Кит.Сегмент("write", "Запись")).Подпись("Роль: " + Имя) .Выбрано(СтрРазделить(Пара, ":")[1]).Малые().СохранятьСразу()) .БезПерезагрузки().Токен("т0кен-сессии"))); КонецЦикла; Таблица = Кит.Таблица(Участники); Таблица.Колонка("name", "Участник"); Таблица.Колонка("role", "Роль"); HTML = Таблица.Рендер();

На телефоне#

На узком экране (до 600 px) подпись сегмента не обрезается многоточием, а переносится во вторую строку: пилюля становится выше, но каждый вариант читается целиком.

Три подписи в два слова — сузьте окно до телефона

HTML = Кит.Сегменты("scheme", Кит.Сегмент("system", "Как в системе"), Кит.Сегмент("light", "Всегда светлая"), Кит.Сегмент("dark", "Всегда тёмная")).Подпись("Тема").Выбрано("system").Рендер();

Переносится подпись только между словами. Колонки на телефоне равные, пока слова в них помещаются; колонка не бывает уже самого длинного слова своей подписи — у выбранного сегмента вместе с галкой. Однословная подпись, которой не хватает равной доли, делает свою колонку шире соседей, а остаток ширины делится поровну: «Вручную» не станет «Вручну / ю».

Однословные подписи рядом с длинной — слово не рвётся

HTML = Кит.Сегменты("source", Кит.Сегмент("manual", "Вручную"), Кит.Сегмент("github", "GitHub"), Кит.Сегмент("regex", "Регулярное выражение")).Подпись("Откуда версия").Выбрано("manual").Рендер();

Слово не рвётся и тогда, когда места на все слова не хватает: если самые длинные слова всех подписей вместе шире экрана, пилюля выходит за край. Четыре сегмента с длинными однословными подписями телефону не по силам — сократите подписи или дайте выбор полем .Вариант(…).

В окне#

Окно уже экрана: базовое — 560 px, на телефоне — 358. Сегменты в окне на любой ширине держатся так же, как на телефоне (дев-сборка 8): пилюля во всю ширину окна, колонка не уже самого длинного слова, подпись переносится между словами, а не обрезается многоточием.

Пять сегментов в окне — подписи целиком

Окно = Кит.Окно("Оформление метки", Кит.Сегменты("style", Кит.Сегмент("stable", "Стабильный"), Кит.Сегмент("deprecated", "Устаревший"), Кит.Сегмент("preview", "Предварительный"), Кит.Сегмент("danger", "Опасный"), Кит.Сегмент("plain", "Обычный")).Подпись("Стиль метки").Выбрано("stable")) .Якорь("oformlenie-metki"); HTML = Кит.Холст(Кит.Кнопка("Оформление").Открывает(Окно), Окно).Рендер();

Без скрипта#

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

Гайдлайны#

ДелайтеДва–четыре варианта, подпись — одно-два слова
Код
HTML = Кит.Сегменты("view",
	Кит.Сегмент("list", "Списком"),
	Кит.Сегмент("cards", "Карточками")).Подпись("Вид списка").Рендер();
Не делайтеСемь вариантов сегментами: подписи сжимаются до многоточий
Код
HTML = Кит.Сегменты("day",
	Кит.Сегмент("1", "Понедельник"), Кит.Сегмент("2", "Вторник"), Кит.Сегмент("3", "Среда"),
	Кит.Сегмент("4", "Четверг"), Кит.Сегмент("5", "Пятница"), Кит.Сегмент("6", "Суббота"),
	Кит.Сегмент("7", "Воскресенье")).Подпись("День").Рендер();

Длинный перечень — это поле выбора: Кит.Поле("day", "День").Вариант(…).

ДелайтеОбязательность наборов проверяет сервер
Код
HTML = Кит.Сегменты("auth",
	Кит.Сегмент("key", "Ключ", Кит.Поле("token", "Ключ доступа")),
	Кит.Сегмент("password", "Пароль", Кит.Поле("secret", "Пароль").Секрет())).Подпись("Способ входа").Рендер();
Не делайтеОбязательное поле в наборе: спрятанное, оно не пустит форму
Код
HTML = Кит.Сегменты("auth",
	Кит.Сегмент("key", "Ключ", Кит.Поле("token", "Ключ доступа").Обязательное()),
	Кит.Сегмент("password", "Пароль", Кит.Поле("secret", "Пароль").Секрет().Обязательное()))
	.Подпись("Способ входа").Рендер();

Браузер не отправит форму, пока пусто обязательное поле, — даже спрятанное в невыбранном наборе, и показать отказ ему негде. Поле набора, без которого нельзя, проверяет сервер для того способа, что пришёл в имени сегментов.

Сводка проекта.

Изменения проекта.

Не делайтеСегменты вместо вкладок: у каждого вида объекта свой адрес
Код
HTML = Кит.Сегменты("tab",
	Кит.Сегмент("overview", "Обзор", Кит.Примечание("Сводка проекта.")),
	Кит.Сегмент("history", "История", Кит.Примечание("Изменения проекта."))).Рендер();

Доступность#

  • Группа — role="radiogroup" с именем из слова Подпись (aria-label): скринридер читает «Тема, группа переключателей, Тёмная, 2 из 3». Без подписи группу называет только соседний текст — дайте её всегда, когда сегменты стоят без видимого заголовка.
  • Сегмент — настоящая радиокнопка внутри своей метки: Tab приходит в группу один раз, стрелки меняют выбор, пробел отмечает. Фокус виден обводкой сегмента.
  • Источник значения связан с группой aria-describedby: скринридер читает его после имени группы, а не отдельной строкой где-то рядом.
  • Отказ (Ошибка) — группа aria-invalid="true", строка отказа с ролью alert связана с группой aria-describedby (вместе с источником, если он есть).
  • Галка выбранного — оформление (aria-hidden): что отмечено, скринридер узнаёт от радиокнопки.
  • Спрятанный набор — display: none: его поля уходят из порядка Tab и из речи скринридера.
  • Настройка «меньше движения» гасит переходы заливки и въезд галки.

Модель#

Кит.Сегменты()src/Классы/холст/Кит.os

Сегменты — выбор одного из немногих (пресет, тема, режим) рядом кнопок в одной пилюле:
выбранный сегмент залит, на нём галка. Под капотом — радиокнопки одного имени: выбор
уходит с формой без скрипта, стрелки меняют его движком браузера. Дети — сегменты
(Кит.Сегмент); дети сегмента — его набор полей: под группой виден набор отмеченного
сегмента, и переключает наборы лист (:has(:checked)), без скрипта. Отмечен ровно один:
сегмент со словом Выбран(), иначе совпавший с Выбрано(Значение), иначе первый. Слова:
Выбрано(Значение), Подпись (имя группы для скринридера), Малые (низкие — для строки
списка), СохранятьСразу (выбор сам отправляет форму, как у тумблера), Источник(Текст)
(откуда значение — мелко справа на строке подписи группы, как у поля), Якорь.

Параметры:
 Имя   - Строка - имя поля (атрибут name радиокнопок): под ним выбор уходит с формой.
 Р1…Р8 - ЭлементЭкрана - (необязательные) сегменты, узлы Кит.Сегмент.

Возвращаемое значение:
 ЭлементЭкрана

Кит.Сегмент()src/Классы/холст/Кит.os

Сегмент — один вариант Кит.Сегменты: значение уходит с формой, подпись видна на сегменте.
Дети — набор полей, который виден, пока сегмент выбран. Печатается только внутри
Кит.Сегменты. Слова: Выбран() — этот сегмент отмечен; Иконка(Имя) — значок перед
подписью, глиф или «бренд:<ключ>» (дев-сборка 10); Когда(Ложь) снимает сегмент
вместе с его набором.

Параметры:
 Значение - Строка - что уходит с формой, когда сегмент выбран.
 Подпись  - Строка - надпись сегмента, одно-два слова.
 Р1…Р7    - ЭлементЭкрана, Строка - (необязательные) набор полей сегмента; строка — текст.

Возвращаемое значение:
 ЭлементЭкрана

Сегменты.Отрисовать()src/Классы/холст/Сегменты.os

Печатает сегменты: группу радиокнопок в пилюле и, если у сегментов есть наборы полей,
панели этих наборов под ней. Без сегментов не печатает ничего.

Параметры:
 Модель - Структура - Атрибуты (ДОВЕРЕННАЯ строка атрибутов группы: класс, id, data-*),
          Имя (атрибут name радиокнопок), Подпись (имя группы для скринридера,
          aria-label), Выбрано (значение выбранного сегмента), СохранятьСразу (выбор сам
          отправляет форму), Сегменты (массив структур: Значение, Подпись, Выбран —
          Булево, Набор — ДОВЕРЕННЫЙ HTML набора полей сегмента или "", Иконка —
          необязательно, дев-сборка 10: значок перед подписью, глиф или «бренд:<ключ>»), Источник
          (необязательно: откуда значение — «по умолчанию · ui.theme»; печатается перед
          группой и стоит справа на строке подписи группы), ИдИсточника (id источника:
          на него ссылается aria-describedby группы), Ошибка (необязательно, дев-сборка
          10: отказ сервера словами — строкой под пилюлей, как отказ под полем; группа
          aria-invalid="true"), ИдОшибки (id строки отказа: на него ссылается
          aria-describedby группы).

Возвращаемое значение:
 Строка - <div class="segments" role="radiogroup"> либо обёртка .segments-set с группой
          и панелями наборов; "" без сегментов.

Кит.ПанельСегмента()src/Классы/холст/Кит.os

Панель сегмента вне его набора (0.13, дев-сборка 7): область, которая видна, пока в той же
форме отмечен сегмент номер Номер у сегментов с именем ИмяСегментов. Наборы под сегментами
(дети Кит.Сегмент) стоят прямо под группой, а панель кладут куда угодно в той же форме —
в другой шаг мастера, в другую группу. Переключает её лист кита тем же приёмом, что наборы
(:has(:checked), до восьми сегментов), без скрипта; движок без :has() показывает все панели.
Поля спрятанной панели уходят с формой, как поля спрятанного набора: лишние отбрасывает
сервер. Короче — словом самих сегментов: Сегменты.Панель(Номер, д…).

Параметры:
 ИмяСегментов - Строка - имя Кит.Сегменты той же формы (их атрибут name).
 Номер        - Число, Строка - номер сегмента с 1 или значение сегмента.
 Р1…Р7        - ЭлементЭкрана, Строка - (необязательные) содержимое панели; строка — текст.

Возвращаемое значение:
 ЭлементЭкрана

ЭлементЭкрана.Панель()src/Классы/холст/ЭлементЭкрана.os

Панель сегмента вне его набора: область, видная, пока в той же форме отмечен сегмент
номер Номер этих сегментов, — то же, что Кит.ПанельСегмента(Имя сегментов, Номер, …).
Узел панели возвращается: его кладут в другой шаг или группу той же формы (0.13,
дев-сборка 7).

Параметры:
 Номер - Число, Строка - номер сегмента с 1 или значение сегмента.
 Р1…Р7 - ЭлементЭкрана, Строка - (необязательные) содержимое панели; строка — текст.

Возвращаемое значение:
 ЭлементЭкрана - новый узел панели (не сами сегменты).