أطلق العنان لقوة بوابات React لإنشاء نوافذ منبثقة وتلميحات جذابة وسهلة الوصول، مما يحسن تجربة المستخدم وبنية المكونات.
بوابات React: إتقان النوافذ المنبثقة والتلميحات لتحسين تجربة المستخدم
في تطوير الويب الحديث، يعد إنشاء واجهات مستخدم بديهية وجذابة أمرًا بالغ الأهمية. توفر React، وهي مكتبة جافاسكريبت شهيرة لبناء واجهات المستخدم، العديد من الأدوات والتقنيات لتحقيق ذلك. إحدى هذه الأدوات القوية هي بوابات React. تتعمق هذه المقالة في عالم بوابات React، مع التركيز على تطبيقها في بناء نوافذ منبثقة وتلميحات سهلة الوصول وجذابة بصريًا.
ما هي بوابات React؟
توفر بوابات React طريقة لعرض العناصر الأبناء لمكون ما داخل عقدة DOM توجد خارج التسلسل الهرمي لـ DOM للمكون الأصلي. بعبارات أبسط، تسمح لك بالتحرر من شجرة مكونات React القياسية وإدراج العناصر مباشرة في جزء مختلف من بنية HTML. هذا مفيد بشكل خاص في الحالات التي تحتاج فيها إلى التحكم في سياق التكديس (stacking context) أو وضع العناصر خارج حدود الحاوية الأصلية لها.
تقليديًا، يتم عرض مكونات React كأبناء لمكوناتها الأصلية داخل DOM. يمكن أن يؤدي هذا أحيانًا إلى تحديات في التنسيق والتخطيط، خاصة عند التعامل مع عناصر مثل النوافذ المنبثقة أو التلميحات التي يجب أن تظهر فوق محتوى آخر أو يتم تحديد موضعها بالنسبة لمنفذ العرض (viewport). توفر بوابات React حلاً عن طريق السماح بعرض هذه العناصر مباشرة في جزء مختلف من شجرة DOM، متجاوزة هذه القيود.
لماذا نستخدم بوابات React؟
هناك العديد من الفوائد الرئيسية التي تجعل بوابات React أداة قيمة في ترسانة تطوير React الخاصة بك:
- تحسين التنسيق والتخطيط: تتيح لك البوابات وضع العناصر خارج حاوية المكون الأصلي، مما يتغلب على مشاكل التنسيق الناتجة عن
overflow: hidden، أو قيودz-index، أو قيود التخطيط المعقدة. تخيل نافذة منبثقة تحتاج إلى تغطية الشاشة بأكملها، حتى لو كانت الحاوية الأصلية لها تحتوي علىoverflow: hidden. تتيح لك البوابات عرض النافذة المنبثقة مباشرة فيbody، متجاوزة هذا القيد. - تعزيز إمكانية الوصول: تعد البوابات ضرورية لإمكانية الوصول، خاصة عند التعامل مع النوافذ المنبثقة. يسمح عرض محتوى النافذة المنبثقة مباشرة في
bodyبإدارة احتجاز التركيز (focus trapping) بسهولة، مما يضمن بقاء المستخدمين الذين يستخدمون قارئات الشاشة أو التنقل عبر لوحة المفاتيح داخل النافذة المنبثقة أثناء فتحها. هذا ضروري لتوفير تجربة مستخدم سلسة وسهلة الوصول. - بنية مكونات أنظف: من خلال عرض محتوى النافذة المنبثقة أو التلميح خارج شجرة المكونات الرئيسية، يمكنك الحفاظ على بنية المكونات الخاصة بك أكثر نظافة وسهولة في الإدارة. يمكن أن يؤدي هذا الفصل بين الاهتمامات إلى جعل الكود الخاص بك أسهل في القراءة والفهم والصيانة.
- تجنب مشاكل سياق التكديس (Stacking Context): يمكن أن تكون إدارة سياقات التكديس في CSS صعبة للغاية. تساعدك البوابات على تجنب هذه المشكلات من خلال السماح لك بعرض العناصر مباشرة في جذر DOM، مما يضمن وضعها دائمًا بشكل صحيح بالنسبة للعناصر الأخرى في الصفحة.
تنفيذ النوافذ المنبثقة باستخدام بوابات React
النوافذ المنبثقة هي نمط شائع في واجهة المستخدم يستخدم لعرض معلومات مهمة أو مطالبة المستخدمين بالإدخال. دعنا نستكشف كيفية إنشاء نافذة منبثقة باستخدام بوابات React.
١. إنشاء جذر البوابة
أولاً، تحتاج إلى إنشاء عقدة DOM حيث سيتم عرض النافذة المنبثقة. يتم ذلك عادةً عن طريق إضافة عنصر div بمعرف (ID) محدد إلى ملف HTML الخاص بك (عادةً في body):
<div id="modal-root"></div>
٢. إنشاء مكون النافذة المنبثقة
بعد ذلك، قم بإنشاء مكون React يمثل النافذة المنبثقة. سيحتوي هذا المكون على محتوى ومنطق النافذة المنبثقة.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
شرح الكود:
- خاصية
isOpen: تحدد ما إذا كانت النافذة المنبثقة مرئية. - خاصية
onClose: دالة لإغلاق النافذة المنبثقة. - خاصية
children: المحتوى الذي سيتم عرضه داخل النافذة المنبثقة. - مرجع
modalRoot: يشير إلى عقدة DOM حيث سيتم عرض النافذة المنبثقة (#modal-root). - خطاف
useEffect: يضمن عرض النافذة المنبثقة فقط بعد تحميل المكون لتجنب المشاكل المتعلقة بعدم توفر جذر البوابة على الفور. ReactDOM.createPortal: هذا هو المفتاح لاستخدام بوابات React. يأخذ وسيطين: عنصر React المراد عرضه (modalContent) وعقدة DOM حيث يجب عرضه (modalRoot.current).- النقر على الغطاء: يغلق النافذة المنبثقة. نستخدم
e.stopPropagation()على حاويةmodal-contentلمنع النقرات داخل النافذة من إغلاقها.
٣. استخدام مكون النافذة المنبثقة
الآن، يمكنك استخدام مكون Modal في تطبيقك:
import React, { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} onClose={closeModal}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
</Modal>
</div>
);
};
export default App;
يوضح هذا المثال كيفية التحكم في رؤية النافذة المنبثقة باستخدام خاصية isOpen ودالتي openModal و closeModal. المحتوى الموجود داخل وسوم <Modal> سيتم عرضه داخل النافذة المنبثقة.
٤. تنسيق النافذة المنبثقة
أضف أنماط CSS لتحديد موضع وتنسيق النافذة المنبثقة. إليك مثال أساسي:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* Ensure it's on top of other content */
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
شرح CSS:
position: fixed: يضمن أن تغطي النافذة المنبثقة منفذ العرض بالكامل، بغض النظر عن التمرير.background-color: rgba(0, 0, 0, 0.5): ينشئ غطاءً شبه شفاف خلف النافذة المنبثقة.display: flex, justify-content: center, align-items: center: يوسط النافذة المنبثقة أفقيًا وعموديًا.z-index: 1000: يضمن عرض النافذة المنبثقة فوق جميع العناصر الأخرى في الصفحة.
٥. اعتبارات إمكانية الوصول للنوافذ المنبثقة
تعد إمكانية الوصول أمرًا بالغ الأهمية عند تنفيذ النوافذ المنبثقة. إليك بعض الاعتبارات الرئيسية:
- إدارة التركيز (Focus): عندما تفتح النافذة المنبثقة، يجب نقل التركيز تلقائيًا إلى عنصر داخل النافذة (على سبيل المثال، أول حقل إدخال أو زر إغلاق). عندما تغلق النافذة، يجب أن يعود التركيز إلى العنصر الذي أدى إلى فتحها. غالبًا ما يتم تحقيق ذلك باستخدام خطاف
useRefفي React لتخزين العنصر الذي تم التركيز عليه مسبقًا. - التنقل باستخدام لوحة المفاتيح: تأكد من أن المستخدمين يمكنهم التنقل في النافذة المنبثقة باستخدام لوحة المفاتيح (مفتاح Tab). يجب أن يكون التركيز محصورًا داخل النافذة، مما يمنع المستخدمين من الخروج منها عن طريق الخطأ. يمكن لمكتبات مثل
react-focus-lockالمساعدة في ذلك. - سمات ARIA: استخدم سمات ARIA لتوفير معلومات دلالية حول النافذة المنبثقة لقارئات الشاشة. على سبيل المثال، استخدم
aria-modal="true"على حاوية النافذة وaria-labelأوaria-labelledbyلتوفير تسمية وصفية للنافذة. - آلية الإغلاق: وفر طرقًا متعددة لإغلاق النافذة المنبثقة، مثل زر إغلاق، أو النقر على الغطاء، أو الضغط على مفتاح Escape.
مثال على إدارة التركيز (باستخدام useRef):
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
const firstFocusableElement = useRef(null);
const previouslyFocusedElement = useRef(null);
useEffect(() => {
setMounted(true);
if (isOpen) {
previouslyFocusedElement.current = document.activeElement;
if (firstFocusableElement.current) {
firstFocusableElement.current.focus();
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
if (previouslyFocusedElement.current) {
previouslyFocusedElement.current.focus();
}
};
}
return () => setMounted(false);
}, [isOpen, onClose]);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
<input type="text" ref={firstFocusableElement} /> <!-- First focusable element -->
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
شرح كود إدارة التركيز:
previouslyFocusedElement.current: يخزن العنصر الذي كان له التركيز قبل فتح النافذة المنبثقة.firstFocusableElement.current: يشير إلى أول عنصر قابل للتركيز *داخل* النافذة المنبثقة (في هذا المثال، حقل إدخال نصي).- عندما تفتح النافذة (
isOpenتكون true):- يتم تخزين العنصر الذي تم التركيز عليه حاليًا.
- يتم نقل التركيز إلى
firstFocusableElement.current. - تتم إضافة مستمع حدث للاستماع إلى مفتاح Escape، لإغلاق النافذة المنبثقة.
- عندما تغلق النافذة (دالة التنظيف):
- يتم إزالة مستمع حدث مفتاح Escape.
- يتم إرجاع التركيز إلى العنصر الذي تم التركيز عليه مسبقًا.
تنفيذ التلميحات باستخدام بوابات React
التلميحات هي نوافذ منبثقة صغيرة وغنية بالمعلومات تظهر عندما يمرر المستخدم الفأرة فوق عنصر ما. يمكن استخدام بوابات React لإنشاء تلميحات يتم وضعها بشكل صحيح، بغض النظر عن تنسيق أو تخطيط العنصر الأصلي.
١. إنشاء جذر البوابة (إذا لم يتم إنشاؤه بالفعل)
إذا لم تكن قد أنشأت بالفعل جذر بوابة للنوافذ المنبثقة، فأضف عنصر div بمعرف محدد إلى ملف HTML الخاص بك (عادةً في body):
<div id="tooltip-root"></div>
٢. إنشاء مكون التلميح
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Tooltip = ({ text, children, position = 'top' }) => {
const [isVisible, setIsVisible] = useState(false);
const [positionStyle, setPositionStyle] = useState({});
const [mounted, setMounted] = useState(false);
const tooltipRoot = useRef(document.getElementById('tooltip-root'));
const tooltipRef = useRef(null);
const triggerRef = useRef(null);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const handleMouseEnter = () => {
setIsVisible(true);
updatePosition();
};
const handleMouseLeave = () => {
setIsVisible(false);
};
const updatePosition = () => {
if (!triggerRef.current || !tooltipRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = triggerRect.top - tooltipRect.height - 5; // 5px spacing
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'bottom':
top = triggerRect.bottom + 5;
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'left':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.left - tooltipRect.width - 5;
break;
case 'right':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.right + 5;
break;
default:
break;
}
setPositionStyle({
top: `${top}px`,
left: `${left}px`,
});
};
const tooltipContent = isVisible && (
<div className="tooltip" style={positionStyle} ref={tooltipRef}>
{text}
</div>
);
return (
<span
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
{mounted && tooltipRoot.current ? ReactDOM.createPortal(tooltipContent, tooltipRoot.current) : null}
</span>
);
};
export default Tooltip;
شرح الكود:
- خاصية
text: النص المراد عرضه في التلميح. - خاصية
children: العنصر الذي يُشغِّل التلميح (العنصر الذي يمرر المستخدم الفأرة فوقه). - خاصية
position: موضع التلميح بالنسبة للعنصر المُشغِّل ('top', 'bottom', 'left', 'right'). الافتراضي هو 'top'. - حالة
isVisible: تتحكم في رؤية التلميح. - مرجع
tooltipRoot: يشير إلى عقدة DOM حيث سيتم عرض التلميح (#tooltip-root). - مرجع
tooltipRef: يشير إلى عنصر التلميح نفسه، ويستخدم لحساب أبعاده. - مرجع
triggerRef: يشير إلى العنصر الذي يُشغِّل التلميح (children). handleMouseEnterوhandleMouseLeave: معالجات الأحداث للتمرير فوق العنصر المُشغِّل.updatePosition: تحسب الموضع الصحيح للتلميح بناءً على خاصيةpositionوأبعاد العنصر المُشغِّل وعنصر التلميح. تستخدمgetBoundingClientRect()للحصول على موضع وأبعاد العناصر بالنسبة لمنفذ العرض.ReactDOM.createPortal: يعرض محتوى التلميح داخلtooltipRoot.
٣. استخدام مكون التلميح
import React from 'react';
import Tooltip from './Tooltip';
const App = () => {
return (
<div>
<p>
Hover over this <Tooltip text="This is a tooltip!
With multiple lines."
position="bottom">text</Tooltip> to see a tooltip.
</p>
<button>
Hover <Tooltip text="Button tooltip" position="top">here</Tooltip> for tooltip.
</button>
</div>
);
};
export default App;
يوضح هذا المثال كيفية استخدام مكون Tooltip لإضافة تلميحات إلى النصوص والأزرار. يمكنك تخصيص نص وموضع التلميح باستخدام خاصيتي text و position.
٤. تنسيق التلميح
أضف أنماط CSS لتحديد موضع وتنسيق التلميح. إليك مثال أساسي:
.tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.8); /* Dark background */
color: white;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 1000; /* Ensure it's on top of other content */
white-space: pre-line; /* Respect line breaks in the text prop */
}
شرح CSS:
position: absolute: يحدد موضع التلميح بالنسبة إلىtooltip-root. تقوم دالةupdatePositionفي مكون React بحساب قيمtopوleftالدقيقة لوضع التلميح بالقرب من العنصر المُشغِّل.background-color: rgba(0, 0, 0, 0.8): ينشئ خلفية داكنة شفافة قليلاً للتلميح.white-space: pre-line: هذا مهم للحفاظ على فواصل الأسطر التي قد تدرجها في خاصيةtext. بدون هذا، سيظهر نص التلميح بالكامل في سطر واحد.
الاعتبارات العالمية وأفضل الممارسات
عند تطوير تطبيقات React لجمهور عالمي، ضع في اعتبارك أفضل الممارسات التالية:
- التدويل (i18n): استخدم مكتبة مثل
react-i18nextأوFormatJSللتعامل مع الترجمات والتوطين. يتيح لك ذلك تكييف تطبيقك بسهولة مع لغات ومناطق مختلفة. بالنسبة للنوافذ المنبثقة والتلميحات، تأكد من ترجمة المحتوى النصي بشكل صحيح. - دعم من اليمين إلى اليسار (RTL): بالنسبة للغات التي تُقرأ من اليمين إلى اليسار (مثل العربية والعبرية)، تأكد من عرض النوافذ المنبثقة والتلميحات بشكل صحيح. قد تحتاج إلى تعديل موضع وتنسيق العناصر لاستيعاب تخطيطات RTL. يمكن أن تكون خصائص CSS المنطقية (مثل
margin-inline-startبدلاً منmargin-left) مفيدة. - الحساسية الثقافية: كن على دراية بالاختلافات الثقافية عند تصميم النوافذ المنبثقة والتلميحات. تجنب استخدام الصور أو الرموز التي قد تكون مسيئة أو غير مناسبة في ثقافات معينة.
- المناطق الزمنية وتنسيقات التاريخ: إذا كانت النوافذ المنبثقة أو التلميحات تعرض تواريخ أو أوقات، فتأكد من تنسيقها وفقًا لمنطقة المستخدم ومنطقته الزمنية. يمكن لمكتبات مثل
moment.js(على الرغم من أنها قديمة، لا تزال تستخدم على نطاق واسع) أوdate-fnsالمساعدة في ذلك. - إمكانية الوصول للقدرات المتنوعة: التزم بإرشادات إمكانية الوصول (WCAG) لضمان أن النوافذ المنبثقة والتلميحات قابلة للاستخدام من قبل الأشخاص ذوي الإعاقة. يشمل ذلك توفير نص بديل للصور، وضمان تباين كافٍ للألوان، وتوفير دعم للتنقل عبر لوحة المفاتيح.
الخاتمة
تعتبر بوابات React أداة قوية لبناء واجهات مستخدم مرنة وسهلة الوصول. من خلال فهم كيفية استخدامها بفعالية، يمكنك إنشاء نوافذ منبثقة وتلميحات تعزز تجربة المستخدم وتحسن بنية وصيانة تطبيقات React الخاصة بك. تذكر إعطاء الأولوية لإمكانية الوصول والاعتبارات العالمية عند التطوير لجمهور متنوع، مما يضمن أن تطبيقاتك شاملة وقابلة للاستخدام من قبل الجميع.