इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

HoverCard

हॉवर कार्ड पेज छोड़े बिना यह प्रीव्यू दिखाता है कि उसका ट्रिगर किस ओर इशारा करता है: मेंशन के पीछे की प्रोफ़ाइल, लिंक के पीछे का पेज, टिकर के पीछे का कोट। डेस्कटॉप और डेस्कटॉप ब्राउज़र पर यह तब खुलता है जब पॉइंटर openDelay (600 ms) तक ट्रिगर पर टिका रहे, या कीबोर्ड फ़ोकस ट्रिगर पर आ जाए। पॉइंटर के ट्रिगर से कार्ड पर और वापस जाने के दौरान, और कीबोर्ड फ़ोकस के ट्रिगर पर या कार्ड के किसी लिंक या बटन पर रहने तक यह खुला रहता है, इसलिए कार्ड की हर चीज़ पॉइंटर और कीबोर्ड दोनों से इस्तेमाल की जा सकती है। पॉइंटर के दोनों से हट जाने और फ़ोकस के किसी में भी न रहने के closeDelay (300 ms) बाद यह बंद हो जाता है। Escape और बाहर प्रेस करने पर यह तुरंत बंद हो जाता है। फ़ोन और टैबलेट पर ट्रिगर पर प्रेस करने से यह खुलता और बंद होता है, और बाहर प्रेस करने से बंद हो जाता है।

el-hover-card.tsx
el-hover-card.tsxLive
import { type JSX, mount, signal, Text, View } from "nsh";
import { HoverCard, type OpenChangeReason } from "nsh/base";
import { desktop, focusRing, Inspector, Log, PopIn, type PopupSide, Segmented, Stage, tint, ui, wide } from "./palette";

type Placement = PopupSide;

/** A card's surface, the look the app gives it; `PopIn` brings it in from the trigger's side. */
const surface = (width: number) => ({ ...ui().card, width, borderRadius: 12, padding: 14, gap: 10 });

/** The popup's box fades on its way out (its ending class, kept while the transition runs). */
const exit = { popupClass: "transition-opacity duration-150 ease-in motion-reduce:transition-none", popupEndingClass: "opacity-0" };

