أين يمكن استخدام مكوّنات الواجهة الأمامية
يمكن عرض مكوّنات الواجهة الأمامية في ثلاثة مواقع داخل Twenty:- اللوحة الجانبية — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر.
- الويدجت (لوحات المعلومات وصفحات السجلات) — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية.
- App settings — يتم تعريفها باستخدام
defineSettingsFrontComponent()، حيث يُعرَض مكوّن الواجهة الأمامية كقسم داخل علامة تبويب Settings في التطبيق، ليحل محل واجهة مستخدم تكوين المتغيرات الافتراضية.
- إقرانه مع عنصر قائمة الأوامر — يقوم بتسجيله في قائمة الأوامر (Cmd+K) واختياريًا كإجراء سريع مُثبّت.
- تضمينه كويدجت في تخطيط صفحة — يضعه في صفحة تفاصيل السجل أو لوحة المعلومات.
- عرِّفه باستخدام
defineSettingsFrontComponent()— يعرضه كقسم داخل علامة تبويب Settings في التطبيق، ليحل محل واجهة مستخدم تكوين المتغيرات الافتراضية.
مثال أساسي
أسرع طريقة لرؤية مكوّن الواجهة الأمامية أثناء العمل هي إقرانه معdefineCommandMenuItem، بحيث يظهر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
yarn twenty dev (أو تشغيل الأمر لمرة واحدة yarn twenty apply)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة:

