Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function AppSidebar({ ...props }: ComponentProps<typeof Sidebar>) {
<SidebarMenuItem>
<SidebarMenuButton
tooltip="Settings"
isActive={pathname === settingsUrl}
isActive={pathname.startsWith(settingsUrl)}
render={<Link to={settingsUrl} />}
>
<Settings />
Expand Down
72 changes: 70 additions & 2 deletions apps/dashboard/src/components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,58 @@
import { createContext, useContext, useEffect, useState } from "react";

type Theme = "dark" | "light" | "system";
export type Theme =
| "dark"
| "light"
| "system"
| "crimson-moon"
| "sepia"
| "midnight-blurple"
| "blurple-twilight"
| "forest"
| "dusk"
| "aurora"
| "sunset"
| "mars"
| "retro-storm"
| "under-the-sea"
| "strawberry-lemonade"
| "neon-nights"
| "citrus-sherbet"
| "desert-khaki"
| "sunrise"
| "hanami"
| "cotton-candy"
| "mint-apple";

// Custom palettes are applied to <html> as a `theme-<key>` class (e.g.
// `theme-sepia`) — the `theme-` prefix keeps them from colliding with Tailwind
// utility classes of the same name (notably `sepia`, `grayscale`, `invert`).
// Each derives its tokens from the shared `.theme-dark` / `.theme-light` marker,
// added alongside the palette class in the effect below.
const DARK_THEMES = [
"crimson-moon",
"sepia",
"midnight-blurple",
"blurple-twilight",
"forest",
"dusk",
"aurora",
"sunset",
"mars",
"retro-storm",
"under-the-sea",
"strawberry-lemonade",
"neon-nights",
] as const;
const LIGHT_THEMES = [
"citrus-sherbet",
"desert-khaki",
"sunrise",
"hanami",
"cotton-candy",
"mint-apple",
] as const;
const CUSTOM_THEMES: readonly string[] = [...DARK_THEMES, ...LIGHT_THEMES];

type ThemeProviderProps = {
children: React.ReactNode;
Expand Down Expand Up @@ -33,7 +85,13 @@ export function ThemeProvider({
useEffect(() => {
const root = window.document.documentElement;

root.classList.remove("light", "dark");
root.classList.remove(
"light",
"dark",
"theme-dark",
"theme-light",
...CUSTOM_THEMES.map((t) => `theme-${t}`),
);

if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
Expand All @@ -45,6 +103,16 @@ export function ThemeProvider({
return;
}

if ((DARK_THEMES as readonly string[]).includes(theme)) {
root.classList.add(`theme-${theme}`, "theme-dark");
return;
}

if ((LIGHT_THEMES as readonly string[]).includes(theme)) {
root.classList.add(`theme-${theme}`, "theme-light");
return;
}

root.classList.add(theme);
}, [theme]);

Expand Down
84 changes: 68 additions & 16 deletions apps/dashboard/src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,89 @@
import { Moon, Sun } from "lucide-react";
import { useTheme } from "@/components/theme-provider";
import { Palette } from "lucide-react";
import { type Theme, useTheme } from "@/components/theme-provider";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";

const BASE_THEMES: { key: Theme; label: string }[] = [
{ key: "light", label: "Light" },
{ key: "dark", label: "Dark" },
{ key: "system", label: "System" },
];

const NITRO_THEMES: { key: Theme; label: string }[] = [
{ key: "crimson-moon", label: "Crimson Moon" },
{ key: "sepia", label: "Sepia" },
{ key: "midnight-blurple", label: "Midnight Blurple" },
{ key: "blurple-twilight", label: "Blurple Twilight" },
{ key: "forest", label: "Forest" },
{ key: "dusk", label: "Dusk" },
{ key: "aurora", label: "Aurora" },
{ key: "sunset", label: "Sunset" },
{ key: "mars", label: "Mars" },
{ key: "retro-storm", label: "Retro Storm" },
{ key: "under-the-sea", label: "Under the Sea" },
{ key: "strawberry-lemonade", label: "Strawberry Lemonade" },
{ key: "neon-nights", label: "Neon Nights" },
{ key: "citrus-sherbet", label: "Citrus Sherbet" },
{ key: "desert-khaki", label: "Desert Khaki" },
{ key: "sunrise", label: "Sunrise" },
{ key: "hanami", label: "Hanami" },
{ key: "cotton-candy", label: "Cotton Candy" },
{ key: "mint-apple", label: "Mint Apple" },
];

export function ModeToggle() {
const { setTheme } = useTheme();
const { theme, setTheme } = useTheme();

const renderItem = ({ key, label }: { key: Theme; label: string }) => {
const active = theme === key;
return (
<DropdownMenuRadioItem
key={key}
value={key}
className={cn(active && "bg-accent text-accent-foreground")}
>
<span
aria-hidden="true"
className={cn(
"size-3.5 shrink-0 rounded-full ring-1 ring-foreground/25 ring-inset",
`theme-swatch-${key}`,
)}
/>
{label}
</DropdownMenuRadioItem>
);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return (
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<Palette className="h-[1.2rem] w-[1.2rem]" />
<span className="sr-only">Toggle theme</span>
</Button>
}
/>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
<DropdownMenuContent align="end" className={"w-fit"}>
<DropdownMenuRadioGroup
value={theme}
onValueChange={(value) => setTheme(value as Theme)}
>
{BASE_THEMES.map(renderItem)}
<DropdownMenuSeparator />
<ScrollArea className="h-64 pr-1">
{NITRO_THEMES.map(renderItem)}
</ScrollArea>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
Loading
Loading