function App() {
  const openDelay = signal(600);
  const closeDelay = signal(300);
  const placement = signal<Placement>("bottom");
  const events = signal<string[]>([]);
  const following = signal(false);
  const price = signal(227.52);
  let timer: ReturnType<typeof setInterval> | undefined;
  const log = (name: string) => ({ open, reason }: { open: boolean; reason: OpenChangeReason }) =>
    events.set([`${name} ${open ? "opened" : "closed"} · ${reason}`, ...events()].slice(0, 6));
  const shared = () => ({ ...exit, openDelay: openDelay(), closeDelay: closeDelay(), placement: placement(), offset: 8, margin: 12 });
  // Running text in a wrapping row: one piece per word, so a line breaks
  // between words, and the row's gap is the space between them.
  const words = (s: string) => s.split(" ").map((w) => <Text style={{ fontSize: 15, color: ui().text }}>{w}</Text>);

  // A mention: the person's card, with a button that works because the
  // pointer can move into the card.
  const mention = () => (
    <HoverCard {...shared()} onOpenChange={log("@ada")} trigger={
      <View role="link" onPress={() => events.set(["onPress /people/ada", ...events()].slice(0, 6))} focusStyle={focusRing(1)} style={{ borderRadius: 4, borderWidth: 1, borderColor: "transparent", paddingHorizontal: 2, marginHorizontal: -3 }} hoverStyle={{ backgroundColor: tint(ui().accent, 0.14) }}>
        <Text style={{ fontSize: 15, fontWeight: "600", color: ui().accent }}>@ada</Text>
      </View>
    }>
      <PopIn side={placement()} style={surface(260)}>
        <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
          <View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: tint(ui().accent, 0.18), alignItems: "center", justifyContent: "center" }}>
            <Text style={{ fontSize: 15, fontWeight: "600", color: ui().accent }}>AL</Text>
          </View>
          <View style={{ flex: 1, gap: 2 }}>
            <Text style={{ fontSize: 15, fontWeight: "600", color: ui().text }}>Ada Lovelace</Text>
            <Text style={{ fontSize: 12, color: ui().muted }}>Analyst · Engines</Text>
          </View>
        </View>
        <Text style={{ fontSize: 13, lineHeight: 18, color: ui().text }}>Writes the first programs for the Analytical Engine.</Text>
        <View
          onPress={() => following.set(!following())}
          style={{ height: 30, borderRadius: 8, alignItems: "center", justifyContent: "center", ...(following() ? ui().raised : ui().accentFill), transitionProperty: "background-color", transitionDuration: 150 }}
        >
          <Text style={{ fontSize: 13, fontWeight: "500", color: following() ? ui().text : ui().onAccent }}>{following() ? "Following" : "Follow"}</Text>
        </View>
      </PopIn>
    </HoverCard>
  );

  // A link with a preview of the page behind it.
  const preview = () => (
    <HoverCard {...shared()} onOpenChange={log("taffy")} trigger={
      <View role="link" onPress={() => events.set(["onPress github.com/DioxusLabs/taffy", ...events()].slice(0, 6))} focusStyle={focusRing(1)} style={{ borderBottomWidth: 1, borderBottomColor: tint(ui().accent, 0.4) }}>
        <Text style={{ fontSize: 15, color: ui().accent }}>Taffy</Text>
      </View>
    }>
      <PopIn side={placement()} style={{ ...surface(280), padding: 0, gap: 0, overflow: "hidden" }}>
        <View style={{ height: 64, backgroundImage: "linear-gradient(135deg, #3b82f6, #8b5cf6)" }} />
        <View style={{ padding: 12, gap: 4 }}>
          <Text style={{ fontSize: 11, color: ui().faint }}>github.com</Text>
          <Text style={{ fontSize: 14, fontWeight: "600", color: ui().text }}>taffy: flexbox, grid and block layout</Text>
          <Text style={{ fontSize: 12, color: ui().muted }}>A high-performance UI layout library written in Rust.</Text>
        </View>
      </PopIn>
    </HoverCard>
  );

  // A ticker whose quote ticks while its card is open.
  const ticker = () => (
    <HoverCard
      {...shared()}
      onOpenChange={log("$AAPL")}
      onOpen={() => (timer = setInterval(() => price.set(Math.round((price() + (Math.random() - 0.48) * 0.6) * 100) / 100), 900))}
      onClose={() => clearInterval(timer)}
      trigger={
        <View focusable focusStyle={focusRing(1)} style={{ paddingHorizontal: 8, height: 26, borderRadius: 13, justifyContent: "center", borderWidth: 1, borderColor: tint(ui().up, 0.3), backgroundColor: tint(ui().up, 0.1) }}>
          <Text style={{ fontSize: 13, fontWeight: "600", color: ui().up }}>$AAPL ▲</Text>
        </View>
      }
    >
      <PopIn side={placement()} style={surface(220)}>
        <Text style={{ fontSize: 12, color: ui().muted }}>Apple Inc. · live</Text>
        <Text style={{ fontSize: 26, fontWeight: "600", color: ui().text, fontVariant: ["tabular-nums"] }}>{price().toFixed(2)}</Text>
        <Text style={{ fontSize: 13, color: price() >= 224.1 ? ui().up : ui().down, fontVariant: ["tabular-nums"] }}>
          {price() >= 224.1 ? "+" : ""}
          {(price() - 224.1).toFixed(2)} today
        </Text>
      </PopIn>
    </HoverCard>
  );

  const stage = (): JSX.Element => (
    <View style={{ gap: 18, alignItems: "flex-start" }}>
      <View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", columnGap: 4, rowGap: 6 }}>
        {words("Ask")}
        {mention()}
        {words("about the layout engine,")}
        {/* The period stays with the link. */}
        <View style={{ flexDirection: "row", alignItems: "center" }}>
          {preview()}
          {words(".")}
        </View>
      </View>
      <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
        {words("Today:")}
        {ticker()}
      </View>
    </View>
  );
  const controls = () => (
    <>
      <Segmented label="openDelay" options={[0, 300, 600, 1200]} value={openDelay()} onChange={(v) => openDelay.set(v)} />
      <Segmented label="closeDelay" options={[0, 300, 1000]} value={closeDelay()} onChange={(v) => closeDelay.set(v)} />
      <Segmented label="placement" options={["top", "bottom", "left", "right"] as Placement[]} value={placement()} onChange={(v) => placement.set(v)} />
    </>
  );
  const changes = () => <Log title="onOpenChange({ open, reason })" lines={events()} minHeight={110} />;

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>hover card</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Rest the pointer on a mention, a link or a ticker, then move into the card" : "Tap a mention, a link or a ticker; tap outside to close"}
      </Text>

      {() =>
        wide() ? (
          <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
            <Stage gap={24}>{stage()}</Stage>
            <Inspector>
              {controls()}
              {changes()}
            </Inspector>
          </View>
        ) : (
          <>
            {controls()}
            <View style={{ flex: 1, minHeight: 220, justifyContent: "center" }}>{stage()}</View>
            {changes()}
          </>
        )
      }
    </View>
  );
}

mount(App);

nsh/base का HoverCard अपनी ओर से कुछ भी ड्रॉ नहीं करता: ट्रिगर आपका एलिमेंट है, और पॉपअप तथा उसका कंटेंट भी आपके हैं।

use nsh_base::hover_card;
use nsh_core::prelude::*;

fn mention() -> impl IntoElement {
    hover_card()
        .offset(px(8.0))
        .popup_class("w-72 gap-2 rounded-xl border border-border bg-popover p-4 shadow-lg")
        .trigger(text("@ada").text_color(rgb(0x2563eb)))
        .child(text("Ada Lovelace").font_weight(FontWeight::SEMIBOLD))
        .child(text("Writes the first programs for the Analytical Engine."))
}

ट्रिगर अपनी भूमिका (role), प्रेस और रूप बनाए रखता है: role="link" और अपने onPress वाला व्यू प्रेस करने पर अब भी वहीं जाता है जहाँ वह जाता था, और कार्ड उसमें एक प्रीव्यू जोड़ देता है। कंटेंट हर बार कार्ड खुलने पर बनाया जाता है, इसलिए वह जो दिखाता है उसे उसी समय लोड कर सकता है। फ़ोन और टैबलेट पर, जहाँ प्रेस से कार्ड खुलता है, ट्रिगर स्क्रीन रीडर को बताता है कि उसका कार्ड खुला है या नहीं। Toolbar में ट्रिगर उसके आइटम में से एक हो सकता है; कार्ड में जो दिखता है वह नहीं हो सकता: उसके बटन और लिंक अपने-अपने Tab स्टॉप रखते हैं और टूलबार का disabled उन्हें जैसा है वैसा ही छोड़ देता है।

#Delays

openDelay कार्ड को तब अचानक खुलने से रोकता है जब पॉइंटर कहीं और जाते हुए ट्रिगर के ऊपर से गुज़रता है; closeDelay पॉइंटर को ट्रिगर और कार्ड के बीच की दूरी पार करने का समय देता है। दोनों मिलीसेकंड में हैं। पॉइंटर और देरी को नेटिव रूप से ट्रैक किया जाता है, इसलिए ट्रिगर पर टिकने और उसके तथा कार्ड के बीच जाने पर कार्ड के खुलने या बंद होने तक आपका कोई कोड नहीं चलता। Infinity का openDelay खोलने का काम ऐप पर (और टच स्क्रीन पर प्रेस पर) छोड़ देता है; Infinity का closeDelay बंद करने का काम Escape, बाहर प्रेस और ऐप पर छोड़ देता है।

<HoverCard openDelay={300} closeDelay={150} trigger={<Text>AAPL</Text>}>
  <Quote symbol="AAPL" />
</HoverCard>

#Open state

open के बिना कार्ड अपनी स्टेट खुद रखता है, जो defaultOpen से शुरू होती है। open के साथ स्टेट ऐप के पास होती है: इसे onOpenChange({ open, reason }) में बदलें, जो खोलने या बंद करने के हर अनुरोध को उसके कारण के साथ रिपोर्ट करता है: "hover" (पॉइंटर), "focus" (कीबोर्ड फ़ोकस), "triggerPress" (टच स्क्रीन पर प्रेस), "pressOutside" या "cancel" (Escape या back)।

const open = signal(false);

<HoverCard open={open()} onOpenChange={(e) => open.set(e.open)} trigger={<Text>@ada</Text>}>
  <Profile id="ada" />
</HoverCard>

#Entrance and exit

हॉवर कार्ड आपके ट्रांज़िशन के साथ आता और जाता है, जैसा Popover करता है। popupStartingClass पॉपअप का पहले फ़्रेम में रूप है, और popupClass की transition-* क्लासेज़ उसे उसके अपने रूप तक ले जाती हैं। popupEndingClass बंद होने के बाद का उसका रूप है: ये ट्रांज़िशन चलने तक कार्ड रहता है और कोई पॉइंटर इनपुट नहीं लेता, फिर चला जाता है, और उसके बाद onClose आता है। उसके जाते समय ट्रिगर पर फिर टिकने से वह वापस आ जाता है। जब उपयोगकर्ता कम मोशन माँगता है, तो वह तुरंत दिखता है और तुरंत चला जाता है।

