दस्तावेज़
Tailwind
NSH की स्टाइलिंग Tailwind CSS 4 का पालन करती है। Rust में, Tailwind की यूटिलिटीज़ उन्हीं नामों और मानों वाले टाइप्ड स्टाइल मेथड हैं (.p_4() है p-4, .rounded_2xl() है rounded-2xl, ZINC.shade(900) है zinc-900); TSX में, हर एलिमेंट class में सीधे क्लासेज़ स्वीकार करता है, जिन्हें Tailwind 4 की डिफ़ॉल्ट थीम के विरुद्ध नेटिव रूप से रिज़ॉल्व किया जाता है। नीचे हर उदाहरण में पहले टाइप्ड मेथड और फिर क्लासेज़ दिखाई गई हैं:
import { mount, Scroll, signal, Text, View } from "nsh";
function App() {
const count = signal(0);
const busy = signal(false);
const tab = signal("Day");
const tabs = ["Day", "Week", "Month"];
const buttons = () => (
<View class="flex-row flex-wrap gap-2">
<View
onPress={() => count.update((n) => n + 1)}
class="rounded-lg bg-lime-300 px-4 py-2 transition-colors duration-150 motion-reduce:transition-none hover:bg-lime-200 active:bg-lime-400 focus-visible:outline-2 focus-visible:outline-sky-500"
>
<Text class="font-medium text-lime-950">Pressed {count()}×</Text>
</View>
<View
onPress={() => busy.set(!busy())}
class="rounded-lg border border-black/10 bg-white px-4 py-2 shadow-sm transition motion-reduce:transition-none hover:bg-zinc-50 active:scale-95 active:bg-zinc-100 dark:border-[#2c2e33] dark:bg-[#24262a] dark:hover:bg-[#2a2c31] dark:active:bg-[#2d2f34] focus-visible:outline-2 focus-visible:outline-sky-500"
>
<Text class="font-medium text-[#15161a] dark:text-[#eceef1]">{busy() ? "Enable" : "Disable"}</Text>
</View>
<View disabled={busy()} onPress={() => count.set(0)} class="rounded-lg bg-emerald-400 px-4 py-2 enabled:hover:bg-emerald-300 active:bg-emerald-500 disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-sky-500">
<Text class="font-medium text-emerald-950">Reset</Text>
</View>
</View>
);
const tabsRow = () => (
<View class="flex-row rounded-xl border border-black/5 bg-[#ecece7] p-1 dark:border-[#1f2124] dark:bg-[#0c0d0f]">
<Each of={tabs}>
{(t) => (
<View
onPress={() => tab.set(t())}
class="flex-1 items-center rounded-lg py-2 transition-colors motion-reduce:transition-none active:opacity-70 focus-visible:outline-2 focus-visible:outline-sky-500"
classList={{ "bg-white shadow-sm dark:bg-[#2a2c31]": tab() === t(), "hover:bg-black/5 dark:hover:bg-white/5": tab() !== t() }}
>
<Text class="text-sm font-medium" classList={{ "text-[#15161a] dark:text-[#eceef1]": tab() === t(), "text-[#565a61] dark:text-[#8d9198]": tab() !== t() }}>
{t()}
</Text>
</View>
)}
</Each>
</View>
);
const gradient = () => (
<View class="overflow-hidden rounded-2xl bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400 p-5 shadow-xl shadow-lime-300/20">
<Text class="text-xl font-bold text-zinc-950">{tab()} view</Text>
<Text class="mt-1 text-sm text-zinc-950/70">bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400</Text>
</View>
);
const grid = () => (
<View class="grid grid-cols-3 gap-2">
<View class="col-span-2 h-14 items-center justify-center rounded-lg bg-sky-950">
<Text class="text-xs text-sky-200">col-span-2</Text>
</View>
<View class="h-14 rounded-lg bg-sky-900" />
<View class="h-14 rounded-lg bg-sky-800" />
<View class="row-span-2 rounded-lg bg-sky-400" />
<View class="h-14 rounded-lg bg-sky-700" />
<View class="h-14 rounded-lg bg-sky-600" />
<View class="h-14 rounded-lg bg-sky-500" />
</View>
);
const list = () => (
<View class="overflow-hidden rounded-xl border border-black/10 bg-white shadow-sm dark:border-[#2c2e33] dark:bg-[#18191c]">
<Each of={["Inbox", "Drafts", "Sent", "Archive"]}>
{(item) => (
<View class="flex-row items-center justify-between border-t border-black/5 px-4 py-3 first:border-t-0 even:bg-[#fafaf7] active:bg-lime-50 dark:border-[#1f2124] dark:even:bg-[#141518] dark:active:bg-[#24262a]">
<Text class="text-[#15161a] dark:text-[#eceef1]">{item()}</Text>
<Text class="text-[#8c9097] dark:text-[#5a5e65]">›</Text>
</View>
)}
</Each>
</View>
);
const motion = () => (
<View class="flex-row items-center justify-around py-2">
<View class="size-8 animate-spin motion-reduce:animate-none rounded-md bg-lime-300" />
<View class="size-8 animate-bounce motion-reduce:animate-none rounded-full bg-sky-400" />
<View class="h-8 w-14 animate-pulse motion-reduce:animate-none rounded-md bg-[#e1e1db] dark:bg-[#2c2e33]" />
</View>
);
return (
<Scroll class="flex-1 bg-[#f3f3ef] dark:bg-[#101113]">
<View class="gap-5 p-5 pt-6">
<Text class="text-2xl font-bold tracking-tight text-[#15161a] dark:text-[#eceef1]">Tailwind classes</Text>
{/* Two columns from `md:` on (768 points): breakpoints follow the window. */}
<View class="gap-5 md:flex-row md:items-start">
<View class="gap-5 md:min-w-0 md:flex-1">
{buttons()}
{tabsRow()}
{gradient()}
</View>
<View class="gap-5 md:min-w-0 md:flex-1">
{grid()}
{list()}
{motion()}
</View>
</View>
</View>
</Scroll>
);
}
mount(App);use nsh_core::element::palette::ZINC;
view()
.flex_row()
.items_center()
.gap_3()
.rounded_2xl()
.bg(AdaptiveColor::new(white(), ZINC.shade(800)))
.p_4()
.shadow_lg()
.active(|s| s.scale(0.98))
.child(
text("Tailwind on native views")
.text_base()
.font_weight(FontWeight::SEMIBOLD)
.text_color(AdaptiveColor::new(ZINC.shade(900), white())),
)<View class="flex-row items-center gap-3 rounded-2xl bg-white p-4 shadow-lg active:scale-[0.98] dark:bg-zinc-800">
<Text class="text-base font-semibold text-zinc-900 dark:text-white">Tailwind on native views</Text>
</View>टाइप्ड मेथड उन्हीं स्टाइल नियमों में लोअर होते हैं जिनमें क्लासेज़ कंपाइल होती हैं, और पार्स करने के लिए कोई स्ट्रिंग नहीं होती। क्लासेज़ के लिए कोई CSS जनरेट नहीं होता और JavaScript में कुछ नहीं चलता: स्ट्रिंग कोर को दी जाती है, जो हर अलग क्लास स्ट्रिंग को एक बार पार्स करता है (और कैश करता है), उसे स्टाइल नियमों में बदलता है और लागू करता है। जो वेरिएंट स्थिति पर निर्भर हैं (pressed, dark mode, विंडो की चौड़ाई), उन्हें स्थिति बदलने पर कोर फिर से लागू करता है। pressed और focused स्थितियाँ नेटिव रूप से ट्रैक होती हैं, इसलिए active: फ़ीडबैक (Rust में .active(..)) के लिए आपके कोड तक कोई राउंड ट्रिप नहीं होता।
क्लासेज़ और style prop एक जैसी नेटिव स्टाइल बनाते हैं और साथ में इस्तेमाल किए जा सकते हैं; यह पेज केवल क्लासेज़ और उनके टाइप्ड समकक्षों की विशिष्टताएँ बताता है। Tailwind रेफ़रेंस में समर्थित हर यूटिलिटी सूचीबद्ध है।
#class और classList
view()
.rounded_lg()
.px_4()
.py_2()
.transition_colors()
.bg(move || if selected.get() { INDIGO.shade(600) } else { ZINC.shade(200) })
.style_when(busy, |s| s.opacity(0.5))<View
class="rounded-lg px-4 py-2 transition-colors"
classList={{ "bg-indigo-600": selected(), "bg-zinc-200": !selected(), "opacity-50": busy() }}
/>classक्लासेज़ की एक स्ट्रिंग है। यह डायनामिक हो सकती है:class={`h-6 rounded bg-${color()}-500`}काम करती है, क्योंकि क्लासेज़ रन टाइम पर रिज़ॉल्व होती हैं।classListक्लासेज़ को शर्तों से मैप करता है: जब तक किसी क्लास का मान truthy है, वह लागू रहती है। यहclassके बाद लागू होता है।- Rust में, क्लोज़र या सिग्नल दिया गया मेथड उसका अनुसरण करता है (
.bg(move || ..)),.style_when(condition, |s| ..)शर्त के बने रहने तक स्टाइल लागू करता है, और.when(condition, |v| ..)एलिमेंट बनते समय एक बार तय करता है।
टकराव कैसे सुलझते हैं:
- बाद की क्लासेज़ जीतती हैं उसी स्ट्रिंग की पहले वाली क्लासेज़ पर:
"p-2 p-4"का अर्थp-4है।tailwind-mergeकी कोई ज़रूरत नहीं: कंपोनेंट वहclassजोड़ सकता है जो उसका कॉलर देता है। Rust में भी बाद के मेथड जीतते हैं। - वेरिएंट क्लासेज़ सादी क्लासेज़ के बाद लागू होती हैं, और स्ट्रिंग में उनकी स्थिति चाहे जो हो, वे Tailwind के वेरिएंट क्रम में लगती हैं (
hover:active:से पहले,max-*:में सबसे चौड़ा पहले, ब्रेकपॉइंट बढ़ते क्रम में,dark:सबसे अंत में)। Tailwind की तरह ही, पहले क्लास का सबसे ऊँचा वेरिएंट तय करता है, फिर उसका अगला सबसे ऊँचा, और जिस क्लास में एक वेरिएंट अधिक हो वह बाद में आती है:dark:hover:bg-a dark:bg-bडार्क मोड में होवर होने परbg-aहै, औरmax-md:p-2 max-lg:p-4mdसे नीचेp-2है। CSS की तरह,odd:bg-a last:bg-bविषम अंतिम पंक्ति कोbg-aदेता है, क्योंकि Tailwindoddकोlastके बाद क्रमबद्ध करता है। styleक्लासेज़ पर जीतता है। स्टाइल कुंजी अपने नाम की CSS प्रॉपर्टी होती है, वही जिसे क्लासेज़ सेट करती हैं, इसलिए वह केवल उसी प्रॉपर्टी को बदलती है:style={{ paddingLeft: 8 }}pl-4को बदलता है औरpr-4को बनाए रखता है।!किसी क्लास को important बनाता है:!p-0।
Rust कंपोनेंट (nsh/base के और ऐप के अपने crate के) TSX से भी class और classList स्वीकार करते हैं; आपकी क्लासेज़ कंपोनेंट की अपनी स्टाइल के बाद मर्ज होती हैं और बराबर रैंक पर जीतती हैं।
#Variants
| वेरिएंट | कब लागू होता है | Rust |
|---|---|---|
active: | व्यू दबा हुआ है | .active(|s| ..) |
hover: | पॉइंटर व्यू के ऊपर है (iPad और Mac के पॉइंटर) | .hover(|s| ..) |
focus:, focus-visible: | इनपुट पर फ़ोकस है | .focus(|s| ..) |
focus-within: | व्यू या उसके किसी डिसेंडेंट पर फ़ोकस है (Overlay की सामग्री उस व्यू का डिसेंडेंट है जिसमें वह है) | .focus_within(..) |
disabled:, enabled: | एलिमेंट का disabled prop true / false है | .disabled_style(|s| ..) |
selected: | एलिमेंट चयनित है (Button का selected, accessibilityState.selected) | .selected_style(..) |
checked: | एलिमेंट चेक्ड है (accessibilityState.checked है true; "mixed" चेक्ड नहीं है) | .checked_style(..) |
indeterminate: | एलिमेंट indeterminate है (accessibilityState.checked है "mixed") | .indeterminate_style(..) |
aria-expanded: | एलिमेंट विस्तारित है (accessibilityState.expanded है true): Popover या Menu का ट्रिगर, जब उसका पॉपअप खुला हो | .expanded_style(..) |
aria-pressed: | एलिमेंट एक टॉगल बटन है जो ऑन है (accessibilityState.pressed है true; "mixed" दबा हुआ नहीं है)। यह active: नहीं है, जो चल रहा प्रेस है | .aria_pressed(..), .pressed_style(..) |
aria-busy:, aria-checked:, aria-selected: | accessibilityState.busy, checked (true, "mixed" नहीं) या selected true है, जैसे Tailwind के aria-* वेरिएंट: pseudo-classes के बाद, उनके नामों के क्रम में | |
data-<name>: | एलिमेंट में data-* एट्रिब्यूट name है, जैसे Tailwind के बूलियन data-* वेरिएंट (&[data-range-start]): कंपोनेंट का पार्ट अपनी स्थितियों के अलावा जो दिखाता है, जैसे कैलेंडर का रेंज की शुरुआत वाला दिन (data-range-start:)। aria-* वेरिएंट के बाद, उनके नामों के क्रम में | Interactive::project_data(name, ..) कंपोनेंट के पार्ट पर एक सेट करता है |
group-active:, group-hover:, group-focus:, group-focus-within:, group-disabled:, group-selected:, group-checked:, group-indeterminate:, group-aria-expanded:, group-aria-pressed:, group-aria-busy:, group-aria-checked:, group-aria-selected:, group-data-<name>: | निकटतम पूर्वज, जिसमें group क्लास है, उस स्थिति में है, या उसमें वह data-* एट्रिब्यूट है | पूर्वज पर .group(); अंदर .group_active(..), .group_hover(..), .group_focus_within(..) |
group-active/name: … | यही, group/name से चिह्नित पूर्वज के लिए | .group_named("name"); .group_active_named("name", ..), … |
first:, last:, odd:, even:, only: | एलिमेंट की अपने सिबलिंग्स के बीच अपनी स्थिति। किसी पंक्ति के लेबल को पंक्ति की स्थिति के अनुसार रंगने के लिए last:text-* पंक्ति पर लगाएँ: लेबल उसे इनहेरिट करता है | पंक्ति के इंडेक्स से सादा Rust (at.is_first(), at.is_last(), at.index() % 2) |
placeholder: | इनपुट के प्लेसहोल्डर को स्टाइल करता है (placeholder:text-zinc-400) | .placeholder_color(..) |
selection: | इनपुट या चयन-योग्य टेक्स्ट, और एलिमेंट के भीतर मौजूद ऐसे एलिमेंट्स, के सिलेक्शन हाइलाइट को रंगता है (selection:bg-violet-200) | .selection_color(..) |
dark: | सिस्टम अपीयरेंस डार्क है | एक AdaptiveColor, या .dark(|s| ..) |
sm:, md:, lg:, xl:, 2xl: | विंडो कम से कम 640, 768, 1024, 1280, 1536 पॉइंट चौड़ी है | .style_when(|| window().width >= px(768.0), |s| ..) |
max-sm:, max-md:, … | विंडो ब्रेकपॉइंट से संकरी है | वही, < के साथ |
min-[600px]:, max-[600px]: | मनमाने ब्रेकपॉइंट | वही |
portrait:, landscape: | विंडो का ओरिएंटेशन | window().width > window().height |
ios:, android:, web: | प्लेटफ़ॉर्म | .when(os() == Os::Ios, |v| ..) |
rtl:, ltr: | लेआउट दिशा | व्यू पर .direction(LayoutDirection::..) |
motion-safe:, motion-reduce: | उपयोगकर्ता की reduce motion सेटिंग | reduce_motion() |
वेरिएंट आपस में जुड़ते हैं: dark:active:bg-zinc-700, md:landscape:flex-row। Rust में, .active(..) स्टाइल में AdaptiveColor का अर्थ dark:active: है।
view()
.group()
.flex_row()
.items_center()
.gap_3()
.rounded_xl()
.bg(white())
.p_3()
.active(|s| s.bg(ZINC.shade(50)))
.child(text("Settings").flex_1().text_color(ZINC.shade(900)))
.child(
text("›")
.text_color(ZINC.shade(300))
.transition_transform()
.group_active(|s| s.translate_x(px(4.0))),
)<View class="group flex-row items-center gap-3 rounded-xl bg-white p-3 active:bg-zinc-50">
<Text class="flex-1 text-zinc-900">Settings</Text>
<Text class="text-zinc-300 transition-transform group-active:translate-x-1">›</Text>
</View>किसी भी एलिमेंट पर disabled (Rust में .disabled(..)) disabled: क्लासेज़ (.disabled_style(..)) लागू करता है और जो माउंट किया गया है उससे उसके प्रेस हैंडलर हटा देता है, इसलिए वह कोई प्रेस फ़ीडबैक भी नहीं दिखाता:
view()
.disabled(saving)
.on_press(save)
.rounded_lg()
.bg(EMERALD.shade(500))
.px_4()
.py_2()
.active(|s| s.bg(EMERALD.shade(600)))
.disabled_style(|s| s.opacity(0.4))
.child(text("Save").font_weight(FontWeight::MEDIUM).text_color(white()))<View disabled={saving()} onPress={save} class="rounded-lg bg-emerald-500 px-4 py-2 active:bg-emerald-600 disabled:opacity-40">
<Text class="font-medium text-white">Save</Text>
</View>ब्रेकपॉइंट विंडो (वह सतह जिस पर NSH रेंडर करता है) से तुलना करते हैं, डिवाइस से नहीं: iPad के स्प्लिट व्यू या आकार बदलने योग्य विंडो में वे विंडो की चौड़ाई का अनुसरण करते हैं, जैसा window() करता है।
#वेब पर Tailwind से क्या अलग है
- इकाइयाँ पॉइंट हैं।
p-4(.p_4()) 16 पॉइंट है (1 स्पेसिंग स्टेप = 4pt, 1rem = 16pt)।pxमें मनमाने मान भी पॉइंट हैं:p-[13px](.p(px(13.0))) 13pt है। flexएक पंक्ति है, जैसा CSS में होता है:class="flex"अपने चाइल्ड को एक पंक्ति में रखता है, जब तक कोई दिशा क्लास कुछ और न कहे (स्ट्रिंग में कहीं भी:flex-col flexएक कॉलम है)। किसी स्टाइल काdisplay: "flex"और Rust का.flex()भी इसी तरह पंक्तियाँ हैं, और जो व्यू कोई display सेट नहीं करता वह NSH का डिफ़ॉल्ट कॉलम रखता है।blockऔरflow-rootब्लॉक लेआउट इस्तेमाल करते हैं, औरinlineतथाinline-blockभी, जैसे ब्राउज़र उन्हें flex या grid पेरेंट में लेआउट करता है। Rust में.flex_row()और.flex_col()दिशा सेट करते हैं।- व्यू पर टेक्स्ट क्लासेज़ इनहेरिट होती हैं।
text-*,font-*,leading-*,tracking-*और व्यू पर अन्य टेक्स्ट यूटिलिटीज़ (Rust में.text_color(..),.font_weight(..), …) उसके अंदर के टेक्स्ट और इनपुट एलिमेंट्स तक पहुँचती हैं, जैसे CSS इनहेरिटेंस करती है:<View class="text-white">उसके लेबल सफ़ेद कर देता है। - हर व्यू अपना एक बॉक्स है।
overflow-x-hiddenऔरoverflow-y-hiddenदोनों अक्षों पर क्लिप करते हैं, जैसाoverflow-hiddenकरता है (स्क्रॉलिंग scroll एलिमेंट का काम है);invisibleव्यू और उसके अंदर की हर चीज़ को छिपा देता है;mix-blend-*व्यू को उसके पीछे बनी हर चीज़ के साथ ब्लेंड करता है;blockबॉक्स अपने मार्जिन और अपने चाइल्ड के मार्जिन को अलग रखते हैं;absoluteव्यू अपने निकटतम पोज़िशन्ड पूर्वज में रखा जाता है, जैसा CSS में होता है;fixedव्यू को उसके रूट व्यू में लेआउट करता है (अन्य एलिमेंटabsoluteकी तरह रखे जाते हैं)। - टेक्स्ट अपनी व्हाइटस्पेस बनाए रखता है। स्ट्रिंग में स्पेस और लाइन ब्रेक जैसे लिखे हैं वैसे ही बनते हैं, और
whitespace-*रैपिंग तय करता है; JSX कंपाइलर लिटरल JSX टेक्स्ट की व्हाइटस्पेस को संकुचित करता है, जैसा JSX करता है। - ट्रांज़िशन प्रॉपर्टी समूहों को एनिमेट करते हैं:
transitionरंग, opacity, शैडो और ट्रांसफ़ॉर्म को एनिमेट करता है,transition-allकॉर्नर रेडियस और बॉर्डर की चौड़ाई को भी; फ़िल्टर, बैकड्रॉप फ़िल्टर, ग्रेडिएंट और लेआउट अपने नए मान तुरंत ले लेते हैं। focus-visible:हैfocus:: यह तब भी लागू होता है जब एलिमेंट पर फ़ोकस हो।- स्थिति के बैकग्राउंड ग्रेडिएंट के ऊपर दिखते हैं। CSS में ग्रेडिएंट
background-colorको ढक लेता है, इसलिएbg-linear-*सतह परactive:bg-*कभी दिखाई नहीं देता; NSH मेंactive:,hover:,focus:याgroup-*:से आया बैकग्राउंड रंग उस स्थिति के बने रहने तक ग्रेडिएंट की जगह ले लेता है (जब तक स्थिति अपना ग्रेडिएंट सेट न करे, जैसेactive:from-*)।activeStyleऔर अन्य स्थिति स्टाइल, तथा एनिमेटेड बैकग्राउंड रंग भी ऐसे ही हैं। - टेक्स्ट और टच सेटिंग्स स्टाइल कुंजियाँ हैं:
line-clamp-*numberOfLinesसेट करता है,text-ellipsisऔरtext-cliptextOverflowसेट करते हैं,truncateदोनों,select-*userSelectसेट करता है औरpointer-events-*pointerEventsसेट करता है, इसलिए वे स्थिति वेरिएंट में काम करती हैं और हर क्लास की तरह इनलाइनstyleसे हार जाती हैं;caret-*,placeholder:text-*औरselection:bg-*इसी तरहcaretColor,placeholderColorऔरselectionColorस्टाइल सेट करते हैं। Rust में येStyledमेथड हैं (.number_of_lines(..),.truncate(),.text_overflow(..),.select_text(),.pointer_events(..), …)।whitespace-nowrapटेक्स्ट को क्लास के रूप में एक पंक्ति में रखता है।object-*इमेज और वीडियो कीobjectFitऔरobjectPositionस्टाइल सेट करते हैं, जिन्हें स्टाइल की कुंजियाँ किसी अन्य की तरह बदल देती हैं। - समर्थित सेट: Tailwind 4 की वे यूटिलिटीज़ और वेरिएंट जिनका नेटिव अर्थ है।
peer-*वेरिएंट, pseudo-elements (before:,after:) और मनमानी प्रॉपर्टीज़ ([mask-type:luminance]) अज्ञात क्लासेज़ के रूप में रिपोर्ट होते हैं, और हर वह यूटिलिटी भी जिसका NSH में कोई नेटिव अर्थ नहीं है, ताकि कोई क्लास कभी ब्राउज़र से अलग अर्थ न रखे। रेंडरिंग संकेत (antialiased,will-change-transform,appearance-none,box-border, …) स्वीकार किए जाते हैं और कुछ नहीं बदलते।
बाकी सब कुछ वैसे ही काम करता है जैसा Tailwind 4 में दस्तावेज़ित है: रंग पैलेट, opacity मॉडिफ़ायर (bg-black/40), मनमाने मान (bg-[#34c759], w-[72px], grid-cols-[1fr_2fr]; p, px, py, pt, pr, pb, pl और m यूटिलिटीज़ पर एनवायरनमेंट वेरिएबल भी, pt-[env(safe-area-inset-top)], pb-[calc(env(keyboard-inset-height)+49px)], देखें Safe area और Keyboard), भिन्न (w-1/2, translate-x-1/2), शैडो और रिंग, ग्रेडिएंट (bg-linear-to-r, bg-radial, bg-conic के साथ from- / via- / to-), फ़िल्टर, backdrop-blur-*, mix-blend-*, ट्रांसफ़ॉर्म, transition-* और duration-*, animate-spin / ping / pulse / bounce, और grid, हर एक Rust में अपने टाइप्ड मेथड के साथ। फ़िल्टर और ब्लेंडिंग की प्लेटफ़ॉर्म सीमाएँ क्लासेज़ पर भी लागू होती हैं।
#Theme
क्लासेज़ Tailwind 4 की डिफ़ॉल्ट थीम के विरुद्ध रिज़ॉल्व होती हैं। इसे nsh.json में "tailwind" के अंतर्गत बढ़ाएँ; आपकी प्रविष्टियाँ डिफ़ॉल्ट में जुड़ जाती हैं (या उसी नाम के डिफ़ॉल्ट को बदल देती हैं):
{
"tailwind": {
"colors": {
"brand": { "50": "#f5f3ff", "500": "#7c3aed", "600": "#6d28d9", "DEFAULT": "#7c3aed" },
"ink": "#1f2328"
},
"radius": { "card": 14 },
"fontFamily": { "display": "Pacifico" },
"breakpoints": { "tablet": 600 }
}
}
इस थीम के साथ bg-brand-500, text-brand (DEFAULT शेड), border-ink, rounded-card, font-display और tablet:flex-row मान्य क्लासेज़ हैं।
| कुंजी | मान | क्या बनाता है |
|---|---|---|
colors | एक CSS रंग, या शेड्स का ऑब्जेक्ट (सादे नाम के लिए "DEFAULT") | bg-*, text-*, border-*, from-*, ring-*, … |
spacing | प्रति स्पेसिंग स्टेप पॉइंट (डिफ़ॉल्ट 4) | p-*, m-*, gap-*, w-*, … |
rem | मनमाने मानों में प्रति rem पॉइंट (डिफ़ॉल्ट 16) | |
breakpoints | पॉइंट | name: वेरिएंट |
container | पॉइंट | max-w-* साइज़ |
fontSize | पॉइंट, या [size, lineHeight] जिसमें लाइन की ऊँचाई आकार का गुणज हो | text-* |
fontWeight | 1–1000 | font-* |
fontFamily | फ़ॉन्ट फ़ैमिली का नाम (सिस्टम फ़ैमिली या इम्पोर्ट किए गए फ़ॉन्ट का नाम) | font-* |
tracking | Ems | tracking-* |
leading | फ़ॉन्ट साइज़ के गुणज | leading-* |
radius | पॉइंट | rounded-* |
shadow, insetShadow, dropShadow | एक CSS box-shadow | shadow-*, inset-shadow-*, drop-shadow-* |
blur | पॉइंट | blur-*, backdrop-blur-* |
perspective | पॉइंट | perspective-* |
aspect | एक अनुपात (1.5) | aspect-* |
ease | [x1, y1, x2, y2] | ease-* |
अज्ञात कुंजियाँ और अमान्य मान बिल्ड एरर हैं (nsh.json: tailwind: unknown theme key 'colours')। थीम बंडल में कंपाइल होती है और पहले फ़्रेम से पहले लागू हो जाती है।
Tailwind थीम आपकी क्लासेज़ को आकार देती है। टाइप्ड मेथड Tailwind के डिफ़ॉल्ट स्केल बनाए रखते हैं, ताकि Rust कंपोनेंट हर ऐप में एक जैसा दिखे, और ऐप का लुक अपनी थीम के डिज़ाइन टोकन से लेते हैं, जो nsh.json में "theme" है (Rust)।
#थीम टोकन
ऐप की थीम ("theme", nsh.json में) उसके डिज़ाइन टोकन रखती है: shadcn/ui के रंग (background, foreground, primary, muted-foreground, border, ring, chart-1, sidebar, …), उसका radius, उसके फ़ॉन्ट font-sans और font-mono, और ऐप द्वारा जोड़ा गया कोई भी टोकन। क्लासें इन्हें Tailwind नामों से इस्तेमाल करती हैं, जैसे CSS @theme के वेरिएबल नाम होते हैं; Rust कंपोनेंट इन्हें nsh_base से theme() के ज़रिए पढ़ते हैं, और TSX इन्हें nsh से token() के ज़रिए पढ़ता है, इसलिए TSX स्क्रीन और Rust कंपोनेंट एक ही थीम का पालन करते हैं:
use nsh_base::theme;
view()
.rounded(move || theme().radius)
.border_1()
.border_color(move || theme().border)
.bg(move || theme().card)
.p_4()
.child(text("Balance").text_color(move || theme().card_foreground))
.child(text("Updated just now").text_color(move || theme().muted_foreground))
.child(
view()
.rounded(move || theme().radius)
.bg(move || theme().primary)
.px_4()
.py_2()
.active(|s| s.bg(move || theme().primary.opacity(0.9)))
.child(text("Continue").text_color(move || theme().primary_foreground)),
)<View class="rounded-(--radius) border border-border bg-card p-4">
<Text class="text-card-foreground">Balance</Text>
<Text class="text-muted-foreground">Updated just now</Text>
<View class="rounded-(--radius) bg-primary px-4 py-2 active:bg-primary/90">
<Text class="text-primary-foreground">Continue</Text>
</View>
</View>टोकन का नाम उसके CSS वेरिएबल का नाम होता है, -- के बिना, जैसा थीम फ़ाइल उसे लिखती है: muted-foreground, radius, chart-1। Rust में shadcn के टोकन theme() के मेथड हैं (primary(), muted_foreground(), radius(), font_mono()), और हर टोकन theme().color("up"), theme().number("radius-card") या theme().font("font-display") होता है।
| रूप | क्या लेता है | उदाहरण |
|---|---|---|
| किसी भी रंग उपयोगिता में रंग का नाम | थीम का रंग, ओपेसिटी के साथ भी | bg-primary, text-muted-foreground/70, bg-up/30, border-t-border, ring-ring, from-accent, placeholder:text-muted-foreground |
(--name), Tailwind 4 का वेरिएबल शॉर्टहैंड | थीम का रंग या संख्या | bg-(--primary), rounded-(--radius), p-(--gap-card) |
(color:--name), (length:--name), (number:--name), (family-name:--name) | वही, टाइप के साथ, जहाँ कोई उपयोगिता कई प्रकार लेती है | text-(color:--up), text-(length:--text-title), font-(family-name:--font-mono) |
font-sans, font-mono | थीम के फ़ॉन्ट, जब वह उन्हें सेट करती है | font-mono |
- आपके अपने टोकन। थीम फ़ाइल का हर नाम एक टोकन है, ठीक वैसे ही जैसे CSS
@themeका हर वेरिएबल:"up": "#16a34a"सेbg-up,text-upऔरbg-up/30बनते हैं;"radius-card": "14px"सेrounded-(--radius-card)बनता है;"font-display": "Pacifico"सेfont-(family-name:--font-display)बनता है। लंबाइयाँ पॉइंट में होती हैं;(number:--name)सादी संख्या देता है। - फ़ॉन्ट।
font-sansऐप का फ़ॉन्ट है: जो भी टेक्स्ट अपना कोई फ़ैमिली सेट नहीं करता, वह इसी से बनाया जाता है, जैसे किसी पेज पर Tailwind का--font-sansलागू होता है।font-monofont-monoका औरTextViewमें कोड का फ़ॉन्ट है। सेट न होने पर दोनों सिस्टम के फ़ॉन्ट होते हैं। - दोनों अपीयरेंस। टोकन के पास थीम का लाइट और डार्क दोनों मान होते हैं:
bg-primary,theme().primaryऔरtoken("primary")अपीयरेंस के साथ अपने आप बदल जाते हैं, इसके लिए किसीdark:क्लास की ज़रूरत नहीं। जो रंग थीम केवल एक अपीयरेंस में सेट करती है, वह दोनों में इस्तेमाल होता है। - आपके Tailwind रंग प्राथमिकता पाते हैं।
"tailwind"colorsया Tailwind पैलेट का कोई नाम वही रंग होता है, इसलिएbg-primaryतभी थीम टोकन है जब Tailwind थीम मेंprimaryनाम का कोई रंग न हो। - ऐप चलते समय थीम बदल सकती है। जब नेटिव ऐप में एम्बेड किए गए ऐप को उस ऐप की थीम दी जाती है, और जब भी थीम बदलती है, तब टोकन नाम वाली क्लासें,
theme()पढ़ने वाली Rust बाइंडिंग औरtoken()पढ़ने वाला TSX कोड अपडेट हो जाते हैं, क्लासें और बाइंडिंग नेटिव रूप से। एम्बेड करने वाले ऐप की थीम में जो टोकन नहीं होते, वे ऐप के अपने"theme"के मान (फिर डिफ़ॉल्ट थीम के मान) रखते हैं। जो होस्ट केवल अपनी ब्रांडिंग जोड़ता है, वह थीम ओवरराइड देता है, यानी वे टोकन जिन्हें उसकी ब्रांडिंग उसी फ़ाइल फ़ॉर्मैट में बदलती है (iOS, Android, डेस्कटॉप परAppView::set_theme_overrides)। - अन्य क्लासों की तरह जाँचे जाते हैं। जो नाम डिफ़ॉल्ट थीम का या ऐप के
"theme"का टोकन नहीं है, वह बिल्ड के समय अज्ञात क्लास है (unknown class 'bg-chart-up')। ऐसा टोकन इस्तेमाल करने के लिए जो केवल एम्बेड करने वाला ऐप देता है, उसे ऐप के"theme"में भी एक मान दें: ऐप के अकेले चलने पर वही मान दिखता है।
टोकन नाम वाली क्लासें हर थीम के लिए एक बार हल की जाती हैं, किसी भी अन्य क्लास की तरह: इन्हें ड्रॉ करने की लागत उतनी ही है जितनी पैलेट के रंग की।
#TSX में token()
nsh से token(name) दिखाए गए अपीयरेंस में टोकन का मान देता है: रंग #rrggbbaa के रूप में, लंबाई पॉइंट की संख्या के रूप में, फ़ॉन्ट उसके फ़ैमिली नाम के रूप में। यह रिऐक्टिव है, इसलिए जो कोड इसे किसी कंपोनेंट, मेमो या इफ़ेक्ट में पढ़ता है, वह थीम या अपीयरेंस बदलने पर फिर चलता है। यह उन मानों के लिए उपयुक्त है जो वहाँ जाते हैं जहाँ कोई क्लास नहीं जाती, जैसे कैनवास या चार्ट:
import { Canvas, token } from "nsh";
<Canvas
style={{ height: 160 }}
draw={(cx, { width, height }) => {
cx.beginPath();
cx.moveTo(0, height);
cx.lineTo(width, 0);
cx.strokeStyle = token("chart-1");
cx.lineWidth = 2;
cx.stroke();
}}
/>
नाम टाइप किए हुए हैं: NSH ऐप के टोकन उसकी थीम से .nsh/types/nsh/tokens.d.ts में लिखता है, इसलिए थीम में "up" होने पर token("up") एक string होता है, और थीम में जो नाम नहीं है, वह टाइप एरर है।
#क्लास जाँच
nsh build, nsh dev, nsh run और nsh test आपके JSX में स्टैटिक क्लास नामों (class में स्ट्रिंग लिटरल और classList की कुंजियाँ) को उसी पार्सर और थीम से जाँचते हैं जो रनटाइम इस्तेमाल करता है, और जो क्लासें मौजूद नहीं हैं उनके बारे में चेतावनी देते हैं:
warn nsh::build App.tsx:12:36: unknown class `bg-brand-550`
चेतावनियों से बिल्ड फ़ेल नहीं होता। रन टाइम पर बनी क्लासें (bg-${color()}-500) तब जाँची जाती हैं जब रनटाइम उन्हें पहली बार देखता है, और वह हर अज्ञात क्लास को एक बार लॉग करता है (unknown classes: ...)। टाइप किए गए मेथड Rust कंपाइलर जाँचता है।
#क्लासें या स्टाइल?
दोनों नेटिव रूप से हल होते हैं और लगभग समान लागत वाले हैं। क्लासें छोटी होती हैं और बिना कोड के वेरिएंट (प्रेस्ड स्टेट, डार्क मोड, ब्रेकपॉइंट) देती हैं। इनलाइन स्टाइल JavaScript में गणना किए गए मान लेते हैं और नेटिव एनिमेटेड मान (opacity: fade, translate: [x, 0]) स्वीकार करते हैं। अधिकांश TSX ऐप स्थिर रूप के लिए क्लासें और गणना किए गए या एनिमेटेड मानों के लिए style इस्तेमाल करते हैं। Rust कंपोनेंट दोनों के लिए टाइप किए गए मेथड इस्तेमाल करते हैं, और एनिमेटेड मानों को .bind_opacity(..), .bind_translate_x(..) और अन्य बाइंडिंग से बाइंड करते हैं।
#संबंधित
- Styling: स्टाइल प्रॉप, टाइप किए गए मेथड और इफ़ेक्ट की प्लेटफ़ॉर्म सीमाएँ।
- Tailwind संदर्भ: हर उपयोगिता और वेरिएंट।
- Layout: फ़्लेक्सबॉक्स और ग्रिड, जो क्लासों के रूप में भी उपलब्ध हैं।