made more blind friendly
This commit is contained in:
parent
b43b6b8e2a
commit
b30579391a
4 changed files with 190 additions and 50 deletions
|
|
@ -20,18 +20,30 @@ export const formatDateRange = (startDate: string, startTime: string, endDate: s
|
|||
} else {
|
||||
return `${format(start, 'MMMM d, yyyy')}\n${formatTime(startDate, startTime)} - ${format(end, 'MMMM d, yyyy')}\n${formatTime(endDate, endTime)}`;
|
||||
}
|
||||
};const EventPopup: React.FC<{ event: Event; position: { top: number; left: number }; onClose: () => void }> = ({ event, position, onClose }) => (
|
||||
};
|
||||
|
||||
const EventPopup: React.FC<{ event: Event; position: { top: number; left: number }; onClose: () => void }> = ({ event, position, onClose }) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"
|
||||
onClick={onClose}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`Event Details: ${event.title}`}
|
||||
>
|
||||
<motion.div
|
||||
className="bg-white p-4 rounded-lg shadow-lg max-w-xs w-full"
|
||||
className="bg-white p-4 rounded-lg shadow-lg max-w-xs w-full relative"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-2 right-2 p-2 text-gray-600 hover:text-gray-900"
|
||||
aria-label="Close event details"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h3 className="font-bold text-lg">{event.title}</h3>
|
||||
<p className="text-sm whitespace-pre-line">{formatDateRange(event.date, event.startTime, event.date, event.endTime)}</p>
|
||||
<p className="text-sm mt-2">{event.description}</p>
|
||||
|
|
@ -64,6 +76,19 @@ const Calendar: React.FC = () => {
|
|||
};
|
||||
}, [selectedEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscapeKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && selectedEvent) {
|
||||
setSelectedEvent(null);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscapeKey);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscapeKey);
|
||||
};
|
||||
}, [selectedEvent]);
|
||||
|
||||
const isEventUpcoming = (eventDate: string) => {
|
||||
const today = startOfDay(new Date());
|
||||
const parsedEventDate = startOfDay(parseISO(eventDate));
|
||||
|
|
@ -104,10 +129,11 @@ const Calendar: React.FC = () => {
|
|||
whileTap={{ scale: 0.9 }}
|
||||
className="text-2xl text-[#8C1D40]"
|
||||
onClick={prevMonth}
|
||||
aria-label={`Previous month: ${monthNames[(currentMonth.getMonth() - 1 + 12) % 12]}`}
|
||||
>
|
||||
←
|
||||
</motion.button>
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40]">
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40]" role="heading" aria-level={2}>
|
||||
{monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()}
|
||||
</h2>
|
||||
<motion.button
|
||||
|
|
@ -115,18 +141,33 @@ const Calendar: React.FC = () => {
|
|||
whileTap={{ scale: 0.9 }}
|
||||
className="text-2xl text-[#8C1D40]"
|
||||
onClick={nextMonth}
|
||||
aria-label={`Next month: ${monthNames[(currentMonth.getMonth() + 1) % 12]}`}
|
||||
>
|
||||
→
|
||||
</motion.button>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-1 bg-gray-200 rounded-lg overflow-hidden">
|
||||
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => (
|
||||
<div key={day} className="bg-[#8C1D40] text-white font-bold p-2 text-center text-xs sm:text-sm">
|
||||
{day}
|
||||
<div
|
||||
className="grid grid-cols-7 gap-1 bg-gray-200 rounded-lg overflow-hidden"
|
||||
role="grid"
|
||||
aria-label="Calendar"
|
||||
>
|
||||
{['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map(day => (
|
||||
<div
|
||||
key={day}
|
||||
className="bg-[#8C1D40] text-white font-bold p-2 text-center text-xs sm:text-sm"
|
||||
role="columnheader"
|
||||
aria-label={day}
|
||||
>
|
||||
{day.charAt(0)}
|
||||
</div>
|
||||
))}
|
||||
{emptyDays.map(day => (
|
||||
<div key={`empty-${day}`} className="bg-white p-2 h-16 sm:h-24"></div>
|
||||
<div
|
||||
key={`empty-${day}`}
|
||||
className="bg-white p-2 h-16 sm:h-24"
|
||||
role="gridcell"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
))}
|
||||
{days.map(day => {
|
||||
const currentDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
|
||||
|
|
@ -138,19 +179,22 @@ const Calendar: React.FC = () => {
|
|||
key={day}
|
||||
className="bg-white p-1 sm:p-2 h-16 sm:h-24 cursor-pointer flex flex-col items-start justify-start overflow-hidden"
|
||||
whileHover={{ backgroundColor: '#FFCCCB' }}
|
||||
role="gridcell"
|
||||
aria-label={`${format(currentDate, 'MMMM d, yyyy')}${dayEvents.length ? `. ${dayEvents.length} events scheduled` : '. No events'}`}
|
||||
>
|
||||
<span className="font-semibold text-xs sm:text-sm">{day}</span>
|
||||
{dayEvents.map((event, index) => (
|
||||
<div
|
||||
<button
|
||||
key={index}
|
||||
className="text-xs bg-[#8C1D40] text-white p-1 mt-1 rounded w-full truncate"
|
||||
onClick={() => handleEventClick(event)}
|
||||
aria-label={`${event.title} on ${format(parseISO(event.date), 'MMMM d')} from ${formatTime(event.date, event.startTime)} to ${formatTime(event.date, event.endTime)}`}
|
||||
>
|
||||
<div className="truncate">{event.title}</div>
|
||||
<div className="hidden sm:block">
|
||||
{formatTime(event.date, event.startTime)} - {formatTime(event.date, event.endTime)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
|
|
@ -167,20 +211,27 @@ const Calendar: React.FC = () => {
|
|||
</AnimatePresence>
|
||||
|
||||
<div className="mt-12">
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40] mb-6">Upcoming Events</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 justify-items-center">
|
||||
{upcomingEvents.map((event, index) => (
|
||||
<EventCard
|
||||
key={index}
|
||||
event={{
|
||||
...event,
|
||||
formattedDateRange: formatDateRange(event.date, event.startTime, event.date, event.endTime)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40] mb-6" role="heading" aria-level={2}>
|
||||
Upcoming Events
|
||||
</h2>
|
||||
<div
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 justify-items-center"
|
||||
role="list"
|
||||
aria-label="List of upcoming events"
|
||||
>
|
||||
{upcomingEvents.map((event, index) => (
|
||||
<div key={index} role="listitem">
|
||||
<EventCard
|
||||
event={{
|
||||
...event,
|
||||
formattedDateRange: formatDateRange(event.date, event.startTime, event.date, event.endTime)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ const Footer: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<footer className="bg-[#8C1D40] text-white py-8">
|
||||
<footer className="bg-[#8C1D40] text-white py-8" role="contentinfo">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex flex-wrap justify-between">
|
||||
<div className="w-full md:w-1/3 mb-6 md:mb-0">
|
||||
|
|
@ -100,10 +100,11 @@ const Footer: React.FC = () => {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-4 transition-transform duration-300 ease-in-out transform hover:scale-110"
|
||||
aria-label="Visit MCDi Facebook Page"
|
||||
>
|
||||
<img
|
||||
src="/logos/facebook-logo.png"
|
||||
alt="Facebook Logo"
|
||||
alt="MCDi Facebook Page"
|
||||
className="w-10 h-10 rounded-full shadow-lg hover:shadow-xl"
|
||||
style={{
|
||||
filter: 'drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.1))',
|
||||
|
|
@ -111,7 +112,7 @@ const Footer: React.FC = () => {
|
|||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="w-full md:w-1/3 mb-6 md:mb-0">
|
||||
<nav className="w-full md:w-1/3 mb-6 md:mb-0" aria-label="Footer Navigation">
|
||||
<h3 className="text-xl font-bold mb-4">Quick Links</h3>
|
||||
<ul className="text-sm grid grid-cols-2 md:grid-cols-1 gap-2">
|
||||
<li><Link to="/" className="hover:text-yellow-200 transition-colors duration-200">Home</Link></li>
|
||||
|
|
@ -120,12 +121,18 @@ const Footer: React.FC = () => {
|
|||
<li><Link to="/sponsors" className="hover:text-yellow-200 transition-colors duration-200">Sponsors</Link></li>
|
||||
<li><Link to="/meet-board" className="hover:text-yellow-200 transition-colors duration-200">Meet MCDi Board</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="w-full md:w-1/3">
|
||||
<h3 className="text-xl font-bold mb-4">Contact Us</h3>
|
||||
<form className="text-sm" onSubmit={handleSubmit}>
|
||||
<h3 className="text-xl font-bold mb-4" id="contact-form">Contact Us</h3>
|
||||
<form
|
||||
className="text-sm"
|
||||
onSubmit={handleSubmit}
|
||||
aria-labelledby="contact-form"
|
||||
>
|
||||
<div className="mb-2">
|
||||
<label className="sr-only" htmlFor="name">Name</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
|
|
@ -133,11 +140,16 @@ const Footer: React.FC = () => {
|
|||
placeholder="Name"
|
||||
className={`w-full p-2 text-black rounded ${errors.name ? 'border-red-500' : ''}`}
|
||||
required
|
||||
aria-required="true"
|
||||
aria-invalid={errors.name ? "true" : "false"}
|
||||
aria-describedby={errors.name ? "name-error" : undefined}
|
||||
/>
|
||||
{errors.name && <p className="text-red-500 text-xs mt-1">{errors.name}</p>}
|
||||
{errors.name && <p id="name-error" className="text-red-500 text-xs mt-1" role="alert">{errors.name}</p>}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="sr-only" htmlFor="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
|
|
@ -145,11 +157,16 @@ const Footer: React.FC = () => {
|
|||
placeholder="Your Email Address"
|
||||
className={`w-full p-2 text-black rounded ${errors.email ? 'border-red-500' : ''}`}
|
||||
required
|
||||
aria-required="true"
|
||||
aria-invalid={errors.email ? "true" : "false"}
|
||||
aria-describedby={errors.email ? "email-error" : undefined}
|
||||
/>
|
||||
{errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
|
||||
{errors.email && <p id="email-error" className="text-red-500 text-xs mt-1" role="alert">{errors.email}</p>}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="sr-only" htmlFor="subject">Subject</label>
|
||||
<input
|
||||
id="subject"
|
||||
type="text"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
|
|
@ -157,11 +174,16 @@ const Footer: React.FC = () => {
|
|||
placeholder="Subject"
|
||||
className={`w-full p-2 text-black rounded ${errors.subject ? 'border-red-500' : ''}`}
|
||||
required
|
||||
aria-required="true"
|
||||
aria-invalid={errors.subject ? "true" : "false"}
|
||||
aria-describedby={errors.subject ? "subject-error" : undefined}
|
||||
/>
|
||||
{errors.subject && <p className="text-red-500 text-xs mt-1">{errors.subject}</p>}
|
||||
{errors.subject && <p id="subject-error" className="text-red-500 text-xs mt-1" role="alert">{errors.subject}</p>}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="sr-only" htmlFor="message">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
|
|
@ -169,8 +191,11 @@ const Footer: React.FC = () => {
|
|||
rows={3}
|
||||
className={`w-full p-2 text-black rounded ${errors.message ? 'border-red-500' : ''}`}
|
||||
required
|
||||
aria-required="true"
|
||||
aria-invalid={errors.message ? "true" : "false"}
|
||||
aria-describedby={errors.message ? "message-error" : undefined}
|
||||
></textarea>
|
||||
{errors.message && <p className="text-red-500 text-xs mt-1">{errors.message}</p>}
|
||||
{errors.message && <p id="message-error" className="text-red-500 text-xs mt-1" role="alert">{errors.message}</p>}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -180,14 +205,16 @@ const Footer: React.FC = () => {
|
|||
style={{display: 'none'}}
|
||||
tabIndex={-1}
|
||||
autoComplete="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-yellow-400 text-[#8C1D40] font-bold py-2 px-4 rounded hover:bg-yellow-300 transition-colors duration-200"
|
||||
aria-label="Send message"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
{status && <p className="mt-2 text-sm">{status}</p>}
|
||||
{status && <p className="mt-2 text-sm" role="status" aria-live="polite">{status}</p>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -199,6 +226,7 @@ const Footer: React.FC = () => {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-yellow-200 hover:text-yellow-400 transition-colors duration-200"
|
||||
aria-label="Visit DeafGain LLC website (opens in new tab)"
|
||||
>
|
||||
DeafGain, LLC
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ const Header: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<header className="bg-[#8C1D40] text-white shadow-md">
|
||||
<header className="bg-[#8C1D40] text-white shadow-md" role="banner">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<img src="/mcdi-logo-small.png" alt="MCDi Logo" className="w-12 h-12 md:w-20 md:h-20 mr-2 md:mr-4" />
|
||||
<h1 className="text-lg md:text-2xl font-bold">Missoula Council of the Deaf, Inc</h1>
|
||||
</div>
|
||||
<nav className="hidden md:block">
|
||||
<nav className="hidden md:block" role="navigation" aria-label="Main navigation">
|
||||
<ul className="flex space-x-6 items-center">
|
||||
<li><NavItem href="/">Home</NavItem></li>
|
||||
<li><AboutDropdown /></li>
|
||||
|
|
@ -39,6 +39,9 @@ const Header: React.FC = () => {
|
|||
<button
|
||||
className="md:hidden text-white"
|
||||
onClick={toggleMenu}
|
||||
aria-label="Toggle navigation menu"
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="mobile-menu"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16m-7 6h7" />
|
||||
|
|
@ -49,6 +52,9 @@ const Header: React.FC = () => {
|
|||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
id="mobile-menu"
|
||||
role="navigation"
|
||||
aria-label="Mobile navigation"
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
|
|
@ -70,11 +76,16 @@ const Header: React.FC = () => {
|
|||
contentLabel="Donate to MCDi"
|
||||
className="modal"
|
||||
overlayClassName="overlay"
|
||||
aria={{
|
||||
labelledby: "donate-modal-title",
|
||||
describedby: "donate-modal-description"
|
||||
}}
|
||||
>
|
||||
<div className="h-full flex flex-col">
|
||||
<h2 className="text-2xl font-bold text-[#8C1D40] mb-4">Donate to MCDi</h2>
|
||||
<div className="flex-grow overflow-y-auto">
|
||||
<h2 id="donate-modal-title" className="text-2xl font-bold text-[#8C1D40] mb-4">Donate to MCDi</h2>
|
||||
<div id="donate-modal-description" className="flex-grow overflow-y-auto">
|
||||
<iframe
|
||||
title="Donation form"
|
||||
src="https://www.zeffy.com/embed/donation-form/1cbff18e-c057-4fa6-8276-a993037de0bb"
|
||||
width="100%"
|
||||
height="100%"
|
||||
|
|
@ -84,6 +95,7 @@ const Header: React.FC = () => {
|
|||
<button
|
||||
onClick={closeDonateModal}
|
||||
className="mt-4 bg-[#8C1D40] text-white font-bold py-2 px-4 rounded"
|
||||
aria-label="Close donation modal"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
|
@ -115,10 +127,14 @@ const AboutDropdown: React.FC = () => {
|
|||
className="relative"
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
onMouseLeave={() => setIsOpen(false)}
|
||||
role="navigation"
|
||||
aria-label="About MCDi dropdown"
|
||||
>
|
||||
<Link
|
||||
to="/about"
|
||||
className="hover:text-yellow-200 transition-colors duration-200"
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="about-dropdown"
|
||||
>
|
||||
<motion.span whileHover={{ scale: 1.05 }} className="inline-block">
|
||||
About MCDi
|
||||
|
|
@ -127,12 +143,13 @@ const AboutDropdown: React.FC = () => {
|
|||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
id="about-dropdown"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="absolute left-0 mt-2 w-48 bg-[#8C1D40] rounded-md shadow-lg z-10"
|
||||
>
|
||||
<div className="py-1">
|
||||
<div className="py-1" role="menu">
|
||||
<DropdownItem href="/meet-board">Meet MCDi Board</DropdownItem>
|
||||
<DropdownItem href="/bylaws">Bylaws</DropdownItem>
|
||||
<DropdownItem href="/minutes">Minutes</DropdownItem>
|
||||
|
|
@ -146,7 +163,11 @@ const AboutDropdown: React.FC = () => {
|
|||
};
|
||||
|
||||
const DropdownItem: React.FC<NavItemProps> = ({ href, children }) => (
|
||||
<Link to={href} className="block px-4 py-2 hover:bg-[#6B1631] transition-colors duration-200">
|
||||
<Link
|
||||
to={href}
|
||||
className="block px-4 py-2 hover:bg-[#6B1631] transition-colors duration-200"
|
||||
role="menuitem"
|
||||
>
|
||||
<motion.span whileHover={{ x: 5 }} className="inline-block">
|
||||
{children}
|
||||
</motion.span>
|
||||
|
|
@ -161,6 +182,7 @@ const DonateButton: React.FC<{ onClick: (e: React.MouseEvent) => void }> = ({ on
|
|||
<button
|
||||
onClick={onClick}
|
||||
className="inline-block bg-yellow-400 text-[#8C1D40] font-bold py-2 px-4 rounded-full hover:bg-yellow-300 transition-colors duration-200 shadow-md"
|
||||
aria-label="Open donation form"
|
||||
>
|
||||
Donate
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -124,9 +124,14 @@ const MeetBoard: React.FC = () => {
|
|||
>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[#8C1D40] mb-8">Meet MCDi Board</h1>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8" role="list">
|
||||
{shuffledMembers.map((member, index) => (
|
||||
<BoardMemberCard key={index} member={member} onClick={() => setSelectedMember(member)} />
|
||||
<BoardMemberCard
|
||||
key={index}
|
||||
member={member}
|
||||
onClick={() => setSelectedMember(member)}
|
||||
aria-label={`View details for ${member.name}, ${member.position}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
@ -141,6 +146,7 @@ const MeetBoard: React.FC = () => {
|
|||
className="w-full"
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onEnded={() => setIsPlaying(false)}
|
||||
aria-label="MCDi Introduction Video"
|
||||
>
|
||||
<source src="/videos/mcdi-intro.mp4" type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
|
|
@ -152,21 +158,35 @@ const MeetBoard: React.FC = () => {
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 p-2 md:p-4"
|
||||
role="group"
|
||||
aria-label="Video controls"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={handleStop} className="text-white mr-2">
|
||||
<button
|
||||
onClick={handleStop}
|
||||
className="text-white mr-2"
|
||||
aria-label="Stop video"
|
||||
>
|
||||
<FaStop />
|
||||
</button>
|
||||
<button onClick={togglePlay} className="text-white mr-2">
|
||||
<button
|
||||
onClick={togglePlay}
|
||||
className="text-white mr-2"
|
||||
aria-label={isPlaying ? "Pause video" : "Play video"}
|
||||
>
|
||||
{isPlaying ? <FaPause /> : <FaPlay />}
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
min={0}
|
||||
max={100}
|
||||
value={progress}
|
||||
onChange={handleSeek}
|
||||
className="w-full mx-2"
|
||||
aria-label="Video progress"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={progress}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
@ -184,6 +204,9 @@ const MeetBoard: React.FC = () => {
|
|||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
|
||||
onClick={() => setSelectedMember(null)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="member-modal-title"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ y: -50, opacity: 0 }}
|
||||
|
|
@ -192,10 +215,16 @@ const MeetBoard: React.FC = () => {
|
|||
className="bg-white rounded-lg p-4 md:p-8 max-w-md w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-4">{selectedMember.name}</h2>
|
||||
<h2 id="member-modal-title" className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-4">{selectedMember.name}</h2>
|
||||
<p className="text-base md:text-lg text-gray-700 mb-2">{selectedMember.position}</p>
|
||||
<p className="text-sm md:text-base text-gray-600 mb-4">{selectedMember.bio}</p>
|
||||
<a href={`mailto:${selectedMember.email}`} className="text-blue-500 hover:underline text-sm md:text-base">{selectedMember.email}</a>
|
||||
<a
|
||||
href={`mailto:${selectedMember.email}`}
|
||||
className="text-blue-500 hover:underline text-sm md:text-base"
|
||||
aria-label={`Send email to ${selectedMember.name}`}
|
||||
>
|
||||
{selectedMember.email}
|
||||
</a>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
|
@ -204,15 +233,25 @@ const MeetBoard: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const BoardMemberCard: React.FC<{ member: BoardMember; onClick: () => void }> = ({ member, onClick }) => (
|
||||
const BoardMemberCard: React.FC<{
|
||||
member: BoardMember;
|
||||
onClick: () => void;
|
||||
'aria-label'?: string;
|
||||
}> = ({ member, onClick, 'aria-label': ariaLabel }) => (
|
||||
<motion.div
|
||||
className="bg-white rounded-lg shadow-md p-4 md:p-6 flex flex-col items-center cursor-pointer"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
onClick={onClick}
|
||||
role="listitem"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<h2 className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-2 md:mb-4">{member.name}</h2>
|
||||
<img src={member.image} alt={member.name} className="w-32 h-32 md:w-48 md:h-48 object-cover rounded-full mb-2 md:mb-4" />
|
||||
<img
|
||||
src={member.image}
|
||||
alt={`${member.name}, ${member.position}`}
|
||||
className="w-32 h-32 md:w-48 md:h-48 object-cover rounded-full mb-2 md:mb-4"
|
||||
/>
|
||||
<p className="text-base md:text-lg text-gray-700">{member.position}</p>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue