ری ایکٹ مقامی ایپلی کیشن میں shadcn/ui کا استعمال کیسے کریں۔ [Full Handbook]

اگر آپ نے کچھ React Native ایپس بنائی ہیں، تو درج ذیل آپ کے لیے واقف ہوں گے۔ تم ہو colors.ts فائلیں اور سادہ Button عنصر پہلے تو سب کچھ ٹھیک لگتا ہے۔ پھر ڈیزائن میں چند بار تبدیلی آتی ہے اور چھوٹے رنگ کے ڈھیر بڑھنے لگتے ہیں۔ آپ کا بٹن ہر بار جب کوئی تھوڑا مختلف انداز کی درخواست کرے گا تو ایک مختلف سہارا منتخب کرے گا۔

سیٹ اپ پہلے کام کر سکتا ہے، لیکن کچھ ڈیزائن تبدیلیوں کے بعد، یہاں تک کہ چھوٹے اپ ڈیٹس کا مطلب پورے پروجیکٹ میں متعدد فائلوں کو چیک کرنا ہو سکتا ہے۔

یہاں یہ ہے کہ ایک عام رنگ کی فائل ایک لمحے میں کیسی دکھتی ہے:

export const colors = {
  primary: '#6e4836',
  primaryPressed: '#5a3a2b',
  textPrimary: '#261d16',
  textSecondary: '#72665e',
  // and forty more
};

اور یہاں ایک عام ہے۔ Button یہ کچھ دوبارہ ڈیزائن کرنے کے بعد کیسا لگتا ہے:

یہ ایک آسان مثال ہے، لیکن آپ کو خیال آتا ہے۔ رنگین اشیاء بڑھتی رہتی ہیں اور بٹن ہر نئی ڈیزائن کی درخواست کو سنبھالنے کے لیے مزید پرپس جمع کرتا ہے۔

ہر تبدیلی معقول معلوم ہوتی ہے۔ لیکن پریشان کن حصہ بعد میں آتا ہے۔ ایک ڈیزائن کے فیصلے کو تبدیل کرتے وقت، آپ کو متعدد فائلوں کو چیک کرنے کی ضرورت ہے تاکہ یہ یقینی بنایا جا سکے کہ کچھ بھی غیر متوقع طور پر تبدیل نہیں ہوا ہے۔

مسئلہ کا ایک حصہ یہ ہے کہ کتنے اجزاء کی لائبریریاں تقسیم کی جاتی ہیں۔ ان کا کوڈ ہے node_modulesلہذا آپ اسے پروپس اور اختیارات کے ذریعے اپنی مرضی کے مطابق بناتے ہیں جو عام طور پر سامنے آتے ہیں۔ اگر لائبریری آپ کے استعمال کے عین مطابق کیس کا احاطہ نہیں کرتی ہے، تو دوسرا سہارا شامل کریں یا اسے اوور رائیڈ کریں۔ اگر آپ ایسا کرتے رہتے ہیں، تو جز کو برقرار رکھنا اصل مسئلہ سے زیادہ مشکل ہو سکتا ہے جسے آپ حل کرنے کی کوشش کر رہے تھے۔

یہ ان وجوہات میں سے ایک ہے جو مجھے shadcn/ui کا نقطہ نظر پسند ہے۔ موجودہ اجزاء کے پیکیجز کو انسٹال نہیں کرتا ہے اور ان اجزاء کو پن کے طور پر مانتا ہے۔ CLI آپ کے پروجیکٹ میں سورس کوڈ کو کاپی کرتا ہے تاکہ آپ ضرورت کے مطابق تبدیلیاں کر سکیں۔ React Native کے لیے، اسی خیال پر ایک پروجیکٹ بنایا گیا ہے جسے React Native Reusables کہتے ہیں۔

اسے عملی بنانے کے لیے، ہم React Native Reusables، Expo، اور Uniwind استعمال کریں گے۔ کافی شاپ پر پک اپ آرڈر کے لیے کارٹ اور ادائیگی کی سکرین. اس میں مقدار کے کنٹرول کے ساتھ آرڈر کا اندراج، پک اپ ٹائم ڈراپ ڈاؤن، ایک نام کا فیلڈ، ایک "آپ کا کپ” ٹوگل، بلنگ کا خلاصہ، نیچے ادائیگی کا بار، اور آرڈر کی تصدیق کا ڈائیلاگ شامل ہے۔ آپ یہ دیکھنے کے لیے shadcn ٹیمپلیٹ کو بھی براؤز کر سکتے ہیں کہ مجموعی انٹرفیس میں دوبارہ قابل استعمال حصے کیسے ظاہر ہوتے ہیں۔

ہم کچھ ایسی چیزوں کی بھی نشاندہی کریں گے جو ان اجزاء کے ساتھ کام کرتے وقت یاد کرنا آسان ہوسکتی ہیں، بشمول:

  • shadcn/ui اور اس کا CLI کیسے کام کرتا ہے اور آپ جزو کوڈ کے مالک کیوں ہیں۔
  • ڈیزائن ٹوکن (نام کے رنگ) رنگوں کی جگہ کیسے لیتے ہیں۔ colors.ts فائل
  • React Native میں اجزاء جیسے کارڈ، سلیکٹ، سوئچ، الرٹ ڈائیلاگ وغیرہ استعمال کرنے کا طریقہ
  • موبائل سے متعلقہ مسائل جیسے کہ سیف ایریا، کی بورڈ، ٹیب ٹارگٹ وغیرہ سے کیسے نمٹا جائے۔
  • لائٹ اور ڈارک موڈ کے درمیان کیسے سوئچ کریں اور پوری تھیم کو ایک فائل میں کیسے تبدیل کریں۔

انڈیکس

شرطیں

پیروی کرنے کے لیے آپ کو ضرورت ہو گی:

  • Node.js 20 یا اس سے زیادہ۔ آپ pnpm، npm، Yarn، یا Bun استعمال کر سکتے ہیں۔ اس ٹیوٹوریل میں کمانڈز pnpm کا استعمال کرتی ہیں، لہذا اگر آپ کچھ اور استعمال کرتے ہیں تو سابقہ ​​کو تبدیل کریں۔
  • React اور TypeScript کا بنیادی علمخاص طور پر درج ذیل ہکس: useState.
  • React Native کا کچھ علماس طرح View, Textاور ScrollView. آپ کو Tailwind کو جاننے کی ضرورت نہیں ہے، لیکن یہ مدد کرتا ہے۔
  • اختیاری: اگر آپ اپنی ایپ کو سمیلیٹر میں چلانا چاہتے ہیں تو ایکس کوڈ یا اینڈرائیڈ اسٹوڈیو۔ سب کچھ ایک ویب براؤزر میں ری ایکٹ نیٹیو ویب کے ذریعے بھی چلتا ہے، جس طرح اس ٹیوٹوریل کے اسکرین شاٹس لیے گئے تھے۔

آپ کو کسی اکاؤنٹ یا API کلید کی ضرورت نہیں ہے، اور آپ کے لیے کوئی قیمت نہیں ہے۔

ایپ بنانے سے پہلے، آئیے اس پروجیکٹ کے ٹولز پر ایک نظر ڈالیں۔ ہر ایک کا ایک مخصوص کام ہوتا ہے، لہذا یہ جاننے سے سیٹ اپ کو پیروی کرنا آسان ہو جائے گا۔

shadcn/ui ویب کے لیے UI اجزاء کا مجموعہ۔ ایک پیکیج کے طور پر انسٹال نہیں کیا گیا ہے۔ اس کے بجائے، ایک کمانڈ لائن ٹول (CLI) آپ کے پروجیکٹ میں ہر جزو کے سورس کوڈ کو کاپی کرتا ہے، اور پھر آپ کوڈ میں ترمیم کر سکتے ہیں۔

مقامی دوبارہ قابل استعمال پر ردعمل ظاہر کریں۔ یہ shadcn/ui کا رد عمل کا مقامی ورژن ہے۔ یہ بٹن، کارڈ، سلیکٹ، اور ڈائیلاگ جیسے اجزاء فراہم کرتا ہے، جو React Native کے لیے لکھا گیا ہے اور Tailwind طرز کے کلاس ناموں کے ساتھ اسٹائل کیا گیا ہے۔ وہی CLI آئیڈیاز استعمال کرتا ہے جیسے shadcn/ui۔

آر این پرائمیٹوز یہ Radix UI کا ایک React Native پورٹ ہے، ایک لائبریری جسے shadcn/ui ویب پر آپریشن کے لیے استعمال کرتا ہے۔ "بنیادی” ایک ایسا جزو ہے جو سلوک کو سنبھالتا ہے لیکن اس کا کوئی انداز نہیں ہے۔ مثال کے طور پر select آدم جانتا ہے کہ ڈراپ ڈاؤن کیسے کھلتا اور بند ہوتا ہے اور اسکرین ریڈر کو اس کا اعلان کیسے کیا جاتا ہے، لیکن اس کا کوئی رنگ یا سرحد نہیں ہے۔ React Native Reusables اوپر کی شکلیں شامل کریں۔

یونی ونڈ ایک ٹول جو آپ کو Tailwind CSS کلاس کے ناموں کا استعمال کرتے ہوئے React Native اجزاء کو اسٹائل کرنے کی اجازت دیتا ہے، جیسے: className="bg-primary p-4". جب کہ ایپ بنڈل ہے، ہم اس کی کلاسوں کو سادہ React Native انداز میں تبدیل کرتے ہیں، اس لیے رن ٹائم پر کوئی اضافی کام نہیں ہوتا ہے۔ Nativewind کچھ ایسا ہی کرتا ہے، اور React Native Reusables دونوں کو سپورٹ کرتا ہے۔ یہ ٹیوٹوریل Uniwind کا استعمال کرتا ہے۔

ایکسپو اور ایکسپو راؤٹر پروجیکٹ سیٹ اپ، ڈویلپمنٹ سرور، اور فائل پر مبنی نیویگیشن فراہم کرتا ہے۔ اگر آپ Next.js استعمال کرتے ہیں تو ایکسپو راؤٹر اسی طرح کام کرتا ہے۔

shadcn/ui کیسے کام کرتا ہے۔

React Native Reusables دستاویزات shadcn کے اقتباس کے ساتھ کھلتی ہیں:

یہ جزوی لائبریری نہیں ہے۔ اس طرح آپ ایک جزو لائبریری بناتے ہیں۔

shadcn/ui کو باقاعدہ انحصار کے طور پر شامل نہ کریں۔ package.json. کہ لکھنا اس کے بجائے. ایک رجسٹری JSON فائلوں کا ایک مجموعہ ہے جو سرور پر ہوسٹ کی جاتی ہے۔ ہر فائل جزو کی وضاحت کرتی ہے اور اس میں وہ معلومات ہوتی ہے جس کی CLI کو اس پروجیکٹ میں شامل کرنے کی ضرورت ہوتی ہے۔ جب آپ CLI چلاتے ہیں، تو یہ متعلقہ فائلوں کو پڑھتا ہے اور آپ کے پروجیکٹ میں اجزاء کے سورس کوڈ کو کاپی کرتا ہے۔

