Cette page a été traduite automatiquement et peut contenir des erreurs. Lire l'original en anglais

API

nsh/base

nsh/base est la couche de composants de base de l’interface d’une app : des composants sans style qui prennent en charge le comportement, l’état, le focus et l’accessibilité, sur toutes les plateformes. Elle se place entre les primitives (nsh, nsh/mobile et nsh/desktop) et la bibliothèque de composants propre à l’app, comme gpui-base se place entre gpui et une bibliothèque stylée dans GPUI Kit (voir Modules). Elle fournit des contrôles (Button, Link, Checkbox, ColorPicker avec son ColorArea, ColorSlider, ColorField, ColorSwatch et ColorEyeDropper, NumberInput, Radio dans un RadioGroup, Slider, Switch, Tab dans Tabs, Toggle dans un ToggleGroup, Select, Combobox, Calendar, TimeField, DatePicker), des palettes de commandes (Command), des barres de contrôles (Toolbar avec ses ToolbarGroup et ToolbarSeparator), des barres de défilement au style propre à l’app (Scrollbar), des régions qui se déplient et se replient (Collapsible, et Accordion avec ses AccordionItem, AccordionHeader, AccordionTrigger et AccordionPanel), une barre de progression (Progress), des codes à usage unique (OtpInput), les pages d’un ensemble (Pagination), des éléments qui contiennent des éléments (Tree), les pièces dont sont faits les graphiques (Plot avec Line, Area, Bar, Rule, Dot, Cell, Grid, Pie et Arc, dans nsh/base/plot), des espaces de travail que l’utilisateur organise (des ResizablePanel dans un ResizablePanelGroup, et les divisions, groupes d’onglets et docks de DockArea), des carrousels (Carousel et ses parties), des documents Markdown ou HTML (TextView), un éditeur de code (Editor), les différences entre des textes (DiffView) et des tableaux de lignes et de colonnes (Table et ses parties, DataTable), des champs de saisie de texte (Input), des popups affichés par-dessus tout (Popover, Menu et ContextMenu avec leurs MenuItem, MenuSeparator et Submenu, Tooltip, Sheet), des toasts (Toast dans un ToastStack, leur cycle de vie dans un ToastManager), la navigation (un NavStack de NavPage piloté par l’état que vous créez avec createNavStack, et Dialog pour le contenu présenté par-dessus tout le reste) et les boîtes de dialogue d’alerte du système (AlertDialog). Les pages et les dialogues décident aussi de la barre d’état (statusBar).

ui-navigation.tsx
ui-navigation.tsxLive
// Navigation: a stack of pages with header options, pushed and popped; in a
// wide window the inbox and the open message side by side (master–detail).
import { type JSX, mount, signal, Text, View } from "nsh";
import { createNavStack, NavPage, NavStack } from "nsh/base";
import { button, buttonText, focusRing, hoverFill, size, tint, ui, wide } from "./palette";

function Button(props: { title: string; variant?: "primary" | "secondary"; onPress: () => void }) {
  const kind = () => (props.variant !== "secondary" ? "primary" : "secondary");
  return (
    <View role="button" onPress={props.onPress} {...button(kind())}>
      <Text style={buttonText(kind())}>{props.title}</Text>
    </View>
  );
}

type Route = { name: "home" } | { name: "detail"; id: number };

const nav = createNavStack<Route>([{ name: "home" }]);

// The page's own header where NSH draws none (the web, the desktops); iOS and
// Android show the system's, from `title` and `headerRight`.
function Header(props: { title: string; right?: JSX.Element }) {
  return (
    <View style={{ backgroundColor: ui().page, borderBottomWidth: 1, borderBottomColor: ui().line }} class="h-12 flex-row items-center px-2">
      <View class="w-20 flex-row">
        <If when={nav.routes().length > 1}>
          <View role="button" accessibilityLabel="Back" onPress={() => nav.pop()} hoverStyle={hoverFill()} focusStyle={focusRing()} class="rounded-md px-2 py-1 active:opacity-60">
            <Text style={{ fontSize: 17, color: ui().accent }}>‹ Back</Text>
          </View>
        </If>
      </View>
      <Text style={{ numberOfLines: 1, fontSize: 17, fontWeight: "600", color: ui().text }} class="flex-1 text-center">
        {props.title}
      </Text>
      <View class="w-20 flex-row justify-end px-2">{props.right}</View>
    </View>
  );
}

// A page's content: a column as wide as a phone, centered in a wider window.
function Body(props: { children: JSX.Element }) {
  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 16, paddingTop: 24 }}>
      <View style={{ width: "100%", maxWidth: 480, alignSelf: "center", gap: 12 }}>{props.children}</View>
    </View>
  );
}

function Home() {
  const opened = signal(0);
  return (
    <NavPage title="Inbox" header={<Header title="Inbox" />}>
      <Body>
        <Text style={{ color: ui().muted }}>{`Opened ${opened()} messages`}</Text>
        <Button
          title="Open message 1"
          onPress={() => {
            opened.set(opened() + 1);
            nav.push({ name: "detail", id: 1 });
          }}
        />
        <Button title="Open message 2" variant="secondary" onPress={() => nav.push({ name: "detail", id: 2 })} />
      </Body>
    </NavPage>
  );
}

function Detail(props: { id: number }) {
  const starred = signal(false);
  const star = () => (
    <Text onPress={() => starred.set(!starred())} style={{ fontSize: 17, color: ui().accent }} hoverStyle={hoverFill()} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
      {starred() ? "★" : "☆"}
    </Text>
  );
  return (
    <NavPage title={`Message ${props.id}`} headerRight={star()} header={<Header title={`Message ${props.id}`} right={star()} />}>
      <Body>
        <Text style={{ color: ui().text }}>{`The body of message ${props.id}.`}</Text>
        <Button title="Next message" onPress={() => nav.push({ name: "detail", id: props.id + 1 })} />
        <Button title="Back to the inbox" variant="secondary" onPress={() => nav.popToRoot()} />
      </Body>
    </NavPage>
  );
}

