Przejdź do głównej zawartości
Nawigacja Otwórz escZamknij

Zestaw UI

@openchamber/sdk/ui to zestaw gotowych kontrolek, które używają kolorów, czcionek i odstępów aplikacji, więc Twoje rozszerzenie wygląda jak reszta OpenChamber bez kopiowania jej styli. Każda kontrolka to funkcja: dajesz jej swój element i ustawienia, ona rysuje się w nim i zwraca { update, dispose }. Ty składasz kontrolki w ekran i trzymasz dane. Zestaw nigdy nie wywołuje Twojego dostawcy ani OpenChamber; host.request, host.attach i resztę nadal wywołujesz samodzielnie. Własny HTML i CSS możesz napisać dla wszystkiego, czego w zestawie brakuje, ale najpierw sięgaj po zestaw: jego kontrolki same podążają za motywem, odstępami i nawykami klawiaturowymi użytkownika, więc zbudowany z nich panel wygląda jak część OpenChamber, a nie strona internetowa w środku.

Najpierw wywołaj applyHostReady

Kontrolki czytają kolory ze zmiennych CSS, które applyHostReady zapisuje na stronie. Ustawia też czcionkę i kolor tekstu aplikacji na korzeniu strony, więc zwykły HTML, który dodasz samodzielnie, też wygląda poprawnie. Wywołaj je w host.onReady przed pierwszym montowaniem, a kolory będą podążać za motywem aplikacji, gdy użytkownik go zmieni.

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.
});

Zestaw używa kolorów tekstu obliczonych przez hosta w delikatnie zabarwionych przyciskach, plakietkach, tytułach powiadomień, komunikatach walidacji i linkach. Nie trzeba samodzielnie obliczać kontrastu.

Do własnego CSS applyHostReady udostępnia --primary-text, --success-text, --warning-text, --error-text i --info-text oraz odpowiadające im aliasy --oc-*-text. Używaj ich do tekstu na neutralnych lub lekko zabarwionych tłach. Dla wypełnień zachowaj kolory bazowe, a dla tekstu na pełnym wypełnieniu --oc-primary używaj --oc-primary-fg. Stosuj każdy stan z onReady, aby kolory były aktualne.

onReady może uruchamiać się wielokrotnie, gdy panel jest otwarty, na przykład po zmianie kontekstu sesji. Stosuj motyw za każdym razem, ale montuj kontrolki i rejestruj listenery tylko raz. Przechowuj szkice poza funkcjami renderującymi. Przy zmianie zakładki ukrywaj istniejące panele lub odtwarzaj ich wartości ze stanu.

Synchronizacja wyboru i pól

mountTabs, mountSelect, mountCheckbox i mountSwitch zgłaszają zmiany przez onChange, ale same nie zmieniają activeId, value ani checked. Zapisz wartość i przekaż ją z powrotem przez update. Pola tekstowe i wyszukiwania pokazują wpisany tekst od razu, ale też wymagają update({ value }), aby późniejsza aktualizacja nie przywróciła starej wartości. onClick nie zmienia wariantu przycisku. Do wyboru trybu użyj mountTabs.

Zamontuj te kontrolki raz w istniejącym root panelu:

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 });
},
});

Aby zamienić formaty, zamień obie wartości w stanie i wywołaj update({ value }) na obu istniejących selectach. Nie montuj nowych kontrolek do zmiany wartości. Przy usuwaniu kontrolki wywołaj dispose().

Co możesz zamontować

FunkcjaCo rysuje
mountButtonPrzycisk. variant to default, secondary, outline, ghost lub destructive; size to default, sm lub xs. loading pokazuje spinner i blokuje kliknięcia.
mountTextFieldPole z etykietą albo textarea z multiline. Opcjonalny tekst helper lub error, maskowanie password i mono dla tokenów i identyfikatorów.
mountSearchFieldPole wyszukiwania z lupą i przyciskiem czyszczenia. Escape je czyści.
mountSelectLista rozwijana. searchable dodaje pole filtra na górze listy. Strzałki przesuwają, Enter wybiera, Escape zamyka.
mountCheckbox, mountSwitchPole wyboru albo przełącznik z etykietą i opcjonalnym description.
mountTabsZakładki w formie pigułek, każda z opcjonalnym licznikiem. Strzałki w lewo i w prawo przechodzą między nimi.
mountBadgeMała pigułka. tone to neutral, primary, success, warning, error lub info.
mountListLista przyjazna klawiaturze. Każdy wiersz może mieć klucz z przodu, tytuł, podtytuł, tekst meta wyrównany do prawej i odznakę.
mountEmptyWyśrodkowany stan pusty z tytułem, linią treści i opcjonalnym przyciskiem.
mountSpinnerPierścień ładowania z opcjonalną etykietą.
mountBannerKolorowy komunikat z tytułem, treścią i opcjonalną akcją.
mountSeparatorCienka linia, opcjonalnie z etykietą pośrodku.
mountProgressPasek postępu od 0 do 100.
mountMenuPrzycisk, który otwiera listę akcji. Pozycje mogą być destrukcyjne, wyłączone albo być separatorem.
mountTextTekst od Twojego dostawcy. Obrazy i linki http(s) w stylu Markdown stają się prawdziwymi obrazami i linkami; cała reszta pozostaje zwykłym tekstem, więc niezaufaną treść można bezpiecznie pokazać.

Obok kontrolek dostajesz trzy proste funkcje pomocnicze. filterSelectOptions(options, query) to dopasowanie, którego używa mountSelect. splitTextMedia(text) to to, co mountText robi przed rysowaniem. moveListSelection to krok klawiatury wspólny dla listy, selecta i menu, na wypadek gdybyś budował własną listę.

Lista z wyszukiwaniem

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 przyjmuje tylko te ustawienia, które się zmieniły. Lista zachowuje podświetlony wiersz między aktualizacjami, dopóki ten wiersz nadal istnieje.

Linki w tekście od dostawcy

Twoja strona działa w sandboxie i nie może sama otworzyć nowej karty. Gdy używasz mountText, przekaż onOpenUrl i oddaj link OpenChamber:

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

Zobacz też