<HoverCard
  popupClass="rounded-xl bg-popover p-4 shadow-lg transition-all duration-150 ease-out"
  popupStartingClass="opacity-0 scale-95"
  popupEndingClass="opacity-0 scale-95"
  trigger={<Text>@ada</Text>}
>
  <Profile id="ada" />
</HoverCard>

यह डिफ़ॉल्ट रूप से ट्रिगर के नीचे आता है (placement, align), जगह न होने पर दूसरी ओर आता है, और सेफ़ एरिया के भीतर रहता है। onPosition({ placement, bounds, available }) बताता है कि वह कहाँ गया।

#Props

PropRustTypeDefaultDescription
open.open(..)booleannoneखुला है या नहीं, ऐप द्वारा नियंत्रित।
defaultOpen.default_open(..)booleanfalseशुरू में खुला, open के बिना।
openDelay.open_delay(..)number600खुलने से पहले पॉइंटर के ट्रिगर पर टिकने, या फ़ोकस के उस पर रहने के मिलीसेकंड; Infinity: अपने आप कभी नहीं।
closeDelay.close_delay(..)number300बंद होने से पहले पॉइंटर के ट्रिगर और कार्ड से दूर रहने, और फ़ोकस के दोनों में न होने के मिलीसेकंड; Infinity: अपने आप कभी नहीं।
placement.placement(..)"top" | "bottom" | "left" | "right""bottom"ट्रिगर का वह किनारा जिस पर यह आता है।
align.align(..)"start" | "center" | "end""center"उस किनारे के साथ यह ट्रिगर से कैसे संरेखित होता है।
offset.offset(..)number0ट्रिगर और कार्ड के बीच की दूरी।
margin.margin(..)number0सेफ़ एरिया के किनारों से यह जो दूरी रखता है।
popupClass, popupStyle.popup_class(..), .popup_style(..)string, StyleProp<ViewStyle>noneपॉपअप का रूप, यानी वह कार्ड जिसमें कंटेंट है।
popupStartingClass, popupStartingStyle.popup_starting_class(..), .popup_starting_style(..)string, StyleProp<ViewStyle>noneपहले फ़्रेम में पॉपअप का रूप, उसके अपने रूप के ऊपर: उसके ट्रांज़िशन यहीं से चलते हैं।
popupEndingClass, popupEndingStyle.popup_ending_class(..), .popup_ending_style(..)string, StyleProp<ViewStyle>noneबंद होने के बाद पॉपअप का रूप, उसके अपने रूप के ऊपर: ट्रांज़िशन चलने तक यह बना रहता है।
trigger.trigger(..)JSX.Elementnoneजिसका यह प्रीव्यू दिखाता है; इसे उसी के सापेक्ष रखा जाता है।
children.child(..)JSX.Elementnoneकंटेंट, जो हर बार खुलने पर बनाया जाता है।

#Events

EventRustDescription
onOpenChange.on_open_change(|e| ..)यह कारण के साथ खुलने या बंद होने का अनुरोध करता है: "hover", "focus", "triggerPress", "pressOutside" या "cancel"।
onOpen.on_open(|| ..)यह दिखाया गया है।
onClose.on_close(|| ..)यह चला गया है, अपने exit के बाद; ऐप द्वारा बंद किए जाने पर भी।
onPosition.on_position(|e| ..)पॉपअप रखा गया: placement (वह किनारा जिस पर गया), bounds (उसका फ़्रेम), available (उस किनारे पर जगह)।

#Accessibility

हॉवर कार्ड अपने ट्रिगर में कुछ जोड़ता है: ट्रिगर अपनी भूमिका (role) और लेबल बनाए रखता है, और प्रेस करने पर वह जो खोलता या करता है वह वैसा ही रहता है। कीबोर्ड उपयोगकर्ताओं को कार्ड तब मिलता है जब फ़ोकस ट्रिगर पर टिकता है, और Escape उसे बंद कर देता है। कार्ड अपना कोई फ़ोकस नहीं लेता, इसलिए Tab पहले की तरह ट्रिगर से आगे बढ़ जाता है। कार्ड के लिंक और बटन कहीं और की तरह फ़ोकस लेते हैं, और उनमें से किसी के पास फ़ोकस रहने तक कार्ड खुला रहता है; कार्ड में जो ज़रूरी है वह उस पेज से भी पहुँच में होना चाहिए जिस पर ट्रिगर ले जाता है।