const messages = [1, 2, 3, 4, 5, 6];

/** A wide window: the inbox on the start side, the selected message beside it. */
function Inbox() {
  const selected = signal(1);
  const starred = signal<number[]>([]);
  const star = (id: number) => (
    <Text
      onPress={() => starred.set(starred().includes(id) ? starred().filter((s) => s !== id) : [...starred(), id])}
      style={{ fontSize: 17, color: ui().accent }}
      hoverStyle={hoverFill()}
      focusStyle={focusRing()}
      activeStyle={{ opacity: 0.7 }}
    >
      {starred().includes(id) ? "★" : "☆"}
    </Text>
  );
  return (
    <View style={{ flex: 1, flexDirection: "row", backgroundColor: ui().page }}>
      <View style={{ width: 320, borderRightWidth: 1, borderRightColor: ui().line }}>
        <Header title="Inbox" />
        <View style={{ padding: 8, gap: 2 }}>
          <Each of={messages}>
            {(id) => (
              <View
                role="button"
                onPress={() => selected.set(id())}
                hoverStyle={selected() === id() ? undefined : hoverFill()}
                style={{
                  height: size.row + 12,
                  paddingHorizontal: 12,
                  borderRadius: 8,
                  justifyContent: "center",
                  gap: 2,
                  backgroundColor: selected() === id() ? tint(ui().accent, 0.14) : "transparent",
                }}
                focusStyle={focusRing()}
                activeStyle={{ opacity: 0.7 }}
              >
                <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
                  <Text style={{ flex: 1, fontSize: size.label, fontWeight: "600", color: ui().text }}>{`Message ${id()}`}</Text>
                  <Text style={{ fontSize: 12, color: ui().accent }}>{starred().includes(id()) ? "★" : ""}</Text>
                </View>
                <Text style={{ numberOfLines: 1, fontSize: 12, color: ui().muted }}>{`The body of message ${id()}.`}</Text>
              </View>
            )}
          </Each>
        </View>
      </View>
      <View style={{ flex: 1, minWidth: 0 }}>
        <Header title={`Message ${selected()}`} right={star(selected())} />
        <View style={{ padding: 24, gap: 12 }}>
          <Text style={{ color: ui().text }}>{`The body of message ${selected()}.`}</Text>
          <Text style={{ fontSize: 13, color: ui().muted }}>Click a message on the left; on a phone it opens as a page of a NavStack.</Text>
          <Button title="Next message" onPress={() => selected.set(selected() === messages.length ? 1 : selected() + 1)} />
        </View>
      </View>
    </View>
  );
}

function App() {
  return (
    <>
      {() =>
        wide() ? (
          <Inbox />
        ) : (
          <NavStack state={nav} style={{ flex: 1 }}>
            {(route) => (route.name === "home" ? <Home /> : <Detail id={route.id} />)}
          </NavStack>
        )
      }
    </>
  );
}

mount(App);

L’ensemble de base comprend aussi Avatar et AvatarGroup : des images avec un état de chargement natif et une rangée qui place ses limit premiers avatars.

L’apparence est celle de l’app : ce qui se trouve dans une page, un dialogue ou un en-tête est votre propre JSX, construit à partir des primitives avec des styles, des classes Tailwind et votre thème, des composants Rust issus de votre propre crate, ou une bibliothèque de composants que la communauté construit sur nsh/base. Chaque hôte sert nsh/base ; les apps Rust utilisent la même API depuis la crate nsh-base (nsh_base::{button, dialog, …}).

use nsh_base::{
    MarkdownExtension, MarkdownOptions, accordion, accordion_header, accordion_item, accordion_panel, accordion_trigger,
    alert_dialog, button, carousel, carousel_content, carousel_item, carousel_next,
    carousel_pagination, carousel_pagination_item, carousel_previous, checkbox, color_area, color_field, color_picker,
    color_slider, color_swatch, dialog, diff_view, dock_area, editor, hover_card, link,
    calendar, combobox, command, date_picker, h_resizable, nav_page, nav_stack, radio, radio_group, resizable_panel, select, switch, tab, tabs, text_view, theme, time_field, toast, toast_stack, toggle, toggle_group, toolbar, toolbar_group, toolbar_separator, tree, v_resizable,
};

Cette page est l’index du module : chaque composant a une page d’élément avec ses exemples, ses props et ses notes de plateforme. Pour des guides pas à pas, voir le Guide de navigation et Formulaires et clavier.