یہ دیکھنے کے لیے کہ یہ کیسا لگتا ہے، درج ذیل رجسٹری اندراج دیکھیں: بیج عنصر بیج ایک چھوٹا لیبل ہے، جیسا کہ کارٹ پر کافی کے آگے "میڈیم” ٹیگ ہے جسے آپ بعد میں بنائیں گے۔ یہ اصل فائل کا مختصر ورژن ہے۔

{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badge",
  "type": "registry:ui",
  "title": "Badge",
  "author": "@mrzachnugent",
  "description": "Displays a badge or a component that looks like a badge.",
  "dependencies": ["@rn-primitives/slot"],
  "registryDependencies": ["https://reactnativereusables.com/r/uniwind/text.json"],
  "files": [
    {
      "path": "./node_modules/@rnr/registry/src/uniwind/components/ui/badge.tsx",
      "content": "import { TextClassContext } from '@/components/ui/text';nimport ...",
      "type": "registry:ui"
    }
  ]
}

فائلیں عوامی ہیں لہذا آپ انہیں براہ راست دیکھ سکتے ہیں۔

curl https://reactnativereusables.com/r/uniwind/badge.json

اہم شعبوں کے معنی درج ذیل ہیں۔

  • content اس میں سٹرنگ کے طور پر ذخیرہ شدہ جزو کا پورا سورس کوڈ ہوتا ہے۔
  • dependencies اجزاء کے لیے درکار npm پیکجوں کی فہرست۔ CLI اسے پہلے انسٹال کرتا ہے۔
  • registryDependencies اجزاء کے لیے درکار دیگر رجسٹری اندراجات کی فہرست۔ اس صورت میں، بیج کی ضرورت ہے: متن اجزاء React Native کے ارد گرد چھوٹے ریپر ہوتے ہیں۔ Text آپ اس کے بارے میں مرحلہ 3 میں پڑھیں گے۔ جب آپ بیج شامل کرنے کے لیے CLI چلاتے ہیں، تو یہ فیلڈ متن کو بھی شامل کرنے کا سبب بنے گی۔

اس طرح آپ کی ایپ میں رجسٹری سے اجزاء درآمد کیے جاتے ہیں۔ رجسٹری اجزاء کی معلومات کو ذخیرہ کرتی ہے، اور CLI اسے ذخیرہ میں فائلیں بنانے کے لیے استعمال کرتی ہے۔ React Native Reusables پس منظر میں shadcn CLI استعمال کرتا ہے، لہذا آپ React Native اجزاء کو شامل کرتے وقت اسی ورک فلو کی پیروی کر سکتے ہیں۔

ایپس بناتے وقت یہ طریقہ کارآمد ہونے کی چند وجوہات یہ ہیں:

  1. آپ درج ذیل فائلوں کے مالک ہیں: اجزاء جیسے: components/ui/button.tsx یہ مخزن میں ہے اور Git diff میں ظاہر ہوتا ہے۔ اگر آپ گہرا پریس چاہتے ہیں تو اس لائن میں ترمیم کریں۔ نئے پروپس بنانے، کانٹے کو برقرار رکھنے، یا مسائل اٹھانے کی ضرورت نہیں ہے۔
  2. ڈیزائن پروپس کے بجائے نامزد رنگوں کے ساتھ زندہ آتا ہے۔ ایک رنگ ایک بار نامزد کردار کے ساتھ اعلان کیا جاتا ہے، اس طرح: primary, muted, borderاور destructive. پھر اسے اپنی کلاس کے نام میں استعمال کریں: className="bg-primary". یہ ہے colors.ts اس کی وجہ یہ ہے کہ رنگ کی وضاحت کے لیے ایک جگہ ہے اور تمام اجزاء وہاں سے پڑھے جاتے ہیں۔ آپ نے اسے مرحلہ 2 میں ترتیب دیا ہے۔
  3. کارروائیاں الگ الگ پیکجوں میں فراہم کی جاتی ہیں۔ RN Primitives میں فوکس ہینڈلنگ، اوپن اور کلوز سٹیٹس، اور سکرین ریڈر سپورٹ جیسی خصوصیات حقیقی وقت میں معاون ہیں۔ آپ کو صرف ایک انداز لکھنا ہے۔

ذہن میں رکھنے کے لئے ایک تجارت ہے۔ رجسٹری اپ ڈیٹس خود بخود لاگو نہیں ہوتے ہیں کیونکہ اجزاء کوڈ پروجیکٹ میں کاپی کیا جاتا ہے۔ ایک بار جب بگ کو اوپر کی طرف طے کر لیا جاتا ہے، آپ کو CLI کو دوبارہ استعمال کرتے ہوئے چلانے کی ضرورت ہوگی: --overwrite. اگر آپ نے اس جزو کو اپنی مرضی کے مطابق بنایا ہے، تو آپ کو اپنی تبدیلیاں دوبارہ لاگو کرنے کی ضرورت پڑ سکتی ہے۔ متعدد اجزاء میں بڑی تبدیلیاں کرنے سے پہلے اس پر غور کرنا ضروری ہے۔

آبائی دوبارہ قابل استعمال خصوصیات کی پیشکش کیا رد عمل ہے

تحریر کے وقت، React Native Reusables میں 30 اجزاء شامل ہیں: accordion، الرٹ، الرٹ ڈائیلاگ، اسپیکٹ ریشو، اوتار، بیج، بٹن، کارڈ، چیک باکس، کولاپس ایبل، سیاق و سباق کا مینو، ڈائیلاگ، ڈراپ ڈاؤن مینو، ہوور کارڈ، ان پٹ، لیبل، مینو بار، پاپ اوور، پروگریس، سلیکٹ گروپ، پاپ اوور، سلیکٹ گروپ سوئچ، ٹیب، ٹیکسٹ، ٹیکسٹیریا، ٹوگل، ٹوگل گروپ، اور ٹول ٹِپ۔ دستیاب اجزاء میں ایک عام ایپ انٹرفیس کے لیے درکار بہت سے اجزاء شامل ہیں۔

رد عمل مقامی ویب سے بہت سے طریقوں سے مختلف ہے، لہذا آپ کے اجزاء ویب ورژن کی قطعی کاپی نہیں ہو سکتے۔ دستاویزات میں بنیادی اختلافات کی فہرست دی گئی ہے، اور یہ سبق آپ کو ہر ایک کے ذریعے لے جائے گا۔ اگر آپ ویب انٹرفیس بنانے کے لیے shadcn/ui بھی استعمال کرتے ہیں، تو shadcn جزو مفید ہو سکتا ہے جب آپ کو کام کرنے کے لیے مزید ریڈی میڈ مثالوں کی ضرورت ہو۔

فرق معنی میں اسے کہاں دیکھ سکتا ہوں؟
پورٹل میں PortalHost ویب پر، تیرتا ہوا مواد جیسے ڈراپ ڈاؤنز document.body. React Native میں نہیں۔ document.bodyتو آپ PortalHost رینڈر کرنے کا جزو۔ مرحلہ 7
طرزیں جھرنا نہیں کرتیں۔ پیرنٹ ویو پر سیٹ کردہ ٹیکسٹ کلر اس کے اندر موجود ٹیکسٹ تک نہیں پہنچتا، اس لیے React Native Reusables Text یہ ایک ایسا جزو ہے جس میں ایک کام ہے۔ مرحلہ 3
موجود نہیں ہے data-* وصف Web shadcn درج ذیل خصوصیات کی بنیاد پر سٹائل کو تبدیل کرتا ہے: data-state. React Native میں کوئی بھی نہیں ہے، لہذا تبدیلی کا انحصار پرپس اور ریاست پر ہے۔ مرحلہ 7
شبیہیں کلاسز کے بجائے پرپس استعمال کرتی ہیں۔ آئیکن لائبریری سے کیا توقع کی جائے۔ color اور size چونکہ یہ ایک سہارا ہے، اس لیے کلاس کا نام قبول کرنے کے لیے آئیکن کو لپیٹ دیا جاتا ہے۔ مرحلہ 3

آپ کیا بنا رہے ہیں

ہم ایک کافی شاپ کے لیے ایک کارٹ اور ادائیگی کی اسکرین بنائیں گے جہاں گاہک اپنے آرڈر دیتے ہیں اور انہیں کاؤنٹر پر جمع کرتے ہیں۔ اسکرین رجسٹری کے 12 اجزاء استعمال کرتی ہے۔ جب ہم اسے اکٹھا کر رہے ہیں، تو ہم کچھ React Native تفصیلات کی بھی وضاحت کریں گے جن کو یاد کرنا آسان ہے، جیسے کہ ڈراپ ڈاؤنز کیسے پیش کیے جاتے ہیں، محفوظ علاقہ کس طرح لے آؤٹ کو متاثر کرتا ہے، اور باقی صفحہ سکرول کرتے وقت چیک آؤٹ بار کو کیسے مرئی رکھا جائے۔

سکرین کا حصہ استعمال شدہ حصے موبائل کے مسائل یہ سکھاتا ہے۔
آئٹم کی قطار کارڈز، اوتار، بیجز، بٹن کوئی مرحلہ وار انداز نہیں ہے۔ truncateچھوٹے ٹیب کا ہدف
مقدار stepper بٹن، آئیکن شبیہیں پرپس استعمال کرتی ہیں، کلاسز نہیں۔
وقت اٹھاؤ انتخاب، لیبل ڈراپ ڈاؤن کے لیے پورٹل کی ضرورت ہے۔
نام کا میدان ان پٹ، لیبل کی بورڈ فیلڈز کا احاطہ کر سکتا ہے۔
اپنا کپ ٹوگل کریں۔ سوئچز، لیبلز لیبل کے متن کو ٹیپ کرنے کے قابل بنائیں
بل الگ کرنے والا، متن غیر ترتیب شدہ نمبرز
چیک آؤٹ بار بٹن، الرٹ ڈائیلاگ نہیں position: fixedاور گھر کے اشارے

مکمل سکرین مندرجہ ذیل ہے۔

ڈارک موڈ میں مکمل کارٹ اسکرین

اگر آپ اپنے کام کا حتمی نتیجہ سے موازنہ کرنا چاہتے ہیں تو مکمل کوڈ GitHub ریپوزٹری میں دستیاب ہے۔

مرحلہ 1: ایک ایپ بنائیں

آپ React Native Reusables CLI کو ایک ابتدائی پروجیکٹ بنانے دے سکتے ہیں۔ ہم ایکسپو راؤٹر، اسٹائل کنفیگریشن، تھیم فائلز، اور کچھ اسٹارٹ اپ اجزاء ترتیب دیں گے۔ شروع کرنے کے لیے، درج ذیل کمانڈ کو چلائیں:

pnpm dlx @react-native-reusables/cli@latest init

CLI سوالات کے جوابات

CLI چار سوالات پوچھتا ہے:

پروجیکٹ کا نام کیا ہے؟ آپ جو نام درج کرتے ہیں وہ فولڈر کا نام بن جاتا ہے۔ یہ تجویز کرتا ہے my-app.

