Dokumentation
Gerät und Umgebung
In einer laufenden App ändert sich die Umgebung ständig: Das Fenster wird in der Größe verändert (Drehung, Split View, ein Mac-Fenster), der Nutzer wechselt in den Dark Mode oder zu einer größeren Textgröße, die Tastatur erscheint und verschwindet. NSH stellt jede dieser Änderungen als reaktiven Accessor bereit: Du rufst ihn wie ein Signal auf, und alles, was ihn gelesen hat (ein Style, ein Text, ein berechneter Wert), aktualisiert sich bei einer Änderung. Es müssen keine Listener hinzugefügt oder entfernt werden.
import { mount, signal, Text, View } from "nsh";
import { colorScheme, fontScale, pixelRatio, platform, windowSize } from "nsh";
function Row(props: { label: string; value: string }) {
return (
<View class="flex-row py-2 border-b border-[#e9e9e4] dark:border-[#1f2124]">
<Text class="flex-1 text-[#565a61] dark:text-[#8d9198]">{props.label}</Text>
<Text class="text-[#15161a] dark:text-[#eceef1]">{props.value}</Text>
</View>
);
}
function App() {
const hairline = () => 1 / pixelRatio();
const rtl = signal(false);
return (
<View class="flex-1 bg-[#f3f3ef] dark:bg-[#101113] px-4 pt-6 gap-4">
<Text class="text-2xl font-bold text-[#15161a] dark:text-[#eceef1]">Environment</Text>
<View class="rounded-2xl bg-white border border-[#e1e1db] dark:bg-[#18191c] dark:border-[#2c2e33] px-4 py-1">
<Row label="windowSize()" value={`${Math.round(windowSize().width)} × ${Math.round(windowSize().height)}`} />
<Row label="colorScheme()" value={colorScheme()} />
<Row label="fontScale()" value={fontScale().toFixed(2)} />
<Row label="platform.os" value={platform.os} />
<Row label="platform.version" value={platform.version} />
<Row label="platform.idiom" value={platform.idiom} />
<Row label="platform.desktop" value={String(platform.desktop)} />
<Row label="Locale" value={new Intl.DateTimeFormat().resolvedOptions().locale} />
<Row label="pixelRatio()" value={String(pixelRatio())} />
</View>
<Text class="text-[#565a61] dark:text-[#8d9198]">
{platform.select({ ios: "Running on iOS.", android: "Running on Android.", web: "Running in a browser.", default: "Running elsewhere." })}
</Text>
<View class="gap-1">
<View class="bg-[#8c9097] dark:bg-[#5a5e65]" style={{ height: hairline() }} />
<Text class="text-xs text-[#8c9097] dark:text-[#5a5e65]">The line above is one device pixel tall.</Text>
</View>
<View class={windowSize().width >= 600 ? "flex-row gap-3" : "gap-3"}>
<View class="flex-1 rounded-xl bg-[#4a6a0a] dark:bg-[#c8f26d] p-4">
<Text class="text-white dark:text-[#13160c] font-semibold">dark: classes follow the system</Text>
</View>
<View class="flex-1 rounded-xl bg-white border border-[#e1e1db] dark:bg-[#24262a] dark:border-[#2c2e33] p-4 hover:bg-zinc-50 dark:hover:bg-[#1d1e22] focus-visible:outline-2 focus-visible:outline-sky-500">
<Text class="text-[#15161a] dark:text-[#eceef1] font-semibold">A row only when the window is 600 pt or wider</Text>
</View>
</View>
<View
onPress={() => rtl.set(!rtl())}
activeStyle={{ opacity: 0.6 }}
style={{ direction: rtl() ? "rtl" : "ltr" }}
class="flex-row items-center gap-3 rounded-2xl bg-white border border-[#e1e1db] dark:bg-[#18191c] dark:border-[#2c2e33] p-4 hover:bg-zinc-50 dark:hover:bg-[#1d1e22] focus-visible:outline-2 focus-visible:outline-sky-500"
>
<Text class="text-lg text-[#4a6a0a] dark:text-[#c8f26d]">⇄</Text>
<Text class="flex-1 text-[#15161a] dark:text-[#eceef1]">Lay out right to left: {rtl() ? "on" : "off"}</Text>
</View>
</View>
);
}
mount(App);In Rust sind die Accessoren Funktionen von nsh_core::prelude; in TSX stammen sie aus den aufgeführten Modulen.
| Rust | TSX | Wert |
|---|---|---|
window() |
windowSize() (nsh) |
Breite und Höhe der Oberfläche in Punkten. |
dark() |
colorScheme() (nsh) |
Dunkel oder hell: das Erscheinungsbild des Systems oder das eigene der App (set_color_scheme, setColorScheme). |
font_scale() |
fontScale() (nsh) |
Die bevorzugte Textgröße des Nutzers relativ zum Standard (1 ist der Standard). |
safe_area() |
safeArea() (nsh) |
Insets der Oberfläche, die nicht von der System-UI verdeckt werden: oben, rechts, unten, links. |
cx.insets() |
safeAreaInsets() (nsh) |
Die Insets an der Stelle, an der die aufrufende Komponente gerendert wird (eine Seite, ein Dialog). |
keyboard_height(), keyboard_duration() |
keyboardFrame() (nsh) |
Höhe und Animationsdauer der Software-Tastatur. |
locale() |
locale() (nsh) |
Die Locale des Nutzers als BCP-47-Tag ("en-US", "zh-Hans-CN"): das, wofür Intl standardmäßig formatiert. |
lifecycle() |
lifecycle() (nsh) |
Ob die App gerade genutzt wird: active, inactive (sichtbar, aber ohne Fokus) oder background (Im Hintergrund). |
reduce_motion(), screen_reader() |
reduceMotion(), screenReaderEnabled() (nsh) |
Siehe Barrierefreiheit. |
use nsh_core::prelude::*;
#[component]
pub fn App() -> impl IntoElement {
view()
.flex_1()
.pt(move || safe_area().top + px(16.0))
.px(px(16.0))
// A light and a dark value: the color follows the appearance natively.
.bg(AdaptiveColor::rgb(0xffffff, 0x0d1117))
.child(
text(move || {
let size = window();
format!(
"{} × {} pt, text at {:.2}×",
size.width.0.round(),
size.height.0.round(),
font_scale()
)
})
.text_color(AdaptiveColor::rgb(0x1f2328, 0xe6edf3)),
)
}import { mount, Text, View, safeArea } from "nsh";
import { colorScheme, fontScale, windowSize } from "nsh";
function App() {
return (
<View style={{ flex: 1, paddingTop: safeArea().top + 16, paddingHorizontal: 16, backgroundColor: colorScheme() === "dark" ? "#0d1117" : "#ffffff" }}>
<Text style={{ color: colorScheme() === "dark" ? "#e6edf3" : "#1f2328" }}>
{Math.round(windowSize().width)} × {Math.round(windowSize().height)} pt, text at {fontScale().toFixed(2)}×
</Text>
</View>
);
}
mount(App);#Fenstergröße und responsives Layout
window() (TSX: windowSize()) ist die Größe der Oberfläche, in die NSH rendert: der Bildschirm auf einem Smartphone, das Fenster auf einem iPad in Split View oder auf einem Mac, die Host-View, wenn NSH eingebettet ist. Verwende sie, um das Layout zu ändern, nicht nur Größen:
use nsh_core::prelude::*;
fn gallery() -> impl IntoElement {
let columns = || {
let width = window().width.0;
if width >= 700.0 { 4.0 } else if width >= 500.0 { 3.0 } else { 2.0 }
};
let side = move || px((window().width.0 - 32.0 - (columns() - 1.0) * 8.0) / columns());
view().flex_row().flex_wrap().gap_2().child(each(
photos,
|p| p.id,
move |p, _| image(move || p.with(|p| p.url.clone())).w(side).aspect_ratio(1.0),
))
}import { Image, View } from "nsh";
import { windowSize } from "nsh";
function Gallery() {
const columns = () => (windowSize().width >= 700 ? 4 : windowSize().width >= 500 ? 3 : 2);
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
<Each of={photos}>
{(p) => <Image src={p().url} style={{ width: (windowSize().width - 32 - (columns() - 1) * 8) / columns(), aspectRatio: 1 }} />}
</Each>
</View>
);
}Oft reichen Flexbox, Prozentwerte und aspect_ratio (aspectRatio) aus, ohne die Größe überhaupt zu lesen; siehe Layout. Rusts style_when(|| window().width.0 >= 640.0, |s| s.flex_row()) schaltet Styles abhängig von der Breite um; in TSX mit Tailwind folgen die Breakpoint-Varianten (sm:, md:, lg:, xl:, 2xl:, max-*:, min-[…]:) und portrait: / landscape: ebenfalls dem Fenster: class="grid grid-cols-2 md:grid-cols-4".
#Dark Mode
Das Erscheinungsbild folgt dem System, solange die App kein eigenes festlegt. In Rust unterstützt du den Dark Mode am einfachsten mit einer Farbe, die einen hellen und einen dunklen Wert hat, AdaptiveColor::rgb(light, dark): Sie wechselt nativ, sobald sich das Erscheinungsbild ändert, ohne dass Code ausgeführt werden muss. In TSX ist es die Tailwind-Variante dark:, die gilt, wenn colorScheme() den Wert "dark" hat:
view()
.flex_1()
.p_4()
.bg(AdaptiveColor::rgb(0xffffff, 0x18181b))
.child(
text("Follows the system appearance")
.text_color(AdaptiveColor::rgb(0x18181b, 0xf4f4f5)),
)<View class="flex-1 bg-white dark:bg-zinc-900 p-4">
<Text class="text-zinc-900 dark:text-zinc-100">Follows the system appearance</Text>
</View>Für alles andere liest du dark() (TSX: colorScheme()) in einem Closure oder einem berechneten Wert; in TSX leitest du bei mehr als ein paar Farben einmalig eine Palette ab:
use nsh_core::prelude::*;
// `dark()` is reactive: the closure runs again when the appearance changes.
view()
.flex_1()
.bg(move || if dark() { rgb(0x0d1117) } else { rgb(0xf6f8fa) })
.child(text("Hello"))import { computed, Text, View } from "nsh";
import { colorScheme } from "nsh";
const palette = computed(() =>
colorScheme() === "dark"
? { background: "#0d1117", surface: "#161b22", text: "#e6edf3", muted: "#8b949e" }
: { background: "#f6f8fa", surface: "#ffffff", text: "#1f2328", muted: "#57606a" },
);
<View style={{ flex: 1, backgroundColor: palette().background }}>
<Text style={{ color: palette().text }}>Hello</Text>
</View>Auch Rust-Komponenten beziehen ihre Farben aus dem Theme, das helle und dunkle Werte hat (nsh.json "theme", siehe Building with Rust), sodass sie ebenfalls mit dem System wechseln. Damit die Statusleiste zu deinem Hintergrund passt, gib deinen Seiten und Dialogen eine statusBar oder der App eine in nsh.json.
#Eine App mit eigenem Erscheinungsbild
Eine App, die immer dunkel (oder immer hell) ist, legt das in nsh.json fest, und dark() und colorScheme(), adaptive Farben, dark:-Klassen, das Theme, der Standardstil der Statusleiste und Alerts folgen ab dem ersten Frame (auch gepackte Apps starten darin):
{ "appearance": "dark" }
set_color_scheme(Appearance::Dark) (TSX: setColorScheme aus nsh) ändert es zur Laufzeit, etwa für eine Einstellung zum Erscheinungsbild innerhalb der App; Appearance::System ("system") folgt wieder dem System:
use nsh_core::prelude::*;
view()
.on_press(move || {
set_color_scheme(if dark() { Appearance::Light } else { Appearance::Dark })
})
.role(AccessibilityRole::Button)
.rounded_lg()
.px_4()
.py_2()
.bg(AdaptiveColor::rgb(0xe5e5e5, 0x262626))
.child(
text(|| if dark() { "Light" } else { "Dark" })
.text_color(AdaptiveColor::rgb(0x171717, 0xfafafa)),
)import { Text, View } from "nsh";
import { colorScheme, setColorScheme } from "nsh";
<View onPress={() => setColorScheme(colorScheme() === "dark" ? "light" : "dark")} role="button" class="rounded-lg px-4 py-2 bg-neutral-200 dark:bg-neutral-800">
<Text class="text-neutral-900 dark:text-neutral-50">{colorScheme() === "dark" ? "Light" : "Dark"}</Text>
</View>#Textgröße
font_scale() (TSX: fontScale()) ist die Einstellung des Nutzers für die Textgröße (Dynamic Type unter iOS, Schriftgröße unter Android) relativ zum Standard. Texte und Eingabefelder folgen ihr von selbst: fontSize: 16 wird größer dargestellt, wenn der Nutzer größeren Text eingestellt hat. Pro Element kannst du dies abschalten oder begrenzen:
Rust (auf text und input) |
TSX (auf <Text> und <Input>) |
Standard | Beschreibung |
|---|---|---|---|
.allow_font_scaling(..) |
allowFontScaling |
true |
Der System-Textgröße folgen. |
.max_font_size_multiplier(..) |
maxFontSizeMultiplier |
0 (keine) |
Die größte Skalierung, die auf diesen Text angewendet wird. |
text("Today")
.text_size(px(34.0))
.font_weight(FontWeight::BOLD)
.max_font_size_multiplier(1.4)<Text style={{ fontSize: 34, fontWeight: "700" }} maxFontSizeMultiplier={1.4}>Today</Text>Lies font_scale(), um Layouts anzupassen, die dem Text nicht automatisch folgen, zum Beispiel um eine Zeile in eine Spalte umzuwandeln, wenn der Text groß wird: .style_when(|| font_scale() > 1.3, |s| s.flex_col()) in Rust, flexDirection: fontScale() > 1.3 ? "column" : "row" in TSX.
Die Textgröße skaliert Text: jeden Text und jedes Eingabefeld, die Schriftskalierung erlauben, sowie die Alerts und Toasts der Plattform. Längen behalten ihre Größe: Abstände, Breiten und Höhen, Eckenradien, Rahmen und Bilder, in Punkten oder in Klassen (p-4, rounded-lg, w-[2rem]). Boxen, deren Größe vom Inhalt bestimmt wird, wachsen mit ihrem Text.
#Sprache und Region
locale() ist die Locale des Nutzers als BCP-47-Tag (locale().as_str() in Rust). In TSX formatiert Intl Zahlen und Datumsangaben standardmäßig dafür, sodass new Intl.NumberFormat().format(1234.5) in en-US den Wert 1,234.5 und in de-DE den Wert 1.234,5 liefert. Lies locale(), um die Strings deiner eigenen Übersetzungen auszuwählen: Die Texte, die sie lesen, aktualisieren sich bei einer Änderung.
use nsh_core::prelude::*;
fn greeting() -> &'static str {
match locale().as_str().split('-').next() {
Some("de") => "Willkommen",
Some("zh") => "欢迎",
_ => "Welcome",
}
}
text(greeting)import { Text } from "nsh";
import { locale } from "nsh";
const greetings: Record<string, string> = { en: "Welcome", de: "Willkommen", zh: "欢迎" };
<Text>{greetings[locale().split("-")[0]] ?? greetings.en}</Text>#In einer einbettenden App
Eine App, die innerhalb einer nativen App angezeigt wird, folgt den eigenen Einstellungen dieser App, sofern sie welche setzt: Die Locale und die Textgröße der Embedding-APIs (AppView::set_locale und set_font_scale in Rust auf GPUI, macOS, Linux und Windows, locale und fontScale in Swift auf iOS, setLocale und setFontScale in Java auf Android) ersetzen Sprache und Textgröße des Systems. locale(), Intl, fontScale() und Text folgen ihnen sofort, so wie sie einer Systemänderung folgen, und die Werte des Systems kehren zurück, wenn die einbettende App sie zurücksetzt. Nutze sie, wenn die native App ihren Nutzern erlaubt, eine eigene Sprache oder Textgröße zu wählen.
#Im Hintergrund
lifecycle() (in TSX identisch) gibt an, ob der Nutzer die App gerade verwendet. Der Wert ist active im Vordergrund, inactive, solange die App noch sichtbar ist, aber eine andere App oder ein anderes Fenster den Fokus hat (oder das System etwas darüber anzeigt: den App-Umschalter, einen eingehenden Anruf), und background, solange sie nicht sichtbar ist: im Hintergrund auf einem Smartphone, minimiert auf einem Desktop, ein verborgener Tab im Web. Wie jeder Accessor aktualisiert er alles, was ihn gelesen hat, sodass ein Effect einen Live-Feed, Polling oder ein Video im Hintergrund pausiert und fortsetzt, wenn der Nutzer zurückkehrt:
effect(move || {
if lifecycle() == Lifecycle::Background {
feed.pause();
} else {
feed.resume();
}
});effect(() => {
if (lifecycle() === "background") feed.pause();
else feed.resume();
});Verwende background, um Arbeit zu pausieren, und speichere Änderungen des Nutzers, sobald sie passieren: Das System kann eine App wenige Sekunden nach dem Wechsel in den Hintergrund anhalten, noch bevor ein Effect, der die Änderung liest, gelaufen ist. Eine App, die innerhalb einer nativen App angezeigt wird, folgt dem Zustand dieser App, und wenn eine App mehrere Fenster hat (ein iPad, ein Desktop), folgt eine View dem Fenster, in dem sie sich befindet. In Tests versetzt app.setLifecycle("background") von nsh/test die App dorthin (Testing).
#Safe Areas
Notches, der Home-Indikator, die Statusleiste, Androids Navigationsleiste und die Titelleiste von Mac Catalyst verdecken Teile der Oberfläche. NSH-Apps zeichnen auf jeder Plattform randlos und erhalten die verdeckten Bereiche als Insets.
safe_area()(TSX:safeArea()) liefert die Insets der Oberfläche.- Die
cx.insets()einer Komponente (TSX:safeAreaInsets()) liefert die Insets, an der Stelle, an der die Komponente gerendert wird: Innerhalb einer Navigations-Seite werden sie unterhalb ihres Headers gemessen, innerhalb eines Dialogs sind es die des Dialogs. Bevorzuge sie in Komponenten, die an mehreren Stellen angezeigt werden können. - CSS’
env(safe-area-inset-top),-right,-bottomund-leftversehen jede View mit Padding oder Abstand entsprechend den Insets an der Stelle, an der sie gerendert wird, inpadding*undmargin*(calc(env(safe-area-inset-bottom) + 8px)addiert eine Länge; Tailwindpt-[env(safe-area-inset-top)]; Rust.p_safe_area(),.pt_safe_area(),.pb_safe_area_plus(px(8.0)), …). Das Padding aktualisiert sich nativ, wenn sich die Insets ändern (Safe area).
use nsh_core::prelude::*;
fn screen() -> impl IntoElement {
view()
.pt_safe_area()
.px_safe_area()
.flex_1()
.bg(white())
.child(content())
.child(bottom_bar())
}
// A component, for its `Scope`: the insets where it renders.
#[component]
fn BottomBar(cx: Scope<NoEvent>) -> impl IntoElement {
// `cx.insets()`: the insets where the component renders.
view()
.flex_row()
.pt(px(8.0))
.pb(move || cx.insets().bottom + px(8.0))
}import { View, safeAreaInsets } from "nsh";
function Screen() {
return (
<View class="flex-1 bg-white pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pl-[env(safe-area-inset-left)]">
<Content />
<BottomBar />
</View>
);
}
function BottomBar() {
const insets = safeAreaInsets();
return <View style={{ paddingBottom: insets().bottom + 8, paddingTop: 8, flexDirection: "row" }}>…</View>;
}Unter Android erhält eine View, die unterhalb einer Toolbar eingebettet ist, nur die Teile der Systemleisten, die sie überlappen, wie bei der Safe Area von UIKit: kein oberes Inset, wenn sie nicht bis zur Statusleiste reicht.
#Die Plattform
os(), os_version(), idiom(), is_desktop() und is_catalyst() in Rust sowie die Konstante platform aus nsh in TSX beschreiben, wo die App läuft. Sie bleiben gleich, solange die App läuft.
| Rust | TSX | Beschreibung |
|---|---|---|
os() |
platform.os |
Os::Ios, Os::Android, Os::MacOs, Os::Windows, Os::Linux oder Os::Web (os().name() ist der String in TSX). Mac Catalyst ist iOS. |
os_version() |
platform.version |
Haupt- und Nebenversion; unter Android das API-Level und 0. |
idiom() |
platform.idiom |
Idiom::Phone, Pad, Mac, Tv oder Desktop; Desktop genau dort, wo is_desktop() gilt. |
is_desktop() |
platform.desktop |
macOS, Windows oder Linux, oder ein Desktop-Browser (Desktop and mobile). |
is_catalyst() |
platform.catalyst |
Mac Catalyst, in beiden Idioms. |
TSX’ platform vollständig:
| Member | Beschreibung |
|---|---|
platform.os |
"ios", "android", "macos", "windows", "linux" oder "web". Mac Catalyst meldet "ios". |
platform.version |
Ein String: iOS und Desktops: die OS-Version ("17.5"); Android: das API-Level ("35"); Web: "0.0". |
platform.idiom |
"phone", "pad", "mac", "tv" oder "desktop". "desktop" genau dort, wo platform.desktop den Wert true hat. "mac" ist das Mac-Idiom von UIKit unter Mac Catalyst, ein mobiles Idiom. |
platform.desktop |
Desktop-Verhalten gilt: macOS, Windows, Linux und Desktop-Browser. desktop={{…}}-Props und platform.select({ desktop }) folgen ihm. |
platform.catalyst |
Die App läuft unter Mac Catalyst (os "ios"), im Mac- oder im iPad-Idiom. |
platform.select(spec) |
Der Wert für diese Plattform; siehe Platform-specific code. |
use nsh_core::{element::Os, prelude::*};
let haptics_supported = os() == Os::Ios || (os() == Os::Android && os_version().0 >= 30);import { platform } from "nsh";
const hapticsSupported = platform.os === "ios" || (platform.os === "android" && Number(platform.version) >= 30);nsh test läuft headless als iOS.
Unter Linux ist platform.os gleich "linux", das Idiom "desktop" und platform.version die Haupt- und Nebenversion des Kernels ("6.12"): Distributionen nummerieren ihre Releases auf eine Weise, die sich nicht vergleichen lässt. Dort sind Safe Area und Tastaturhöhe 0, colorScheme() folgt live der Einstellung des Desktops (dem XDG-Settings-Portal), fontScale() der Textskalierung des Desktops und pixelRatio() der Skalierung des Displays, einschließlich gebrochener Werte.
Unter macOS (dem AppKit-Backend) ist platform.os gleich "macos", das Idiom "desktop" und platform.version die macOS-Version. Safe Area und Tastaturhöhe sind 0, colorScheme() folgt live dem Erscheinungsbild des Systems, fontScale() ist 1 und pixelRatio() die Backing-Skalierung des Displays.
Unter Windows ist platform.os gleich "windows", das Idiom "desktop" und platform.version "10.0", auch unter Windows 11 (Windows hat die Versionsnummer beibehalten). Safe Area und Tastaturhöhe sind 0, colorScheme() folgt live Settings > Personalization > Colors, fontScale() der Einstellung Text size (1 bis 2,25), reduceMotion() der Einstellung für Animationseffekte, pixelRatio() der Skalierung des Monitors, und Intl formatiert im regionalen Format des Nutzers.
In einem Browser (the web runtime) ist platform.os gleich "web" und platform.version "0.0". Das Idiom ist "desktop" mit einer Maus und einem Fenster von mindestens 600 Punkten Breite, "pad" auf einem Tablet und "phone" auf einem Smartphone, festgelegt beim Start der App. Die Locale, reduceMotion(), fontScale() (die Textgröße des Browsers), pixelRatio(), die Safe Area und die Tastaturhöhe stammen vom Browser und folgen ihm live.
#Pixeldichte
pixel_ratio() und round_to_nearest_pixel(points) in Rust sowie pixelRatio() aus nsh in TSX rechnen zwischen Punkten und Geräte-Pixeln um.
| Rust | TSX | Beschreibung |
|---|---|---|
pixel_ratio() |
pixelRatio() |
Geräte-Pixel pro Punkt (2 oder 3 auf den meisten Smartphones), reaktiv. |
round_to_nearest_pixel(points) |
Math.round(x * pixelRatio()) / pixelRatio() |
Punkte, gerundet auf das nächstgelegene Geräte-Pixel. |
Eine Haarlinie, ein Geräte-Pixel hoch:
view()
.h(move || px(1.0 / pixel_ratio()))
.bg(rgb(0xd0d7de))import { View } from "nsh";
import { pixelRatio } from "nsh";
<View style={{ height: 1 / pixelRatio(), backgroundColor: "#d0d7de" }} />pixel_ratio() und pixelRatio() sind reaktiv, sodass eine Haarlinie einem Fenster folgt, das auf einen Bildschirm mit anderer Dichte verschoben wird.
#Rechts-nach-links
Wenn die Sprache des Geräts von rechts nach links geschrieben wird (Arabisch, Hebräisch, Persisch, …), legt NSH die App von rechts nach links aus: Zeilen laufen von rechts, die logischen Style-Eigenschaften nehmen ihre Rechts-nach-links-Seiten an, Text wird rechts ausgerichtet, und native Widgets werden gespiegelt. Die Layoutrichtung folgt der Sprache des Nutzers von selbst. Der Style direction einer View ("ltr", "rtl" oder "inherit"; .direction(LayoutDirection::..) in Rust) legt die Richtung ihres Teilbaums fest; auf der Root-View legt er die der App fest.
Änderungen werden sofort angewendet, ohne die App neu zu starten; die Demo auf dieser Seite schaltet den Style direction um.
Schreibe Layouts, die in beide Richtungen funktionieren, mit den logischen Eigenschaften: in Rust ms / me und ps / pe (die Namen von Tailwind), in TSX marginStart / marginEnd, paddingStart / paddingEnd, start / end, borderTopStartRadius, textAlign: "start" und so weiter, die in Links-nach-rechts-Layouts links und rechts bedeuten und von rechts nach links das Gegenteil. left und right (marginLeft, paddingRight, left, textAlign: "left", …) behalten in beiden ihre Seiten, wie in CSS. Der Style direction ("inherit", "ltr", "rtl") überschreibt die Richtung für einen Teilbaum, zum Beispiel um eine Telefonnummer oder die Steuerelemente eines Mediaplayers von links nach rechts zu belassen. Die Tailwind-Varianten rtl: und ltr: gelten je nach Richtung.
Horizontale Scroll-Views und Listen beginnen in Rechts-nach-links-Layouts am rechten Ende. Unter Android muss die Host-App android:supportsRtl="true" in ihrem Manifest deklarieren, damit native Widgets gespiegelt werden.
#Siehe auch
nshs Geräte-API: die Referenz.- Platform-specific code:
platform.select, Plattform-Props und -Module. - Accessibility: Screenreader und reduzierte Bewegung.
- Safe area, Keyboard und die Statusleiste.
- Tailwind:
dark:, Breakpoint- und Orientierungsvarianten.