एलिमेंट
Link
Link उपयोगकर्ता को कहीं ले जाता है: किसी वेब पेज पर, किसी दूसरे ऐप (mailto:, tel:) में, या ऐप की ही किसी स्क्रीन पर। व्यवहार इसी का होता है: प्रेस, कीबोर्ड फ़ोकस मिलने पर Return और Space, disabled स्थिति, और स्क्रीन रीडर के लिए link role। रूप-रंग ऐप का होता है: Link का अपना कोई रंग, underline या लेबल नहीं होता, और जो दिखता है वह उसके children ही हैं। href बताता है कि वह कहाँ जाता है; यह खोलने के लिए डेटा है, स्क्रीन पर दिखने वाला टेक्स्ट कभी नहीं।
import { mount, signal, Text, View } from "nsh";
import { Link } from "nsh/base";
import { desktop, focusRing, Icon, Log, Panel, size, Split, tint, ui } from "./palette";
// Links of the app's look: the accent with a quiet underline that fills in
// under the pointer, violet once visited (the app remembers what it
// opened), the palette's focus ring, dimmed while disabled. The color and
// the underline move with `transition-colors`, still with reduced motion.
const visited = signal(new Set<string>());
const events = signal<string[]>([]);
const route = signal("/docs/link");
const opened = ({ href }: { href: string }) => {
visited.set(new Set(visited()).add(href));
events.set([`onOpen ${href}`, ...events()].slice(0, 4));
};
function DocLink(props: { href: string; label: string; openWith?: "system" | "app"; disabled?: boolean; current?: boolean; icon?: boolean; inline?: boolean }) {
const seen = () => visited().has(props.href);
const color = () => (props.disabled ? ui().faint : props.current ? ui().text : seen() ? ui().violet : ui().accent);
return (
<Link
href={props.href}
openWith={props.openWith ?? "app"}
disabled={props.disabled}
current={props.current}
onOpen={(e: { href: string }) => {
opened(e);
if ((props.openWith ?? "app") === "app") route.set(e.href);
}}
class={`flex-row items-center gap-1 rounded-sm transition-colors motion-reduce:transition-none active:opacity-60 ${props.inline ? "align-text-bottom" : "self-start"}`}
focusStyle={focusRing(2)}
>
<Text
class="transition-colors motion-reduce:transition-none"
style={{
fontSize: props.inline ? 15 : size.label,
fontWeight: props.current ? "600" : "500",
color: color(),
textDecorationLine: props.current || props.disabled ? "none" : "underline",
textDecorationColor: tint(color(), 0.4),
}}
hoverStyle={props.disabled || props.current ? undefined : { textDecorationColor: color() }}
>
{props.label}
</Text>
{props.icon ? <Icon name="external" size={13} tint={color()} /> : null}
</Link>
);
}
function App() {
const crumb = (href: string, label: string) => <DocLink href={href} label={label} current={route() === href} />;
const slash = () => <Text style={{ color: ui().faint, fontSize: size.label }}>/</Text>;
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>link</Text>
<Text style={{ color: ui().muted }}>{desktop ? "Click a link, or Tab to one and press Return" : "Tap a link"}</Text>
<Split
gap={14}
inspector={
<>
<Panel title="route · visited">
<Text style={{ fontSize: 13, color: ui().text }}>
{route()} · {visited().size} visited
</Text>
</Panel>
<Log title="onOpen({ href })" lines={events()} minHeight={110} />
</>
}
>
<View style={{ ...ui().card, borderRadius: 16, padding: desktop ? 16 : 18, gap: 14 }}>
<View style={{ flexDirection: "row", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
{crumb("/docs", "Docs")}
{slash()}
{crumb("/docs/elements", "Elements")}
{slash()}
{crumb("/docs/link", "Link")}
</View>
<Text style={{ fontSize: 15, lineHeight: 22, color: ui().text }}>
{"Read the "}
<DocLink href="/docs/release-notes" label="release notes" inline />
{" and the "}
<DocLink href="/docs/migration" label="migration guide" inline />
{" before you update."}
</Text>
<View style={{ height: 1, backgroundColor: ui().line }} />
<View style={{ gap: 10 }}>
<DocLink href="https://example.com/" label="example.com" openWith="system" icon />
<DocLink href="/docs/archive" label="Archived docs" disabled />
</View>
</View>
</Split>
</View>
);
}
mount(App);use nsh_base::{link, theme};
use nsh_core::prelude::*;
fn docs() -> impl IntoElement {
link()
.href("https://example.com/docs")
.text_color(move || theme().primary)
.underline()
.hover(|s| s.opacity(0.8))
.focus(|s| s.rounded_sm().outline(px(2.0)).outline_color(move || theme().ring))
.child(text("Read the docs"))
}import { mount, Text } from "nsh";
import { Link } from "nsh/base";
function App() {
return (
<Link
href="https://example.com/docs"
class="self-start text-primary underline hover:opacity-80 focus:rounded-sm focus:outline-2 focus:outline-ring"
>
<Text>Read the docs</Text>
</Link>
);
}
mount(App);Link nsh/base से आता है, जो Rust में nsh-base crate है। Rust में link() एक ऐसा link बनाता है जिसमें हर prop के लिए एक setter (.href(..), .open_with(..)), हर event के लिए एक listener (.on_open(..), .on_press(..)), link के लिए typed style methods और उसकी सामग्री के लिए .child(..) होता है।
#खोलना
प्रेस, क्लिक, या फ़ोकस वाले link पर Return या Space उसका href खोलता है:
onOpen({ href })बताता है कि वह कहाँ जाता है।openWith="system"(डिफ़ॉल्ट) के साथ, किसी दूसरे ऐप या साइट का URL, यानी scheme वाला URL (https:,mailto:,tel:, या किसी दूसरे ऐप द्वारा रजिस्टर किया गया scheme), ऐप के बाहर खुलता है, ठीक वैसे जैसेopenUrlउसे खोलता है: पहले embedding ऐप का router उसे लेता है, फिर सिस्टम। कोई भी अन्यhref, यानी ऐप का अपना path (/orders/42) या#anchor, रिपोर्ट किया जाता है और ऐप के पास ही रहता है।onPress()प्रेस की रिपोर्ट देता है।
openWith="app" के साथ, ऐप का onOpen handler ही तय करता है कि link कैसे खुले: उसका अपना कोई route, in-app web view में कोई पेज, या पहले कोई पुष्टि।
import { Link } from "nsh/base";
<Link href={`/orders/${order.id}`} openWith="app" onOpen={({ href }) => router.push(href)} class="text-primary">
<Text>Order #{order.id}</Text>
</Link>
जब ऐप nsh.json ("openUrl": true, या { "schemes": ["https", "mailto"] }) में capabilities घोषित करता है, तब ऐप के बाहर URL खोलने के लिए openUrl capability चाहिए।
#Props
| Prop | Rust | Type | Default | विवरण |
|---|---|---|---|---|
href | .href(..) | string | none | link कहाँ जाता है: कोई URL, या ऐप का अपना path। onOpen के साथ रिपोर्ट होता है, कभी दिखाया नहीं जाता। |
openWith | .open_with(LinkOpenWith::App) | "system" | "app" (LinkOpenWith) | "system" | href को कौन खोलता है: सिस्टम किसी दूसरे ऐप या साइट का URL खोलता है; "app" इसे onOpen पर छोड़ देता है। |
disabled | .disabled(..) | boolean | false | कोई प्रेस नहीं, प्रेस फ़ीडबैक नहीं, कीबोर्ड फ़ोकस नहीं; disabled: styles लागू होते हैं, और स्क्रीन रीडर बताते हैं कि यह dimmed है। |
current | .current(..) | boolean | false | उस पेज का link जिस पर उपयोगकर्ता है, किसी navigation या breadcrumb में: स्क्रीन रीडर बताते हैं कि यह वर्तमान पेज है। |
accessibilityLabel | .accessibility_label(..) | string | none | स्क्रीन रीडर क्या बोलें, जब सामग्री बहुत कम बताती हो (जैसे कोई icon)। |
hitSlop | .hit_slop(..) | number | Partial<Edges> | none | इसकी सीमाओं के बाहर कितनी दूर तक प्रेस अब भी गिने जाते हैं, points में: सभी तरफ़ के लिए एक संख्या, या नाम वाली तरफ़ें। जैसा view के hitSlop में होता है। |
children | .child(..) | JSX.Element | none | सामग्री: कोई लेबल, कोई icon, ऐप जो भी दिखाए। |
class, style | style methods | string, StyleProp<ViewStyle> | none | link का रूप: उसका रंग, underline, padding; टेक्स्ट प्रॉपर्टीज़ उसके अंदर के texts तक पहुँचती हैं। |
activeStyle, hoverStyle, focusStyle, disabledStyle | .active(|s| ..), .hover(..), .focus(..), .disabled_style(..) | StyleProp<ViewStyle> | none | pressed, hovered, focused या disabled होने पर link की style, <state>: classes की तरह। |
#Events
| Event | Rust | विवरण |
|---|---|---|
onOpen | .on_open(|href: &SharedString| ..) | link href खोलता है: openWith="app" के साथ handler उसे खोलता है, "system" के साथ वह सुनता है कि सिस्टम से क्या खोलने को कहा गया। यह onPress से पहले आता है। |
onPress | .on_press(|| ..) | link को दबाया गया: tap, क्लिक, या कीबोर्ड फ़ोकस होने पर Return या Space। |
Disabled link कुछ भी रिपोर्ट नहीं करता, और उस पर किया गया प्रेस उसके आसपास के किसी pressable view तक नहीं पहुँचता।
#States
ऐप हर state को classes या style props से style करता है:
<Link
href="https://example.com/changelog"
class="self-start text-primary underline decoration-primary/40 transition-colors hover:decoration-primary active:opacity-70 focus:outline-2 focus:outline-ring disabled:text-muted-foreground disabled:no-underline"
>
<Text>Changelog</Text>
</Link>
Visited link को याद रखना ऐप का काम है: वह जो hrefs खोल चुका है उन्हें रखता है और उन links को अपनी state के अनुसार style करता है।
const visited = signal(new Set<string>());
<Link
href={href}
onOpen={({ href }) => visited.set(new Set(visited()).add(href))}
class={visited().has(href) ? "text-violet-600 underline" : "text-primary underline"}
>
<Text>{label}</Text>
</Link>
#टेक्स्ट के अंदर
<Text> के अंदर Link पैराग्राफ़ का एक inline box होता है, जैसा टेक्स्ट के अंदर हर view होता है: यह लाइन में बैठता है, अपनी states, फ़ोकस और role रखता है, और एक ही लाइन पर रहता है। align-text-bottom एक लाइन के टेक्स्ट वाले link को पैराग्राफ़ की baseline पर रखता है:
<Text class="text-[15px] text-foreground">
By continuing you accept the{" "}
<Link href="https://example.com/terms" class="align-text-bottom">
<Text class="text-[15px] text-primary underline">terms of service</Text>
</Link>
.
</Text>
जो link पैराग्राफ़ की लाइनों के साथ wrap होता है, वह role="link" और onPress के साथ nested <Text> होता है (Text देखें), जैसे TextView किसी document के links बनाता है।
#hitSlop के साथ छोटा target
Link उँगली से छोटा हो सकता है: कोई icon, कोई छोटा शब्द। hitSlop उसके पास पड़ने वाले प्रेस को गिनने योग्य बनाता है, हर तरफ़ से points में, उसके layout या रूप को बदले बिना: सभी तरफ़ के लिए एक संख्या, या नाम वाली तरफ़ें ({ top: 8, bottom: 8 }), जैसे view का hitSlop उन्हें लेता है।
<Link href="https://example.com/help" hitSlop={12} accessibilityLabel="Help" class="size-5 items-center justify-center">
<Text>?</Text>
</Link>
#Accessibility
- स्क्रीन रीडर के लिए link एक ही element है, जिसका role
"link"है; उसका लेबल उसके children का टेक्स्ट है, याaccessibilityLabel।hrefडेटा ही रहता है: स्क्रीन रीडर लेबल बोलते हैं। currentको वर्तमान पेज के रूप में बोला जाता है (ARIA काaria-current="page"), औरdisabledको वैसे बोला जाता है जैसे प्लेटफ़ॉर्म dimmed element को बोलता है।- macOS और web पर link कीबोर्ड फ़ोकस का एक पड़ाव होता है: Tab उस तक पहुँचता है, Return और Space उसे खोलते हैं, और
focus:styles दिखाते हैं कि फ़ोकस कहाँ है।
सामान्य रूप से लेबल, roles और announcements के लिए Accessibility देखें।
#प्लेटफ़ॉर्म नोट्स
- iOS, Android और Mac Catalyst: प्रेस वह tap है जो link पर समाप्त होता है।
- डेस्कटॉप (macOS, Windows, Linux, GPUI) और web: प्रेस एक क्लिक है। macOS और web पर Return और Space भी कीबोर्ड फ़ोकस वाले link को खोलते हैं।
- Web link को
role="link"वाले element के रूप में रेंडर करता है। - हर प्लेटफ़ॉर्म वही बनाता है जो ऐप style करता है।