ایک ٹیمپلیٹ کا انتخاب کریں۔ تین اختیارات ہیں اور آپ کو جس کی ضرورت ہے وہ ڈیفالٹ کے ذریعے منتخب نہیں کیا جاتا ہے۔

ٹرمینل میں ٹیمپلیٹ سلیکشن پرامپٹ

❯ Minimal (Nativewind)
  Minimal (Uniwind)
  Clerk auth (Nativewind)

نیچے تیر کو ایک بار دبائیں۔ کم از کم. یہ بعد میں تبدیل کرنے کے لیے مشکل انتخاب میں سے ایک ہے۔ Nativewind اور Uniwind React Native میں Tailwind استعمال کرنے کے دو طریقے ہیں، اور CLI ہر ایک کے لیے الگ الگ رجسٹریاں برقرار رکھتا ہے۔ آپ جو اجزاء یونی ونڈ کے لیے لکھتے ہیں وہ ان سے مختلف ہیں جو آپ Nativewind کے لیے لکھتے ہیں، اس لیے اگر آپ Nativewind کا انتخاب کرتے ہیں تو اس ٹیوٹوریل کا کوڈ مماثل نہیں ہوگا۔

کیا آپ انحصار کو انسٹال کرنا چاہتے ہیں؟ ہاں کو منتخب کریں اور پیکیج مینیجر کو منتخب کریں جسے آپ استعمال کرتے ہیں۔

کیا آپ اپنے Git ذخیرہ کو شروع کرنا چاہتے ہیں؟ براہ کرم ہاں کو منتخب کریں۔ میں سپلیش اسکرین کو تبدیل کرنے کی کوشش کر رہا ہوں اور اس کا موازنہ کرنے کے لیے کچھ وعدے کرنا مددگار ثابت ہوگا۔

پروجیکٹ کی ساخت پر ایک نظر ڈالیں۔

CLI درج ذیل فائلیں تیار کرتا ہے۔

shadcn-react-native-app/
├── app/
│   ├── _layout.tsx        # ThemeProvider, Stack, and the PortalHost
│   ├── +html.tsx          # web only, the root HTML shell
│   ├── +not-found.tsx
│   └── index.tsx          # the starter screen
├── components/ui/
│   ├── button.tsx
│   ├── icon.tsx
│   └── text.tsx
├── lib/
│   ├── theme.ts           # the same palette again, in JavaScript
│   └── utils.ts           # cn()
├── global.css             # the theme tokens
├── components.json        # the shadcn config, same as on the web
├── metro.config.js        # withUniwindConfig
└── uniwind-types.d.ts     # generated, do not edit

اس سے پہلے کہ ہم آگے بڑھیں، آئیے اس ڈھانچے کی چند فائلوں کو دیکھتے ہیں۔ آپ اسے پورے ٹیوٹوریل میں استعمال کریں گے۔

  • components.json یہ وہی shadcn کنفیگریشن فائل ہے جو ویب پر استعمال ہوتی ہے۔ CLI کو بتاتا ہے کہ جزو کہاں رکھنا ہے (@/components/ui) اور کہاں cn() مددگار زندگی (@/lib/utils)۔
  • global.css آپ کے پاس تھیم ٹوکنز ہوں گے جو آپ مرحلہ 2 میں سیکھیں گے۔
  • lib/utils.ts برآمد cn()کلاس کے ناموں کو یکجا کرنے اور کلاسوں کے درمیان تنازعات کو حل کرنے کے لیے ایک مددگار۔
  • app/_layout.tsx شامل PortalHost عنصر یہ بچا ہوا سہاروں کی طرح لگتا ہے، لیکن اسے حذف نہیں کیا جانا چاہیے۔ مرحلہ 7 وضاحت کرتا ہے کہ کیوں۔

یونی ونڈ ریڈنگ global.css میٹرو آپ کی ایپ کو بنڈل کرتا ہے اور کلاس سٹرنگز کو React مقامی طرز کی اشیاء میں تبدیل کرتا ہے۔ یہ تعمیر کے وقت ہوتا ہے، لہذا اس میں کافی وقت لگتا ہے۔ className سٹرنگز آپ کی ایپ کو سست نہیں کرتی ہیں۔

ایپ چلائیں

ڈویلپمنٹ سرور شروع کریں۔

pnpm dev

دبائیں w براؤزر میں کھولنے کے لیے i iOS سمیلیٹر کے لیے یا a اینڈرائیڈ کے لیے۔ ویلکم اسکرین کے ہیڈر میں تھیم ٹوگل ہے جس سے ظاہر ہوتا ہے کہ تھیم کی ترتیب اثر میں ہے۔

اگر کوئی مسئلہ ظاہر ہوتا ہے تو آپ کسی بھی وقت تشخیصی کمانڈز چلا سکتے ہیں۔

pnpm dlx @react-native-reusables/cli@latest doctor

اپنی ترتیبات کو چیک کریں اور کسی بھی طرز کی لائبریریوں کا پتہ لگنے کی اطلاع دیں۔ اس پروجیکٹ کے لیے، آؤٹ پٹ ہونا چاہیے:

ℹ Styling Library: Uniwind
✔ All checks passed.

اگر یہ Nativewind کہتا ہے، تو جزو شامل کرنے سے پہلے اسے درست کریں۔

اگر سیٹ اپ کام کرتا ہے، تو آپ کو ایک خوش آمدید اسکرین نظر آئے گی۔ یہ یقینی بنانے کے لیے کہ لائٹ اور ڈارک موڈ دونوں کام کر رہے ہیں اور آپ کے کنسول میں کوئی خرابیاں نہیں ہیں، ہیڈر میں تھیم ٹوگلز کا استعمال کریں۔

سپلیش سکرین

مرحلہ 2: اپنی تھیم فائلوں کو سمجھیں۔

اب جبکہ ایپ چل رہی ہے، آئیے تھیم کی ترتیبات پر ایک نظر ڈالتے ہیں۔ کہ global.css فائل وہ جگہ ہے جہاں آپ اپنے اجزاء میں استعمال ہونے والے رنگوں کی وضاحت کرتے ہیں۔ یہ قدر پہلے تھی۔ colors.ts یا theme.ts یہ وہ ترتیب ہے جو فائل کو بدل دیتی ہے۔

@import "tailwindcss";
@import "uniwind";

@import "tw-animate-css";

@theme {
  --radius: 10px;
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);

  --spacing-hairline: hairlineWidth();
}

@layer theme {
  :root {
    @variant light {
      --color-background: oklch(1 0 0);
      --color-foreground: oklch(0.145 0 0);
      --color-card: oklch(1 0 0);
      --color-primary: oklch(0.205 0 0);
      --color-muted-foreground: oklch(0.556 0 0);
      --color-border: oklch(0.922 0 0);
      /* the rest of the list */
    }

    @variant dark {
      --color-background: oklch(0.145 0 0);
      --color-foreground: oklch(0.985 0 0);
      --color-card: oklch(0.205 0 0);
      --color-primary: oklch(0.922 0 0);
      --color-muted-foreground: oklch(0.708 0 0);
      --color-border: oklch(1 0 0 / 10%);
      /* the rest of the list */
    }
  }
}

اس فائل میں دو اصول ہیں جو آپ کو جاننے کی ضرورت ہے:

سب سے پہلے --color- سابقہ ​​کلاس کا نام بناتا ہے۔ اعلان کرتے وقت --color-primaryTailwind اسے خود بخود آپ کے لیے بنائے گا۔ bg-primary, text-primaryاور border-primary. اگر آپ اعلان کریں۔ --primary اس کے بجائے، آپ کو صرف CSS متغیرات ملتے ہیں جو کچھ بھی استعمال نہیں کرتے ہیں۔

یہ اہم ہے کیونکہ زیادہ تر shadcn تھیم جنریٹر متغیر نامی ویب فارم تیار کرتے ہیں۔ --primary. اگر آپ متغیر کا نام تبدیل کیے بغیر اس آؤٹ پٹ کو پیسٹ کرتے ہیں، تو آپ کچھ نہیں توڑیں گے، لیکن کچھ نہیں بدلے گا۔ مرحلہ 12 آپ کو تبدیل کرنے کا طریقہ دکھاتا ہے۔

دوسرا @variant light اور @variant dark تبدیلی :root اور .dark. ویب پر ڈارک موڈ شامل کر کے کام کرتا ہے: .dark یہ صفحات کے بارے میں ایک کلاس ہے۔ فون میں کلاسز شامل کرنے کے لیے کوئی صفحہ نہیں ہے، اس لیے Uniwind سوئچ کا انتظام کرتا ہے۔ جب آپ کال کرتے ہیں۔ Uniwind.setThemeفعال اقدار کا تبادلہ کریں۔ ہم اسے مرحلہ 11 میں استعمال کرتے ہیں۔

آپ فائل کا استعمال بھی دیکھ سکتے ہیں: oklch() رنگ فونز میں ایسے براؤزر نہیں ہوتے ہیں جو اس فارمیٹ کو پڑھ سکیں، لیکن یونی وِنڈ رنگوں کو اس فارمیٹ میں تبدیل کرتا ہے جسے بنڈلنگ کے دوران React Native سمجھتا ہے، لہذا آپ بہرحال جدید ترین رنگین ترکیب استعمال کر سکتے ہیں۔

پیلیٹ بھی اس میں موجود ہیں: lib/theme.ts

جب میں اسے کھولتا ہوں۔ lib/theme.tsاپنے جاوا اسکرپٹ آبجیکٹ جیسا رنگ واپس حاصل کرنے کے لیے۔ یہ نقل ضروری ہے کیونکہ React نیویگیشن، جو ہیڈر اور اسکرین کا پس منظر کھینچتی ہے، سادہ JavaScript آبجیکٹ سے رنگ پڑھتی ہے نہ کہ CSS متغیرات سے۔ نتیجے کے طور پر، آپ کو پیلیٹ کی دو کاپیاں مطابقت پذیری میں رکھنے کی ضرورت ہے۔ 12 مراحل ظاہر کرتے ہیں کہ جب وہ مختلف ہوتے ہیں تو کیا ہوتا ہے۔

اسے آزمائیں: ایک رنگ تبدیل کریں۔

آئیے ایک ٹوکن کو تبدیل کرتے ہیں تاکہ یہ دیکھیں کہ تھیم کیسے کام کرتی ہے۔ اپنی ہلکی تھیم کے ڈیفالٹ رنگوں کو آسانی سے پہچانی جانے والی اقدار میں اپ ڈیٹ کریں۔

--color-primary: oklch(0.6 0.24 25);

فائل کو محفوظ کریں اور ویلکم اسکرین پر ایک نظر ڈالیں۔ کہ دستاویزات کو براؤز کریں۔ بٹن اب سرخ ہے حالانکہ آپ نے اس میں ترمیم نہیں کی ہے۔ button.tsx:

ایک ٹوکن تبدیل کرنے کے بعد اسٹارٹ بٹن سرخ ہوگیا۔

استعمال ہونے والے تمام اجزاء ہیں۔ bg-primary ایک نیا رنگ منتخب کریں۔ میں نے صرف ایک ٹوکن تبدیل کیا، لیکن بٹن کو اس کے اجزاء کی فائل کو چھوئے بغیر اپ ڈیٹ کر دیا گیا۔ جاری رکھنے سے پہلے قدر کو دوبارہ تبدیل کریں۔

مرحلہ 3: تین بنیادی اجزاء پڑھیں

اسٹارٹ اپ پروجیکٹ پہلے سے ہی تین اجزاء فراہم کرتا ہے: Text, Buttonاور Icon. اسکرین بنانے سے پہلے، آئیے اسے دیکھنے کے لیے چند منٹ نکالیں۔ یہ ایک ایسا نمونہ متعارف کراتا ہے جسے رجسٹری کے دوسرے اجزاء میں دوبارہ دیکھا جا سکتا ہے۔

متن کا جزو

React Native میں، سٹائلز CSS کی طرح جھڑپ نہیں ہوتے۔ اگر آپ متن کا رنگ سیٹ کرتے ہیں۔ Viewاس کے اندر کا متن اس رنگ کا وارث نہیں ہوتا ہے۔ یہ اجزاء کے ساتھ ایک مسئلہ ہے جیسے: سفید متن خود بخود ظاہر ہوتا ہے۔

آئیکن عنصر

آئیکن lucide-react-native لے color اور size ایک سہارے کے طور پر، کلاس کے نام کے طور پر نہیں۔ کہ Icon جزو components/ui/icon.tsx یونی وِنڈ کو سکھائیں کہ ایک چیز کو دوسری چیز میں کیسے تبدیل کیا جائے۔

const StyledIcon = withUniwind(IconImpl, {
  size: { fromClassName: 'className', styleProperty: 'width' },
  color: { fromClassName: 'className', styleProperty: 'color' },
});

یہ کوڈ width اپنی کلاس سٹرنگ میں، آپ اسے اس طرح پاس کرتے ہیں: sizeاور یہ پڑھتا ہے color اور اسے اس طرح پاس کریں: color. اسی لیے یہ توقع کے مطابق کام کرتا ہے۔ withUniwind یہ اس وقت مفید ہوتا ہے جب بھی آپ فریق ثالث کا کوئی ایسا جزو استعمال کرتے ہیں جس سے آپ جہاں کلاس استعمال کرنا چاہتے ہیں وہاں پرپس حاصل کرتے ہیں۔

آپ کو اس مرحلے پر کچھ کرنے کی ضرورت نہیں ہے۔ اہم حصہ یہ سمجھنا ہے کہ کیا ہے۔ TextClassContext اس پر کلک کرنے کے بعد، رجسٹری کے مختلف اجزاء کی پیروی کرنا بہت آسان ہے۔

مرحلہ 4: مطلوبہ اجزاء شامل کریں۔

ٹیمپلیٹ میں پہلے سے ہی تین اجزاء ہیں: ادائیگی کی سکرین کو کچھ اور چیزوں کی ضرورت ہے۔ CLI ایک ساتھ سب کچھ شامل کر سکتا ہے۔

pnpm dlx @react-native-reusables/cli@latest add card separator input label select switch badge alert-dialog avatar

CLI پتہ لگائے گا کہ آپ Uniwind استعمال کر رہے ہیں اور ایک مماثل ورژن بنائیں گے۔ یہ آپ کی ضرورت کی ہر چیز کو بھی انسٹال کرتا ہے۔ @rn-primitives پیکیج اور نام کی فائل شامل کریں۔ native-only-animated-view.tsxمنتخب کریں اور AlertDialog اسے حرکت پذیری کے لیے استعمال کریں۔

آؤٹ پٹ ہے:

ℹ Styling Library: Uniwind
✔ Created 10 files:
  - components/ui/separator.tsx
  - components/ui/input.tsx
  - components/ui/label.tsx
  - components/ui/switch.tsx
  - components/ui/avatar.tsx
  - components/ui/native-only-animated-view.tsx
  - components/ui/card.tsx
  - components/ui/badge.tsx
  - components/ui/select.tsx
  - components/ui/alert-dialog.tsx
ℹ Skipped 3 files: (files might be identical, use --overwrite to overwrite)
  - components/ui/text.tsx
  - components/ui/icon.tsx
  - components/ui/button.tsx

CLI نے 10 فائلیں تیار کیں، جو کہ درخواست کردہ 9 فائلوں سے زیادہ ہیں۔ native-only-animated-view.tsx سلیکٹ اور الرٹ ڈائیلاگ کا انحصار۔ ٹیمپلیٹ نے پہلے ہی فراہم کردہ تین فائلوں کو چھوڑ دیا ہے، لہذا کاپیاں اوور رائٹ نہیں ہوں گی۔ یہ کام کی جگہ پر "فائل کے مالک ہیں” کا خیال ہے۔

چلائیں pnpm dev ایک بار پھر، چیک کریں کہ آیا ایپ لانچ ہوتی رہتی ہے۔ کہ components/ui/ فولڈر میں اب 13 فائلیں ہونی چاہئیں۔

مرحلہ 5: ڈیٹا اور اسکرین کی ترتیبات

اس سے پہلے کہ ہم UI بنانا شروع کریں، آئیے کچھ نمونہ مینو ڈیٹا شامل کریں اور اس بات کا تعین کریں کہ اسکرین کی حالتیں کہاں ظاہر ہوں گی۔ آئیے قیمتوں کو فلوٹنگ پوائنٹ نمبرز کے بجائے سینٹ میں اسٹور کریں۔ یہ مندرجہ ذیل حساب کے نتائج کو روکتا ہے: $4.7499999.

یہ اسکرین کے مرکزی جزو کی حالت کو بھی برقرار رکھتا ہے اور متعلقہ اقدار کو پرپس کے طور پر پاس کرتا ہے۔ اس طرح، آپ کی انوائس خود بخود اپ ڈیٹ ہو جائے گی جب آپ آئٹم کی مقدار تبدیل کریں گے یا اپنا کپ آپشن منتخب کریں گے۔

بنانا lib/menu.ts:

export type CartItem = {
  id: string;
  name: string;
  detail: string;
  size?: 'Small' | 'Medium' | 'Large';
  emoji: string;
  /** Cents. Money in floats is how you end up charging someone $4.7499999. */
  unitPrice: number;
  qty: number;
};

export const INITIAL_CART: CartItem[] = [
  {
    id: 'flat-white',
    name: 'Flat white',
    detail: 'Oat milk, extra shot',
    size: 'Medium',
    emoji: '☕',
    unitPrice: 475,
    qty: 2,
  },
  {
    id: 'cold-brew',
    name: 'Cold brew',
    detail: 'Hold the ice',
    size: 'Large',
    emoji: '🧋',
    unitPrice: 525,
    qty: 1,
  },
  {
    id: 'croissant',
    name: 'Almond croissant',
    detail: 'Warmed',
    emoji: '🥐',
    unitPrice: 390,
    qty: 1,
  },
];

export const PICKUP_TIMES = [
  { value: 'asap', label: 'As soon as it is ready' },
  { value: '15', label: 'In 15 minutes' },
  { value: '30', label: 'In 30 minutes' },
  { value: '60', label: 'In an hour' },
];

export const CUP_DISCOUNT = 25;
export const TAX_RATE = 0.0825;

export function money(cents: number) {
  const sign = cents < 0 ? '-' : '';
  return `${sign}$${(Math.abs(cents) / 100).toFixed(2)}`;
}

export function billFor(items: CartItem[], ownCup: boolean) {
  const subtotal = items.reduce((sum, item) => sum + item.unitPrice * item.qty, 0);
  const discount = ownCup && subtotal > 0 ? CUP_DISCOUNT : 0;
  const tax = Math.round((subtotal - discount) * TAX_RATE);

  return { subtotal, discount, tax, total: subtotal - discount + tax };
}

کہ PICKUP_TIMES فہرست اجزاء کے اندر کی بجائے اس فائل میں ہے کیونکہ اسے دو مختلف اجزاء کی ضرورت ہے۔ آپ مرحلہ 7 میں اس کی وجہ جان سکتے ہیں۔

سکرین لے آؤٹ

بنیادی طور پر یہ اس طرح ہے۔ app/index.tsx لگتا ہے۔ کسی ایسے حصے پر تبصرہ کریں جو ابھی تک موجود نہیں ہے، اور آپ بعد میں مرحلہ 10 میں پوری فائل بنائیں گے۔


  
    {/* the three cards */}
  

  

اس ترتیب میں تین تفصیلات ہیں جن کا مزید باریک بینی سے جائزہ لینے کی ضرورت ہے۔

  • سب سے پہلے className کو ScrollView اسکرول کنٹینر کو اسٹائل کرتا ہے، مواد کو نہیں۔ یہ کلاس کے نام کا ورژن ہے۔ style اور contentContainerStyle React Native میں تقسیم کریں۔ اگر آپ پیڈنگ شامل کریں۔ classNameکاٹ gap یہ کام نہیں کرتا۔ استعمال کریں contentContainerClassName ہر اس چیز کے بارے میں جو آپ کے مواد پر لاگو ہونا چاہئے۔ یونی ونڈ اس پروپ کو اس میں شامل کرتا ہے: ScrollView.
  • موجود نہیں ہے space-y-4. سی ایس ایس میں space-y-* یہ بہن بھائیوں کو منتخب کرکے کام کرتا ہے، React Native میں سلیکٹرز نہیں ہوتے ہیں۔ استعمال کریں gap-4 اس کے بجائے، iOS، Android، اور ویب کی حمایت کے ساتھ React Native سب سے اوپر ہے۔
  • keyboardShouldPersistTaps="handled" جب تک کی بورڈ کھلا رہے گا تب تک ٹیبز کام کرتی رہیں گی۔ بصورت دیگر، کی بورڈ ظاہر ہونے کے بعد پہلا نل اسے بند کر دے گا، جس سے صارف کو دو بار ٹیپ کرنے پر مجبور ہو جائے گا۔ automaticallyAdjustKeyboardInsets ایک iOS خصوصیت جو مواد کو اسکرول کرتی ہے تاکہ فوکسڈ ان پٹ کی بورڈ کے پیچھے چھپا نہ رہے۔

اس مرحلے پر، آپ کو صرف نئے عنوان کے ساتھ ایک ہیڈر اور اس کے نیچے ایک خالی صفحہ نظر آئے گا۔

نئے ہیڈر ٹائٹل کے ساتھ خالی اسکرین

مرحلہ 6: آرڈر کی قطار بنائیں

ایک چھوٹی سی جگہ پر آرڈر لائن بہت زیادہ چل رہی ہے، بشمول اوتار، پروڈکٹ کے نام، سائز کے بیجز، قیمتوں کا تعین، اور مقدار کا کنٹرول۔ لمبے نام خاص طور پر موبائل فون پر مشکل ہوتے ہیں، اس لیے ہم ترتیب کو احتیاط سے سنبھالیں گے۔

بنانا components/cart/order-row.tsx:

import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { money, type CartItem } from '@/lib/menu';
import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react-native';
import { View } from 'react-native';

type OrderRowProps = {
  item: CartItem;
  onChangeQty: (id: string, qty: number) => void;
};

