fix(ui): put the theme switcher where it's actually visible — the sidebar
The previous commit added the theme button to shell/Header.tsx, which is imported NOWHERE (dead component — the app shell renders its own top bar, desktop has none at all), so the tester couldn't find any button. New ThemeSwitcher: segmented Light / Mid / Dark control at the bottom of the sidebar (desktop sidebar + mobile drawer, above the user box) — always visible on every page. Dead Header.tsx deleted so the next change doesn't land there again.
This commit is contained in:
@@ -4,6 +4,7 @@ import { Fragment } from 'react';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { XMarkIcon, ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
|
||||
import Navigation from './Navigation';
|
||||
import ThemeSwitcher from './ThemeSwitcher';
|
||||
import { ShieldCheckIcon } from '@heroicons/react/24/solid';
|
||||
import { UserInfo } from '../../../types';
|
||||
import api from '../../../lib/api';
|
||||
@@ -59,6 +60,7 @@ export default function Drawer({ sidebarOpen, setSidebarOpen, user }: DrawerProp
|
||||
</div>
|
||||
<Navigation />
|
||||
<div className="mt-auto pb-4">
|
||||
<ThemeSwitcher />
|
||||
{user && (
|
||||
<div className="mb-4 px-2 py-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -99,6 +101,7 @@ export default function Drawer({ sidebarOpen, setSidebarOpen, user }: DrawerProp
|
||||
</div>
|
||||
<Navigation />
|
||||
<div className="mt-auto pb-4">
|
||||
<ThemeSwitcher />
|
||||
{user && (
|
||||
<div className="mb-4 px-2 py-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import {
|
||||
ArrowRightOnRectangleIcon,
|
||||
Bars3Icon,
|
||||
MoonIcon,
|
||||
SunIcon,
|
||||
UserCircleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { MoonIcon as MoonIconSolid } from '@heroicons/react/24/solid';
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
||||
import useTheme from '@/hooks/useTheme';
|
||||
import api from '@/lib/api';
|
||||
|
||||
interface HeaderProps {
|
||||
setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Header = ({ setSidebarOpen }: HeaderProps) => {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState<{ name?: string, username?: string }>({ name: 'User' });
|
||||
|
||||
useEffect(() => {
|
||||
api.get('/auth/me')
|
||||
.then((res) => setUser(res.data))
|
||||
.catch(() => setUser({ name: 'User' }));
|
||||
}, []);
|
||||
|
||||
const handleSignOut = () => {
|
||||
api.post('/auth/logout')
|
||||
.catch(() => null)
|
||||
.finally(() => router.push('/login'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center justify-between border-b border-base-300 bg-base-100 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<button
|
||||
type="button"
|
||||
className="-m-2.5 p-2.5 text-base-content lg:hidden"
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
<div className="h-6 w-px bg-base-300 lg:hidden" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
|
||||
<div className="relative flex flex-1"></div>
|
||||
<div className="flex items-center gap-x-4 lg:gap-x-6">
|
||||
|
||||
{/* Theme cycle: light → mid (soft dark) → dark (full dark) */}
|
||||
<button
|
||||
type="button"
|
||||
className="-m-2.5 p-2.5 flex items-center gap-1.5 text-base-content hover:text-primary"
|
||||
onClick={toggleTheme}
|
||||
title={`Theme: ${theme} — click to switch (light → mid → dark)`}
|
||||
>
|
||||
<span className="sr-only">Switch theme</span>
|
||||
{theme === 'light' && <SunIcon className="h-6 w-6" aria-hidden="true" />}
|
||||
{theme === 'mid' && <MoonIcon className="h-6 w-6" aria-hidden="true" />}
|
||||
{theme === 'dark' && <MoonIconSolid className="h-6 w-6" aria-hidden="true" />}
|
||||
<span className="hidden sm:inline text-xs font-mono uppercase text-base-content/60">{theme}</span>
|
||||
</button>
|
||||
|
||||
<div className="dropdown dropdown-end">
|
||||
<div role="button" tabIndex={0} className="flex items-center gap-x-1 cursor-pointer">
|
||||
<span className="text-sm font-semibold leading-6 text-base-content font-mono">{user.username || user.name}</span>
|
||||
<ChevronDownIcon className="h-5 w-5 text-base-content/50" aria-hidden="true" />
|
||||
</div>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li>
|
||||
<Link href="/settings" className="flex items-center gap-x-2">
|
||||
<UserCircleIcon className="h-4 w-4" /> Profile
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={handleSignOut} className="flex items-center gap-x-2 text-error">
|
||||
<ArrowRightOnRectangleIcon className="h-4 w-4" /> Logout
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { SunIcon, MoonIcon } from '@heroicons/react/24/outline';
|
||||
import { MoonIcon as MoonIconSolid } from '@heroicons/react/24/solid';
|
||||
import useTheme, { Theme } from '@/hooks/useTheme';
|
||||
|
||||
// Segmented Light / Mid / Dark control for the (always dark) sidebar.
|
||||
const OPTIONS: { value: Theme; label: string; Icon: typeof SunIcon }[] = [
|
||||
{ value: 'light', label: 'Light', Icon: SunIcon },
|
||||
{ value: 'mid', label: 'Mid', Icon: MoonIcon },
|
||||
{ value: 'dark', label: 'Dark', Icon: MoonIconSolid },
|
||||
];
|
||||
|
||||
export default function ThemeSwitcher() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
return (
|
||||
<div className="mb-3 flex rounded-lg bg-gray-800/60 border border-gray-700/50 p-1">
|
||||
{OPTIONS.map(({ value, label, Icon }) => (
|
||||
<button
|
||||
key={value}
|
||||
onClick={() => setTheme(value)}
|
||||
title={`${label} theme`}
|
||||
className={`flex-1 flex items-center justify-center gap-1 rounded-md px-2 py-1.5 text-[11px] font-mono uppercase tracking-wide transition-colors ${
|
||||
theme === value
|
||||
? 'bg-gray-700 text-white'
|
||||
: 'text-gray-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user