-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/quicklab navbar #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // DesktopNavbar.jsx | ||
| import { Search } from 'lucide-react'; | ||
| import DarkModeToggle from '../ui/DarkModeToggle'; | ||
|
|
||
| export default function DesktopNavbar({ searchQuery, setSearchQuery }) { | ||
| return ( | ||
| <nav className="hidden lg:block fixed top-0 left-0 right-0 z-50 bg-white dark:bg-black border-b border-slate-200 dark:border-slate-800 shadow-sm"> | ||
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | ||
| <div className="flex items-center justify-between h-16"> | ||
| {/* Logo */} | ||
| <div className="flex items-center space-x-2"> | ||
| <div className="text-2xl font-bold"> | ||
| <span className="text-slate-900 dark:text-slate-50">Quick</span> | ||
| <span className="text-yellow-500 dark:text-yellow-400">Labs</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Search Bar */} | ||
| <div className="flex-1 max-w-xl mx-8"> | ||
| <div className="relative"> | ||
| <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | ||
| <Search className="h-5 w-5 text-slate-500 dark:text-slate-400" /> | ||
| </div> | ||
| <input | ||
| type="text" | ||
| value={searchQuery} | ||
| onChange={(e) => setSearchQuery(e.target.value)} | ||
| placeholder="Search labs..." | ||
| className="block w-full pl-10 pr-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-50 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Right Side Actions */} | ||
| <div className="flex items-center space-x-4"> | ||
| {/* Dark Mode Toggle */} | ||
| <DarkModeToggle /> | ||
|
|
||
| {/* Login/Register Buttons */} | ||
| <button className="px-4 py-2 text-slate-700 dark:text-slate-300 hover:text-yellow-600 dark:hover:text-yellow-400 font-medium transition-colors"> | ||
| Login | ||
| </button> | ||
| <button className="px-4 py-2 text-slate-700 dark:text-slate-300 hover:text-yellow-600 dark:hover:text-yellow-400 font-medium transition-colors"> | ||
| Register | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </nav> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // MobileNavbar.jsx | ||
| import { Search, Home, BookOpen, User } from 'lucide-react'; | ||
| import DarkModeToggle from '../ui/DarkModeToggle'; | ||
|
|
||
| export default function MobileNavbar({ searchQuery, setSearchQuery }) { | ||
| return ( | ||
| <> | ||
| {/* Top Bar - Search Only */} | ||
| <div className="lg:hidden fixed top-0 left-0 right-0 z-50 bg-white dark:bg-black border-b border-slate-200 dark:border-slate-800 shadow-sm"> | ||
| <div className="px-4 py-3"> | ||
| {/* Logo */} | ||
| <div className="flex items-center space-x-3 mb-2"> | ||
| <div className="text-xl font-bold whitespace-nowrap"> | ||
| <span className="text-slate-900 dark:text-slate-50">Quick</span> | ||
| <span className="text-yellow-500 dark:text-yellow-400">Labs</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="relative"> | ||
| <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | ||
| <Search className="h-5 w-5 text-slate-500 dark:text-slate-400" /> | ||
| </div> | ||
| <input | ||
| type="text" | ||
| value={searchQuery} | ||
| onChange={(e) => setSearchQuery(e.target.value)} | ||
| placeholder="Search labs..." | ||
| className="block w-full pl-10 pr-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-50 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all" | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Bottom Navigation Bar */} | ||
| <div className="lg:hidden fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-black border-t border-slate-200 dark:border-slate-800 shadow-lg"> | ||
| <div className="flex items-center justify-around h-16 px-2"> | ||
| {/* Home/Labs */} | ||
| <button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95"> | ||
| <Home className="h-6 w-6" /> | ||
| <span className="text-xs font-medium">Home</span> | ||
| </button> | ||
|
|
||
| {/* Labs */} | ||
| <button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95"> | ||
| <BookOpen className="h-6 w-6" /> | ||
| <span className="text-xs font-medium">Labs</span> | ||
| </button> | ||
|
|
||
| {/* Dark Mode Toggle */} | ||
| <div className="flex flex-col items-center justify-center w-full h-full"> | ||
| <DarkModeToggle /> | ||
| <span className="text-xs font-medium text-slate-500 dark:text-slate-400 mt-1"> | ||
| Theme | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* Login/Account */} | ||
| <button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95"> | ||
| <User className="h-6 w-6" /> | ||
| <span className="text-xs font-medium">Account</span> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Spacer for fixed navbars */} | ||
| <div className="lg:hidden h-28" /> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Navbar.jsx | ||
| import { useState } from 'react'; | ||
| import DesktopNavbar from './DesktopNavbar'; | ||
| import MobileNavbar from './MobileNavbar'; | ||
|
|
||
| export default function Navbar() { | ||
| const [searchQuery, setSearchQuery] = useState(''); | ||
|
|
||
| return ( | ||
| <> | ||
| {/* Desktop Navbar - Hidden on mobile */} | ||
| <DesktopNavbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> | ||
|
|
||
| {/* Mobile Navbar - Hidden on desktop */} | ||
| <MobileNavbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| // quicklab-theme.js | ||
|
|
||
| export const colors = { | ||
| primary: '#eab308', // yellow-500 | ||
| primaryDark: '#ca8a04', // yellow-600 | ||
| primaryLight: '#facc15', // yellow-400 | ||
| secondary: '#1e293b', // black-800 | ||
| accent: '#3b82f6', // blue-500 | ||
| accentDark: '#2563eb', // blue-600 | ||
| light: '#f8fafc', // slate-50 | ||
| dark: '#000000', // pure black | ||
| darkSurface: '#0f172a', // slate-900 | ||
| success: '#22c55e', // green (from QuickMed) | ||
| warning: '#f59e0b', // amber | ||
| danger: '#ef4444', // red | ||
| info: '#3b82f6', // blue | ||
| text: { | ||
| light: '#0f172a', | ||
| dark: '#f8fafc', | ||
| muted: { | ||
| light: '#64748b', | ||
| dark: '#94a3b8', | ||
| }, | ||
| }, | ||
| border: { | ||
| light: '#e2e8f0', | ||
| dark: '#1e293b', | ||
| }, | ||
| }; | ||
|
|
||
| export const spacing = { | ||
| xs: '0.25rem', | ||
| sm: '0.5rem', | ||
| md: '1rem', | ||
| lg: '1.5rem', | ||
| xl: '2rem', | ||
| '2xl': '3rem', | ||
| }; | ||
|
|
||
| export const breakpoints = { | ||
| sm: '640px', | ||
| md: '768px', | ||
| lg: '1024px', | ||
| xl: '1280px', | ||
| '2xl': '1536px', | ||
| }; | ||
|
|
||
| export const shadows = { | ||
| sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', | ||
| md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', | ||
| lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', | ||
| xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', | ||
| yellow: '0 4px 14px 0 rgba(234, 179, 8, 0.39)', // yellow glow | ||
| blue: '0 4px 14px 0 rgba(59, 130, 246, 0.39)', // blue glow | ||
| }; | ||
|
|
||
| export const transitions = { | ||
| fast: '150ms ease-in-out', | ||
| normal: '200ms ease-in-out', | ||
| slow: '300ms ease-in-out', | ||
| }; | ||
|
|
||
| // Theme toggle functionality | ||
| export const initTheme = () => { | ||
| const theme = localStorage.getItem('quicklab-theme') || 'light'; | ||
| document.documentElement.classList.toggle('dark', theme === 'dark'); | ||
| return theme; | ||
| }; | ||
|
|
||
| export const toggleTheme = () => { | ||
| const isDark = document.documentElement.classList.toggle('dark'); | ||
| localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light'); | ||
| return isDark ? 'dark' : 'light'; | ||
| }; | ||
|
|
||
| export const setTheme = (theme) => { | ||
| document.documentElement.classList.toggle('dark', theme === 'dark'); | ||
| localStorage.setItem('quicklab-theme', theme); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| @import 'tailwindcss'; | ||
|
|
||
| /* Enable dark mode with class selector */ | ||
| @custom-variant dark (&:where(.dark, .dark *)); | ||
|
Comment on lines
+1
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check Tailwind CSS version in package.json
cat package.json | jq '.dependencies.tailwindcss, .devDependencies.tailwindcss' 2>/dev/null || cat package.json | grep -A 2 -B 2 'tailwindcss'
# Also check for tailwind.config file
echo "=== Tailwind Config Files ==="
fd -t f "tailwind.config" -x cat {} 2>/dev/null || echo "No tailwind.config file found"Repository: genzz-dev/QuickClinic Length of output: 228 🏁 Script executed: #!/bin/bash
# Check for lock files
echo "=== Lock Files ==="
if [ -f "package-lock.json" ]; then
echo "Found package-lock.json"
grep -A 2 '"tailwindcss"' package-lock.json | head -20
elif [ -f "yarn.lock" ]; then
echo "Found yarn.lock"
grep -A 3 "tailwindcss" yarn.lock | head -20
elif [ -f "pnpm-lock.yaml" ]; then
echo "Found pnpm-lock.yaml"
grep -A 3 "tailwindcss" pnpm-lock.yaml | head -20
else
echo "No lock file found"
fi
# Check full package.json structure
echo -e "\n=== Full package.json ==="
cat package.jsonRepository: genzz-dev/QuickClinic Length of output: 734 🏁 Script executed: #!/bin/bash
# Check if tailwindcss appears in lock file at all
echo "=== Checking for tailwindcss in lock file ==="
if grep -q '"tailwindcss"' package-lock.json; then
echo "Found tailwindcss in package-lock.json:"
grep -A 10 '"tailwindcss"' package-lock.json | head -30
else
echo "tailwindcss NOT found in package-lock.json"
fi
# Check framer-motion dependencies to see if it depends on tailwindcss
echo -e "\n=== Checking framer-motion dependencies ==="
grep -A 30 '"framer-motion"' package-lock.json | grep -E '(version|dependencies)' | head -20Repository: genzz-dev/QuickClinic Length of output: 276 🌐 Web query:
💡 Result: Short answer — Tailwind v4 introduced the @custom-variant/@variant directive to declare custom variants in CSS. Example forms from the official docs:
Notes: several v4 users reported large CSS/performance changes when migrating custom variants (see related GitHub discussion). [3] Sources: Tailwind CSS dependency is missing and v4 is required. The project does not have "dependencies": {
"tailwindcss": "^4.0.0"
}🤖 Prompt for AI Agents |
||
|
|
||
| @theme { | ||
| /* QuickLab Light Mode Colors - Yellow/Gold Spectrum */ | ||
| --color-lab-yellow-50: #fefce8; | ||
| --color-lab-yellow-100: #fef9c3; | ||
| --color-lab-yellow-200: #fef08a; | ||
| --color-lab-yellow-300: #fde047; | ||
| --color-lab-yellow-400: #facc15; | ||
| --color-lab-yellow-500: #eab308; | ||
| --color-lab-yellow-600: #ca8a04; | ||
| --color-lab-yellow-700: #a16207; | ||
| --color-lab-yellow-800: #854d0e; | ||
| --color-lab-yellow-900: #713f12; | ||
|
|
||
| /* QuickLab Black/Gray Spectrum */ | ||
| --color-lab-black-50: #f8fafc; | ||
| --color-lab-black-100: #f1f5f9; | ||
| --color-lab-black-200: #e2e8f0; | ||
| --color-lab-black-300: #cbd5e1; | ||
| --color-lab-black-400: #94a3b8; | ||
| --color-lab-black-500: #64748b; | ||
| --color-lab-black-600: #475569; | ||
| --color-lab-black-700: #334155; | ||
| --color-lab-black-800: #1e293b; | ||
| --color-lab-black-900: #0f172a; | ||
|
|
||
| /* QuickLab Blue Accent */ | ||
| --color-lab-blue-400: #60a5fa; | ||
| --color-lab-blue-500: #3b82f6; | ||
| --color-lab-blue-600: #2563eb; | ||
| --color-lab-blue-700: #1d4ed8; | ||
| } | ||
|
|
||
| /* Dark Mode Overrides */ | ||
| @layer base { | ||
| .dark { | ||
| --color-lab-yellow-400: #facc15; | ||
| --color-lab-yellow-500: #facc15; | ||
| --color-lab-yellow-600: #eab308; | ||
| --color-lab-yellow-700: #ca8a04; | ||
| --color-lab-blue-500: #60a5fa; | ||
| --color-lab-blue-600: #3b82f6; | ||
| } | ||
| } | ||
|
|
||
| /* Custom Component Classes */ | ||
| @layer components { | ||
| /* Primary Button */ | ||
| .btn-quicklab-primary { | ||
| background-color: rgb(234 179 8); /* yellow-500 */ | ||
| color: rgb(15 23 42); /* black-900 */ | ||
| padding: 0.75rem 1.5rem; | ||
| border-radius: 0.5rem; | ||
| transition: background-color 0.2s; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .dark .btn-quicklab-primary { | ||
| background-color: rgb(250 204 21); /* yellow-400 */ | ||
| color: rgb(15 23 42); /* black-900 - stays dark */ | ||
| } | ||
|
|
||
| .btn-quicklab-primary:hover { | ||
| background-color: rgb(202 138 4); /* yellow-600 */ | ||
| } | ||
|
|
||
| .dark .btn-quicklab-primary:hover { | ||
| background-color: rgb(234 179 8); /* yellow-500 */ | ||
| } | ||
|
|
||
| /* Secondary Button (Black) */ | ||
| .btn-quicklab-secondary { | ||
| background-color: rgb(30 41 59); /* black-800 */ | ||
| color: rgb(250 204 21); /* yellow-400 */ | ||
| padding: 0.75rem 1.5rem; | ||
| border-radius: 0.5rem; | ||
| transition: all 0.2s; | ||
| font-weight: 600; | ||
| border: 2px solid rgb(234 179 8); /* yellow-500 */ | ||
| } | ||
|
|
||
| .dark .btn-quicklab-secondary { | ||
| background-color: rgb(248 250 252); /* light in dark mode */ | ||
| color: rgb(30 41 59); /* dark text */ | ||
| border-color: rgb(250 204 21); /* yellow-400 */ | ||
| } | ||
|
|
||
| .btn-quicklab-secondary:hover { | ||
| background-color: rgb(15 23 42); /* black-900 */ | ||
| border-color: rgb(234 179 8); /* yellow-500 */ | ||
| } | ||
|
|
||
| /* Card Component */ | ||
| .card-quicklab { | ||
| background-color: rgb(248 250 252); /* surface light */ | ||
| border: 1px solid rgb(226 232 240); /* border light */ | ||
| border-radius: 0.75rem; | ||
| padding: 1.5rem; | ||
| box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); | ||
| transition: all 0.2s; | ||
| } | ||
|
|
||
| .dark .card-quicklab { | ||
| background-color: rgb(15 23 42); /* surface dark */ | ||
| border-color: rgb(30 41 59); /* border dark */ | ||
| } | ||
|
|
||
| .card-quicklab:hover { | ||
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | ||
| border-color: rgb(234 179 8); /* yellow-500 */ | ||
| } | ||
|
|
||
| .dark .card-quicklab:hover { | ||
| border-color: rgb(250 204 21); /* yellow-400 */ | ||
| } | ||
|
|
||
| /* Badge Component */ | ||
| .badge-quicklab { | ||
| background-color: rgb(254 249 195); /* yellow-100 */ | ||
| color: rgb(133 77 14); /* yellow-800 */ | ||
| padding: 0.25rem 0.75rem; | ||
| border-radius: 9999px; | ||
| font-size: 0.875rem; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .dark .badge-quicklab { | ||
| background-color: rgb(113 63 18); /* yellow-900 */ | ||
| color: rgb(253 224 71); /* yellow-300 */ | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { Routes, Route } from 'react-router-dom'; | ||
| import NotFoundPage from '../components/ui/NotFoundPage'; | ||
| import Navbar from '../components/quicklab/Navbar'; | ||
| export default function QuickLabRoutes() { | ||
| return ( | ||
| <> | ||
| <Navbar /> | ||
| <Routes> | ||
| <Route path="*" element={<NotFoundPage />} /> | ||
| </Routes> | ||
| </> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add SSR compatibility checks for document access.
The theme functions directly access
document.documentElementandlocalStoragewithout checking if they exist. This will cause runtime errors in SSR environments (like Next.js or during server-side rendering).Apply this diff to add environment checks:
export const initTheme = () => { + if (typeof window === 'undefined') return 'light'; const theme = localStorage.getItem('quicklab-theme') || 'light'; document.documentElement.classList.toggle('dark', theme === 'dark'); return theme; }; export const toggleTheme = () => { + if (typeof window === 'undefined') return 'light'; const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light'); return isDark ? 'dark' : 'light'; }; export const setTheme = (theme) => { + if (typeof window === 'undefined') return; document.documentElement.classList.toggle('dark', theme === 'dark'); localStorage.setItem('quicklab-theme', theme); };This ensures the code won't break if the application later adopts SSR or is executed in a Node.js environment during build time.
📝 Committable suggestion
🤖 Prompt for AI Agents