export function OrderRow({ item, onChangeQty }: OrderRowProps) {
  const lastOne = item.qty <= 1;

  return (
    
      
        
          {item.emoji}
        
      

      
        
          
            
              
                {item.name}
              
              {item.size ? (
                
                  {item.size}
                
              ) : null}
            
            
              {item.detail}
            
          
          
            {money(item.unitPrice * item.qty)}
          
        

        
          
          {item.qty}
          
        
      
    
  );
}

اس جزو کی کچھ تفصیلات پر گہری نظر ڈالنے کے قابل ہے۔

سب سے پہلے Avatar مطالبہ alt سہارے TypeScript اس کے بغیر مرتب نہیں ہوگا۔ قدر ایک قابل رسائی لیبل بن جاتی ہے، اس لیے اسکرین ریڈرز ‘تصویر’ کے بجائے صرف ‘فلیٹ سفید’ کہتے ہیں۔

رداس کو اوتار فال بیک میں دہرایا جاتا ہے۔ Avatar اور AvatarFallback ہر ایک ہے۔ rounded-full بنیادی طور پر۔ بس اسے ترتیب دیں۔ rounded-2xl کو Avatarایموجی کا پس منظر دائرے کو برقرار رکھتا ہے اور گول مربع سے باہر نکل جاتا ہے۔

آپ بھی استعمال کریں۔ numberOfLines={1} اس کے بجائے truncate. ٹیل ونڈ کا truncate پر بھروسہ کریں text-overflow: ellipsisReact Native اس کی حمایت نہیں کرتا ہے اور Uniwind اسے تبدیل نہیں کرتا ہے۔ کلاسز کو کسی بھی چیز میں مرتب نہیں کیا جاتا ہے، اتنے لمبے پروڈکٹ کے نام قیمتوں کو اسکرین سے دور کر دیتے ہیں۔ کہ numberOfLines پرپس ایسا کرنے کا ری ایکٹ مقامی طریقہ ہیں، اور وہ ویب بلڈز میں بھی کام کرتے ہیں۔

نام میں shrink اور اس کالم میں flex-1. بغیر shrinkلمبے نام چھوٹے نہیں ہوں گے اور بیج کو قطار سے باہر دھکیل دیں گے۔ یہ عام Flexbox کا رویہ ہے، لیکن یہ فونز پر زیادہ نظر آتا ہے کیونکہ اس کے ساتھ کام کرنے کی چوڑائی کم ہوتی ہے۔

hitSlop={8} چھوٹے بٹنوں کو ٹیپ کرنا آسان ہے۔ ایپل کم از کم 44 پوائنٹس کے ٹچ ہدف کی تجویز کرتا ہے، جبکہ اینڈرائیڈ 48 ڈی پی کی تجویز کرتا ہے۔ size-7 بٹن صرف 28 پوائنٹس چوڑا ہے۔ کہ hitSlop سہارا بٹن کی شکل کو تبدیل کیے بغیر ٹچ ایبل ایریا کو بڑا بناتا ہے۔ کوئی سی ایس ایس کے برابر نہیں ہے۔

آخر کار tabular-nums نمبروں کو ایک ہی چوڑائی رکھتا ہے۔ زیادہ تر فونٹس میں نمبر ہوتے ہیں۔ 1 تنگ 8لہذا، جب بھی مقدار میں تبدیلی آتی ہے، قیمت ایک طرف ہو جاتی ہے۔ یونی ونڈ کنورژن tabular-nums مقامی لوگوں کا ردعمل fontVariantتو یہ iOS، Android اور ویب پر کام کرتا ہے۔

اس جزو کو شامل کرنے اور اسے کارڈ کے اندر پیش کرنے کے بعد (جیسا کہ مرحلہ 10 میں کیا گیا ہے)، آپ کو درج ذیل تین قطاریں نظر آئیں گی۔

کارڈ کے اندر قطار ترتیب دیں۔

جب آپ فلیٹ وائٹ پر پلس کو تھپتھپاتے ہیں تو قیمت اس سے بدل جائے گی: $9.50 کو $14.25. 1 کی مقدار والی آئٹم پر ٹریش کین آئیکن کو ٹیپ کرنے سے وہ قطار ہٹ جائے گی۔ جب بھی مقدار 1 تک پہنچ جاتی ہے تو مائنس بٹن کوڑے دان کے آئیکن میں تبدیل کر دیتا ہے۔

مرحلہ 7: پک اپ کے اختیارات بنائیں

پک اپ کارڈ تین کنٹرولز کے ساتھ آتا ہے: سلیکٹ، انٹر، اور سوئچ۔ ان پٹ اور سوئچ بہت آسان ہیں۔ سلیکٹ میں تھوڑی زیادہ احتیاط کی ضرورت ہے کیونکہ پلیٹ فارم سے وابستہ تین خرابیاں ہیں، جن میں سے دو خاموشی سے ناکام ہونے کا سبب بن سکتی ہیں۔

بنانا components/cart/pickup-card.tsx:

import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Text } from '@/components/ui/text';
import { PICKUP_TIMES } from '@/lib/menu';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

type PickupCardProps = {
  pickupValue: string;
  onPickupValueChange: (value: string) => void;
  name: string;
  onNameChange: (value: string) => void;
  ownCup: boolean;
  onOwnCupChange: (value: boolean) => void;
};

export function PickupCard({
  pickupValue,
  onPickupValueChange,
  name,
  onNameChange,
  ownCup,
  onOwnCupChange,
}: PickupCardProps) {
  const insets = useSafeAreaInsets();
  // Look the label up from our own data. The Option handed back by onValueChange
  // does not carry a usable label on web. See the note below.
  const selected = PICKUP_TIMES.find((time) => time.value === pickupValue);
  const contentInsets = {
    top: insets.top,
    bottom: insets.bottom,
    left: 16,
    right: 16,
  };

  return (
    
      
        
        
      

      
        
        
      

      
        
          
          Takes 25¢ off and saves a lid.
        
        
      
    
  );
}

مسئلہ 1: پورٹلز اور پورٹل ہوسٹ

جب ڈراپ ڈاؤن کھلتا ہے، تو اسے اسکرین پر موجود دیگر مواد کے اوپر ظاہر ہونا چاہیے۔ اس لیے SelectContent یہ جزو کے درخت میں اسی جگہ پر پیش نہیں ہوتا ہے جہاں اس کا اعلان کیا گیا تھا۔ یہ استعمال کرتا ہے @rn-primitives/portal ایپ کے دوسرے حصوں میں مواد پیش کریں۔ اس ٹیکنالوجی کو پورٹل کہا جاتا ہے۔

ویب پر، پورٹل کو اس طرح پیش کیا گیا ہے: document.bodyہر صفحہ میں ایک ہے۔ React Native میں ایسی کوئی بات نہیں ہے۔ لہذا، پورٹل پیکیج میموری میں پورٹل مواد کی فہرست کو برقرار رکھتا ہے اور PortalHost اس فہرست کو پڑھتا اور دکھاتا ہے۔ یہ اس کوڈ کا آسان ورژن ہے۔ node_modules/@rn-primitives/portal/dist/index.js:

var DEFAULT_PORTAL_HOST = "INTERNAL_PRIMITIVE_DEFAULT_HOST_NAME";
var updatePortal = (hostName, name, children) => { /* writes into the map */ };
function PortalHost({ name = DEFAULT_PORTAL_HOST }) { /* reads the map */ }

اگر نہیں PortalHost جب نصب کیا جاتا ہے، مواد کو محفوظ کیا جاتا ہے لیکن ظاہر نہیں ہوتا ہے۔ ڈراپ ڈاؤن کچھ بھی نہیں کھولتا ہے اور کوئی غلطی یا انتباہات ظاہر نہیں ہوتے ہیں۔

ٹیمپلیٹ پہلے ہی موجود ہے۔ PortalHost روٹ لے آؤٹ کے نیچے:


  
  
  

It looks like unnecessary code, but you need to keep it. The AlertDialog in Step 10 uses it too.

Problem 2: Safe Area Insets

There’s another issue to consider when showing a dropdown on a phone. Parts of the screen are reserved for the operating system, such as the area around the notch and the home indicator. If a dropdown opens near the bottom of the screen, one of its options could appear underneath the home indicator. In that area, a tap may be handled by the operating system instead of your app.

To avoid this, pass the safe area insets to the content:

const insets = useSafeAreaInsets();
const contentInsets = { top: insets.top, bottom: insets.bottom, left: 16, right: 16 };


The insets prop is part of PositionedContentProps in @rn-primitives/types, so every positioned component in the registry accepts it, including Popover, DropdownMenu, HoverCard, Tooltip, and Select. It’s a good habit to pass it every time.

Problem 3: The Select Value Is an Object

One more thing to watch out for is the value returned by onValueChange. It may look like you’re selecting a string, but the callback returns an Option object:

type Option = { value: string; label: string } | undefined;

This causes a bug that only appears on one platform. If you store the whole Option in state and show option.label later, such as in a confirmation dialog, it displays correctly on a phone ("In 30 minutes”) but incorrectly in the browser ("30”).

The reason is in the primitive’s source code. This is the native version, from select.js:

onValueChange({ value: itemValue, label });

And this is the web version, from select.web.js. It uses Radix, which only returns a value:

onValueChange({ value: val, label: val });

So the label is correct on native, but on the web it’s a copy of the value. If you only test in a simulator, you won’t notice this problem.

The fix is to keep only the value in state and look up the label from your own list:

const selected = PICKUP_TIMES.find((time) => time.value === pickupValue);

This is why PICKUP_TIMES is in lib/menu.ts. The pickup card needs it to build the options, and the main screen needs it to write the confirmation message.

Making the Label Tappable

The switch itself is quite small, so users may tap the label next to it instead. It makes sense to let that label toggle the switch as well. You can do that by adding an onPress handler to the Label:



Giving both elements the same nativeID connects them for screen readers. The onPress on the Label is what makes the text toggle the switch, because React Native has no automatic behavior.

While you’re here, open components/ui/switch.tsx. On the web, shadcn colors a switch with data-[state=checked]:bg-defaultDOM میں سیٹ کی گئی خصوصیات کو پڑھتا ہے۔ React Native میں نہیں۔ data-* چونکہ یہ ایک خاصیت ہے، جزو جاوا اسکرپٹ میں پروپ کے لیے چیک کرتا ہے۔

props.checked ? 'bg-primary' : 'bg-input dark:bg-input/80',

نتیجہ وہی نکلتا ہے۔ نقل کرتے وقت اس بات کو ذہن میں رکھیں۔ data-[state=...] یہ ویب مثال میں سلیکٹر ہے اور اس کا کوئی اثر نہیں ہے۔

نام کی فیلڈ کے لیے کی بورڈ کا سہارا

Input ہے TextInput بارڈر اور رداس کا اطلاق ہوتا ہے۔ آپ کو ہر ٹیکسٹ ان پٹ کے لیے درج ذیل چار پروپس سیٹ کرنے ہوں گے۔

  • autoCapitalize="words" نام میں ہر ایک لفظ کو بڑا کرتا ہے۔
  • autoComplete="name" آپریٹنگ سسٹم نام درج کرنے کی پیشکش کرے گا۔
  • returnKeyType="done" واپسی کلید کے لیبل کو "ہو گیا” میں تبدیل کریں۔
  • keyboardType="number-pad" جب اعداد کی ضرورت ہو تو عددی کی بورڈ دکھاتا ہے۔

