This commit is contained in:
2026-03-16 10:20:01 -04:00
parent c46dcb440d
commit 7c47f2d2eb
25 changed files with 2057 additions and 284 deletions

View File

@@ -37,11 +37,11 @@ export default function HamburgerMenu() {
const isAdmin = user?.is_admin ?? false;
return (
<div className="flex flex-col z-40 relative ml-6">
<div className="relative z-40 ml-6 flex flex-col">
{/* Checkbox input for state management */}
<input
type="checkbox"
className="absolute lg:hidden z-50 -top-2 w-7 h-7 flex cursor-pointer opacity-0"
className="absolute -top-2 z-50 flex size-7 cursor-pointer opacity-0 lg:hidden"
id="mobile-nav-checkbox"
checked={isOpen}
onChange={(e) => setIsOpen(e.target.checked)}
@@ -49,7 +49,7 @@ export default function HamburgerMenu() {
{/* Hamburger icon lines with CSS animations - hidden on desktop */}
<span
className="lg:hidden bg-black w-7 h-0.5 z-40 mt-0.5 dark:bg-white transition-transform transition-background transition-opacity duration-500"
className="transition-background z-40 mt-0.5 h-0.5 w-7 bg-black transition-opacity transition-transform duration-500 lg:hidden dark:bg-white"
style={{
transformOrigin: '5px 0px',
transition: 'transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), opacity 0.55s ease',
@@ -57,7 +57,7 @@ export default function HamburgerMenu() {
}}
/>
<span
className="lg:hidden bg-black w-7 h-0.5 z-40 mt-1 dark:bg-white transition-transform transition-background transition-opacity duration-500"
className="transition-background z-40 mt-1 h-0.5 w-7 bg-black transition-opacity transition-transform duration-500 lg:hidden dark:bg-white"
style={{
transformOrigin: '0% 100%',
transition: 'transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), opacity 0.55s ease',
@@ -66,7 +66,7 @@ export default function HamburgerMenu() {
}}
/>
<span
className="lg:hidden bg-black w-7 h-0.5 z-40 mt-1 dark:bg-white transition-transform transition-background transition-opacity duration-500"
className="transition-background z-40 mt-1 h-0.5 w-7 bg-black transition-opacity transition-transform duration-500 lg:hidden dark:bg-white"
style={{
transformOrigin: '0% 0%',
transition: 'transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), opacity 0.55s ease',
@@ -77,7 +77,7 @@ export default function HamburgerMenu() {
{/* Navigation menu with slide animation */}
<div
id="menu"
className="fixed -ml-6 h-full w-56 lg:w-48 bg-white dark:bg-gray-700 shadow-lg"
className="fixed -ml-6 h-full w-56 bg-white shadow-lg lg:w-48 dark:bg-gray-700"
style={{
top: 0,
paddingTop: 'env(safe-area-inset-top)',
@@ -96,8 +96,8 @@ export default function HamburgerMenu() {
}
}
`}</style>
<div className="h-16 flex justify-end lg:justify-around">
<p className="text-xl font-bold dark:text-white text-right my-auto pr-8 lg:pr-0">
<div className="flex h-16 justify-end lg:justify-around">
<p className="my-auto pr-8 text-right text-xl font-bold lg:pr-0 dark:text-white">
AnthoLume
</p>
</div>
@@ -107,7 +107,7 @@ export default function HamburgerMenu() {
key={item.path}
to={item.path}
onClick={() => setIsOpen(false)}
className={`flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 ${
className={`my-2 flex w-full items-center justify-start border-l-4 p-2 pl-6 transition-colors duration-200 ${
location.pathname === item.path
? 'border-purple-500 dark:text-white'
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100'
@@ -120,7 +120,7 @@ export default function HamburgerMenu() {
{/* Admin section - only visible for admins */}
{isAdmin && (
<div className={`flex flex-col gap-4 p-2 pl-6 my-2 transition-colors duration-200 border-l-4 ${
<div className={`my-2 flex flex-col gap-4 border-l-4 p-2 pl-6 transition-colors duration-200 ${
hasPrefix(location.pathname, '/admin')
? 'border-purple-500 dark:text-white'
: 'border-transparent text-gray-400'
@@ -129,7 +129,7 @@ export default function HamburgerMenu() {
<Link
to="/admin"
onClick={() => setIsOpen(false)}
className={`flex justify-start w-full ${
className={`flex w-full justify-start ${
location.pathname === '/admin' && !hasPrefix(location.pathname, '/admin/')
? 'dark:text-white'
: 'text-gray-400 hover:text-gray-800 dark:hover:text-gray-100'
@@ -146,7 +146,7 @@ export default function HamburgerMenu() {
key={item.path}
to={item.path}
onClick={() => setIsOpen(false)}
className={`flex justify-start w-full ${
className={`flex w-full justify-start ${
location.pathname === item.path
? 'dark:text-white'
: 'text-gray-400 hover:text-gray-800 dark:hover:text-gray-100'
@@ -162,9 +162,9 @@ export default function HamburgerMenu() {
)}
</nav>
<a
className="flex flex-col gap-2 justify-center items-center p-6 w-full absolute bottom-0 text-black dark:text-white"
className="absolute bottom-0 flex w-full flex-col items-center justify-center gap-2 p-6 text-black dark:text-white"
target="_blank"
href="https://gitea.va.reichard.io/evan/AnthoLume"
href="https://gitea.va.reichard.io/evan/AnthoLume" rel="noreferrer"
>
<span className="text-xs">v1.0.0</span>
</a>

View File

@@ -54,19 +54,19 @@ export default function Layout() {
}
return (
<div className="bg-gray-100 dark:bg-gray-800 min-h-screen">
<div className="min-h-screen bg-gray-100 dark:bg-gray-800">
{/* Header */}
<div className="flex items-center justify-between w-full h-16">
<div className="flex h-16 w-full items-center justify-between">
{/* Mobile Navigation Button with CSS animations */}
<HamburgerMenu />
{/* Header Title */}
<h1 className="text-xl font-bold dark:text-white px-6 lg:ml-44">
<h1 className="px-6 text-xl font-bold lg:ml-44 dark:text-white">
{currentPageTitle}
</h1>
{/* User Dropdown */}
<div className="relative flex items-center justify-end w-full p-4 space-x-4" ref={dropdownRef}>
<div className="relative flex w-full items-center justify-end space-x-4 p-4" ref={dropdownRef}>
<button
onClick={() => setIsUserDropdownOpen(!isUserDropdownOpen)}
className="relative block text-gray-800 dark:text-gray-200"
@@ -75,8 +75,8 @@ export default function Layout() {
</button>
{isUserDropdownOpen && (
<div className="transition duration-200 z-20 absolute right-4 top-16 pt-4">
<div className="w-40 origin-top-right bg-white rounded-md shadow-lg dark:shadow-gray-800 dark:bg-gray-700 ring-1 ring-black ring-opacity-5">
<div className="absolute right-4 top-16 z-20 pt-4 transition duration-200">
<div className="w-40 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 dark:bg-gray-700 dark:shadow-gray-800">
<div
className="py-1"
role="menu"
@@ -86,7 +86,7 @@ export default function Layout() {
<Link
to="/settings"
onClick={() => setIsUserDropdownOpen(false)}
className="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600"
className="text-md block px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
role="menuitem"
>
<span className="flex flex-col">
@@ -95,7 +95,7 @@ export default function Layout() {
</Link>
<button
onClick={handleLogout}
className="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 w-full text-left"
className="text-md block w-full px-4 py-2 text-left text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
role="menuitem"
>
<span className="flex flex-col">
@@ -109,10 +109,10 @@ export default function Layout() {
<button
onClick={() => setIsUserDropdownOpen(!isUserDropdownOpen)}
className="flex items-center gap-2 text-gray-500 dark:text-white text-md py-4 cursor-pointer"
className="text-md flex cursor-pointer items-center gap-2 py-4 text-gray-500 dark:text-white"
>
<span>{userData?.username || 'User'}</span>
<span className="text-gray-800 dark:text-gray-200 transition-transform duration-200" style={{ transform: isUserDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}>
<span className="text-gray-800 transition-transform duration-200 dark:text-gray-200" style={{ transform: isUserDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}>
<ChevronDown size={20} />
</span>
</button>
@@ -121,7 +121,7 @@ export default function Layout() {
{/* Main Content */}
<main className="relative overflow-hidden" style={{ height: 'calc(100dvh - 4rem - env(safe-area-inset-top))' }}>
<div id="container" className="h-[100dvh] px-4 overflow-auto md:px-6 lg:ml-48" style={{ paddingBottom: 'calc(5em + env(safe-area-inset-bottom) * 2)' }}>
<div id="container" className="h-dvh overflow-auto px-4 md:px-6 lg:ml-48" style={{ paddingBottom: 'calc(5em + env(safe-area-inset-bottom) * 2)' }}>
<Outlet />
</div>
</main>

View File

@@ -113,16 +113,16 @@ export function SkeletonCard({
return (
<div className={cn('bg-white dark:bg-gray-700 rounded-lg p-4 border dark:border-gray-600', className)}>
{showAvatar && (
<div className="flex items-start gap-4 mb-4">
<div className="mb-4 flex items-start gap-4">
<SkeletonAvatar />
<div className="flex-1">
<Skeleton variant="text" className="w-3/4 mb-2" />
<Skeleton variant="text" className="mb-2 w-3/4" />
<Skeleton variant="text" className="w-1/2" />
</div>
</div>
)}
{showTitle && (
<Skeleton variant="text" className="w-1/2 mb-4 h-6" />
<Skeleton variant="text" className="mb-4 h-6 w-1/2" />
)}
{showText && (
<SkeletonText lines={textLines} />
@@ -152,7 +152,7 @@ export function SkeletonTable({
<tr className="border-b dark:border-gray-600">
{Array.from({ length: columns }).map((_, i) => (
<th key={i} className="p-3">
<Skeleton variant="text" className="w-3/4 h-5" />
<Skeleton variant="text" className="h-5 w-3/4" />
</th>
))}
</tr>
@@ -160,7 +160,7 @@ export function SkeletonTable({
)}
<tbody>
{Array.from({ length: rows }).map((_, rowIndex) => (
<tr key={rowIndex} className="border-b dark:border-gray-600 last:border-0">
<tr key={rowIndex} className="border-b last:border-0 dark:border-gray-600">
{Array.from({ length: columns }).map((_, colIndex) => (
<td key={colIndex} className="p-3">
<Skeleton variant="text" className={colIndex === columns - 1 ? 'w-1/2' : 'w-full'} />
@@ -199,9 +199,9 @@ export function PageLoader({ message = 'Loading...', className = '' }: PageLoade
return (
<div className={cn('flex flex-col items-center justify-center min-h-[400px] gap-4', className)}>
<div className="relative">
<div className="w-12 h-12 border-4 border-gray-200 dark:border-gray-600 border-t-blue-500 rounded-full animate-spin" />
<div className="size-12 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500 dark:border-gray-600" />
</div>
<p className="text-gray-500 dark:text-gray-400 text-sm font-medium">{message}</p>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">{message}</p>
</div>
);
}
@@ -220,7 +220,7 @@ export function InlineLoader({ size = 'md', className = '' }: InlineLoaderProps)
return (
<div className={cn('flex items-center justify-center', className)}>
<div className={`${sizeMap[size]} border-gray-200 dark:border-gray-600 border-t-blue-500 rounded-full animate-spin`} />
<div className={`${sizeMap[size]} animate-spin rounded-full border-gray-200 border-t-blue-500 dark:border-gray-600`} />
</div>
);
}

View File

@@ -43,14 +43,14 @@ export function Table<T extends Record<string, any>>({
<tr className="border-b dark:border-gray-600">
{Array.from({ length: columns }).map((_, i) => (
<th key={i} className="p-3">
<Skeleton variant="text" className="w-3/4 h-5" />
<Skeleton variant="text" className="h-5 w-3/4" />
</th>
))}
</tr>
</thead>
<tbody>
{Array.from({ length: rows }).map((_, rowIndex) => (
<tr key={rowIndex} className="border-b dark:border-gray-600 last:border-0">
<tr key={rowIndex} className="border-b last:border-0 dark:border-gray-600">
{Array.from({ length: columns }).map((_, colIndex) => (
<td key={colIndex} className="p-3">
<Skeleton variant="text" className={colIndex === columns - 1 ? 'w-1/2' : 'w-full'} />
@@ -79,7 +79,7 @@ export function Table<T extends Record<string, any>>({
{columns.map((column) => (
<th
key={String(column.key)}
className={`text-left p-3 text-gray-500 dark:text-white ${column.className || ''}`}
className={`p-3 text-left text-gray-500 dark:text-white ${column.className || ''}`}
>
{column.header}
</th>
@@ -91,7 +91,7 @@ export function Table<T extends Record<string, any>>({
<tr>
<td
colSpan={columns.length}
className="text-center p-3 text-gray-700 dark:text-gray-300"
className="p-3 text-center text-gray-700 dark:text-gray-300"
>
{emptyMessage}
</td>

View File

@@ -70,7 +70,7 @@ export function Toast({ id, type, message, duration = 5000, onClose }: ToastProp
<div
className={`${baseStyles} ${typeStyles[type]} ${
isAnimatingOut
? 'opacity-0 translate-x-full'
? 'translate-x-full opacity-0'
: 'animate-slideInRight opacity-100'
}`}
>
@@ -80,7 +80,7 @@ export function Toast({ id, type, message, duration = 5000, onClose }: ToastProp
</p>
<button
onClick={handleClose}
className={`ml-2 opacity-70 hover:opacity-100 transition-opacity ${textStyles[type]}`}
className={`ml-2 opacity-70 transition-opacity hover:opacity-100 ${textStyles[type]}`}
aria-label="Close"
>
<X size={18} />

View File

@@ -59,7 +59,7 @@ function ToastContainer({ toasts }: ToastContainerProps) {
}
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-sm w-full pointer-events-none">
<div className="pointer-events-none fixed bottom-4 right-4 z-50 flex w-full max-w-sm flex-col gap-2">
<div className="pointer-events-auto">
{toasts.map((toast) => (
<Toast key={toast.id} {...toast} />