TSXRustCe que c’est
Buttonbutton()Une cible d’appui avec les états désactivé et sélectionné et un rôle pour les lecteurs d’écran.
Linklink()Une cible d’appui qui mène quelque part : elle ouvre son href via le système, ou l’app l’ouvre (openWith="app", onOpen).
Avatar, AvatarGroupavatar(), avatar_group()La photo d’une personne, ou un repli (les initiales, l’icône de l’app) pendant son chargement, en cas d’échec et en son absence ; et une rangée qui affiche les limit premiers et un débordement. L’état de chargement est natif.
Checkboxcheckbox()Un contrôle coché, décoché ou indéterminé avec un indicateur que l’app stylise et marque.
Radio, RadioGroupradio(), radio_group()Une option d’un ensemble dont l’utilisateur choisit une seule, et l’ensemble ; l’app décide laquelle est cochée.
Switchswitch()Un contrôle activé / désactivé avec une piste et un curseur que l’app stylise.
Tab, Tabstab(), tabs()Un onglet d’une liste d’onglets, et la liste ; l’app décide lequel est sélectionné et affiche son contenu.
Toggle, ToggleGrouptoggle(), toggle_group()Un bouton activé ou désactivé, et une barre d’outils de ces boutons ; l’app décide lesquels sont enfoncés.
Toolbar, ToolbarGroup, ToolbarSeparatortoolbar(), toolbar_group(), toolbar_separator()Une barre de boutons, de bascules et de liens : un seul arrêt du focus clavier dont les flèches se déplacent le long de la barre ; des groupes d’éléments liés et des lignes entre eux.
Carousel et ses partiescarousel(), carousel_content(), carousel_item(), carousel_previous(), carousel_next(), carousel_pagination(), carousel_pagination_item()Des diapositives affichées une à la fois grâce au défilement propre à la plateforme, avec précédent / suivant, une pagination, les touches et une boucle continue ; l’app détient index.
Selectselect()Une valeur parmi une liste d’éléments : un popup au niveau du déclencheur sur ordinateur, une feuille sur téléphone ; l’app détient value et dessine le déclencheur.
Comboboxcombobox()Une ou plusieurs valeurs (multiple) parmi une liste d’éléments filtrée par la saisie dans son champ ; l’app détient value et stylise le champ.
Calendarcalendar()Une date ou une plage de dates choisie parmi des mois de jours, avec des vues du mois et de l’année et le clavier ; l’app détient value et dessine les éléments.
TimeFieldtime_field()Une heure de la journée modifiée segment par segment (heure, minute, seconde, AM/PM) ; l’app détient value.
DatePickerdate_picker()Une date, une plage ou une date et une heure choisies dans un calendrier, dans un popup au niveau du déclencheur sur ordinateur, une feuille sur téléphone ; l’app détient value et dessine le déclencheur.
Collapsiblecollapsible()Une région dont le contenu se déplie et se replie ; l’app détient open et son déclencheur.
Accordion, AccordionItem, AccordionHeader, AccordionTrigger, AccordionPanelaccordion(), accordion_item(), accordion_header(), accordion_trigger(), accordion_panel()Des sections empilées qui se déplient et se replient sous leurs titres, une à la fois ou plusieurs (multiple) ; l’app détient value, les éléments ouverts.
OtpInputotp_input()Un code à usage unique dans des cellules que l’app stylise ; l’app détient value, onComplete indique quand il est complet.
Paginationpagination()Les pages d’un ensemble : Précédent, les pages autour de la page courante avec des points de suspension, les enfants de l’app et Suivant ; l’app détient page.
Commandcommand()Une palette de commandes : un champ de recherche sur les commandes de l’app, filtrées nativement par libellé et mots-clés, dans une liste virtualisée ; Entrée ou un appui choisit (onSelect), dans un Dialog pour une palette Cmd+K.
Treetree()Des éléments qui contiennent des éléments, virtualisés : dépliés et repliés, un sélectionné, un seul arrêt du focus clavier ; l’app détient expanded et selected, ou l’arbre garde les siens.
Progressprogress()Une barre de progression ; l’app détient value (0 à 100) et indeterminate.
NumberInputnumber_input()Un nombre saisi dans un champ ou incrémenté avec des boutons et des touches ; l’app détient value (ou le champ garde sa propre valeur à partir de defaultValue).
Scrollbarscrollbar()Une barre de défilement au style propre à l’app pour la vue défilante ou la liste qu’elle désigne (scroll) ; son curseur suit le contenu, et ses glissements et appuis le font défiler.
Sliderslider()Une valeur, ou une plage de deux, choisie le long d’une piste ; l’app détient value (ou le curseur garde sa propre valeur à partir de defaultValue).
ColorPicker, ColorArea, ColorSlider, ColorField, ColorSwatch, ColorEyeDroppercolor_picker(), color_area(), color_slider(), color_field(), color_swatch(), color_eye_dropper()Un sélecteur de couleur et ses parties : une zone de saturation et de luminosité, des curseurs pour n’importe quel canal de RGB, HSL, HSB ou OKLCH et pour l’alpha, des champs pour la couleur ou un canal, des nuances, une pipette ; l’app détient value (ou le sélecteur garde sa propre valeur à partir de defaultValue).
ResizablePanelGroup, ResizablePanelh_resizable(), v_resizable(), resizable_panel()Des panneaux côte à côte ou empilés, avec des poignées que l’utilisateur fait glisser pour les redimensionner.
DockAreadock_area()Un espace de travail de divisions, de groupes d’onglets et de docks en bordure que l’utilisateur organise ; sa disposition est une donnée de l’app.
NavStack, NavPage, createNavStacknav_stack(), nav_page()Une pile de navigation native et ses pages.
Dialogdialog()Du contenu présenté par-dessus tout le reste : feuilles, parcours plein écran, dialogues.
AlertDialogalert_dialog()L’alerte ou la feuille d’actions de la plateforme.
TextViewtext_view()Un document Markdown, HTML ou en blocs, dessiné avec des textes et des vues, pour les réponses d’IA diffusées en flux.
Editoreditor()Un éditeur de code : coloration syntaxique, plusieurs curseurs de saisie, repli de code, recherche et remplacement, diagnostics.
DiffViewdiff_view()Les différences entre deux textes ou d’un patch, côte à côte ou unifiées, avec des actions par modification.
Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaptiontable(), table_row(), …Un tableau des lignes et cellules propres à l’app, avec les rôles d’un tableau pour les lecteurs d’écran.
DataTabledata_table()Un tableau d’un nombre quelconque de lignes et de colonnes : sélection, tri, colonnes fixes, redimensionnables et déplaçables, lignes servies une page à la fois.
Inputnsh_base::input()Le champ de saisie de texte natif de la plateforme, sur une ou plusieurs lignes.
Popoverpopover()Contenu ancré à un déclencheur, au-dessus de tout.
HoverCardhover_card()Un aperçu de ce que désigne son déclencheur, tant que le pointeur reste dessus (un appui sur les écrans tactiles).
Menu, ContextMenu, MenuItem, MenuSeparator, Submenumenu(), …Des menus : popups avec sous-menus en cascade sur ordinateur, feuilles sur téléphone ; éléments cochables et choix unique.
Tooltiptooltip()Une info-bulle à côté de son déclencheur.
Sheetsheet()Un panneau sur un bord de l’écran, au-dessus d’un fond.
Toast, ToastStack, ToastManagertoast(), toast_stack(), ToastManagerDes toasts sans style : une racine sémantique, une pile de couches, leur cycle de vie.
statusBar sur les pages et les dialogues.status_bar(..)La barre d’état tant qu’une page ou un dialogue est affiché. Voir Barre d’état.