ایک ساتھ automaticallyAdjustKeyboardInsets مرحلہ 5 میں اسکرول ویو فیلڈز کو تب تک دکھائی دے گا جب تک کی بورڈ کھلا رہے گا۔ اگر آپ کو مزید کنٹرول کی ضرورت ہے، react-native-keyboard-controller یہ پہلے سے ہی ٹیمپلیٹ کا انحصار ہے اور فراہم کرتا ہے: KeyboardAwareScrollView اور KeyboardProvider.

جب آپ ڈراپ ڈاؤن کھولتے ہیں، تو یہ ٹرگر کے بالکل نیچے اسی چوڑائی کے ساتھ ظاہر ہوتا ہے اور موجودہ قدر کے آگے ایک چیک مارک ظاہر ہوتا ہے۔

عوامی پک اپ ٹائم ڈراپ ڈاؤن

ایک وقت منتخب کریں اور دیکھیں کہ آیا ٹرگر اپ ڈیٹ ہوتا ہے۔ "Get My Cup” کے الفاظ پر ٹیپ کریں اور دیکھیں کہ آیا سوئچ ٹوگل ہوتا ہے۔ نام کی فیلڈ میں داخل ہوں اور یقینی بنائیں کہ آپ کا کی بورڈ اس کا احاطہ نہیں کر رہا ہے۔ اگر ڈراپ ڈاؤن کھلتا ہے اور کچھ ظاہر نہیں ہوتا ہے۔ PortalHost.

مرحلہ 8: بل کے اجزاء کی تعمیر

بل کے اجزاء بہت چھوٹے ہیں، لیکن یہاں دو تفصیلات پر خصوصی توجہ دینے کی ضرورت ہے۔ یہ خاص طور پر درست ہے اگر کوئی ترتیب میں ترمیم کرنے کے دوران اقدار بدل جائے۔

بنانا components/cart/bill.tsx:

import { Separator } from '@/components/ui/separator';
import { Text } from '@/components/ui/text';
import { cn } from '@/lib/utils';
import { money } from '@/lib/menu';
import { View } from 'react-native';

type BillProps = {
  subtotal: number;
  discount: number;
  tax: number;
  total: number;
};

function Line({ label, value, tone }: { label: string; value: number; tone?: 'muted' | 'credit' }) {
  return (
    
      {label}
      
        {money(value)}
      
    
  );
}

export function Bill({ subtotal, discount, tax, total }: BillProps) {
  return (
    
      
      {discount > 0 ?  : null}
      
      
      
        Total
        {money(total)}
      
    
  );
}

سب سے پہلے tabular-nums تمام نمبروں تک۔ متناسب فونٹس میں دائیں سیدھ میں کی گئی قیمتیں اعشاریہ پوائنٹ کے ساتھ منسلک نہیں ہوتی ہیں۔ tabular-nums اس میں ترمیم کریں جیسا کہ آپ آرڈر لائن میں کریں گے۔

دوسرا، رعایت کی قطاریں صرف تب دکھائیں جب رعایت لاگو ہو۔ کوڈ ہے۔ {discount > 0 ? : null} اس کے بجائے {discount && }. اگر JSX میں بائیں طرف && نمبر یہ ہیں۔ 0رد عمل 0. React Native میں Text جزو میں ایک خرابی پیش آگئی۔ Text strings must be rendered within a component. بولین ٹیسٹ کا استعمال اور واپس کرنا null یہ کیڑے کی ایک پوری قسم سے بچتا ہے۔

اسے جانچنے کے لیے، Self Cup سوئچ کو آن کریں اور نیچے سکرول کریں۔ نہیں میرا اپنا کپ ایک لائن ظاہر ہونی چاہئے۔ -$0.25ٹیکسوں کو اس سے تبدیل کرنا چاہیے: $1.54 کو $1.52اس کی وجہ یہ ہے کہ ٹیکسوں کا حساب رعایتی ذیلی ٹوٹل پر کیا جاتا ہے۔

سیلف کپ ڈسکاؤنٹ کے ساتھ انوائس لاگو ہے۔

کل تبدیلیاں درج ذیل ہیں: $20.19 کو $19.92یہ 25 سینٹ سے 27 سینٹ کم ہے۔ اضافی دو سینٹ کم ٹیکسوں سے آتے ہیں۔

مرحلہ 9: ایک مستحکم ادائیگی بار بنائیں

میں چاہتا ہوں کہ ٹوٹل اور چیک آؤٹ کے بٹن اس وقت نظر آتے رہیں جب صارف کارٹ میں اسکرول کر رہا ہو۔ ویب پر، آپ کر سکتے ہیں: position: sticky اور bottom: 0. React Native اس طرح کام نہیں کرتا، لیکن آپ ایک سادہ فلیکس لے آؤٹ کے ساتھ وہی نتیجہ حاصل کر سکتے ہیں۔

آپ Flexbox کا استعمال کرکے وہی نتیجہ حاصل کرسکتے ہیں۔ یہ مرحلہ 5 سے دوبارہ ترتیب ہے۔


  {/* content */}
  

بیرونی View یہ ایک کالم ہے۔ کہ ScrollView ہے flex-1لہذا یہ باقی تمام اونچائی کو استعمال کرتا ہے۔ چیک آؤٹ بار اس کا بھائی ہے جو اس کے پیچھے رکھا گیا ہے، لہذا یہ نیچے ہے اور اسکرول نہیں کرتا ہے۔ کسی پوزیشننگ یا زیڈ انڈیکس کی ضرورت نہیں ہے۔

ہوم انڈیکیٹر پروسیسنگ

ہوم بٹن کے بغیر فون پر، نیچے والے 34 یا اس سے زیادہ سسٹم سے تعلق رکھتے ہیں۔ وہاں پینٹ کیے گئے بٹنوں کو دبانا مشکل یا ناممکن ہے۔ اس کو روکنے کے لیے، نیچے کے محفوظ علاقے میں پیڈنگ شامل کریں۔

const insets = useSafeAreaInsets();


نیچے کی پیڈنگ ان لائن ہے۔ style اسے کلاس کے بجائے استعمال کیا جاتا ہے کیونکہ اس کی قیمت ڈیوائس پر منحصر ہوتی ہے اور یہ صرف اس وقت معلوم ہوتی ہے جب ایپ چلتی ہے۔ استعمال کریں className اور style ایک جیسے عناصر کے ساتھ کوئی مسئلہ نہیں ہے، اگر یہ ارادہ ہے.

ایکشن بار کے بغیر آلات اور براؤزر ٹیبز پر، insets.bottom 0 تو شامل کریں۔ 12 یہ بار کو تنگ نظر آنے سے روکتا ہے۔

اس سے پورا جزو بنائیں: components/cart/checkout-bar.tsx. اس میں مرحلہ 10 میں بیان کردہ تصدیقی ڈائیلاگ شامل ہے۔

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { money } from '@/lib/menu';
import * as Haptics from 'expo-haptics';
import { ArrowRightIcon } from 'lucide-react-native';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

type CheckoutBarProps = {
  total: number;
  itemCount: number;
  pickupLabel: string;
  name: string;
  onConfirm: () => void;
};

