Files
neo-blog/web/src/components/common/theme-toggle.tsx
Snowykami 64b1c54911
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 11s
feat: enhance post management with pagination, search, and order functionality
- Added search input for filtering posts by keywords.
- Implemented pagination controls for navigating through posts.
- Introduced order selector for sorting posts based on various criteria.
- Enhanced post item display with additional metrics (view count, like count, comment count).
- Added dropdown menu for post actions (edit, view, toggle privacy, delete).
- Integrated double confirmation for delete action.
- Updated user profile to support background image upload.
- Improved user security settings with better layout and validation.
- Refactored auth context to use useCallback for logout function.
- Added command palette component for improved command execution.
- Introduced popover component for better UI interactions.
- Implemented debounce hooks for optimized state updates.
- Updated localization files with new keys for improved internationalization.
- Added tailwind configuration for styling.
2025-09-25 00:51:29 +08:00

88 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useDevice } from "@/contexts/device-context";
import { Sun, Moon, Monitor } from "lucide-react";
import { motion } from "motion/react";
import type React from "react";
import { cn } from "@/lib/utils";
type ThemeMode = "light" | "dark" | "system";
// PC端三状态轮换按钮
export function ThemeModeCycleButton(props: React.ButtonHTMLAttributes<HTMLButtonElement> & { mode: ThemeMode; setMode: (m: ThemeMode) => void }) {
const { mode, setMode, className, style, onClick, ...rest } = props;
const nextMode = (mode: ThemeMode): ThemeMode => {
if (mode === "light") return "dark";
if (mode === "dark") return "system";
return "light";
};
const icon = mode === "light" ? <Sun className="w-4 h-4" /> : mode === "dark" ? <Moon className="w-4 h-4" /> : <Monitor className="w-4 h-4" />;
const label = mode.charAt(0).toUpperCase() + mode.slice(1);
const baseCls = "flex items-center gap-2 px-2 py-2 rounded-full bg-muted hover:bg-accent border border-input text-sm font-medium transition-all";
const mergedClassName = cn(baseCls, className);
return (
<button
className={mergedClassName}
style={style}
onClick={(e) => {
setMode(nextMode(mode));
onClick?.(e);
}}
title={`切换主题(当前:${label}`}
{...rest}
>
{icon}
</button>
);
}
// 移动端:横向按钮组
export function ThemeModeSegmented(props: React.HTMLAttributes<HTMLDivElement> & { mode: ThemeMode; setMode: (m: ThemeMode) => void }) {
const { mode, setMode, className, style, ...rest } = props;
const modes: { value: ThemeMode; icon: React.ReactNode; label: string }[] = [
{ value: "light", icon: <Sun className="w-4 h-4" />, label: "Light" },
{ value: "system", icon: <Monitor className="w-4 h-4" />, label: "System" },
{ value: "dark", icon: <Moon className="w-4 h-4" />, label: "Dark" },
];
const activeIndex = modes.findIndex((m) => m.value === mode);
const baseCls = "relative inline-flex bg-muted rounded-full p-1 gap-1 overflow-hidden";
return (
<div className={cn("theme-mode-segmented-wrapper", className)} style={style} {...rest}>
<div className={baseCls}>
{/* 滑动高亮块 */}
<motion.div
layout
transition={{ type: "spring", stiffness: 400, damping: 30 }}
className="absolute w-12 h-8 rounded-full bg-white/70 shadow-sm z-1 top-1"
style={{
left: `calc(0.25rem + ${activeIndex} * (3rem + 0.25rem))`,
}}
/>
{modes.map((m) => (
<button
key={m.value}
className={cn(
"relative flex items-center justify-center w-12 h-8 rounded-full text-sm font-medium transition-all z-10",
mode === m.value ? "text-primary" : "text-muted-foreground"
)}
onClick={() => setMode(m.value)}
type="button"
>
{m.icon}
</button>
))}
</div>
</div>
);
}
// 总组件:根据设备类型渲染
export function ThemeModeToggle(props: React.HTMLAttributes<HTMLElement> & { showSegmented?: boolean }) {
const { mode, setMode } = useDevice();
const Comp: React.ElementType = props.showSegmented ? ThemeModeSegmented : ThemeModeCycleButton;
const { className, style } = props;
return <Comp mode={mode} setMode={setMode} className={className} style={style} />;
}