En Rust, theme() lit le thème de l’app sous forme de design tokens (theme().primary, theme().radius), les mêmes tokens qu’utilisent les classes de thème de TSX.

#Contrôles

#Button

Une cible d’appui qui gère son comportement et n’a pas d’apparence : appuis, états désactivé et sélectionné et son rôle pour les lecteurs d’écran. Son apparence est celle des class et style de l’app avec des variantes d’état, son contenu est ses enfants. Voir Button.

Une cible d’appui qui mène quelque part et n’a pas d’apparence : appuis, Entrée et Espace lorsqu’elle a le focus clavier, état désactivé, rôle de lien pour les lecteurs d’écran et page courante d’une navigation (current). href est sa destination, jamais affichée : un appui la signale (onOpen), puis ouvre une page web ou le lien d’une autre app en dehors de l’app, ou, avec openWith="app", laisse l’ouverture aux routes propres de l’app. Voir Link.

#Checkbox

Un contrôle coché, décoché ou indéterminé qui gère son comportement et n’a pas d’apparence : appuis, activation au clavier, état désactivé et rôle et valeur de case à cocher pour les lecteurs d’écran (mixte tant qu’il est indéterminé), la valeur étant détenue par l’app (checked, indeterminate, onChange). L’app stylise la case à cocher et son indicateur, dont les classes checked:, indeterminate: et disabled: suivent la case à cocher, et place sa propre marque dans l’indicateur (indicator). Voir Checkbox.

#Radio et RadioGroup

Une option d’un ensemble dont l’utilisateur choisit exactement une, et l’ensemble qui les entoure. Un radio gère son comportement et n’a pas d’apparence : appuis, activation au clavier, état désactivé et rôle et état coché de bouton radio pour les lecteurs d’écran. Le groupe est le groupe de boutons radio qu’annoncent les lecteurs d’écran et ne contient aucune valeur : l’app indique à chaque radio s’il est coché (checked), et un appui sur un radio non coché le demande (onChange). L’indicateur et le libellé de l’app sont les enfants du radio, dont les classes group-checked: le suivent. Voir Radio.

#Switch

Un contrôle activé / désactivé qui gère son comportement et n’a pas d’apparence : appuis, activation au clavier, état désactivé et rôle et valeur d’interrupteur pour les lecteurs d’écran, la valeur étant détenue par l’app (checked, onChange). L’app stylise l’interrupteur, sa piste et son curseur, dont les classes checked: et disabled: suivent l’interrupteur. Voir Switch.

#Tab et Tabs

Les onglets d’une liste d’onglets et la liste qui les entoure. Un onglet gère son comportement et n’a pas d’apparence : appuis, activation au clavier, état désactivé et rôle et état sélectionné d’onglet pour les lecteurs d’écran. La liste est la liste d’onglets qu’annoncent les lecteurs d’écran et ne contient aucune valeur : l’app indique à chaque onglet s’il est sélectionné (selected), reçoit les appuis (onPress) et affiche elle-même le contenu de la section sélectionnée. Le libellé de l’app est l’enfant de l’onglet, dont les classes group-selected: le suivent. L’indicateur de la liste (indicatorClass, indicatorStyle), un soulignement ou une pastille que l’app stylise, se place sur l’onglet sélectionné et glisse nativement vers le suivant. Voir Tabs.

Des diapositives affichées une à la fois (les photos d’un produit, un onboarding, une bannière, une rangée de cartes). Le contenu est une vue défilante native qui s’aimante au début des diapositives : un doigt, la molette et le pavé tactile les déplacent dans la même frame que le pointeur. Au repos, le carousel signale la diapositive affichée avec onChange. Précédent et suivant (CarouselPrevious, CarouselNext), la pagination (des CarouselPaginationItems dans un CarouselPagination, avec un indicateur qui rebondit vers la diapositive affichée) et les touches fléchées la déplacent nativement, en bouclant sans rupture aux extrémités avec looping. La diapositive affichée est le index de l’app, que son propre minuteur fait avancer pour les diapositives qui défilent d’elles-mêmes (onDragStart / onDragEnd, onFocus / onBlur indiquent quand attendre) ; l’apparence et chaque libellé sont ceux de l’app. Voir Carousel.

#Toggle et ToggleGroup

Un bouton activé ou désactivé (gras, un favori, une option d’affichage), et une barre d’outils de boutons de ce type. Un toggle possède son comportement et n’a pas d’apparence propre : appuis, activation au clavier, état désactivé et rôle de bouton avec son état pressé pour les lecteurs d’écran. Le groupe est la barre d’outils qu’annoncent les lecteurs d’écran, un seul arrêt du focus clavier dont les touches fléchées passent d’un toggle à l’autre sans les presser, et il ne contient aucune valeur : l’app indique à chaque toggle s’il est pressé (pressed), reçoit la valeur qu’il demande (onChange), et conserve autant de toggles pressés qu’elle le souhaite, ou exactement un. disabled sur le groupe désactive tous les toggles qu’il contient. L’icône ou le libellé de l’app constitue les enfants du toggle, dont les classes group-aria-pressed: les suivent. Voir Toggle.

#Toolbar, ToolbarGroup et ToolbarSeparator

Une barre de contrôles : la mise en forme d’un éditeur de texte, les commandes de lecture d’un lecteur multimédia, une palette d’outils verticale. La barre d’outils est celle qu’annoncent les lecteurs d’écran (nommée par accessibilityLabel), et constitue un seul arrêt du focus clavier sur les Buttons, Toggles et Links qu’elle contient : Tab entre sur l’élément quitté en dernier, les touches fléchées de son axis se déplacent le long de la barre, et Début et Fin sautent à ses extrémités, sans rien presser. Un ToolbarGroup est une série nommée d’éléments liés (annuler et rétablir, un alignement), et un ToggleGroup dans une barre d’outils est l’un de ses groupes, ses toggles étant les éléments de la barre. Un ToolbarSeparator est un trait que l’app dessine entre deux séries. disabled sur la barre d’outils ou sur un groupe désactive tous les éléments qu’il contient. La barre d’outils ne contient aucune valeur et n’a pas d’apparence propre : chaque contrôle garde son propre état, et l’app stylise la barre, ses groupes et ses séparateurs. Voir Toolbar.

#Select

Une valeur choisie dans une liste d’éléments fournis comme données (items : une valeur et un titre chacun, éventuellement désactivé ou dans une section), sans apparence propre. Le déclencheur de l’app ouvre la liste : une popup au niveau du déclencheur sur les ordinateurs, une feuille en bas sur les téléphones et les tablettes. Le select gère le clavier (Haut, Bas, Début, Fin, Page précédente et suivante, saisie prédictive, Retour et Échap), la recherche (searchable), la ligne en surbrillance (focus: sur itemClass ; renderItem reçoit ce qu’est un élément, jamais son état) et les rôles combobox, listbox et option pour les lecteurs d’écran. L’app possède la valeur (value, onChange) ou la laisse au select (defaultValue), et stylise chaque partie ou dessine les lignes. Voir Select.

#Combobox

Des valeurs choisies dans une liste d’éléments, comme celle d’un select, filtrée par la saisie dans le champ de la combobox, sans apparence propre : une seule valeur, ou plusieurs avec multiple, un choix basculant alors l’élément tandis que la liste reste ouverte. La valeur est un tableau que l’app possède (value, onChange) ou qu’elle laisse à la combobox (defaultValue) ; onConfirm la communique à la fermeture de la liste. La requête peut être celle de l’app (query, onQueryChange), un pied de liste s’affiche sous les lignes et un bouton d’effacement vide la valeur. Voir Combobox.

#Calendar

Une date, ou avec range une plage de dates, choisie parmi des mois de jours, sans apparence propre. Un mois ou plusieurs (numberOfMonths) s’affichent côte à côte ; l’en-tête les fait défiler et ouvre une vue des mois ou une page d’années. La valeur est une date ISO ("2026-10-28") ou [start, end] que l’app possède (value, onChange) ou qu’elle laisse au calendrier (defaultValue) ; disabledMatcher empêche de choisir certains jours, et les jours reçoivent le clavier comme un seul arrêt de Tab. L’app stylise les parties et dessine les éléments (renderItem, recevant ce qu’est un élément ; ses états sont ses classes, les jours d’une plage selected: avec data-range-start:, data-range-middle: et data-range-end:). Voir Calendar.

#TimeField

Une heure de la journée modifiée un segment à la fois, sans apparence propre : l’heure, la minute, la seconde (precision) et AM/PM (hourCycle="h12"), par incrémentation, saisie ou pavé numérique d’un téléphone. La valeur est "HH:MM" ("HH:MM:SS") sur une horloge de 24 heures, celle de l’app (value, onChange) ou celle du champ lui-même (defaultValue). Voir TimeField.

#DatePicker

Une date choisie dans un calendrier qui s’ouvre depuis le déclencheur de l’app, comme le fait la liste d’un select : une popup sur les ordinateurs, une feuille sur les téléphones. La valeur prend les formes des inputs HTML : une date ISO, [start, end] avec range ou une date et une heure avec timePrecision (un champ d’heure sous le calendrier), celle de l’app (value, onChange) ou celle du sélecteur lui-même (defaultValue). Des préréglages la définissent, un bouton d’effacement la vide, et chaque prop de Calendar est transmise. Voir DatePicker.

#Collapsible

Une région dont le contenu se déplie et se replie, sans apparence propre : ses enfants s’affichent toujours (un résumé, le déclencheur), son content après eux tant que open. L’app possède open et le déclencheur qui le bascule, et y indique l’état déplié aux lecteurs d’écran. Le contenu est créé une fois et conservé pendant qu’il est replié, de sorte que ce qu’il contient garde son état. Avec reveal, il s’ouvre et se ferme nativement avec une hauteur à effet de ressort. Voir Collapsible.

#Accordion

Des sections empilées qui se déplient et se replient sous leurs titres, sans apparence propre : un Accordion de AccordionItems, chacun étant un AccordionHeader autour de son AccordionTrigger et d’un AccordionPanel. L’app possède value, les valeurs des éléments ouverts, et définit celui que onChange signale lorsqu’un déclencheur est pressé : un élément à la fois, ou plusieurs avec multiple. Les lecteurs d’écran entendent un groupe de titres dont les boutons indiquent si leur élément est déplié, et chaque panneau ouvert comme une région. Avec reveal, un panneau s’ouvre et se ferme nativement avec une hauteur à effet de ressort ; avec keepMounted, son contenu reste dans l’arbre tant qu’il est fermé. Voir Accordion.

#Avatar

L’image d’une personne, sans apparence propre : le repli (les initiales de name, ou le fallback de l’app) et l’image par-dessus, transparente tant qu’elle n’est pas chargée, toutes deux aussi grandes que l’avatar, puis les enfants de l’app (une pastille de présence). Les événements de chargement et d’erreur de l’image modifient nativement son statut (onStatusChange), et fallbackDelay retient le repli pendant le chargement d’une image. Les lecteurs d’écran entendent une image étiquetée avec le nom. Un AvatarGroup affiche ses limit premiers avatars, ne crée aucune vue pour les autres, et affiche son overflow tant que total est plus grand. Voir Avatar.

#Progress

Une barre de progression linéaire, sans apparence propre : une piste contenant un indicateur, l’indicateur occupant value pour cent de la largeur de la piste. L’app possède value (de 0 à 100) et indeterminate ; les lecteurs d’écran reçoivent le rôle de barre de progression, la plage et la valeur, et aucune valeur lorsqu’elle est indéterminée, cas où la largeur et le mouvement de l’indicateur appartiennent à l’app. Voir Progress.

#NumberInput

Un nombre saisi dans un champ ou incrémenté, sans apparence propre : le champ (Input), le bouton de décrémentation avant lui et celui d’incrémentation après lui (ou les deux en colonne à droite, controlsRight). L’app possède value (un nombre ou null ; ou l’input conserve la sienne à partir de defaultValue) et reçoit onChange avec le nombre que le champ lit. Le champ accepte un nombre ou le texte en cours de saisie, y compris les chiffres et signes d’autres claviers ; les boutons, Haut / Bas et Page précédente / Page suivante l’incrémentent de step, en conservant les décimales, jusqu’à min et max, et quitter le champ y ramène une valeur saisie. Le champ est un spin button pour les lecteurs d’écran, dont l’incrémentation et la décrémentation le font varier. Voir NumberInput.

#Slider

Une valeur, ou une plage de deux, choisie le long d’une piste, sans apparence propre : une piste qui reçoit les appuis et les glissements, l’indicateur à l’intérieur sur lequel les valeurs sont projetées, la plage et un curseur par valeur. L’app possède value (un nombre ou [start, end] ; ou le slider conserve la sienne à partir de defaultValue) et reçoit onChange pour chaque nouvelle valeur, alignée sur step à partir de min, et onChangeEnd une fois par changement ; scale répartit les valeurs de façon linéaire ou par ordres de grandeur, axis dispose la piste horizontalement ou verticalement. Chaque curseur est un élément ajustable pour les lecteurs d’écran avec sa propre valeur, un arrêt du focus clavier, et se déplace avec les touches de slider de WAI-ARIA. Voir Slider.

#Scrollbar

Une barre de défilement à l’apparence propre à l’app pour une vue défilante ou une liste, placée où l’app le souhaite, sans apparence par défaut : une piste (le class de la barre) et un curseur dedans (thumbClass). Elle nomme sa vue défilante (scroll, son ref) et suit son décalage, son contenu et sa zone visible ; un glissement du curseur fait défiler le contenu, un appui sur la piste amène le milieu du curseur à cet endroit. mode l’affiche pendant le défilement du contenu (jusqu’à motion.idle après), tant que le pointeur est sur la barre, ou en permanence ; axis la rend horizontale. Scrollbar.setTheme donne à chaque vue défilante et à chaque liste une barre à l’apparence de l’app par-dessus sa zone visible, sans rien à placer, et une vue accepte un thème qui lui est propre via sa prop scrollbar. Voir Scrollbar.

#ColorPicker

Un sélecteur de couleur sans apparence propre : la racine contient la couleur et les parties à l’intérieur l’affichent et la modifient, disposées par l’app. ColorArea est la saturation et la luminosité de HSB, ColorSlider n’importe quel canal de RGB, HSL, HSB ou OKLCH ou l’alpha (horizontal ou vertical), ColorField la couleur sous n’importe quelle forme CSS ou le nombre d’un canal, ColorSwatch un préréglage qu’un appui choisit ou un aperçu de la couleur, ColorEyeDropper un bouton qui prélève une couleur à l’écran (macOS, le web). L’app possède value (ou le sélecteur conserve la sienne à partir de defaultValue) et reçoit onChange et onChangeEnd avec la couleur et son texte tel que format l’écrit ; le sélecteur conserve les canaux de chaque espace tels que l’utilisateur les a définis. Les curseurs sont des éléments ajustables pour les lecteurs d’écran et des arrêts du focus clavier. Voir ColorPicker.

#OtpInput

Un code à usage unique numérique de longueur fixe, sans apparence propre : un champ de texte natif unique au-dessus des cellules reçoit le clavier, de sorte que la saisie, le collage, le pavé numérique et le remplissage automatique de code du système relèvent de la plateforme, et les lecteurs d’écran trouvent un seul champ de texte. Seuls les chiffres sont acceptés, jusqu’à length ; Retour arrière supprime le dernier. L’app possède value (ou fournit defaultValue) et reçoit onChange et onComplete ; elle stylise les cellules (focus: sur celle qui reçoit le prochain chiffre, data-filled: sur celles qui contiennent un chiffre), leurs groupes et le curseur de saisie. Voir OtpInput.

#Pagination

Les pages d’un ensemble entre lesquelles naviguer, un repère de navigation sans apparence propre : Précédent, la première et la dernière page, la page courante avec ses voisines (visiblePages), une ellipse pour chaque série omise, les enfants de l’app et Suivant ; compact conserve Précédent, les enfants et Suivant. L’app possède page et reçoit onChange({ page }) ; la page courante est selected: et la page courante pour les lecteurs d’écran. L’app stylise les éléments (itemClass) ou les dessine (renderItem). Voir Pagination.

#Command

