feat: add animated theme toggler and refine dark mode styling
This commit is contained in:
parent
a55612d109
commit
5a87ee40aa
9 changed files with 119 additions and 45 deletions
|
|
@ -43,7 +43,7 @@
|
|||
"dexie": "^4.3.0",
|
||||
"framer-motion": "^11.18.2",
|
||||
"katex": "^0.16.28",
|
||||
"lucide-react": "^0.563.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"motion": "^12.34.0",
|
||||
"next": "^16.1.6",
|
||||
|
|
@ -1451,7 +1451,7 @@
|
|||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="],
|
||||
"lucide-react": ["lucide-react@0.575.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
"dexie": "^4.3.0",
|
||||
"framer-motion": "^11.18.2",
|
||||
"katex": "^0.16.28",
|
||||
"lucide-react": "^0.563.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"motion": "^12.34.0",
|
||||
"next": "^16.1.6",
|
||||
|
|
|
|||
BIN
studio/frontend/public/blacklogo.png
Normal file
BIN
studio/frontend/public/blacklogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
BIN
studio/frontend/public/whitelogo.png
Normal file
BIN
studio/frontend/public/whitelogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
|
|
@ -3,6 +3,7 @@ import {
|
|||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
|
|
@ -160,6 +161,11 @@ export function Navbar() {
|
|||
|
||||
{/* Right: docs/tour desktop */}
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<AnimatedThemeToggler
|
||||
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground [&_svg]:size-4"
|
||||
title="Toggle theme"
|
||||
aria-label="Toggle theme"
|
||||
/>
|
||||
<HoverCard openDelay={200} closeDelay={100}>
|
||||
<HoverCardTrigger asChild={true}>
|
||||
<a
|
||||
|
|
|
|||
82
studio/frontend/src/components/ui/animated-theme-toggler.tsx
Normal file
82
studio/frontend/src/components/ui/animated-theme-toggler.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
import { flushSync } from "react-dom"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface AnimatedThemeTogglerProps extends React.ComponentPropsWithoutRef<"button"> {
|
||||
duration?: number
|
||||
}
|
||||
|
||||
export const AnimatedThemeToggler = ({
|
||||
className,
|
||||
duration = 400,
|
||||
...props
|
||||
}: AnimatedThemeTogglerProps) => {
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const updateTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains("dark"))
|
||||
}
|
||||
|
||||
updateTheme()
|
||||
|
||||
const observer = new MutationObserver(updateTheme)
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
const toggleTheme = useCallback(async () => {
|
||||
if (!buttonRef.current) return
|
||||
|
||||
await document.startViewTransition(() => {
|
||||
flushSync(() => {
|
||||
const newTheme = !isDark
|
||||
setIsDark(newTheme)
|
||||
document.documentElement.classList.toggle("dark")
|
||||
localStorage.setItem("theme", newTheme ? "dark" : "light")
|
||||
})
|
||||
}).ready
|
||||
|
||||
const { top, left, width, height } =
|
||||
buttonRef.current.getBoundingClientRect()
|
||||
const x = left + width / 2
|
||||
const y = top + height / 2
|
||||
const maxRadius = Math.hypot(
|
||||
Math.max(left, window.innerWidth - left),
|
||||
Math.max(top, window.innerHeight - top)
|
||||
)
|
||||
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${maxRadius}px at ${x}px ${y}px)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
duration,
|
||||
easing: "ease-in-out",
|
||||
pseudoElement: "::view-transition-new(root)",
|
||||
}
|
||||
)
|
||||
}, [isDark, duration])
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={toggleTheme}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
>
|
||||
{isDark ? <Sun /> : <Moon />}
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { LightRays } from "@/components/ui/light-rays";
|
||||
import {
|
||||
shouldShowTrainingView,
|
||||
useDatasetPreviewDialogStore,
|
||||
|
|
@ -73,14 +72,6 @@ export function StudioPage(): ReactElement {
|
|||
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden bg-background">
|
||||
<LightRays
|
||||
count={6}
|
||||
color="rgba(34, 197, 94, 0.14)"
|
||||
blur={30}
|
||||
speed={18}
|
||||
length="62vh"
|
||||
style={{ opacity: 0.45 }}
|
||||
/>
|
||||
<main className="relative z-10 mx-auto max-w-7xl px-4 py-4 sm:px-6">
|
||||
<GuidedTour {...tour.tourProps} celebrate={isConfigTour} />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
import { LightRays } from "@/components/ui/light-rays";
|
||||
import { useTrainingRuntimeStore } from "@/features/training";
|
||||
import type { ReactElement } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
|
|
@ -33,14 +32,6 @@ export function TrainingView(): ReactElement {
|
|||
|
||||
return (
|
||||
<div className={cn("relative", showOverlay && "min-h-[72vh]")}>
|
||||
<LightRays
|
||||
count={4}
|
||||
color="rgba(16, 185, 129, 0.13)"
|
||||
blur={28}
|
||||
speed={20}
|
||||
length="52vh"
|
||||
style={{ opacity: 0.35 }}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-10 flex flex-col gap-6 transition-[filter]",
|
||||
|
|
|
|||
|
|
@ -93,38 +93,37 @@
|
|||
/*--shadow-2xl: 0px 0px 0px 0px hsl(0 0% 0% / 0);*/
|
||||
--tracking-normal: 0em;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.1014 0.0229 160.0332);
|
||||
--foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--card: oklch(0.1216 0.02 169.327);
|
||||
--card-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--popover: oklch(0.1216 0.02 169.327);
|
||||
--popover-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--background: oklch(0.24 0 0);
|
||||
--foreground: oklch(0.98 0 0);
|
||||
--card: oklch(0.28 0 0);
|
||||
--card-foreground: oklch(0.98 0 0);
|
||||
--popover: oklch(0.28 0 0);
|
||||
--popover-foreground: oklch(0.98 0 0);
|
||||
--primary: oklch(0.6929 0.1396 166.5513);
|
||||
--primary-foreground: oklch(1 0 0);
|
||||
--secondary: oklch(0.2015 0.0403 165.8147);
|
||||
--secondary-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--muted: oklch(0.2002 0 0);
|
||||
--muted-foreground: oklch(0.6993 0 0);
|
||||
--accent: oklch(0.2015 0.0403 165.8147);
|
||||
--accent-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--secondary: oklch(0.33 0 0);
|
||||
--secondary-foreground: oklch(0.98 0 0);
|
||||
--muted: oklch(0.33 0 0);
|
||||
--muted-foreground: oklch(0.70 0 0);
|
||||
--accent: oklch(0.33 0 0);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
--border: oklch(0.2502 0.0213 164.5852);
|
||||
--input: oklch(0.2502 0.0213 164.5852);
|
||||
--border: oklch(0.38 0 0);
|
||||
--input: oklch(0.38 0 0);
|
||||
--ring: oklch(0.6929 0.1396 166.5513);
|
||||
--chart-1: oklch(0.7511 0.1407 166.2284);
|
||||
--chart-2: oklch(0.75 0.14 136.5572);
|
||||
--chart-3: oklch(0.7554 0.1285 197.339);
|
||||
--chart-4: oklch(0.7503 0.1199 346.7805);
|
||||
--chart-5: oklch(0.799 0.1196 84.6633);
|
||||
--sidebar: oklch(0.15 0 0);
|
||||
--sidebar-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--sidebar: oklch(0.24 0 0);
|
||||
--sidebar-foreground: oklch(0.98 0 0);
|
||||
--sidebar-primary: oklch(0.6929 0.1396 166.5513);
|
||||
--sidebar-primary-foreground: oklch(1 0 0);
|
||||
--sidebar-accent: oklch(0.2015 0.0403 165.8147);
|
||||
--sidebar-accent-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--sidebar-border: oklch(0.2502 0.0213 164.5852);
|
||||
--sidebar-accent: oklch(0.33 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.98 0 0);
|
||||
--sidebar-border: oklch(0.38 0 0);
|
||||
--sidebar-ring: oklch(0.6929 0.1396 166.5513);
|
||||
--destructive-foreground: oklch(1 0 0);
|
||||
--radius: 1.2rem;
|
||||
|
|
@ -142,14 +141,14 @@
|
|||
--shadow-2xs: 0px 0px 0px 0px hsl(0 0% 0% / 0);
|
||||
--shadow-xs: 0px 0px 0px 0px hsl(0 0% 0% / 0);
|
||||
--shadow-sm: 0px 0px 0px 0px hsl(0 0% 0% / 0), 0px 1px 2px 0px
|
||||
hsl(0 0% 0% / 0);
|
||||
hsl(0 0% 0% / 0);
|
||||
--shadow: 0px 0px 0px 0px hsl(0 0% 0% / 0), 0px 1px 2px 0px hsl(0 0% 0% / 0);
|
||||
--shadow-md: 0px 0px 0px 0px hsl(0 0% 0% / 0), 0px 2px 4px 0px
|
||||
hsl(0 0% 0% / 0);
|
||||
hsl(0 0% 0% / 0);
|
||||
--shadow-lg: 0px 0px 0px 0px hsl(0 0% 0% / 0), 0px 4px 6px 0px
|
||||
hsl(0 0% 0% / 0);
|
||||
hsl(0 0% 0% / 0);
|
||||
--shadow-xl: 0px 0px 0px 0px hsl(0 0% 0% / 0), 0px 8px 10px 0px
|
||||
hsl(0 0% 0% / 0);
|
||||
hsl(0 0% 0% / 0);
|
||||
--shadow-2xl: 0px 0px 0px 0px hsl(0 0% 0% / 0);
|
||||
}
|
||||
|
||||
|
|
@ -359,3 +358,8 @@
|
|||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
::view-transition-old(root), ::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue