Aller au contenu
Naviguer Ouvrir escFermer

Kit UI

@openchamber/sdk/ui est un ensemble de contrôles prêts à l’emploi qui utilisent les couleurs, polices et espacements de l’application, pour que votre extension ressemble au reste d’OpenChamber sans copier ses styles. Chaque contrôle est une fonction : vous lui donnez un élément qui vous appartient et ses réglages, il se dessine dedans et renvoie { update, dispose }. Vous assemblez les contrôles en un écran et vous gardez la main sur les données. Le kit n’appelle jamais votre fournisseur ni OpenChamber ; c’est toujours vous qui appelez host.request, host.attach et le reste. Vous pouvez écrire votre propre HTML et CSS pour tout ce que le kit ne couvre pas, mais commencez par le kit : ses contrôles suivent automatiquement le thème, les espacements et les habitudes clavier de l’utilisateur, donc un panneau construit avec eux se sent partie intégrante d’OpenChamber plutôt qu’un site web à l’intérieur.

Appelez applyHostReady en premier

Les contrôles lisent leurs couleurs dans des variables CSS que applyHostReady écrit sur la page. Il applique aussi la police et la couleur de texte de l’application à la racine de la page, donc le HTML brut que vous ajoutez vous-même s’affiche correctement lui aussi. Appelez-le dans host.onReady avant le premier montage, et les couleurs suivront le thème de l’application quand l’utilisateur en change.

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

Le kit utilise les couleurs de texte calculées par l’hôte pour les boutons teintés, badges, titres d’avis, messages de validation et liens. Vous n’avez pas besoin de calculer le contraste.

Pour votre propre CSS, applyHostReady fournit --primary-text, --success-text, --warning-text, --error-text et --info-text, ainsi que les alias --oc-*-text correspondants. Utilisez-les pour le texte sur des fonds neutres ou légèrement teintés. Gardez les couleurs de base pour les fonds et utilisez --oc-primary-fg pour le texte sur un aplat --oc-primary. Appliquez chaque instantané onReady pour garder les couleurs à jour.

onReady peut s’exécuter plusieurs fois pendant que le panneau est ouvert, notamment quand le contexte de session change. Appliquez le thème à chaque appel, mais montez les contrôles et inscrivez les listeners une seule fois. Gardez les brouillons hors des fonctions de rendu. Lors d’un changement d’onglet, masquez les panneaux existants ou restaurez leurs valeurs depuis votre état.

Synchroniser la sélection et les champs

mountTabs, mountSelect, mountCheckbox et mountSwitch signalent les changements via onChange, mais ne modifient pas eux-mêmes activeId, value ou checked. Enregistrez la valeur et renvoyez-la avec update. Les champs de texte et de recherche affichent la saisie immédiatement, mais nécessitent aussi update({ value }) pour éviter qu’une mise à jour ultérieure restaure une ancienne valeur. onClick ne change pas la variante d’un bouton. Préférez mountTabs pour choisir un mode.

Montez ces contrôles une fois dans le root existant du panneau :

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

Pour inverser les formats, échangez les deux valeurs dans l’état et appelez update({ value }) sur les deux select existants. Ne montez pas de nouveaux contrôles pour changer leurs valeurs. Appelez dispose() au retrait d’un contrôle.

Ce que vous pouvez monter

FonctionCe qu’elle dessine
mountButtonUn bouton. variant est default, secondary, outline, ghost ou destructive ; size est default, sm ou xs. loading affiche un spinner et bloque les clics.
mountTextFieldUn champ avec libellé, ou un textarea avec multiline. Texte helper ou error optionnel, masquage password, et mono pour les tokens et les ids.
mountSearchFieldUne zone de recherche avec une loupe et un bouton d’effacement. Échap la vide.
mountSelectUne liste déroulante. searchable ajoute un filtre en haut de la liste. Les flèches déplacent, Entrée choisit, Échap ferme.
mountCheckbox, mountSwitchUne case à cocher ou un interrupteur avec un libellé et une description optionnelle.
mountTabsDes onglets en forme de pilule, chacun avec un compteur optionnel. Les flèches gauche et droite passent de l’un à l’autre.
mountBadgeUne petite pilule. tone est neutral, primary, success, warning, error ou info.
mountListUne liste utilisable au clavier. Chaque ligne peut avoir une clé en tête, un titre, un sous-titre, un texte méta aligné à droite et un badge.
mountEmptyUn état vide centré avec un titre, une ligne de texte et un bouton optionnel.
mountSpinnerUn anneau de chargement avec un libellé optionnel.
mountBannerUne notice colorée avec un titre, un texte et une action optionnelle.
mountSeparatorUne ligne fine, avec un libellé au milieu si besoin.
mountProgressUne barre de progression de 0 à 100.
mountMenuUn bouton qui ouvre une liste d’actions. Les éléments peuvent être destructifs, désactivés, ou un séparateur.
mountTextDu texte venant de votre fournisseur. Les images et liens http(s) au format Markdown deviennent de vraies images et de vrais liens ; tout le reste reste du texte brut, donc un contenu non fiable peut être affiché sans risque.

Trois helpers simples sont livrés à côté des contrôles. filterSelectOptions(options, query) est la correspondance qu’utilise mountSelect. splitTextMedia(text) est ce que fait mountText avant de dessiner. moveListSelection est le pas clavier partagé par la liste, le select et le menu, au cas où vous construiriez votre propre liste.

Une liste avec recherche

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 ne prend que les réglages qui ont changé. La liste conserve sa ligne surlignée d’une mise à jour à l’autre tant que cette ligne existe encore.

Liens dans le texte du fournisseur

Votre page tourne dans un sandbox et ne peut pas ouvrir un nouvel onglet toute seule. Quand vous utilisez mountText, passez onOpenUrl et confiez le lien à OpenChamber :

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

Voir aussi