حقول التكوين
وضع مكوّن أمامي على صفحة
إضافةً إلى الأوامر، يمكنك تضمين مكوّن أمامي مباشرةً في صفحة سجل عبر إضافته كودجت في تخطيط صفحة. لمزيد من التفاصيل، راجع تخطيطات الصفحات.مكوّن إعدادات مخصص
لاستبدال واجهة مستخدم تكوين المتغيرات المولَّدة تلقائيًا في علامة تبويب Settings في تطبيقك بمكوّنك الخاص، عرِّفه باستخدامdefineSettingsFrontComponent بدلًا من defineFrontComponent. يستخدم نفس حقول الإعدادات (باستثناء isHeadless، الذي لا يُقبل لأن مكوّن الإعدادات يعرض دائمًا واجهة مستخدم مرئية)، ويُحدِّد أيضًا هذا المكوّن باعتباره واجهة إعدادات التطبيق.
يتم عرض المكوّن كقسم داخل علامة تبويب الإعدادات، وليس كبديل لعلامة التبويب بالكامل. الأقسام التي يديرها نظام Twenty — الترقية التلقائية، و App URL، والاتصالات — يتم عرضها دائمًا أعلاه ولا يمكن تجاوزها بواسطة التطبيق.
src/front-components/app-settings.tsx
عديم الرأس مقابل غير عديم الرأس
تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيارisHeadless:
غير عديم الرأس (افتراضي) — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون isHeadless تساوي false أو يتم تجاهلها.
عديم الرأس (isHeadless: true) — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه.
src/front-components/sync-tracker.tsx
null، فإن Twenty يتخطّى عرض حاوية له — ولن تظهر مساحة فارغة في التخطيط. لا يزال لدى المكوّن إمكانية الوصول إلى جميع الخطافات وواجهة برمجة الاتصال مع المضيف.
مكوّنات Command في SDK
توفر حزمةtwenty-sdk أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء.
استوردها من twenty-sdk/front-component:
Command— يشغّل رد نداء غير متزامن عبر الخاصيةexecute.CommandLink— ينتقل إلى مسار في التطبيق. الخصائص:to،params،queryParams،options.CommandModal— يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداءexecute. الخصائص:title،subtitle،execute،confirmButtonText،confirmButtonAccent.CommandOpenSidePanelPage— يفتح صفحة في اللوحة الجانبية. الـ props تعتمد علىpage— على سبيل المثال،ViewRecordيأخذrecordId+objectNameSingular(بالإضافة إلى معرّفtabاختياري لفتح السجل في تبويب معيّن)، بينما الصفحات الأخرى تأخذpageTitle+pageIcon.
Command لتشغيل إجراء من قائمة الأوامر:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
CommandModal لطلب التأكيد قبل التنفيذ:
src/front-components/delete-draft.tsx
CommandOpenSidePanelPage لفتح السجل الحالي في اللوحة الجانبية على تبويب معيّن. tab هو معرّف تبويب تخطيط الصفحة (تستخدم التخطيطات الافتراضية معرّفات مثل company-tab-emails أو company-tab-timeline؛ وتستخدم التخطيطات المخصّصة معرّف التبويب نفسه). إذا لم يكن المعرّف موجودًا في تخطيط السجل، فسيتم فتح التبويب الافتراضي بدلًا منه:
src/front-components/open-company-emails.tsx
استدعاء دالة منطقية
تعمل مكونات الواجهة الأمامية على جانب المتصفح داخل Web Worker مُعزَل داخل iframe ذو origin غير شفاف، بينما تعمل الدوال المنطقية على جانب الخادم. لا توجد استدعاءات مباشرة ضمن العملية بين الاثنين — بدلاً من ذلك، يصل مكون الواجهة الأمامية إلى الدالة المنطقية عبر HTTP. يتم الوصول إلى الدالة المنطقية المُعلَنة باستخدامhttpRouteTriggerSettings عبر HTTP عند مسار التوجيه الخاص بها. يتعامل RestApiClient مع المسارات التي تبدأ بـ /s/ باعتبارها مسارات للتطبيق، ويُحوِّلها إلى عنوان الـ URL الذي تُقدَّم منه الدوال الخاصة بك، ويُجري عملية المصادقة عليها باستخدام TWENTY_APP_ACCESS_TOKEN.
على Twenty Cloud، يتم تقديم الدوال المنطقية المُفعَّلة عبر HTTP على نطاق مخصص لكل مساحة عمل عند https://\<your-workspace-subdomain>.withtwenty.com\<path>. للمتصلين الخارجيين، انسخ عنوان URL الدقيق من إعدادات HTTP trigger الخاصة بالدالة أو من علامة تبويب Settings في التطبيق.
يمكن لمكون واجهة أمامية عديم الرأس تنفيذ الاستدعاء عند التركيب عبر مكون Command، ثم إلغاء التركيب تلقائيًا:
src/front-components/sync-prs.tsx
RestApiClient هو قيمة httpRouteTriggerSettings.path الخاصة بدالة المنطق (logic function) مع إضافة البادئة /s. أبقِ isAuthRequired: true؛ فرمز TWENTY_APP_ACCESS_TOKEN الذي تُنشئه Twenty لمكوِّنك هو ما يصادق على الطلب:
src/logic-functions/fetch-prs.logic-function.ts
يتم حقن
TWENTY_APP_ACCESS_TOKEN تلقائيًا — انظر متغيرات التطبيق. نظرًا لأن متغيرات التطبيق السرية لا تُعرَض أبدًا على مكونات الواجهة الأمامية، احتفِظ بمفاتيح واجهة برمجة التطبيقات والمنطق الحساس الآخر داخل الدالة المنطقية، وليس في مكون الواجهة الأمامية.استدعاء واجهة REST API الخاصة بـ Twenty
لاستدعاء مسارات HTTP الخاصة بالتطبيق أو لقراءة سجلات Twenty وكتابتها من مكوِّن واجهة أمامية، استخدمRestApiClient من twenty-client-sdk/rest. يُرسل المسارات من نوع /s/... إلى عنوان URL الأساسي للدوال في مساحة العمل الخاصة بك، ويُرسل أي مسار آخر، بما في ذلك /rest/...، إلى TWENTY_API_URL.
تدعم
options كلًا من headers وquery (سجل لمعاملات query-string؛ يتم تخطي القيم nullish) وAbortSignal عبر signal. يتم تسلسل كائن body غير من النوع FormData إلى JSON تلقائيًا. عند حدوث 401، يقوم العميل بتحديث رمز الوصول مرة واحدة عبر المضيف ثم يعيد محاولة الطلب.
يتم تحديد عنوان URL الأساسي والرمز من بيئة التشغيل بشكل افتراضي. مرِّر معاملات تجاوز (overrides) إلى المُنشئ (constructor) عند الحاجة — على سبيل المثال في الاختبارات:
RestApiClientError يعرِّض خصائص status وstatusText وurl بالإضافة إلى body بعد تحليله (parsed):
الوصول إلى سياق وقت التشغيل
داخل مكوّنك، استخدم خطافات SDK للوصول إلى المستخدم الحالي، والسجل، ومثيل المكوّن:src/front-components/record-info.tsx
متغيرات التطبيق
متغيرات التطبيق المُعرَّفة فيdefineApplication() مع isSecret: false تكون متاحة داخل مكوّنات الواجهة عبر أداة getApplicationVariable:
src/front-components/greeting.tsx
getApplicationVariable دائمًا سلسلة نصية (أو undefined)، بغضّ النظر عن type المُعلَن للمتغيّر. تُسلسَل السلسلة النصية بشكل متسق حسب النوع (القيم المنطقية على هيئة “true” / “false”، الأعداد كسلاسل عشرية، والمصفوفات/الكائنات كـ JSON)، وهو نفس التنسيق المستخدم مع process.env في وظائف المنطق — قم بتحليلها بنفسك (Number(...)، JSON.parse(...)، === 'true'). انظر قسم أنواع المتغيرات.
متغيرات النظام التالية تكون متاحة دائمًا عبر process.env:
TWENTY_FUNCTIONS_URL
يقوم Twenty أيضًا بحقن TWENTY_FUNCTIONS_URL في مكوِّنات الواجهة الأمامية والدوال المنطقية: وهو عنوان URL الأساسي الذي تُقدَّم منه الدوال المنطقية المُفعَّلة عبر HTTP في تطبيقك.
وهو موجود لأن ذلك العنوان (URL) ليس دائمًا هو خادم Twenty نفسه. على Twenty Cloud، يتم تقديم مسارات التطبيق على نطاق مخصص لكل مساحة عمل (https://\<your-workspace-subdomain>.withtwenty.com، أو نطاق التطبيق العمومي الأساسي عندما يتم تكوينه) بحيث تعمل الاستجابات المنشأة من التطبيق على أصل (origin) معزول بدلاً من أصل تطبيق Twenty. تُقدِّم النسخ المستضافة ذاتيًا والمحلية مسارات التطبيق تحت البادئة /s على الخادم نفسه وقد لا تضبط المتغير إطلاقًا. نظرًا لاختلاف عنوان URL الأساسي حسب مساحة العمل وحسب كل نسخة، لا يمكن لشفرتك (code) أن تُضمِّنه بشكل ثابت (hard-code) — يقوم الخادم بحقن القيمة الصحيحة وقت التشغيل.
نادرًا ما تحتاج إلى قراءته مباشرة. استدعِ مساراتك عبر RestApiClient باستخدام مسار يبدأ بالبادئة /s/، وسيقوم العميل بحل عنوان URL نيابةً عنك: يزيل بادئة /s ويستخدم TWENTY_FUNCTIONS_URL كهدف، مع الرجوع إلى \<TWENTY_API_URL>/s عندما لا يكون المتغير مضبوطًا. استخدم resolveUrl('/s/\<path>') للحصول على عنوان URL مطلق بدون إرسال طلب، على سبيل المثال لاستخدامه في رابط. اقرأ المتغير مباشرةً فقط عند إنشاء عنوان URL يدويًا:
واجهة الاتصال مع المضيف
يمكن للمكوّنات الأمامية تشغيل التنقّل والنوافذ المنبثقة والإشعارات باستخدام دوال منtwenty-sdk:
فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء:
src/front-components/archive-record.tsx
العمل مع سجلات متعددة
استخدمuseSelectedRecordIds() لمعالجة عدة سجلات محددة. هذا مفيد للعمليات المجمّعة:
src/front-components/bulk-export.tsx
src/command-menu-items/bulk-export.command-menu-item.ts
الأصول العامة
يمكن للمكوّنات الأمامية الوصول إلى ملفات من دليلpublic/ للتطبيق باستخدام getPublicAssetUrl:
التنسيق
تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام:- أنماط مضمنة —
style={{ color: 'red' }} - مكوّنات Twenty UI — مكتبة المكوّنات الخاصة بـ Twenty؛ راجع استخدام مكوّنات Twenty UI أدناه
- Emotion — CSS-in-JS مع
@emotion/react - Styled-components — أنماط
styled.div - Tailwind CSS — أصناف مساعدة
- أي مكتبة CSS-in-JS متوافقة مع React
استخدام مكوّنات Twenty UI
توفّر Twenty مكتبة المكوّنات الخاصة بها على شكل حزمةtwenty-ui. يمكن للمكوّنات الأمامية استخدامه للأزرار، والوسوم، وشارات الحالة، والرقاقات، والصور الرمزية، والأيقونات، والطباعة، ورموز السمات التي تتطابق تلقائيًا مع نسق مساحة العمل الفاتح أو الداكن.
التثبيت
أضِف الحزمة إلى تطبيقك، مع تثبيتها على الإصدار الذي تأتي به نسخة Twenty لديك:twenty-ui تكون مضمّنة في المكوّن الأمامي لديك وقت البناء، لذلك تحتاج فقط إلى أن تكون تابعة (dependency) لتطبيقك — ولا يوجد ما يلزم تهيئته وقت التشغيل.
استيراد المكوّنات
استورِد من المسار الفرعي المطابق بدلًا من جذر الحزمة، حتى لا ينتهي الأمر إلا بالمكوّنات التي تستخدمها داخل حزمة التطبيق (bundle) لديك:الأيقونات
استورِد الأيقونات الفردية منtwenty-ui/icon:
IconsProvider، وuseIcons، وiconsState — لأنها تجلب مجموعة أيقونات Tabler الكاملة (عدة ميغابايت).
التنسيق ورموز السمات
تتكيّف مكوّنات Twenty UI تلقائيًا مع نسق مساحة العمل الفاتح أو الداكن — إذ يطبّق المُصيّر (renderer) مخطط الألوان النشط على المضيف، وتضبط المكوّنات ألوانها وفقًا له. لاستخدام نفس رموز التصميم في أنماطك المضمّنة (inline styles)، استدعِ الخطّافuseTheme(). يُرجِع هذا الخطّاف رموز سمة Twenty (للمسافات، والألوان، وأنصاف الأقطار، والخطوط) المرتبطة بالنسق النشِط، دون الحاجة إلى إعداد ThemeProvider في المكوّن لديك:
useTheme() خطّاف، فإنك تقرأ الرموز داخل جسم المكوّن، لذا تعكس القيم دائمًا النسق المباشر (الحالي). تُصدَّر خريطة الرموز نفسها أيضًا كثابت themeCssVariables، لكن يُفضَّل استخدام useTheme() في المكوّنات الأمامية — إذ يمكن أن يكون الثابت على مستوى الوحدة الذي يفكّ مرجعية themeCssVariables غير معرَّف أثناء استخراج بيان التطبيق (app manifest).
للتفرّع بناءً على النظام النشِط (active scheme) صراحةً، اقرأه باستخدام useColorScheme() من twenty-sdk/front-component، والذي يعيد 'light' أو 'dark'.
القيود الحالية
مكوّنات Front قيد التطوير النشط. الترسيم والتنسيق والتعامل مع الأحداث تعمل بشكل جيد. أي شيء يتجاوز مرحلة الترسيم (قياس عنصر، استدعاء دالة DOM على ref، إنشاء portal خارج الشجرة الخاصة بك، أو لمس تخزين المتصفح) مفقود أو غير مكتمل حاليًا، ومعظم هذه الحالات تفشل بصمت: لا يتم رمي استثناء، ولا يظهر خطأ TypeScript أيضًا، لأن القالب مكتوب استنادًا إلى DOM الكامل للمتصفح. إذا كان أحد هذه الأمور يعيقك، افتح تذكرة ليتم إعطاؤه أولوية.التخطيط والقياس
لا يوجد أي شيء يمكنه قياس نفسه بعد.
لذلك فإن recharts
ResponsiveContainer، و Floating UI / Popper، وvirtualization للقوائم، والسحب لتغيير الحجم لا تعمل بعد. قم بتنفيذ التخطيط في CSS بدلًا من ذلك: ورقة الأنماط الخاصة بك تصل إلى الصفحة الحقيقية، لذا فإن flexbox و grid و aspect-ratio و clamp() و @container كلها تتصرف بشكل طبيعي.
requestAnimationFrame, fetch, setTimeout و queueMicrotask تعمل بدون بادئة window.. فقط window.requestAnimationFrame(...) وما شابهها ترمي استثناء.الوصول إلى DOM
يعطيكref عنصرًا في sandbox، وليس HTMLElement.
فجوة الـ portal هي السبب في أن عناصر popover في Radix و Headless UI و MUI و react-select لا ترسُم أي شيء بشكل افتراضي. معظمها يقبل خاصية container؛ وجّهها إلى عنصر قمت بترسيمه.
الأحداث
أحداث الماوس، والمؤشر، واللمس، والسحب، ولوحة المفاتيح، والتركيز، وinput/change/submit، وscroll/wheel/contextmenu وanimationend/transitionend تمر إلى المضيف، بالإضافة إلى عدد قليل لكل عنصر: load/error على <img>، والحافظة والتكوين على <input>/\<textarea>، والوسائط على \<video>/\<audio>، وtoggle على \<details>/\<dialog>. أي شيء آخر (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, التقاط المؤشر، onLoad خارج <img>) يتم إسقاطه دون تحذير.
document.addEventListener() و window.addEventListener() تُسجِّلان بدون خطأ لكن لا تُطلقان أبدًا، وهذا هو السبب في أن السحب يتوقف بمجرد أن يغادر المؤشر العنصر الذي بدأ عليه. event.preventDefault() لا يعبر أيضًا؛ إرسال النماذج، وdragover/drop ونقرات الروابط محمية مسبقًا من أجلك.
السمات والأنماط
كل عنصر يمرّر خصائصه الخاصة إلى DOM المضيف (href على \<a>, و src/alt على <img>, و value/placeholder/disabled على <input>, وهكذا)، بالإضافة إلى مجموعة مشتركة على كل عنصر: id, className, style, title, tabIndex, role, draggable وأي سمة aria-* / data-* (مفصولة بشرطات، لذا يتم إسقاط ariaLabel). أي شيء خارج ذلك يتم تجاهله بصمت، لذا عبّر عن الحالة المخصّصة كـ data-*.
يتم حقن CSS الخاص بالمكوّن، سواءً من import './styles.css' أو CSS-in-JS أو عنصر \<style>، في وسم \<head> لصفحة المضيف بدون نطاق. لذا تتصادم أسماء الأصناف مع أسماء Twenty نفسها (قم بإضافة بادئة لها، ولا تكتب أبدًا محددًا مثل div { ... })، و @media تطابِق نافذة المتصفح بدلاً من الودجت الخاص بك (استخدم @container مع container-type الخاص بك). خصائص style المضمنة لا تتأثر.
التخزين والشبكة
localStorage وsessionStorage وIndexedDB وملفات تعريف الارتباط وواجهة برمجة تطبيقات Cache وBroadcastChannel كلها غير متاحة، لأن المكوّن يعمل في عامل (worker) بأصل غير شفاف. للاحتفاظ بالحالة، استدعِ دالة منطقية واستخدم مخزن القيم المفتاحية الخاص بها.
fetch يعمل، مع بعض التحفّظات:
- يتم تمرير الاستدعاءات إلى Twenty API ومسارات تطبيقك عبر المضيف، لذا يُفضَّل استخدام
RestApiClient. في الاستدعاءات الممرَّرة عبر الوكيل، يتم إسقاطAbortSignalوخياراتRequestInitالأخرى، ولا يتم دعم سوى الأجسام من نوعstringوURLSearchParams. - تغادر النطاقات (origins) الأخرى صندوق العزل مع
Origin: null، لذلك يجيب طرف ثالث لواجهة برمجة التطبيقات فقط إذا أرسلAccess-Control-Allow-Origin: *. استدعِها من دالة منطقية بدلًا من ذلك. fetch('/rest/people')لا يتطابق أبدًا مع Twenty API، لأن صندوق العزل ليس لديه عنوان URL للصفحة لحساب مسار نسبي بناءً عليه.
فجوات أخرى
- محتويات الملفات. يُرجِع
<input type="file">إلى معالجك بيانات تعريف الملف فقط، وليس البايتات، لذلك فإنFileReaderوعمليات الرفع غير ممكنة بعد. - حمولات السحب والإفلات. أحداث السحب تُطلق، لكن
event.dataTransferهيundefined. - الوحدات المدمجة في Node.
fsوpathوnode:cryptoتفشل في عملية البناء، لذا انقل ذلك العمل إلى دالة منطقية. Web Crypto وfetchوTextEncoderوURLمتوفرة. - يتم دائمًا إعادة عزل
\<iframe>دونallow-same-origin، لذلك يعمل التضمين الذي يعتمد على جلسته الخاصة في وضع “تسجيل الخروج”. ليس لديهonLoadأيضًا.