Une palette de commandes : un champ de recherche sur les commandes de l’app (items : un id, un label, keywords, un group, un shortcut à afficher, disabled, checked), filtrées nativement pendant la saisie (le libellé ou un mot-clé contient la requête, sans tenir compte de la casse ; les sections sans correspondance disparaissent), dans une liste virtualisée qui ne crée que les lignes visibles. Haut et Bas déplacent la surbrillance, Retour ou un appui choisit (onSelect({ id })), Échap efface la requête puis signale onCancel, où l’app ferme un Dialog autour de la palette. La requête est celle de l’app (query, onQueryChange) ou celle du champ lui-même ; avec filterable={false} et loading, l’app y répond depuis un serveur. L’app stylise chaque partie (itemClass avec focus: pour la surbrillance, checked:, disabled:) et dessine les lignes (renderItem, recevant le raccourci tel qu’il est affiché ici). Voir Command.

#Tree

Des éléments qui contiennent des éléments : les fichiers d’un projet, une barre latérale de réglages, un plan. items est une donnée (TreeItem : un id, un label, children, disabled), et les lignes sont virtualisées : seules celles visibles sont créées, et celles des enfants d’un élément replié ne le sont jamais, si bien qu’un arbre de milliers d’éléments reste rapide. L’app possède les identifiants dépliés et l’élément sélectionné (expanded, selected, avec onExpandedChange et onSelectedChange) ou l’arbre conserve les siens (defaultExpanded, defaultSelected). Un appui sélectionne une ligne et déplie ou replie un dossier ; l’arbre est un seul arrêt du focus clavier dont les touches fléchées déplacent la sélection (Droite et Gauche déplient, replient, entrent dans un dossier et en sortent), avec Début, Fin, Retour, Espace et la saisie prédictive. L’app stylise les lignes (itemClass avec selected:, aria-expanded:, focus:) et les dessine (renderItem, recevant le depth de chaque élément). Voir Tree.

#Plot (nsh/base/plot)

