انتقل إلى المحتوى
TopClass UI

المزوّد

مزوّد واحد في جذر تطبيقك، مع الاتجاه والإعداد المحلي كخصائص. وجوده إلزامي، وكل ما يفعله مذكور في هذه الصفحة.

أين يوضع

في التخطيط الجذري، محيطًا بكل شيء. تأتي direction وlocale من المسار، ولا تُقرأ من DOM أبدًا.

app/[locale]/layout.tsx
import { TopClassProvider } from 'topclassui/provider'
import { ThemeScript } from 'topclassui/theme'

export default async function RootLayout({ children, params }: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  const dir = locale === 'ar' ? 'rtl' : 'ltr'
  return (
    <html lang={locale} dir={dir} data-theme="dark" suppressHydrationWarning>
      <head><ThemeScript /></head>
      <body>
        <TopClassProvider direction={dir} locale={locale}>
          {children}
        </TopClassProvider>
      </body>
    </html>
  )
}

الخصائص

الخاصية, النوع, القيمة الافتراضية, ما الذي تفعله
الخاصيةالنوعالقيمة الافتراضيةما الذي تفعله
childrenReactNode-تطبيقك.
classNamestring-أصناف للعنصر div الوحيد الذي يعرضه المزوّد، وهو جذر العزل.
direction'ltr' | 'rtl'مطلوبةاتجاه النص، من المسار. تتبعه النوافذ المنبثقة والإيماءات ومواضع العناصر.
localestringمطلوبةيختار النصوص المضمّنة ومنسّقات Intl.
labelsProviderLabels-غيّر أي نص مضمّن، ويبقى الباقي على قيمه الافتراضية.
numberingSystem'latn' | 'arab'-الأرقام في العدّادات والتواريخ. إذا تُركت، يقرّرها الإعداد المحلي، وإلا فالأرقام اللاتينية.
adaptive{ query?: string | undefined }{ query: '(width < 48rem)' }العرض الذي تنتقل تحته المكوّنات المتكيّفة إلى تخطيط الهاتف.
collectionPartial<CollectionDefaults>{ visibleRows: 7, virtualizeAbove: 200, loadMoreThreshold: 10, estimateRowHeight: 36, overscan: 6 }القيم الافتراضية لكل قائمة: الصفوف الظاهرة، والعرض الجزئي للعناصر، والتحميل صفحة بعد صفحة.
toastToastSettingsInput{ placement: 'bottom-end', limit: 3, duration: 5000, tone: 'neutral' }مكان ظهور الإشعارات، وعددها في وقت واحد، ومدّتها. يقرؤها المزوّد الخارجي وحده.

لماذا الاتجاه خاصية

قراءة document.dir داخل تأثير (effect) تعطي الخادم جوابًا والمتصفح جوابًا آخر لإطار واحد، فيظهر وميض بالاتجاه الخاطئ. أما الخاصية القادمة من التخطيط فلها القيمة نفسها في المكانين.

خطأ
useEffect(() => {
  setDir(document.documentElement.dir)
}, [])
صواب
<html dir={dir}>
  <TopClassProvider direction={dir}>

ما الذي يعرضه

مزوّد الاتجاه

النوافذ المنبثقة تقرأ الاتجاه من هنا، فتُفتح قائمة Select في الجهة الصحيحة بالعربية.

منطقة الإشعارات

منطقة حيّة واحدة، موضعها يتبع الاتجاه، مع هامش المنطقة الآمنة.

مزوّد التلميحات

تأخير مشترك، فيُفتح التلميح الثاني فورًا.

جذر العزل

عنصر div عليه isolation: isolate، فتتراكب الطبقات فوق صفحتك بلا تعارض في z-index.

ThemeScript

سكربت صغير مضمّن، يأتي أولًا في head، يقرأ التفضيل المحفوظ ويضبط data-theme قبل أول رسم. من دونه تُعرض الصفحة بالوضع الافتراضي ثم تتبدّل، فيظهر وميض بالوضع الخاطئ. السكربت نص ثابت، لذلك تستطيع سياسة أمان المحتوى (CSP) الصارمة أن تسمح به عبر البصمة أدناه.

import { ThemeScript } from 'topclassui/theme'

<head><ThemeScript /></head>
سياسة أمان المحتوى
Content-Security-Policy: script-src 'self' 'sha256-DXE/BJ+WKtAgobNXKiOEuUkho9yY2f/ky8nwg8sLL/8='

في تطبيق صفحة واحدة

في تطبيق صفحة واحدة لا يعمل أي شيء يعرضه React قبل أول رسم، لذلك يوضع السكربت نفسه في index.html فوق سكربت الوحدة: الصق نص THEME_SCRIPT من topclassui/theme داخل وسم script. وتكون القيم الأولى لـ lang وdir وdata-theme سمات ثابتة على عنصر html. وعندما يغيّر الزائر اللغة، حدّث سمات html وخصائص المزوّد في الوقت نفسه.

index.html
<!doctype html>
<html lang="ar" dir="rtl" data-theme="dark">
  <head>
    <meta charset="UTF-8" />
    <script>(function(){try{var m=["light","dark","ocean"];var t=null;try{t=localStorage.getItem('tc-theme')}catch(e){}if(!t||t==='system'||m.indexOf(t)<0){t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.dataset.theme=t}catch(e){}})()</script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
التاليمكوّنك الأول