Elementos
Área segura
A safe area mantém o conteúdo longe do notch ou da Dynamic Island, da barra de status, do indicador de home, de um cabeçalho de navegação e da barra de título do Mac Catalyst. Qualquer view se ajusta a ela com o env() do CSS:
env(safe-area-inset-top), env(safe-area-inset-right), env(safe-area-inset-bottom) e
env(safe-area-inset-left), em seu padding ou margin. Esses valores são os insets da view raiz em que o nó é renderizado:
dentro de uma NavPage, os insets abaixo do cabeçalho da página; dentro de um Dialog, os insets do
dialog; nos demais casos, a safe area da surface. Em um navegador, a página é a raiz e as variáveis são as do
próprio navegador.
import { mount, signal, Text, View } from "nsh";
import { safeArea, safeAreaInsets } from "nsh";
import { button, buttonText, desktop, tint, ui } from "./palette";
type Edge = "top" | "right" | "bottom" | "left";
const EDGES: Edge[] = ["top", "right", "bottom", "left"];
// `calc(env(safe-area-inset-<edge>) + 16px)` on the edges chosen, 16 px on the others.
const side = (edges: Edge[], e: Edge) => (edges.includes(e) ? (`calc(env(safe-area-inset-${e}) + 16px)` as const) : 16);
const padding = (edges: Edge[]) => ({
paddingTop: side(edges, "top"),
paddingRight: side(edges, "right"),
paddingBottom: side(edges, "bottom"),
paddingLeft: side(edges, "left"),
});
const format = (i: { top: number; right: number; bottom: number; left: number }) =>
`top ${Math.round(i.top)} · right ${Math.round(i.right)} · bottom ${Math.round(i.bottom)} · left ${Math.round(i.left)}`;
// Called in a component: the insets where it renders (the root here, so the
// surface's safe area; inside a page or a dialog, theirs).
function Insets() {
const insets = safeAreaInsets();
return <Text style={{ color: ui().text, fontVariant: ["tabular-nums"] }}>{format(insets())}</Text>;
}
function App() {
const edges = signal<Edge[]>([...EDGES]);
const toggle = (edge: Edge) => edges.update((list) => (list.includes(edge) ? list.filter((e) => e !== edge) : EDGES.filter((e) => e === edge || list.includes(e))));
const label = () => ({ fontSize: 12, color: ui().muted });
return (
// The tinted band around the card is the view's padding: the insets of
// the edges chosen, plus 16 pt.
<View style={{ flex: 1, backgroundColor: tint(ui().accent, 0.16), ...padding(edges()) }}>
<View
style={{
flex: 1,
width: "100%",
maxWidth: 560,
alignSelf: "center",
backgroundColor: ui().page,
borderRadius: 16,
borderWidth: 1,
borderStyle: "dashed",
borderColor: ui().faint,
padding: 20,
gap: 14,
}}
>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>safe area insets</Text>
<Text style={{ color: ui().muted, lineHeight: 20 }}>
{desktop
? "A desktop window or a browser page has no system UI over it: the insets are 0, so the view pads only by its 16 pt (the tinted band)."
: "On a phone the insets are the status bar or notch on top and the home indicator below; env(safe-area-inset-*) adds them to its 16 pt. This browser frame has none, so they are 0 here."}
</Text>
<Text style={label()}>edges ({desktop ? "click" : "tap"} to toggle)</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
<Each of={EDGES}>
{(edge) => {
const on = () => edges().includes(edge());
return (
<View
role="button"
onPress={() => toggle(edge())}
{...button("secondary", { on })}
>
<Text style={buttonText("secondary", { on })}>{edge()}</Text>
</View>
);
}}
</Each>
</View>
<View style={{ ...ui().inset, borderRadius: 12, padding: 14, gap: 6 }}>
<Text style={label()}>edges</Text>
<Text style={{ color: ui().text }}>{edges().length ? `[${edges().map((e) => `"${e}"`).join(", ")}]` : "[] (no inset padded)"}</Text>
<Text style={label()}>safeArea() · the surface's insets</Text>
<Text style={{ color: ui().text, fontVariant: ["tabular-nums"] }}>{format(safeArea())}</Text>
<Text style={label()}>safeAreaInsets() · where it renders</Text>
<Insets />
</View>
</View>
</View>
);
}
mount(App);use nsh_core::prelude::*;
fn app() -> impl IntoElement {
view()
.flex_1()
.bg(rgb(0xfff8c5))
.p_safe_area()
.child(text("Clear of the notch").text_2xl().font_weight(FontWeight::BOLD))
}import { mount, Text, View } from "nsh";
function App() {
return (
<View
style={{
flex: 1,
backgroundColor: "#fff8c5",
paddingTop: "env(safe-area-inset-top)",
paddingRight: "env(safe-area-inset-right)",
paddingBottom: "env(safe-area-inset-bottom)",
paddingLeft: "env(safe-area-inset-left)",
}}
>
<Text style={{ fontSize: 24, fontWeight: "700" }}>Clear of the notch</Text>
</View>
);
}
mount(App);A cor de fundo preenche toda a área, inclusive a área atrás da barra de status, e o conteúdo começa abaixo dela. Os valores acompanham os insets: ao girar o dispositivo ou ao mudar para outra página ou dialog, o padding é atualizado nativamente.
#Valores
| Valor | Exemplo | Significado |
|---|---|---|
env(name) |
paddingTop: "env(safe-area-inset-top)" |
O inset. |
env(name, <length>) |
env(safe-area-inset-top, 20px) |
O inset; o comprimento substitui um nome desconhecido, como no CSS. |
calc(env(name) + <length>) |
calc(env(safe-area-inset-bottom) + 16px) |
O inset mais um comprimento, para conteúdo que mantém seu próprio espaçamento. |
calc(<length> + env(name)) |
calc(16px + env(safe-area-inset-left)) |
O mesmo. |
Eles se aplicam a padding, paddingHorizontal, paddingVertical, paddingTop, paddingRight, paddingBottom,
paddingLeft e às mesmas chaves de margin. No Tailwind, são valores arbitrários de p, px, py, pt, pr, pb,
pl e das utilities m (os espaços podem ser omitidos): pt-[env(safe-area-inset-top)],
pb-[calc(env(safe-area-inset-bottom)+16px)]. O tipo TypeScript desses valores é EnvValue. Veja
a referência de estilos.
Em Rust, os métodos de Styled os definem: .p_safe_area(), .px_safe_area(), .py_safe_area(), .pt_safe_area(),
.pr_safe_area(), .pb_safe_area(), .pl_safe_area(), os mesmos métodos de m e .pt_safe_area_plus(px(16.0))
(e pr, pb, pl, mt, mr, mb, ml) para um inset mais um comprimento.
#Escolhendo as bordas
Aplique padding apenas nas bordas que precisam dele, por exemplo quando uma tab bar ou uma barra inferior própria trata o inset inferior:
view()
.flex_1()
.pt_safe_area()
.px_safe_area()<View class="flex-1 pt-[env(safe-area-inset-top)] px-[calc(env(safe-area-inset-left)+16px)]">
…
</View>#Quando usar
Uma NavPage com um cabeçalho visível e opaco já posiciona seu conteúdo abaixo do cabeçalho, então o inset superior ali é 0. Os insets da safe area servem para:
- a raiz de um app sem pilha de navegação,
- uma página com
headerVisible={false}ouheaderTransparent, - o conteúdo de um Dialog em tela cheia,
- barras próprias nas bordas da tela.
#safeArea and safeAreaInsets
Quando você precisar dos números no código, leia-os diretamente. Ambos os accessors TSX vêm de nsh, retornam um objeto Edges
({ top, right, bottom, left } em points) e são reativos: o código que os lê é executado novamente quando eles mudam, por
exemplo ao girar o dispositivo.
| TSX | Rust | Retorna |
|---|---|---|
safeArea() |
safe_area() |
A safe area da surface: as partes livres da UI do sistema (notch, indicador de home, barra de título). |
safeAreaInsets() |
cx.insets() |
Os insets do local onde o componente é renderizado: os da página mais próxima (abaixo do cabeçalho), os do dialog mais próximo ou, na ausência deles, a safe area da surface. Esses são os valores das variáveis safe-area-inset-*. Em TSX, chame-o dentro de um componente; ele retorna um accessor. Um componente Rust do seu crate os lê com cx.insets(). |
use nsh_core::prelude::*;
fn bottom_bar() -> impl IntoElement {
view()
.flex_row()
.p_2()
.pb(move || if safe_area().bottom > Points::ZERO { px(4.0) } else { px(8.0) })
.child(text("Bottom bar"))
}import { Text, View, safeArea } from "nsh";
function BottomBar() {
const insets = safeArea();
return (
<View style={{ flexDirection: "row", padding: 8, paddingBottom: insets().bottom > 0 ? 4 : 8 }}>
<Text>Bottom bar</Text>
</View>
);
}Para aplicar padding com o próprio inset, env() é a forma mais simples: ele é atualizado nativamente, sem executar código.
#Notas por plataforma
- iOS: os insets vêm da safe area do UIKit: a barra de status, o notch ou a Dynamic Island e o indicador de home.
- Android: o NSH informa as partes das barras do sistema e do display cutout que se sobrepõem à view do NSH. Quando o NSH está incorporado abaixo de uma toolbar nativa, o inset superior da view é 0.
- Mac Catalyst: o inset superior é a barra de título da janela.
- Desktop (macOS, Windows, Linux, GPUI) e web: o conteúdo de uma página começa abaixo de seu
header, e um dialog fornece ao seu conteúdo insets próprios, como nos celulares.
#Relacionados
- Teclado:
env(keyboard-inset-height), conteúdo mantido acima do teclado virtual. - Dispositivo e ambiente: a safe area entre os demais valores do dispositivo.