Les éléments dont sont faits les graphiques, comme gpui-base et son plot, dans un module à part : import { Plot, Line, Area, Bar, Grid, Pie, Arc, Sankey, Treemap } from "nsh/base/plot"``. A Plot holds the app's rows (data, generic over their type, so xandysont des clés vérifiées ou des fonctions d'une ligne), les échellespoint, linearouband de ses axes, la ligne sous le pointeur (onHoverChange) et son mouvement (motion : l'apparition, le fondu et le glissement du survol) ; ses marques se dessinent nativement à partir des lignes, sans JS par point, et se peignent avec les propriétés de peinture SVG (stroke-, fill-), currentColorlorsqu'aucune n'est définie ; unAreaou unBaraffiche un arrière-plan (un dégradé,bg-linear-*) à l'intérieur de ses formes. Les barres se placent dans les bandes d'une échelle band, côte à côte ou empilées dans un stack, grandissent à l'apparition du plot et s'estompent loin de la ligne sous le pointeur, avec un élément labelau bout de chaque barre. La ligne en croix du survol, les points (propres à chaque marque avechoverDotClass) et l'infobulle (tooltipPlacement, maintenue à l'intérieur du plot) sont des éléments que l'app stylise et remplit, déplacés nativement ; les étiquettes des axes sont des éléments propres à l'app. Un Piedécoupe le tour en une part par ligne (un donut avec uninnerRadius), chacune peinte avec ses propres classes (sliceClass, fonction de sa ligne), et un Arc dessine un secteur entre deux angles (la piste et la valeur d'une jauge) ; sur un camembert, le plot signale la part sous le pointeur, qui se soulève (hoverLift) tandis que les autres s'estompent (hoverDim). Un Sankey dessine des flux entre les lignes (links), disposés comme d3-sankey les dispose, chaque nœud et chaque ruban étant peint avec ses propres classes, les nœuds étant étiquetés, survolables et se déplaçant vers de nouveaux flux. Un Treemap pave les lignes sous forme de hiérarchie de rectangles imbriqués (id, parent), l'aire de chaque feuille étant son value, colorée par catégorie ou selon un dégradé du colorScale` du plot, ses étiquettes s’affichant là où elles tiennent. Voir Plot.

#Espaces de travail

#ResizablePanelGroup et ResizablePanel

Des panneaux côte à côte ou empilés, avec une poignée entre chaque paire que l’utilisateur fait glisser pour partager l’espace. Le groupe gère le glissement, qui s’exécute nativement, les limites de chaque panneau (minSize, maxSize) et le curseur de redimensionnement ; les tailles appartiennent à l’app (size, signalées avec onResize), tout comme l’apparence des poignées et le contenu des panneaux. Voir Resizable.

#DockArea

Un espace de travail ancrable : des panneaux dans des groupes d’onglets, des groupes divisés côte à côte ou empilés, et des docks sur les bords gauche, droit et inférieur. La disposition est une donnée que l’app possède (layout, signalée avec onLayoutChange) ; la zone gère le glisser-déposer des onglets, le redimensionnement, les docks, le zoom et le verrouillage, conserve le contenu de chaque panneau lorsqu’il se déplace, et présente la même disposition avec des tiroirs et une feuille inférieure lorsqu’elle est étroite. Les onglets, les poignées, l’indicateur de dépôt et le contenu des panneaux appartiennent à l’app. Voir Dock.

#Documents

#TextView

TextView affiche un document en Markdown, en HTML ou en blocs que l’app construit, dessiné avec des éléments et des vues Text : titres, listes, code, tableaux, images, formules, notes de bas de page et syntaxe propre à l’app. Il est conçu pour les réponses de chat IA diffusées en flux, avec une typographie de lecture aux couleurs du thème que l’app restylise partie par partie, et des fonctions de rendu qui placent le contenu de l’app. Voir TextView.

#Code

#Editor

Editor est un éditeur de code pour plus de cinquante langages : coloration syntaxique, numéros de ligne, curseurs multiples, repli, retour à la ligne automatique, recherche et remplacement, diagnostics et décorations, avec les raccourcis des éditeurs de code. Le texte appartient à l’app (value et onChange), tout comme la barre de recherche, qui pilote le search de l’éditeur. Il est livré avec une apparence construite à partir des tokens du thème que l’app restylise partie par partie. Voir Editor.

#DiffView

DiffView affiche les changements entre deux textes, ou dans les fichiers d’un patch, comme les outils de revue de code : côte à côte ou unifié, avec les mots modifiés au sein des lignes mis en évidence, les lignes inchangées repliées en séparateurs que l’on peut déplier, dans les couleurs de syntaxe du Editor. Les actions de l’app conservent ou annulent chaque changement. Voir DiffView.

#Table

Table et ses parties disposent quelques lignes que l’app écrit comme enfants, et offrent un tableau aux lecteurs d’écran : groupes de lignes, lignes, en-têtes de colonnes et cellules. Chaque partie prend l’apparence de l’app. Voir Table.

#DataTable

DataTable affiche des lignes et des colonnes et défile dans les deux sens sur un nombre quelconque des unes et des autres, en ne produisant que les cellules visibles. Les lignes, colonnes ou cellules se sélectionnent ; les colonnes se redimensionnent, se déplacent, restent fixes et se trient (c’est l’app qui trie) ; l’app fournit les lignes page par page et peut dessiner les cellules et les titres. Voir DataTable.

Un NavStack de NavPage est UINavigationController sur iOS et une pile avec barre d’outils et retour prédictif sur Android ; sur les ordinateurs de bureau et le web, chaque page affiche son propre header de l’app. Voir NavStack, qui couvre aussi createNavStack et la sortie d’une page.

#Dialog

Dialog présente du contenu au-dessus de tout le reste, y compris l’en-tête de la pile, dans sa propre couche de fenêtre native. Sa fenêtre contextuelle et son arrière-plan sont stylisés par l’app. Voir Dialog.

#Boîtes de dialogue d’alerte

#AlertDialog

AlertDialog.show(options) affiche l’alerte ou la feuille d’actions propre à la plateforme et se résout avec { action }, le id de l’action choisie par l’utilisateur. Voir AlertDialog.

#Input

Input est le champ de saisie de texte natif de la plateforme : un champ monoligne ou multiligne dont la saisie, le curseur, la sélection, la correction automatique et le remplissage automatique sont gérés par la plateforme. L’app possède sa valeur et son apparence. Ses props ont pour type InputProps (avec IOSInputProps pour ios={{…}}), exporté à côté. Voir Input.

#Barre d’état

La barre d’état appartient à ce qui est à l’écran, comme dans UIKit, où chaque view controller déclare son preferredStatusBarStyle et où celui du dessus décide. Dans NSH, les pages et les dialogues sont ces view controllers : un NavPage ou un Dialog déclare la barre d’état qu’il souhaite avec statusBar, et l’app déclare sa valeur par défaut dans nsh.json.

use nsh_base::nav_page;
use nsh_core::prelude::*;

fn photo_viewer() -> Screen {
    nav_page().header_visible(false).status_bar(
        StatusBarSetting::default()
            .foreground(StatusBarStyle::Light)
            .hidden(true)
            .animated(true),
    )
}

statusBar accepte ces champs, tous facultatifs (en Rust, un StatusBarStyle seul, ou les méthodes de construction de StatusBarSetting du même nom) :

ChampTypeDescription
foreground"auto" | "light" | "dark" (Rust : StatusBarStyle::Default, Light, Dark)La couleur du contenu (heure, batterie, signal) : "light" pour les arrière-plans sombres, "dark" pour les clairs, "auto" celle de la plateforme pour l’apparence courante.
hiddenbooleanMasque la barre d’état.
animatedbooleanAnime le changement lorsque la page ou le dialogue apparaît et disparaît.

La valeur est réactive : statusBar={{ foreground: dark() ? "light" : "dark" }} suit le signal, tout comme une closure Rust qui en lit un.

#Lequel s’applique

La page ou le dialogue affiché le plus récemment décide, champ par champ : une page poussée sur une pile est au-dessus des pages en dessous, et un dialogue ouvert est au-dessus des pages. Un champ qu’il omet provient de celui du dessous, et en fin de compte de la valeur par défaut de l’app. Lorsque la page est retirée ou que le dialogue se ferme, celui du dessous s’applique de nouveau.

nav_page()
    .title("Inbox")
    .status_bar(StatusBarStyle::Dark);

// Pushed over it: hidden, with the inbox's dark content when it shows again.
nav_page()
    .title("Photo")
    .status_bar(StatusBarSetting::default().hidden(true));

#La valeur par défaut de l’app

"statusBar" de nsh.json est le réglage sous chaque page et chaque dialogue, comme UIStatusBarStyle et UIStatusBarHidden d’Info.plist dans une app UIKit :

{
  "statusBar": { "foreground": "light", "hidden": false }
}

Les deux champs sont facultatifs. Sans lui, le contenu est "auto" et la barre s’affiche. Les apps iOS empaquetées l’affichent aussi sur l’écran de lancement.

#La barre d’état sur chaque plateforme

  • iOS : le masquage et l’affichage s’animent par un fondu. Dans une app intégrée à une app native, c’est le view controller de l’app hôte qui décide de l’apparence de la barre d’état. L’hôte applique le réglage de NSH en renvoyant NshAppView.preferredStatusBarStyle, NshAppView.prefersStatusBarHidden et NshAppView.preferredStatusBarUpdateAnimation depuis son view controller.
  • Android : les apps dessinent bord à bord, derrière la barre d’état (Android 15, API 35) : l’arrière-plan propre à l’app est celui de la barre, et foreground garde son contenu lisible dessus.
  • Ordinateurs de bureau et web : la barre de titre de la fenêtre et les barres du navigateur appartiennent au système et au navigateur ; statusBar et "statusBar" de nsh.json sont destinés aux téléphones.
{
  "capabilities": {
    "statusBar": true
  }
}

#Voir aussi