export function CheckoutBar({ total, itemCount, pickupLabel, name, onConfirm }: CheckoutBarProps) {
  const insets = useSafeAreaInsets();
  const empty = itemCount === 0;

  function confirm() {
    // expo-haptics ships a web implementation that falls back to navigator.vibrate,
    // so this needs no platform guard.
    Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
    onConfirm();
  }

  return (
    
      
        
          {empty ? 'Nothing in the cart' : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`}
        
        {money(total)}
      

      
        
          
        
        
          
            Send this to the counter?
            
              {`${money(total)} for ${name.trim() || 'pickup'}, ${pickupLabel.toLowerCase()}. We start making it right away, so it cannot be cancelled after this.`}
            
          
          
            
              Not yet
            
            
              Send it
            
          
        
      
    
  );
}

پتلی سرحدوں کے بارے میں ایک نوٹ

border-t ایک منطقی نقطہ کی موٹائی کے ساتھ ایک لکیر کھینچتا ہے۔ یہ زیادہ تر جدید اسکرینوں پر 2 یا 3 فزیکل پکسلز ہیں۔ یہ اصل 1-پکسل لائن کے آگے بھاری لگتا ہے۔ مقامی کا رد عمل StyleSheet.hairlineWidth یہ سب سے پتلی لائن فراہم کرتا ہے جسے آپ اسکرین پر کھینچ سکتے ہیں، اور ٹیمپلیٹ اس لائن کو ٹیل ونڈ سے منسلک کرنے کے لیے استعمال کرتا ہے۔ global.css:

--spacing-hairline: hairlineWidth();

یہ اضافہ کرتا ہے۔ hairline ٹیل ونڈ کا وقفہ پیمانہ h-hairline ایک فزیکل سیپریٹر لائن فراہم کرتا ہے جو 1 پکسل لمبی ہے۔ عام غلطیاں سیکشن ایک ایسے مسئلے کی وضاحت کرتا ہے جس کا آپ کو سامنا ہو سکتا ہے اگر آپ بعد میں اسے اوور رائڈ کے طور پر استعمال کرنے کی کوشش کریں۔

یہ وہیں چیک آؤٹ کاؤنٹر ہے۔ اسکرین کے نیچے تک سکرول کریں اور یقینی بنائیں کہ آپ بلز کے تحت ہیں۔

نیچے سکرول کریں اور نیچے ادائیگی بار کو تھپتھپائیں۔

جب میں اوپر کی طرف واپس سکرول کرتا ہوں تو بار اسی پوزیشن میں رہتا ہے۔

اوپر سکرول کریں اور نیچے ادائیگی بار دیکھیں

نشان والے فون پر، بٹن ایکشن بار کے اوپر ہونا چاہیے۔ اگر آپ تمام آئٹمز کو ہٹانے کے لیے کوڑے دان کے بٹن کا استعمال کرتے ہیں، تو بٹن غیر فعال ہو جائے گا اور لیبل پر یہ کہنا چاہیے کہ "آپ کی کارٹ میں کچھ نہیں ہے۔”

مرحلہ 10: تصدیقات شامل کریں اور اسکرین کو صاف کریں۔

ادائیگی بار میں پہلے سے ہی ایک تصدیقی ڈائیلاگ شامل ہے۔ پوری اسکرین کو ایک ساتھ رکھنے سے پہلے، React Native نفاذ اور ویب ورژن کے درمیان دو فرق ہیں جنہیں آپ کو پہلے دیکھنا چاہیے۔

1. بھی PortalHost

iOS پر اسے اس طرح لپیٹ دیا گیا ہے: FullWindowOverlay سے react-native-screensلہذا، یہ مرکزی نیویگیشن ہیڈر کے نیچے کی بجائے اوپر بنایا گیا ہے۔ اندرونی طور پر کارروائی کی گئی۔ alert-dialog.tsxیہی وجہ ہے کہ وہ فائلیں بظاہر غیر متعلقہ چیزیں درآمد کر رہی ہیں۔

AlertDialogFooter استعمال کریں flex-col-reverse یہ صرف درج ذیل مقامات پر ایک قطار بن جاتی ہے: sm: بریک پوائنٹ۔ آپ کے فون پر، آپ کا بنایا ہوا آخری بٹن سب سے اوپر ظاہر ہوتا ہے۔ اگر آپ پہلے کینسل اور ایکشن سیکنڈ لکھتے ہیں تو اوکے کا بٹن وہیں رکھا جائے گا جہاں آپ کا انگوٹھا ہے، اور کینسل بٹن اس کے نیچے رکھا جائے گا۔

تصدیقی ڈائیلاگ باکس

ہیپٹکس

ایک بار جب گاہک آرڈر کی تصدیق کرتا ہے، confirm فنکشن کال Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success). یہ معاون آلات کو ایک مختصر کامیابی کی نبض فراہم کرتا ہے۔

چونکہ براؤزرز میں مقامی ایپس کی طرح ہیپٹک سپورٹ نہیں ہے، اس لیے آپ ان سے توقع کر سکتے ہیں کہ وہ اس کال کو پلیٹ فارم چیک میں لپیٹ دیں۔ اس صورت میں، expo-haptics اس میں پہلے سے ہی ایک ویب نفاذ شامل ہے جو استعمال کرتا ہے: navigator.vibrateiOS سفاری کی جگہ لے لیتا ہے۔ اگر براؤزر ہیپٹکس فراہم نہیں کرسکتا ہے، تو کال کچھ نہیں کرتی، لہذا کسی اضافی پلیٹ فارم کی جانچ کی ضرورت نہیں ہے۔

notificationAsync اس سے مراد کامیابی یا ناکامی جیسے نتائج ہیں۔ impactAsync جسمانی تصادم کے لیے، selectionAsync آپ کو بس اختیارات کی فہرست میں آگے بڑھنا ہے۔ اگر آپ غلط استعمال کرتے ہیں تو آپ کو کوئی غلطی نہیں ملے گی، لیکن یہ تھوڑا سا محسوس ہوگا۔

مکمل سکرین

اب آپ مرکزی سکرین پر سب کچھ جمع کر سکتے ہیں۔ یہاں ہم ریاست کو برقرار رکھتے ہیں اور اس کا استعمال کرتے ہوئے بل کا حساب لگاتے ہیں: billForہر جزو کو مطلوبہ اقدار اور کال بیکس منتقل کریں۔

جب آپ آرڈر دیتے ہیں تو اسے اس طرح ترتیب دیا جاتا ہے۔ orderNumberاس کے بجائے، ایک تصدیقی پیغام اسکرین پر ظاہر ہوگا۔ اس تصدیق میں کہیں بھی تشریف لے جانے کے بجائے، ہم ہیڈر کے عنوان کو "آرڈر بھیجے گئے” میں تبدیل کر دیتے ہیں، اس طرح پورا بہاؤ ایک فائل اور ایک راستے میں رہتا ہے۔

تبدیلی app/index.tsx کے ساتھ:

'@/comComponents/cart/bill' سے { بل } حاصل کریں۔ '@/comComponents/cart/checkout-bar' سے { CheckoutBar } درآمد کریں؛ '@/comComponents/cart/order-row' سے { OrderRow } درآمد کریں؛ '@/comComponents/cart/pickup-card' سے { PickupCard } حاصل کریں۔ '@/comComponents/ui/button' سے { بٹن } درآمد کریں۔ درآمد کریں {Card, CardContent, CardDescription, CardHeader, CardTitle } سے '@/comComponents/ui/card'؛ '@/comComponents/ui/icon' سے { icon } درآمد کریں۔ '@/comComponents/ui/separator' سے { Separator } درآمد کریں؛ '@/comComponents/ui/text' سے { text } درآمد کریں۔ درآمد کریں { billFor, INITIAL_CART, PICKUP_TIMES, CartItem type} '@/lib/menu' سے؛ 'expo-router' سے {stack} حاصل کریں۔ درآمد کریں { CircleCheckIcon, MoonStarIcon, SunIcon} 'lucide-react-native' سے؛ 'react' سے React میں * درآمد کریں۔ 'react-native' سے { ScrollView, View } درآمد کریں؛ درآمد کریں { Uniwind, useUniwind } 'uniwind' سے؛ const SCREEN_OPTIONS = { عنوان: 'آرڈر'، ہیڈر رائٹ: () => }; ایکسپورٹ ڈیفالٹ فنکشن کارٹ اسکرین () { const [items, setItems] = response.useState(INITIAL_CART)؛ const [pickupValue, setPickupValue] = React.useState(PICKUP_TIMES[0]قدر) const [name, setName] = React.useState(''); const [ownCup, setOwnCup] = React.useState(false); const [orderNumber, setOrderNumber] = response.useState(آپ)؛ const بل = بل کے لیے (آئٹمز، اپنا کپ)؛ const itemCount = items.reduce((count, item) => count + item.qty، 0)؛ const PickupLabel = PICKUP_TIMES.find((time) => time.value === PickupValue)?.label ?? PICKUP_TIMES[0]برانڈ functionchangeQty(id: string، quantity: number) { setItems((current) => مقدار <= 0
        ? current.filter((item) => item.id !== id) : current.map((item) => (item.id === id ? { ...item, qty } : item)) ); } فنکشن placeOrder() { setOrderNumber(`A-${Math.floor(Math.random() * 90 + 10)}`); } فنکشن اسٹارٹ اوور() { سیٹ آئٹمز(INITIAL_CART)؛ setOwnCup(false)؛ سیٹ نام ('')؛ سیٹ پک اپ ویلیو(PICKUP_TIMES[0]قدر) setOrderNumber(null)؛ } if (orderNumber) { واپس کریں (<>
        
        
          
          
            
              
              
              
              ہم اس میں ہیں
            
            
              
              
              
              {`${name.trim() || کے لیے ${orderNumber} آرڈر کریں۔ 'تلاش کریں'}۔ کاؤنٹر کے آخری سرے پر آئیں اور کپ پر اپنا نام تلاش کریں۔`}
            
          
          
        
      
      
      
      >) } واپسی (<>
      
      
        
          
            
              فرن ووڈ کافی
              112 میرین ڈرائیو، کاؤنٹر پک اپ
            
            
              
              
              
              {items.length === 0؟ (
                آپ کی شاپنگ کارٹ خالی ہے۔ کچھ گرم شامل کریں۔
              ) : null} {items.map((item, index) => (
                
                  {انڈیکس > 0؟  :null}
                  
                
              
              
              
              ))}
            
          

          
            
              تلاش کریں
            
            
              
            
          

          
            
              بل
            
            
              
            
          
        

        
      
    
    
    
    >) } const THEME_ICONS = { روشنی: SunIcon، سیاہ: MoonStarIcon، }؛ فنکشن ThemeToggle() { const { تھیم } = useUniwind(); واپسی (
    
  );
}

Notice that pickupLabel is the fix from Step 7 in use. The screen looks up the label from PICKUP_TIMES and passes a plain string to CheckoutBar, so CheckoutBar never touches an Option object or the incorrect web label.

To test it, place an order. The dialog should show your name and the pickup time you chose (such as "in 30 minutes”), not the raw value 30. Confirm the order, and you should see the confirmation screen:

The order sent screen

Tap "Start another order” and the cart returns to its original state.

The checkout screen is now working, and each part is kept in its own component. You can use the same approach when you build other screens: keep the component focused, pass the data it needs through props, and let the shared theme handle the styling.

Step 11: Add Dark Mode

We already added the theme toggle in Step 10. Here’s the component again so we can look at how it switches between light and dark mode:

import { Uniwind, useUniwind } from 'uniwind';

function ThemeToggle() {
  const { theme } = useUniwind();

  return (
    
  );
}

Uniwind.setTheme changes the values behind every token, so everything that uses bg-card, text-muted-foreground, and similar classes updates at once. useUniwind subscribes the component to the current theme, so the icon changes too.

This code also uses the ios: and web: prefixes. Uniwind supports platform variants, so web:mx-4 only adds margin in the browser, and ios:size-9 only applies on iOS. It’s similar to Platform.select, but it stays inside the class string.

Three things aren’t handled by setTheme, and if you forget them, you end up with a screen that’s only partly dark. The template already sets up the first two.

React Navigation takes its colors from a plain object, so the root layout needs to pass it the right one:

const { theme } = useUniwind();


2. The Status Bar

The clock and battery icons are drawn by the operating system, so you need to tell it which style to use. Without this, you can get dark text on a dark bar.


3. Saving the Choice

Uniwind.setTheme only applies to the current session. The next time the app starts, it follows the system setting again. To keep the user’s choice, save it with expo-secure-store or AsyncStorage and call setTheme when the app starts. You can call setTheme('system') to go back to following the operating system.

When you tap the header icon, the cards, borders, inputs, switch, header, and status bar should all change together. You can explore more ready-made shadcn blocks if you want examples of how these components are used in larger interfaces:

The screen in dark mode

Look for anything that stays light while the rest goes dark. A light header usually means the ThemeProvider is not receiving the new NAV_THEME.

Step 12: Create Your Own Theme

The screen is currently using the default gray theme. Let’s change it to a coffee-inspired palette with warm cream backgrounds and brown accents. This will also help us see why the palette needs to be updated in both global.css and lib/theme.ts.

Update the Tokens

Replace the @layer theme block in global.css with this coffee palette:

@layer theme {
  :root {
    @variant light {
      --color-background: oklch(0.98 0.008 75);
      --color-foreground: oklch(0.24 0.02 60);
      --color-card: oklch(1 0.003 90);
      --color-card-foreground: oklch(0.24 0.02 60);
      --color-popover: oklch(1 0.003 90);
      --color-popover-foreground: oklch(0.24 0.02 60);
      --color-primary: oklch(0.44 0.06 45);
      --color-primary-foreground: oklch(0.99 0.01 85);
      --color-secondary: oklch(0.93 0.03 80);
      --color-secondary-foreground: oklch(0.35 0.05 45);
      --color-muted: oklch(0.95 0.012 80);
      --color-muted-foreground: oklch(0.52 0.02 60);
      --color-accent: oklch(0.92 0.04 78);
      --color-accent-foreground: oklch(0.32 0.05 45);
      --color-destructive: oklch(0.577 0.245 27.325);
      --color-border: oklch(0.89 0.02 75);
      --color-input: oklch(0.89 0.02 75);
      --color-ring: oklch(0.44 0.06 45);
    }

    @variant dark {
      --color-background: oklch(0.17 0.012 60);
      --color-foreground: oklch(0.96 0.008 85);
      --color-card: oklch(0.22 0.016 62);
      --color-card-foreground: oklch(0.96 0.008 85);
      --color-popover: oklch(0.22 0.016 62);
      --color-popover-foreground: oklch(0.96 0.008 85);
      --color-primary: oklch(0.85 0.09 75);
      --color-primary-foreground: oklch(0.22 0.03 55);
      --color-secondary: oklch(0.29 0.022 62);
      --color-secondary-foreground: oklch(0.94 0.01 85);
      --color-muted: oklch(0.27 0.018 62);
      --color-muted-foreground: oklch(0.72 0.015 75);
      --color-accent: oklch(0.31 0.03 65);
      --color-accent-foreground: oklch(0.95 0.01 85);
      --color-destructive: oklch(0.704 0.191 22.216);
      --color-border: oklch(1 0 0 / 12%);
      --color-input: oklch(1 0 0 / 15%);
      --color-ring: oklch(0.85 0.09 75);
    }
  }
}

If you don’t want to choose thirty colors by hand, you can generate a theme with the shadcn theme generator or tweakcn. Both export CSS variables, but you need to make two changes before the output works in this project:

  1. Rename each variable to include the --color- prefix. For example, --primary becomes --color-primary.
  2. Move the :root block inside @variant light, and the .dark block inside @variant dark.

Also remove --radius from what you paste, because the template already defines it along with the derived sizes. Remove any --shadow-* variables too, since nothing in the registry reads them.

Update lib/theme.ts

After you save and reload, the result looks like this:

The new theme with a white header

The page is cream, the button is brown, and the badges have the right color, but the header is still white with a visible line under it. The reason is that React Navigation draws the header using lib/theme.ts, and that file still has the old palette. Nothing is broken and nothing warns you, so this mistake is easy to miss. If you’re designing the same interface in Figma, the shadcn Figma resources can also help you keep the design and implementation aligned.

Update lib/theme.ts to match:

import { DarkTheme, DefaultTheme, type Theme } from 'expo-router/react-navigation';

export const THEME = {
  light: {
    background: '#fcf8f3',
    foreground: '#261d16',
    card: '#fffffd',
    primary: '#6e4836',
    border: '#e3d9cd',
    destructive: '#e7000b',
    /* the rest of the list */
  },
  dark: {
    background: '#130e0a',
    foreground: '#f4f1ec',
    card: '#201913',
    primary: '#f1c68b',
    border: '#3a3129',
    destructive: '#ff6467',
    /* the rest of the list */
  },
};

export const NAV_THEME: Record<'light' | 'dark', Theme> = {
  light: {
    ...DefaultTheme,
    colors: {
      background: THEME.light.background,
      border: THEME.light.border,
      card: THEME.light.background,
      notification: THEME.light.destructive,
      primary: THEME.light.primary,
      text: THEME.light.foreground,
    },
  },
  dark: {
    ...DarkTheme,
    colors: {
      background: THEME.dark.background,
      border: THEME.dark.border,
      card: THEME.dark.background,
      notification: THEME.dark.destructive,
      primary: THEME.dark.primary,
      text: THEME.dark.foreground,
    },
  },
};

This isn’t a direct copy of global.css, so three points need explanation:

  • Use hex colors instead of oklch(): Uniwind converts oklch() for your classes, but React Navigation does not, so give it a format that React Native can read directly. You can convert a color with any online converter, or paste the oklch() value into your browser’s developer tools color picker and read the hex value.
  • Map React Navigation’s card to your background, not your card. The two names look the same but mean different things. In shadcn, card is a raised surface on top of the page. In React Navigation, card is the background of the screen and header. If you map them by name, the header ends up a shade lighter than the page. You won’t notice this with the stock template because it uses the same value for card and background. It only shows up once you create a theme where cards stand out from the page, like this one.
  • Use a solid color in place of a transparent border. In dark mode, --color-border is oklch(1 0 0 / 12%), which is white at 12% opacity. A navigation theme needs a solid color, so work out what that border looks like on your background and use that value.

After this change, the header and page match in both light and dark mode, and you didn’t edit any component files:

The final theme with a matching header

The final theme in the other mode

Switch between light and dark a few times and watch the header. If it’s a shade lighter or darker than the page, one of the values in lib/theme.ts is still from the old palette.

Common Mistakes and How to Fix Them

If you’re coming from web development, some of these differences may catch you off guard. I’ve collected the issues covered in the tutorial in the table below, along with the fix for each one. You can refer back to it when something doesn’t behave as expected.

What you do What happens The fix
Medium Red screen: "Text strings must be rendered within a .” Wrap the text: Medium. The same applies to Button.
{count && } when count is 0 The same error, caused by a stray 0 {count > 0 ? : null}
space-y-3 on a column Nothing happens and there is no warning Use gap-3 on the parent
className="p-4" on a ScrollView Padding is clipped and gaps are ignored Use contentContainerClassName
truncate on a long name The text isn’t truncated and the price is pushed off screen Use numberOfLines={1}
Using flex and expecting a row Items stack vertically React Native defaults to a column, so add flex-row
hover:bg-accent Nothing happens on a device Use active: for the pressed state
Deleting PortalHost from the layout Dropdowns open into nothing, with no error Put it back
A Select near the bottom of the screen The last option is under the home indicator Pass insets to SelectContent
Showing option.label from onValueChange Correct on native, but the raw value on the web Look up the label from your own data
A bottom bar with only p-4 The button is under the gesture bar Use paddingBottom: insets.bottom + 12 in style
28-point stepper buttons Hard to tap with a thumb Add hitSlop={8}
Changing only global.css for a new theme The header keeps the old colors Update lib/theme.ts too
without alt TypeScript won’t compile alt is required, and it becomes the accessibility label
rounded-2xl on Avatar only A circular fallback inside rounded corners Repeat it on AvatarFallback
Guarding haptics with Platform.OS !== 'web' A working web fallback is removed expo-haptics already handles the web
Right-aligned prices in a normal font Digits shift whenever the number changes Use tabular-nums, which also works on native
Both height classes are kept See below

The last row needs more explanation, because it looks like a bug in cn(), but it’s not.

cn() uses tailwind-merge, which resolves conflicts between classes. It knows the default Tailwind scale, but it doesn’t know about custom values you declare in global.css. The value hairline comes from --spacing-hairline: hairlineWidth(), so tailwind-merge can’t tell that h-hairline and h-[1px] ایک ہی پراپرٹی کو سیٹ کرنا دونوں کو محفوظ رکھتا ہے:

cn('h-[1px] w-full', 'h-hairline my-1')  // 'h-[1px] w-full h-hairline my-1'
cn('h-[1px] w-full', 'h-2 my-1')         // 'w-full h-2 my-1'

دوسرا نتیجہ درست ہے۔ پہلے میں، سی ایس ایس کی ترتیب کا تعین کیا جاتا ہے کہ کون سی اونچائی جیتتی ہے۔ اس ٹیوٹوریل میں دیگر اوور رائیڈز میں شامل ہیں: size-14 پر size-8یہ نام ہے، تو یہ صحیح کام کرتا ہے۔ tailwind-merge میں اسے پہچانتا ہوں۔ اگر آپ چاہتے ہیں کہ وہ حسب ضرورت پیمانوں کو سمجھیں، تو آپ انہیں یہ استعمال کرکے سکھا سکتے ہیں: extendTailwindMerge کو lib/utils.ts. بصورت دیگر، بے ترتیب قدر کا استعمال کریں یا جزو فائل میں براہ راست ترمیم کریں۔

سٹیپر بٹنوں کے لیے ایک متعلقہ مثال بھی ہے۔ وہ گزر جاتے ہیں size-7 کو Button جس کے آئیکن کا سائز ہے۔ h-10 w-10 sm:h-9 sm:w-9:

cn('h-10 w-10 sm:h-9 sm:w-9', 'size-7 rounded-full')
// 'sm:h-9 sm:w-9 size-7 rounded-full'

size-7 بیس کی جگہ لے لیتا ہے۔ h-10 w-10لیکن دونوں sm: کلاسیں وہی رہیں گی۔ کیونکہ tailwind-merge ریسپانسیو ویریئنٹس کو مختلف کلاسز سمجھیں۔ فون پر کوئی فرق نہیں ہے۔ sm: یہ 640px سے شروع ہوتا ہے اور فون اس سے کم ہوتے ہیں۔ تاہم، ڈیسک ٹاپ براؤزرز میں، بٹن 36 پوائنٹس تک بڑھ جاتا ہے۔

نتیجہ

اس ٹیوٹوریل میں، ہم نے React Native Reusables، Expo، اور Uniwind کا استعمال کرتے ہوئے ایک کافی شاپ شاپنگ کارٹ اور چیک آؤٹ اسکرین بنایا ہے۔ آرڈر کی قطار، پک اپ کے اختیارات، انوائس کا خلاصہ، چسپاں ادائیگی بار، اور تصدیقی ڈائیلاگ شامل کیا گیا۔ ہم نے یہ بھی دیکھا کہ کس طرح رجسٹری اور CLI آپ کے پروجیکٹ میں اجزاء کا سورس کوڈ شامل کرتے ہیں، اور تھیم ٹوکنز آپ کے ایپ کے رنگوں کو کیسے کنٹرول کرتے ہیں۔

جب کوئی چیز توقع کے مطابق کام نہیں کر رہی ہے تو اس کی عادت ڈالنے کے قابل ایک چیز جزو کے ماخذ کی جانچ کرنا ہے۔ جزو آپ کے پروجیکٹ میں ہے، لہذا آپ اسے کھول سکتے ہیں اور دیکھ سکتے ہیں کہ یہ کیسے کام کرتا ہے۔ اس طرح ہم نے ویب اور مقامی کے درمیان سلیکٹ لیبلز میں فرق کو سمجھا۔ یہ حسب ضرورت کو بھی آسان بناتا ہے کیونکہ آپ دیکھ سکتے ہیں کہ اس کے ارد گرد کام کرنے کے بجائے اس کے کن حصوں کو تبدیل کرنے کی ضرورت ہے۔

آپ ہمارے GitHub ذخیرے میں مکمل پروجیکٹ تلاش کر سکتے ہیں۔ اگر آپ مسائل کا شکار ہیں یا اپنے کوڈ کا تیار شدہ سکرین سے موازنہ کرنا چاہتے ہیں تو ریپوزٹری کو کلون کریں اور عمل درآمد کو چیک کریں۔ اگر آپ اپنے اگلے پروجیکٹ کے لیے مزید مکمل نقطہ آغاز تلاش کر رہے ہیں، تو shadcn ٹیمپلیٹ پر ایک نظر ڈالیں۔

میں نے فون کے سائز کے ویو پورٹ میں React Native Web کا استعمال کرتے ہوئے اسکرین شاٹس حاصل کیے۔ یہی پروجیکٹ iOS اور Android پر بھی چلتا ہے۔ پلیٹ فارم کے فرق کی وضاحت متعلقہ سیکشنز میں کی گئی ہے، بشمول مرحلہ 7 میں لیبل اور پورٹل ہوسٹ کا انتخاب، مرحلہ 9 میں سیف زون ہینڈلنگ، اور مرحلہ 10 میں ہیپٹکس۔

وسائل

مقامی دوبارہ قابل استعمال پر رد عمل ظاہر کریں۔

اوزار استعمال کیا جاتا ہے

API استعمال کیا گیا۔

اوپر تک سکرول کریں۔