Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
128 lines
3.4 KiB
TypeScript
128 lines
3.4 KiB
TypeScript
// Modified by the independent AgentDesk fork for support reliability and availability.
|
|
"use client"
|
|
|
|
import { useEffect, useSyncExternalStore } from "react"
|
|
import { CircleIcon, DropletsIcon, PaletteIcon } from "lucide-react"
|
|
|
|
import { useI18n } from "@/i18n/provider"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuRadioGroup,
|
|
DropdownMenuRadioItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu"
|
|
|
|
type PaletteMode = "plain" | "blue" | "green" | "gray"
|
|
|
|
const PALETTE_STORAGE_KEY = "dashboard_palette"
|
|
const DEFAULT_PALETTE: PaletteMode = "plain"
|
|
const PALETTE_CHANGE_EVENT = "dashboard-palette-change"
|
|
|
|
const paletteOptions: Array<{
|
|
value: PaletteMode
|
|
labelKey: string
|
|
swatch: string
|
|
}> = [
|
|
{
|
|
value: "plain",
|
|
labelKey: "palette.plain",
|
|
swatch: "bg-[#2475fc]",
|
|
},
|
|
{
|
|
value: "green",
|
|
labelKey: "palette.green",
|
|
swatch: "bg-teal-700",
|
|
},
|
|
{
|
|
value: "gray",
|
|
labelKey: "palette.gray",
|
|
swatch: "bg-slate-500",
|
|
},
|
|
{
|
|
value: "blue",
|
|
labelKey: "palette.blue",
|
|
swatch: "bg-blue-600",
|
|
},
|
|
]
|
|
|
|
function readPalette(): PaletteMode {
|
|
if (typeof window === "undefined") {
|
|
return DEFAULT_PALETTE
|
|
}
|
|
|
|
const stored = window.localStorage.getItem(PALETTE_STORAGE_KEY)
|
|
return stored === "plain" ||
|
|
stored === "blue" ||
|
|
stored === "green" ||
|
|
stored === "gray"
|
|
? stored
|
|
: DEFAULT_PALETTE
|
|
}
|
|
|
|
function subscribeToPalette(onChange: () => void) {
|
|
const onStorage = (event: StorageEvent) => {
|
|
if (event.key === null || event.key === PALETTE_STORAGE_KEY) onChange()
|
|
}
|
|
window.addEventListener("storage", onStorage)
|
|
window.addEventListener(PALETTE_CHANGE_EVENT, onChange)
|
|
return () => {
|
|
window.removeEventListener("storage", onStorage)
|
|
window.removeEventListener(PALETTE_CHANGE_EVENT, onChange)
|
|
}
|
|
}
|
|
|
|
const readServerPalette = () => DEFAULT_PALETTE
|
|
|
|
function applyPalette(value: PaletteMode) {
|
|
document.documentElement.dataset.palette = value
|
|
window.localStorage.setItem(PALETTE_STORAGE_KEY, value)
|
|
window.dispatchEvent(new Event(PALETTE_CHANGE_EVENT))
|
|
}
|
|
|
|
export function PaletteToggle() {
|
|
const t = useI18n()
|
|
const palette = useSyncExternalStore(subscribeToPalette, readPalette, readServerPalette)
|
|
|
|
useEffect(() => {
|
|
document.documentElement.dataset.palette = palette
|
|
}, [palette])
|
|
|
|
function handleChange(value: string) {
|
|
const nextPalette: PaletteMode =
|
|
value === "blue" || value === "green" || value === "gray"
|
|
? value
|
|
: "plain"
|
|
applyPalette(nextPalette)
|
|
}
|
|
|
|
const ActiveIcon =
|
|
palette === "plain"
|
|
? CircleIcon
|
|
: palette === "green"
|
|
? DropletsIcon
|
|
: PaletteIcon
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
render={<Button variant="outline" size="sm" />}
|
|
aria-label={t("palette.toggle")}
|
|
>
|
|
<ActiveIcon />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-52 min-w-52">
|
|
<DropdownMenuRadioGroup value={palette} onValueChange={handleChange}>
|
|
{paletteOptions.map((option) => (
|
|
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
|
<span className={`size-2.5 rounded-full ${option.swatch}`} />
|
|
<span className="flex-1">{t(option.labelKey)}</span>
|
|
</DropdownMenuRadioItem>
|
|
))}
|
|
</DropdownMenuRadioGroup>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|