Перейти до вмісту
Навігація Відкрити escЗакрити

UI-набір

@openchamber/sdk/ui - це набір готових елементів, які використовують кольори, шрифти й відступи застосунку, тож ваше розширення виглядає як решта OpenChamber без копіювання його стилів. Кожен елемент - функція: ви даєте їй свій DOM-елемент і налаштування, вона малює себе там і повертає { update, dispose }. Ви складаєте з елементів екран і тримаєте дані в себе. Набір ніколи не звертається ні до вашого провайдера, ні до OpenChamber; host.request, host.attach і решту ви викликаєте самі. Для всього, чого в наборі немає, можна писати власний HTML і CSS, але спершу беріть набір: його елементи самі підхоплюють тему, відступи й клавіатурні звички користувача, тож зібрана з них панель відчувається частиною OpenChamber, а не сайтом усередині нього.

Спершу викличте applyHostReady

Елементи читають кольори з CSS-змінних, які applyHostReady записує на сторінку. Вона також задає шрифт і колір тексту застосунку на корені сторінки, тож звичайний HTML, який ви додаєте самі, теж виглядає як слід. Викликайте її всередині host.onReady перед першим монтуванням, і кольори підхоплюватимуть тему застосунку, коли користувач її перемикає.

import { applyHostReady } from "@openchamber/sdk/ui";
let mounted = false;
host.onReady((ctx) => {
applyHostReady(ctx, document.documentElement);
if (mounted) return;
mounted = true;
// Mount controls and register listeners once here.
});

Набір використовує обчислені хостом кольори тексту для тонованих кнопок, бейджів, заголовків сповіщень, повідомлень про помилки введення й посилань. Самостійно розраховувати контраст не потрібно.

Для власного CSS applyHostReady надає --primary-text, --success-text, --warning-text, --error-text та --info-text, а також відповідні псевдоніми --oc-*-text. Використовуйте їх для тексту на нейтральному або злегка тонованому фоні. Для заливок залишайте базові кольори, а для тексту на суцільній заливці --oc-primary використовуйте --oc-primary-fg. Застосовуйте кожен знімок onReady, щоб кольори залишалися актуальними.

onReady може спрацьовувати багато разів, поки панель відкрита, зокрема при зміні контексту сесії. Застосовуйте тему щоразу, але монтуйте елементи й реєструйте слухачів лише один раз. Зберігайте чернетки поза функціями малювання. При перемиканні вкладок приховуйте наявні панелі або відновлюйте значення зі стану, а не очищайте їх.

Синхронізація вибору та введення

mountTabs, mountSelect, mountCheckbox і mountSwitch повідомляють про зміни через onChange, але самі не змінюють activeId, value або checked. Збережіть значення й передайте його назад через update. Текстові та пошукові поля одразу показують введений текст, але також потребують update({ value }), інакше наступне оновлення може повернути старе значення. onClick кнопки не змінює її variant. Для вибору режиму краще використати mountTabs.

Змонтуйте ці елементи один раз у наявному root панелі:

import { mountSelect, mountTabs } from "@openchamber/sdk/ui";
let activeId = "convert";
const tabs = mountTabs(root, {
items: [{ id: "convert", label: "Convert" }, { id: "format", label: "Format" }],
activeId,
onChange: (next) => {
activeId = next;
tabs.update({ activeId });
// Show the matching panel without discarding its draft values.
},
});
let format = "json";
const select = mountSelect(root, {
options: [{ id: "json", label: "JSON" }, { id: "yaml", label: "YAML" }],
value: format,
onChange: (next) => {
format = next;
select.update({ value: format });
},
});

Для кнопки обміну форматами поміняйте обидва значення у стані й викличте update({ value }) на обох наявних select. Не монтуйте нові елементи для зміни значень. При видаленні елемента викликайте dispose().

Що можна змонтувати

ФункціяЩо малює
mountButtonКнопка. variant - default, secondary, outline, ghost або destructive; size - default, sm або xs. loading показує спінер і блокує кліки.
mountTextFieldПоле з підписом або textarea з multiline. Необов’язковий текст helper чи error, маскування password і mono для токенів та id.
mountSearchFieldПоле пошуку з лупою і кнопкою очищення. Escape очищає його.
mountSelectВипадний список. searchable додає фільтр угорі списку. Стрілки рухають, Enter вибирає, Escape закриває.
mountCheckbox, mountSwitchПрапорець або перемикач із підписом і необов’язковим description.
mountTabsВкладки-пігулки, кожна з необов’язковим лічильником. Стрілки вліво і вправо перемикають між ними.
mountBadgeМаленька пігулка. tone - neutral, primary, success, warning, error або info.
mountListСписок, зручний для клавіатури. Кожен рядок може мати ключ спереду, заголовок, підзаголовок, метатекст праворуч і бейдж.
mountEmptyПорожній стан по центру із заголовком, рядком тексту і необов’язковою кнопкою.
mountSpinnerКільце завантаження з необов’язковим підписом.
mountBannerКольорове повідомлення із заголовком, текстом і необов’язковою дією.
mountSeparatorТонка лінія, за бажанням із підписом посередині.
mountProgressСмуга прогресу від 0 до 100.
mountMenuКнопка, що відкриває список дій. Пункти можуть бути деструктивними, вимкненими або роздільником.
mountTextТекст від вашого провайдера. Зображення у стилі Markdown і http(s)-посилання стають справжніми зображеннями і посиланнями; усе інше лишається простим текстом, тому ненадійний вміст показувати безпечно.

Поруч з елементами йдуть три прості помічники. filterSelectOptions(options, query) - те саме зіставлення, яке використовує mountSelect. splitTextMedia(text) - те, що mountText робить перед малюванням. moveListSelection - крок клавіатури, спільний для списку, випадного списку і меню, якщо ви будуєте власний список.

Список із пошуком

import { applyHostReady, mountEmpty, mountList, mountSearchField } from "@openchamber/sdk/ui";
let mounted = false;
host.onReady((ctx) => {
applyHostReady(ctx, document.documentElement);
if (mounted) return;
mounted = true;
const root = document.querySelector("#root")!;
const searchRoot = root.appendChild(document.createElement("div"));
const listRoot = root.appendChild(document.createElement("div"));
const emptyRoot = root.appendChild(document.createElement("div"));
let query = "";
let empty: { dispose: () => void } | null = null;
const list = mountList(listRoot, {
items: [],
onSelect: (id) => {
const task = tasks.find((item) => item.id === id);
if (task) void host.attach({ providerId: "acme-hello", id, title: task.title, url: task.url });
},
});
const paint = () => {
const rows = tasks
.filter((task) => task.title.toLowerCase().includes(query.toLowerCase()))
.map((task) => ({ id: task.id, leading: task.key, title: task.title, meta: task.updated }));
list.update({ items: rows });
empty?.dispose();
empty = rows.length === 0
? mountEmpty(emptyRoot, { title: "No tasks match", body: "Try a shorter search." })
: null;
};
const search = mountSearchField(searchRoot, {
value: query,
placeholder: "Search tasks",
onChange: (next) => {
query = next;
search.update({ value: next });
paint();
},
});
paint();
});

update приймає лише ті налаштування, що змінилися. Список зберігає виділений рядок між оновленнями, поки цей рядок є на місці.

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

Ваша сторінка працює в пісочниці і не може сама відкрити нову вкладку. Коли використовуєте mountText, передайте onOpenUrl і віддайте посилання OpenChamber:

import { mountText } from "@openchamber/sdk/ui";
mountText(root, {
text: comment.body,
onOpenUrl: (url) => void host.openUrl(url),
});

Дивіться також