Fix dark theme gradients on home page
This commit is contained in:
parent
2e429b0af3
commit
512a5df89a
17 changed files with 521 additions and 116 deletions
|
|
@ -10,6 +10,15 @@
|
|||
- Planning authentication implementation for production deployment
|
||||
|
||||
## Recent Changes
|
||||
- **FIXED**: Theme selector functionality has been completely resolved
|
||||
- Identified and fixed conflicting theme systems (custom context vs next-themes)
|
||||
- Updated ThemeSelector.tsx to use next-themes useTheme hook with resolvedTheme
|
||||
- Modified layout.tsx to use proper next-themes configuration (attribute="class", defaultTheme="system")
|
||||
- Removed conflicting custom context files (ThemeContext.tsx, ClientProviders.tsx)
|
||||
- Theme switching now works properly with light/dark/system modes
|
||||
- Used best practices from Perplexity MCP research for Next.js 13+ App Router theme implementation
|
||||
- Current minor issue: Responsive design on smaller screens hides Donate button and theme selector (to be addressed later)
|
||||
|
||||
- Improved dark theme appearance and accessibility:
|
||||
- Enhanced contrast for navigation menu items in dark mode by using white text
|
||||
- Fixed site title "Olathe Club of the Deaf" contrast in dark mode by changing to white
|
||||
|
|
@ -84,15 +93,14 @@
|
|||
- Applied the same blue color used in the "Upcoming Events" button for consistency
|
||||
|
||||
## Next Steps
|
||||
1. Complete backend API integrations:
|
||||
- Connect admin UI components to backend endpoints (✓ Events management implemented, ✓ Members management implemented, ✓ Videos management implemented, ✓ Documents management implemented)
|
||||
- Add real data loading with loading states (✓ Implemented for Events, ✓ Implemented for Members, ✓ Implemented for Videos, ✓ Implemented for Documents)
|
||||
- Implement error handling for API requests (✓ Implemented for Events, ✓ Implemented for Members, ✓ Implemented for Videos, ✓ Implemented for Documents)
|
||||
- Set up client-side data validation (✓ Implemented for Events, ✓ Implemented for Members, ✓ Implemented for Videos, ✓ Implemented for Documents)
|
||||
1. Fix responsive navigation design:
|
||||
- Investigate why mobile menu button isn't showing on smaller screens
|
||||
- Ensure Donate button and theme selector are accessible on all screen sizes
|
||||
- Test navigation functionality across different viewport sizes
|
||||
2. Implement remaining admin features:
|
||||
- Add form handlers for CRUD operations (✓ Implemented for Events, ✓ Implemented for Members, ✓ Implemented for Videos, ✓ Implemented for Documents)
|
||||
- Create media upload components (✓ Implemented for Videos, ✓ Implemented for Documents)
|
||||
- Add user notification system (Next priority)
|
||||
- Enhance admin dashboard with real-time statistics
|
||||
- Add advanced filtering and search capabilities
|
||||
3. Deploy staging environment:
|
||||
- Set up Docker containers for testing
|
||||
- Configure environment variables
|
||||
|
|
@ -149,6 +157,11 @@
|
|||
- Enhanced Apply Filters button with more prominent blue styling to improve visibility
|
||||
|
||||
## Special Considerations
|
||||
- **RESOLVED**: Theme selector functionality has been completely fixed
|
||||
- Successfully implemented next-themes for proper theme management
|
||||
- Removed conflicting custom context that was causing state management issues
|
||||
- Theme switching now works seamlessly with proper SSR/hydration handling
|
||||
- Minor responsive design issue on smaller screens to be addressed in next phase
|
||||
- CRITICAL: Re-enable authentication middleware before production deployment
|
||||
- Members routes (POST, PUT, DELETE, bulk-action)
|
||||
- Events routes (POST, PUT, DELETE, registration)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
- [x] Build media upload functionality
|
||||
- [x] Simplify document management with improved PDF preview
|
||||
- [x] Fix API route for proper document handling
|
||||
- [x] Fix theme selector functionality with proper next-themes implementation
|
||||
- [ ] Implement user notification system
|
||||
- [ ] Create advanced filtering for data tables
|
||||
- [ ] Develop data export functionality
|
||||
|
|
@ -113,6 +114,8 @@
|
|||
- [x] Improve paragraph text contrast in About and Membership sections
|
||||
- [x] Fix "Join Our Community" section text contrast by changing background color
|
||||
- [x] Standardize button styling by changing "View All Events" and "View All Board Members" to primary style
|
||||
- [x] Fix theme selector functionality with proper next-themes integration
|
||||
- [ ] Fix responsive navigation design for smaller screen sizes
|
||||
- [ ] Conduct comprehensive accessibility audit
|
||||
- [ ] Implement screen reader optimizations
|
||||
- [ ] Refine keyboard navigation
|
||||
|
|
@ -172,9 +175,9 @@
|
|||
- [ ] Add comprehensive JSDoc comments
|
||||
|
||||
## Project Stats
|
||||
- **Completed Tasks:** 68
|
||||
- **Completed Tasks:** 70
|
||||
- **In Progress Tasks:** 0
|
||||
- **Upcoming Tasks:** 21
|
||||
- **Completion Rate:** ~77%
|
||||
- **Completion Rate:** ~78%
|
||||
- **Current Phase:** Phase 4 - API Integration & Backend Functionality
|
||||
- **Next Major Milestone:** Implement user notification system
|
||||
- **Next Major Milestone:** Implement user notification system and fix responsive navigation
|
||||
|
|
|
|||
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"framer-motion": "^12.6.0",
|
||||
"next": "^15.3.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
|
|
@ -4821,6 +4822,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"framer-motion": "^12.6.0",
|
||||
"next": "^15.3.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function AboutPage() {
|
|||
<section className="py-16 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6">About Olathe Club of the Deaf</h1>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">About Olathe Club of the Deaf</h1>
|
||||
<p className="text-xl text-gray-700 dark:text-gray-300">
|
||||
Connecting and empowering the deaf community in Olathe since 1975.
|
||||
</p>
|
||||
|
|
@ -88,8 +88,8 @@ export default function AboutPage() {
|
|||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<div className="w-40 h-40 bg-gray-200 dark:bg-gray-700 rounded-full mx-auto mb-4"></div>
|
||||
<h3 className="text-xl font-semibold">Board Member</h3>
|
||||
<p className="text-primary dark:text-primary-light">Position</p>
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">Board Member</h3>
|
||||
<p className="text-blue-600 dark:text-blue-400">Position</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -141,7 +141,7 @@ export default function AboutPage() {
|
|||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link
|
||||
href="/membership"
|
||||
className="bg-white text-primary hover:bg-gray-100 px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
className="bg-white text-primary hover:bg-gray-100 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
>
|
||||
Become a Member
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function ContactPage() {
|
|||
<section className="py-16 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6">Contact Us</h1>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">Contact Us</h1>
|
||||
<p className="text-xl text-gray-700 dark:text-gray-300">
|
||||
We'd love to hear from you! Get in touch with any questions, ideas, or feedback.
|
||||
</p>
|
||||
|
|
@ -39,17 +39,17 @@ export default function ContactPage() {
|
|||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2">Email</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Email</h3>
|
||||
<a
|
||||
href="mailto:info@olathedeafclub.com"
|
||||
className="text-primary hover:underline"
|
||||
className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline"
|
||||
>
|
||||
info@olathedeafclub.com
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2">Meeting Location</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Meeting Location</h3>
|
||||
<address className="not-italic text-gray-700 dark:text-gray-300">
|
||||
123 Main Street<br />
|
||||
Olathe, KS 66061
|
||||
|
|
@ -57,7 +57,7 @@ export default function ContactPage() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2">Connect With Us</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Connect With Us</h3>
|
||||
<div className="flex space-x-4">
|
||||
<a
|
||||
href="https://facebook.com"
|
||||
|
|
@ -126,7 +126,7 @@ export default function ContactPage() {
|
|||
<p className="text-lg text-gray-700 dark:text-gray-300">
|
||||
We meet at 123 Main Street, Olathe, KS 66061
|
||||
</p>
|
||||
<p className="mt-2 text-primary dark:text-primary-light">
|
||||
<p className="mt-2 text-blue-600 dark:text-blue-400">
|
||||
<a
|
||||
href="https://maps.google.com"
|
||||
target="_blank"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function DonatePage() {
|
|||
<section className="py-16 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6">Support Our Mission</h1>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">Support Our Mission</h1>
|
||||
<p className="text-xl text-gray-700 dark:text-gray-300">
|
||||
Your donations help us continue to serve and support the deaf community in Olathe.
|
||||
</p>
|
||||
|
|
@ -197,25 +197,25 @@ export default function DonatePage() {
|
|||
<h2 className="text-3xl font-bold mb-12 text-center">Donation FAQs</h2>
|
||||
<div className="max-w-3xl mx-auto space-y-6">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">Is my donation tax-deductible?</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Is my donation tax-deductible?</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Yes, the Olathe Club of the Deaf is a 501(c)(3) nonprofit organization. All donations are tax-deductible to the extent allowed by law.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">Can I specify how my donation is used?</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Can I specify how my donation is used?</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Yes, you can designate your donation for a specific program or project. Please include a note with your donation or contact us to discuss your preferences.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">Do you accept donations by check or mail?</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Do you accept donations by check or mail?</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Yes, checks can be made payable to "Olathe Club of the Deaf" and mailed to our office address. Please contact us for the current mailing address.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">Can I donate in memory or honor of someone?</h3>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Can I donate in memory or honor of someone?</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Absolutely. Memorial and honorary donations are a meaningful way to celebrate someone special. We can send a notification of your gift to the person or family you designate.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ const EventCard = ({
|
|||
return (
|
||||
<div className="card">
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">{title}</h3>
|
||||
<p className="text-primary font-medium mb-2">{date}</p>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">{title}</h3>
|
||||
<p className="text-blue-600 dark:text-blue-400 font-medium mb-2">{date}</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">{description}</p>
|
||||
<Link href={`/events/${slug}`} className="text-primary hover:underline font-medium">
|
||||
<Link href={`/events/${slug}`} className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline font-medium">
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -85,7 +85,7 @@ export default function EventsPage() {
|
|||
<section className="py-16 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6">Events & Calendar</h1>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">Events & Calendar</h1>
|
||||
<p className="text-xl text-gray-700 dark:text-gray-300">
|
||||
Connect with the deaf community through our regular events and activities.
|
||||
</p>
|
||||
|
|
@ -126,25 +126,25 @@ export default function EventsPage() {
|
|||
<h2 className="text-3xl font-bold mb-8 text-center">Event Categories</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="card p-6 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">Social Events</h3>
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Social Events</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Regular gatherings and celebrations for members to connect and socialize.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">Educational Workshops</h3>
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Educational Workshops</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Learning opportunities and skill development sessions for all ages.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">Board Meetings</h3>
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Board Meetings</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Open meetings for members to participate in club governance and planning.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">Special Events</h3>
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Special Events</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Annual celebrations, holiday parties, and community gatherings.
|
||||
</p>
|
||||
|
|
@ -162,7 +162,7 @@ export default function EventsPage() {
|
|||
</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="bg-white text-primary hover:bg-gray-100 px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
className="bg-white text-primary hover:bg-gray-100 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
/* Brand Colors - consistent across themes */
|
||||
--color-primary: #1a56db;
|
||||
--color-primary-light: #3b82f6;
|
||||
--color-primary-dark: #1e40af;
|
||||
|
|
@ -13,12 +14,36 @@
|
|||
--color-accent-light: #ffca28;
|
||||
--color-accent-dark: #ff8f00;
|
||||
|
||||
--color-text: #171717;
|
||||
--color-text-light: #6b7280;
|
||||
/* Light Theme Variables - Enhanced Modern Design */
|
||||
--color-text: #1e293b;
|
||||
--color-text-light: #64748b;
|
||||
--color-text-muted: #94a3b8;
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-secondary: #f8fafc;
|
||||
--color-bg-tertiary: #f1f5f9;
|
||||
--color-bg-soft: #fefefe;
|
||||
--color-bg-warm: #fefdf9;
|
||||
--color-border: #e2e8f0;
|
||||
--color-border-light: #f1f5f9;
|
||||
|
||||
/* Light theme gradients */
|
||||
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--gradient-secondary: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
--gradient-soft: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
|
||||
--gradient-cool: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
--gradient-hero: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
/* Enhanced shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* Typography */
|
||||
--font-sans: "Inter", system-ui, sans-serif;
|
||||
--font-display: "Inter", system-ui, sans-serif;
|
||||
|
||||
/* Layout */
|
||||
--border-radius-sm: 0.25rem;
|
||||
--border-radius: 0.5rem;
|
||||
--border-radius-lg: 0.75rem;
|
||||
|
|
@ -27,12 +52,39 @@
|
|||
--shadow-focus: 0 0 0 3px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-text: #f3f4f6;
|
||||
--color-text-light: #e5e7eb;
|
||||
--color-primary-light: #60a5fa;
|
||||
}
|
||||
/* Dark Theme Variables (fully preserved) */
|
||||
.dark {
|
||||
/* Dark theme text colors */
|
||||
--color-text: #f3f4f6;
|
||||
--color-text-light: #e5e7eb;
|
||||
--color-text-muted: #9ca3af;
|
||||
|
||||
/* Dark theme backgrounds */
|
||||
--color-bg: #0f172a;
|
||||
--color-bg-secondary: #1e293b;
|
||||
--color-bg-tertiary: #334155;
|
||||
--color-bg-soft: #1e293b;
|
||||
--color-bg-warm: #1e293b;
|
||||
|
||||
/* Dark theme borders */
|
||||
--color-border: #475569;
|
||||
--color-border-light: #374151;
|
||||
|
||||
/* Dark theme primary colors */
|
||||
--color-primary-light: #60a5fa;
|
||||
|
||||
/* Dark theme gradients */
|
||||
--gradient-primary: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
--gradient-secondary: linear-gradient(135deg, #374151 0%, #475569 100%);
|
||||
--gradient-soft: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
--gradient-cool: linear-gradient(135deg, #1e293b 0%, #374151 100%);
|
||||
--gradient-hero: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
|
||||
/* Dark theme shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Custom styles for focus and skip navigation */
|
||||
|
|
@ -87,10 +139,20 @@
|
|||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: var(--border-radius-lg);
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 14px 0 rgba(26, 86, 219, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
box-shadow: 0 6px 20px 0 rgba(26, 86, 219, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
|
|
@ -113,30 +175,44 @@
|
|||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
background-color: var(--color-bg-soft);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border-light);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--color-primary-light);
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
color: var(--color-text);
|
||||
}
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.dark .card {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.dark .card:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.dark .btn-secondary {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--color-primary-light);
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.dark .btn-secondary:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dark h2,
|
||||
.dark h3 {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Add top padding to account for fixed navigation */
|
||||
|
|
@ -144,6 +220,50 @@ main {
|
|||
padding-top: 4rem;
|
||||
}
|
||||
|
||||
/* Form styling */
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dark .form-label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #111827;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.dark .form-input {
|
||||
color: #f9fafb;
|
||||
background-color: #374151;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
.dark .form-input:focus {
|
||||
border-color: var(--color-primary-light);
|
||||
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2);
|
||||
}
|
||||
|
||||
/* Custom file input styling */
|
||||
.file-input {
|
||||
display: block;
|
||||
|
|
@ -168,8 +288,6 @@ main {
|
|||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.file-input {
|
||||
color: #d1d5db;
|
||||
}
|
||||
.dark .file-input {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Inter } from 'next/font/google';
|
|||
import './globals.css';
|
||||
import Navigation from '../components/organisms/Navigation';
|
||||
import Footer from '../components/organisms/Footer';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
|
||||
// Load Inter font
|
||||
const inter = Inter({
|
||||
|
|
@ -48,22 +49,30 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={inter.variable}>
|
||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
{/* Skip to content link for accessibility */}
|
||||
<a href="#main-content" className="skip-to-content">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
<header>
|
||||
<Navigation />
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem={true}
|
||||
storageKey="ocd-theme"
|
||||
themes={['light', 'dark', 'system']}
|
||||
>
|
||||
{/* Skip to content link for accessibility */}
|
||||
<a href="#main-content" className="skip-to-content">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
<header>
|
||||
<Navigation />
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function MembershipPage() {
|
|||
<section className="py-16 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6">Become a Member</h1>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">Become a Member</h1>
|
||||
<p className="text-xl text-gray-700 dark:text-gray-300">
|
||||
Join our vibrant community and connect with other deaf and hard-of-hearing individuals in Olathe.
|
||||
</p>
|
||||
|
|
@ -89,8 +89,8 @@ export default function MembershipPage() {
|
|||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
<div className="card p-6">
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">Regular</h3>
|
||||
<p className="text-3xl font-bold text-primary">$30/year</p>
|
||||
<h3 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">Regular</h3>
|
||||
<p className="text-3xl font-bold text-primary dark:text-blue-400">$30/year</p>
|
||||
</div>
|
||||
<ul className="space-y-3 text-gray-600 dark:text-gray-300 mb-8">
|
||||
<li className="flex items-start">
|
||||
|
|
@ -117,8 +117,8 @@ export default function MembershipPage() {
|
|||
<div className="card p-6 border-primary">
|
||||
<div className="text-center mb-6">
|
||||
<span className="inline-block bg-primary text-white px-3 py-1 rounded-full text-sm font-medium mb-2">Most Popular</span>
|
||||
<h3 className="text-2xl font-bold mb-2">Family</h3>
|
||||
<p className="text-3xl font-bold text-primary">$45/year</p>
|
||||
<h3 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">Family</h3>
|
||||
<p className="text-3xl font-bold text-primary dark:text-blue-400">$45/year</p>
|
||||
</div>
|
||||
<ul className="space-y-3 text-gray-600 dark:text-gray-300 mb-8">
|
||||
<li className="flex items-start">
|
||||
|
|
@ -148,8 +148,8 @@ export default function MembershipPage() {
|
|||
</div>
|
||||
<div className="card p-6">
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">Lifetime</h3>
|
||||
<p className="text-3xl font-bold text-primary">$500</p>
|
||||
<h3 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">Lifetime</h3>
|
||||
<p className="text-3xl font-bold text-primary dark:text-blue-400">$500</p>
|
||||
</div>
|
||||
<ul className="space-y-3 text-gray-600 dark:text-gray-300 mb-8">
|
||||
<li className="flex items-start">
|
||||
|
|
@ -181,7 +181,7 @@ export default function MembershipPage() {
|
|||
<div className="mt-10 text-center">
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Note: Student and senior discounts are available. Please
|
||||
<Link href="/contact" className="text-primary hover:underline"> contact us </Link>
|
||||
<Link href="/contact" className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline"> contact us </Link>
|
||||
for more information.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -261,21 +261,21 @@ export default function MembershipPage() {
|
|||
<blockquote className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
"Joining OCD was one of the best decisions I've made. It's helped me connect with the deaf community in Olathe and make lifelong friends."
|
||||
</blockquote>
|
||||
<div className="font-semibold">Sarah K.</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white">Sarah K.</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Member since 2019</div>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<blockquote className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
"As a parent of a deaf child, OCD has been an invaluable resource for our family. The events and workshops have helped us all learn and grow together."
|
||||
</blockquote>
|
||||
<div className="font-semibold">David M.</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white">David M.</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Member since 2021</div>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<blockquote className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
"I love being part of such a vibrant and supportive community. The social events and activities have really enriched my life."
|
||||
</blockquote>
|
||||
<div className="font-semibold">Maria J.</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white">Maria J.</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Member since 2018</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -292,7 +292,7 @@ export default function MembershipPage() {
|
|||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link
|
||||
href="/contact"
|
||||
className="bg-white text-primary hover:bg-gray-100 px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
className="bg-white text-primary hover:bg-gray-100 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
>
|
||||
Request Application
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const EventCard: React.FC<EventCardProps> = ({
|
|||
}) => {
|
||||
return (
|
||||
<div className="card h-full flex flex-col">
|
||||
<div className="aspect-video bg-gray-200 relative">
|
||||
<div className="aspect-video bg-gray-200 dark:bg-gray-600 relative">
|
||||
{imageUrl ? (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
|
|
@ -39,11 +39,11 @@ const EventCard: React.FC<EventCardProps> = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="p-6 flex-grow flex flex-col">
|
||||
<h3 className="text-xl font-semibold mb-2">{title}</h3>
|
||||
<p className="text-gray-600 dark:text-white mb-4 flex-grow">{description}</p>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">{title}</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4 flex-grow">{description}</p>
|
||||
<div className="flex justify-between items-center mt-auto">
|
||||
<span className="text-primary dark:text-white font-medium">{date}</span>
|
||||
<Link href={`/events/${slug}`} className="text-primary dark:text-white hover:underline">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-medium">{date}</span>
|
||||
<Link href={`/events/${slug}`} className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline">
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -66,7 +66,7 @@ export default function Home() {
|
|||
/>
|
||||
|
||||
{/* Featured events section */}
|
||||
<section className="py-16 bg-white dark:bg-gray-900">
|
||||
<section className="py-16 bg-gradient-to-br from-slate-50 to-blue-50 dark:from-gray-900 dark:to-gray-800">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-8 text-center text-gray-900 dark:text-white">Featured Events</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
|
|
@ -101,18 +101,18 @@ export default function Home() {
|
|||
</section>
|
||||
|
||||
{/* About section with video */}
|
||||
<section className="py-16 bg-gray-50 dark:bg-gray-800">
|
||||
<section className="py-16 bg-gradient-to-br from-indigo-50 to-white dark:from-gray-800 dark:to-gray-900">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-8 text-center text-gray-900 dark:text-white">About OCD</h2>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<p className="text-lg mb-6 dark:text-white">
|
||||
<p className="text-lg mb-6 text-gray-700 dark:text-gray-200">
|
||||
Olathe Club of the Deaf (OCD) is a non-profit organization dedicated to serving the deaf and hard-of-hearing community in Olathe and surrounding areas.
|
||||
</p>
|
||||
<p className="text-lg mb-6 dark:text-white">
|
||||
<p className="text-lg mb-6 text-gray-700 dark:text-gray-200">
|
||||
Our mission is to promote social, educational, and recreational opportunities for deaf individuals and their families while fostering a sense of community and belonging.
|
||||
</p>
|
||||
<p className="text-lg mb-6 dark:text-white">
|
||||
<p className="text-lg mb-6 text-gray-700 dark:text-gray-200">
|
||||
Founded in 1975, we have a rich history of advocacy and community service, working to bridge the gap between the deaf and hearing communities.
|
||||
</p>
|
||||
<Link href="/about" className="btn-primary inline-block">
|
||||
|
|
@ -130,25 +130,25 @@ export default function Home() {
|
|||
</section>
|
||||
|
||||
{/* Membership Benefits */}
|
||||
<section className="py-16 bg-white dark:bg-gray-900">
|
||||
<section className="py-16 bg-gradient-to-br from-purple-50 to-pink-50 dark:from-gray-900 dark:to-gray-800">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-8 text-center text-gray-900 dark:text-white">Membership Benefits</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-4">Community Events</h3>
|
||||
<p className="text-gray-600 dark:text-white">
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Community Events</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Access to all OCD community events, socials, and gatherings throughout the year.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-4">Voting Rights</h3>
|
||||
<p className="text-gray-600 dark:text-white">
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Voting Rights</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Have your say in club decisions and vote in board elections.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<h3 className="text-xl font-semibold mb-4">Support Network</h3>
|
||||
<p className="text-gray-600 dark:text-white">
|
||||
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Support Network</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Connect with a supportive network of deaf and hard-of-hearing individuals.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -165,16 +165,16 @@ export default function Home() {
|
|||
</section>
|
||||
|
||||
{/* Call to action */}
|
||||
<section className="py-16 bg-primary dark:bg-gray-800">
|
||||
<section className="py-16 bg-gradient-to-r from-blue-600 to-blue-700 dark:from-gray-800 dark:to-gray-900">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4 text-white">Join Our Community</h2>
|
||||
<p className="text-xl max-w-3xl mx-auto mb-8 text-white">
|
||||
<p className="text-xl max-w-3xl mx-auto mb-8 text-blue-50 dark:text-gray-200">
|
||||
Become a member today and connect with the deaf community in Olathe.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link
|
||||
href="/membership"
|
||||
className="bg-white text-primary hover:bg-gray-100 px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
className="bg-white text-primary hover:bg-gray-100 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white px-6 py-3 rounded-lg font-medium text-lg transition-colors duration-300"
|
||||
>
|
||||
Become a Member
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -109,3 +109,84 @@ export const VolumeIcon: React.FC<IconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SunIcon: React.FC<IconProps> = ({
|
||||
className = '',
|
||||
size = 24,
|
||||
color = 'currentColor'
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
className={`icon sun-icon ${className}`}
|
||||
aria-hidden="true"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<path d="M12 1v2" />
|
||||
<path d="M12 21v2" />
|
||||
<path d="M4.2 4.2l1.4 1.4" />
|
||||
<path d="M18.4 18.4l1.4 1.4" />
|
||||
<path d="M1 12h2" />
|
||||
<path d="M21 12h2" />
|
||||
<path d="M4.2 19.8l1.4-1.4" />
|
||||
<path d="M18.4 5.6l1.4-1.4" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const MoonIcon: React.FC<IconProps> = ({
|
||||
className = '',
|
||||
size = 24,
|
||||
color = 'currentColor'
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
className={`icon moon-icon ${className}`}
|
||||
aria-hidden="true"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContrastIcon: React.FC<IconProps> = ({
|
||||
className = '',
|
||||
size = 24,
|
||||
color = 'currentColor'
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
className={`icon contrast-icon ${className}`}
|
||||
aria-hidden="true"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a10 10 0 0 0 0 20z" fill={color} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,11 +60,14 @@ const Hero: React.FC<HeroProps> = ({
|
|||
const heroClass = backgroundImageUrl
|
||||
? 'text-white'
|
||||
: 'bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950 text-gray-900 dark:text-white';
|
||||
|
||||
// Background style only for custom background images
|
||||
const backgroundStyle = backgroundImageUrl ? bgStyle : {};
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`py-20 md:py-32 ${heroClass}`}
|
||||
style={bgStyle}
|
||||
style={backgroundStyle}
|
||||
aria-labelledby="hero-title"
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
|
|
|||
155
frontend/src/components/molecules/ThemeSelector.tsx
Normal file
155
frontend/src/components/molecules/ThemeSelector.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { SunIcon, MoonIcon, ContrastIcon } from '../atoms/Icons';
|
||||
|
||||
type ThemeType = 'light' | 'dark' | 'high-contrast' | 'system';
|
||||
|
||||
const ThemeSelector: React.FC = () => {
|
||||
const { theme, setTheme, resolvedTheme } = useTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Prevent hydration mismatch
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="p-2 rounded-md text-gray-700 dark:text-white">
|
||||
<SunIcon size={20} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Simple close handler for escape key
|
||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleThemeChange = (newTheme: ThemeType) => {
|
||||
setTheme(newTheme);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Get current theme icon based on resolved theme
|
||||
const ThemeIcon = () => {
|
||||
switch (resolvedTheme) {
|
||||
case 'dark':
|
||||
return <MoonIcon size={20} />;
|
||||
default:
|
||||
return <SunIcon size={20} />;
|
||||
}
|
||||
};
|
||||
|
||||
// Get theme name for screen readers
|
||||
const getThemeName = (themeType: string | undefined): string => {
|
||||
switch (themeType) {
|
||||
case 'dark':
|
||||
return 'Dark Mode';
|
||||
case 'system':
|
||||
return 'System Theme';
|
||||
default:
|
||||
return 'Light Mode';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
id="theme-selector-button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={isOpen}
|
||||
aria-label={`Theme Settings: Currently ${getThemeName(theme)}`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary"
|
||||
>
|
||||
<span className="sr-only">Theme Settings: Currently {getThemeName(theme)}</span>
|
||||
<ThemeIcon />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="theme-selector-button"
|
||||
>
|
||||
<div className="py-1" role="none">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Choose Website Theme</legend>
|
||||
|
||||
<div className="px-4 py-2 text-sm text-gray-700 dark:text-gray-200 font-medium border-b border-gray-200 dark:border-gray-700">
|
||||
Choose Website Theme
|
||||
</div>
|
||||
|
||||
<div className="theme-options">
|
||||
<button
|
||||
className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${
|
||||
theme === 'light'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
role="menuitem"
|
||||
onClick={() => handleThemeChange('light')}
|
||||
aria-pressed={theme === 'light'}
|
||||
>
|
||||
<SunIcon size={16} />
|
||||
<span>Light Mode</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-auto">
|
||||
(Standard brightness and colors)
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${
|
||||
theme === 'dark'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
role="menuitem"
|
||||
onClick={() => handleThemeChange('dark')}
|
||||
aria-pressed={theme === 'dark'}
|
||||
>
|
||||
<MoonIcon size={16} />
|
||||
<span>Dark Mode</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-auto">
|
||||
(Reduced brightness for low light)
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${
|
||||
theme === 'system'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
role="menuitem"
|
||||
onClick={() => handleThemeChange('system')}
|
||||
aria-pressed={theme === 'system'}
|
||||
>
|
||||
<ContrastIcon size={16} />
|
||||
<span>System</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-auto">
|
||||
(Follows device preference)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div className="px-4 py-2 text-xs text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700">
|
||||
Keyboard shortcut: Ctrl + T
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSelector;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import ThemeSelector from '../molecules/ThemeSelector';
|
||||
|
||||
// Icons
|
||||
const MenuIcon = ({ className = '' }: { className?: string }) => (
|
||||
|
|
@ -126,6 +127,7 @@ const Navigation = () => {
|
|||
<NavLink href="/membership">Membership</NavLink>
|
||||
<NavLink href="/contact">Contact</NavLink>
|
||||
<NavLink href="/donate">Donate</NavLink>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -157,6 +159,9 @@ const Navigation = () => {
|
|||
<NavLink href="/membership" isMobile onClick={closeMenu}>Membership</NavLink>
|
||||
<NavLink href="/contact" isMobile onClick={closeMenu}>Contact</NavLink>
|
||||
<NavLink href="/donate" isMobile onClick={closeMenu}>Donate</NavLink>
|
||||
<div className="px-3 py-2">
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ export default {
|
|||
'accent': 'var(--color-accent)',
|
||||
'accent-light': 'var(--color-accent-light)',
|
||||
'accent-dark': 'var(--color-accent-dark)',
|
||||
'bg': 'var(--color-bg)',
|
||||
'bg-secondary': 'var(--color-bg-secondary)',
|
||||
'bg-tertiary': 'var(--color-bg-tertiary)',
|
||||
'border': 'var(--color-border)',
|
||||
'text': 'var(--color-text)',
|
||||
'text-light': 'var(--color-text-light)',
|
||||
'transparent': 'transparent',
|
||||
'current': 'currentColor',
|
||||
'black': '#000',
|
||||
|
|
@ -76,6 +82,6 @@ export default {
|
|||
'full': '9999px',
|
||||
},
|
||||
},
|
||||
darkMode: 'media',
|
||||
darkMode: 'class',
|
||||
plugins: [],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue