style(code): 格式化代码缩进和布局样式

- 统一调整 admin.ts 文件中的接口定义缩进格式
- 优化 animated-theme-toggler.tsx 组件的代码结构和缩进
- 规范 app-config-modal.tsx 中 JSX 元素的嵌套格式
- 整理 app-providers.tsx 中的组件层级缩进
- 标准化 app-theme.ts 中的对象属性缩进格式
This commit is contained in:
HouYunFei
2026-05-22 23:19:25 +08:00
parent cee08cb5a8
commit e319481976
93 changed files with 11406 additions and 10424 deletions
+190 -225
View File
@@ -1,263 +1,228 @@
"use client"
"use client";
import { useEffect, useRef, useState } from "react"
import {
animate,
motion,
useInView,
useMotionValue,
useReducedMotion,
useTransform,
type HTMLMotionProps,
} from "motion/react"
import { useEffect, useRef, useState } from "react";
import { animate, motion, useInView, useMotionValue, useReducedMotion, useTransform, type HTMLMotionProps } from "motion/react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const DEFAULT_COLORS = ["#c679c4", "#fa3d1d", "#ffb005", "#e1e1fe", "#0358f7"]
const BAND_HALF = 17
const SWEEP_START = -BAND_HALF
const SWEEP_END = 100 + BAND_HALF
const DEFAULT_COLORS = ["#c679c4", "#fa3d1d", "#ffb005", "#e1e1fe", "#0358f7"];
const BAND_HALF = 17;
const SWEEP_START = -BAND_HALF;
const SWEEP_END = 100 + BAND_HALF;
const sweepEase = (t: number) =>
t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2
const sweepEase = (t: number) => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2);
function buildGradient(pos: number, colors: string[], textColor: string) {
const bandStart = pos - BAND_HALF
const bandEnd = pos + BAND_HALF
const bandStart = pos - BAND_HALF;
const bandEnd = pos + BAND_HALF;
if (bandStart >= 100) {
return `linear-gradient(90deg, ${textColor}, ${textColor})`
}
const n = colors.length
const parts: string[] = []
if (bandStart >= 100) {
return `linear-gradient(90deg, ${textColor}, ${textColor})`;
}
const n = colors.length;
const parts: string[] = [];
if (bandStart > 0)
parts.push(`${textColor} 0%`, `${textColor} ${bandStart.toFixed(2)}%`)
if (bandStart > 0) parts.push(`${textColor} 0%`, `${textColor} ${bandStart.toFixed(2)}%`);
colors.forEach((c, i) => {
const pct = n === 1 ? pos : bandStart + (i / (n - 1)) * BAND_HALF * 2
parts.push(`${c} ${pct.toFixed(2)}%`)
})
colors.forEach((c, i) => {
const pct = n === 1 ? pos : bandStart + (i / (n - 1)) * BAND_HALF * 2;
parts.push(`${c} ${pct.toFixed(2)}%`);
});
if (bandEnd < 100)
parts.push(`transparent ${bandEnd.toFixed(2)}%`, `transparent 100%`)
if (bandEnd < 100) parts.push(`transparent ${bandEnd.toFixed(2)}%`, `transparent 100%`);
return `linear-gradient(90deg, ${parts.join(", ")})`
return `linear-gradient(90deg, ${parts.join(", ")})`;
}
function measureWidths(el: HTMLElement, texts: string[]) {
const ghost = el.cloneNode() as HTMLElement
Object.assign(ghost.style, {
position: "absolute",
visibility: "hidden",
pointerEvents: "none",
width: "auto",
whiteSpace: "nowrap",
})
el.parentElement!.appendChild(ghost)
const widths = texts.map((t) => {
ghost.textContent = t
return ghost.getBoundingClientRect().width
})
ghost.remove()
return widths
const ghost = el.cloneNode() as HTMLElement;
Object.assign(ghost.style, {
position: "absolute",
visibility: "hidden",
pointerEvents: "none",
width: "auto",
whiteSpace: "nowrap",
});
el.parentElement!.appendChild(ghost);
const widths = texts.map((t) => {
ghost.textContent = t;
return ghost.getBoundingClientRect().width;
});
ghost.remove();
return widths;
}
/**
* Props for {@link DiaTextReveal}.
*/
export interface DiaTextRevealProps extends Omit<
HTMLMotionProps<"span">,
"ref" | "children" | "style" | "animate" | "transition" | "color"
> {
/**
* Text to reveal. Pass multiple strings to rotate when {@link DiaTextRevealProps.repeat} is `true`.
*/
text: string | string[]
/**
* Colors sampled across the moving gradient band. Defaults to a built-in palette.
*/
colors?: string[]
/**
* CSS color for revealed text after the sweep and for leading/trailing regions during the animation.
* @defaultValue `"var(--foreground)"`
*/
textColor?: string
/**
* Duration of one sweep pass, in seconds.
* @defaultValue `1.5`
*/
duration?: number
/**
* Delay before the sweep starts, in seconds.
* @defaultValue `0`
*/
delay?: number
/**
* When `text` is an array, replay the sweep and advance to the next string after each completion.
* @defaultValue `false`
*/
repeat?: boolean
/**
* Pause between cycles when {@link DiaTextRevealProps.repeat} is `true`, in seconds.
* @defaultValue `0.5`
*/
repeatDelay?: number
/**
* If `true`, the animation starts only after the element enters the viewport.
* @defaultValue `true`
*/
startOnView?: boolean
/**
* Passed to `useInView`: if `true`, in-view detection fires at most once (no replay on scroll-back).
* @defaultValue `true`
*/
once?: boolean
/**
* Additional class names for the animated `span` (e.g. typography utilities).
*/
className?: string
/**
* When `text` has multiple entries, use the widest strings width for layout instead of animating width per line.
* @defaultValue `false`
*/
fixedWidth?: boolean
export interface DiaTextRevealProps extends Omit<HTMLMotionProps<"span">, "ref" | "children" | "style" | "animate" | "transition" | "color"> {
/**
* Text to reveal. Pass multiple strings to rotate when {@link DiaTextRevealProps.repeat} is `true`.
*/
text: string | string[];
/**
* Colors sampled across the moving gradient band. Defaults to a built-in palette.
*/
colors?: string[];
/**
* CSS color for revealed text after the sweep and for leading/trailing regions during the animation.
* @defaultValue `"var(--foreground)"`
*/
textColor?: string;
/**
* Duration of one sweep pass, in seconds.
* @defaultValue `1.5`
*/
duration?: number;
/**
* Delay before the sweep starts, in seconds.
* @defaultValue `0`
*/
delay?: number;
/**
* When `text` is an array, replay the sweep and advance to the next string after each completion.
* @defaultValue `false`
*/
repeat?: boolean;
/**
* Pause between cycles when {@link DiaTextRevealProps.repeat} is `true`, in seconds.
* @defaultValue `0.5`
*/
repeatDelay?: number;
/**
* If `true`, the animation starts only after the element enters the viewport.
* @defaultValue `true`
*/
startOnView?: boolean;
/**
* Passed to `useInView`: if `true`, in-view detection fires at most once (no replay on scroll-back).
* @defaultValue `true`
*/
once?: boolean;
/**
* Additional class names for the animated `span` (e.g. typography utilities).
*/
className?: string;
/**
* When `text` has multiple entries, use the widest strings width for layout instead of animating width per line.
* @defaultValue `false`
*/
fixedWidth?: boolean;
}
export function DiaTextReveal({
text,
colors = DEFAULT_COLORS,
textColor = "var(--foreground)",
duration = 1.5,
delay = 0,
repeat = false,
repeatDelay = 0.5,
startOnView = true,
once = true,
className,
fixedWidth = false,
...props
}: DiaTextRevealProps) {
const texts = Array.isArray(text) ? text : [text]
const isMulti = texts.length > 1
const prefersReducedMotion = useReducedMotion()
export function DiaTextReveal({ text, colors = DEFAULT_COLORS, textColor = "var(--foreground)", duration = 1.5, delay = 0, repeat = false, repeatDelay = 0.5, startOnView = true, once = true, className, fixedWidth = false, ...props }: DiaTextRevealProps) {
const texts = Array.isArray(text) ? text : [text];
const isMulti = texts.length > 1;
const prefersReducedMotion = useReducedMotion();
const spanRef = useRef<HTMLSpanElement>(null)
const optsRef = useRef({
colors,
textColor,
duration,
delay,
repeat,
repeatDelay,
texts,
})
optsRef.current = {
colors,
textColor,
duration,
delay,
repeat,
repeatDelay,
texts,
}
const spanRef = useRef<HTMLSpanElement>(null);
const optsRef = useRef({
colors,
textColor,
duration,
delay,
repeat,
repeatDelay,
texts,
});
optsRef.current = {
colors,
textColor,
duration,
delay,
repeat,
repeatDelay,
texts,
};
const indexRef = useRef(0)
const hasPlayedRef = useRef(false)
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined)
const playRef = useRef<() => void>(null!)
const stopRef = useRef<(() => void) | null>(null)
const indexRef = useRef(0);
const hasPlayedRef = useRef(false);
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const playRef = useRef<() => void>(null!);
const stopRef = useRef<(() => void) | null>(null);
const [activeIndex, setActiveIndex] = useState(0)
const [measuredWidths, setMeasuredWidths] = useState<number[]>([])
const [activeIndex, setActiveIndex] = useState(0);
const [measuredWidths, setMeasuredWidths] = useState<number[]>([]);
const sweepPos = useMotionValue(SWEEP_START)
const sweepPos = useMotionValue(SWEEP_START);
const backgroundImage = useTransform(sweepPos, (pos) =>
buildGradient(pos, optsRef.current.colors, optsRef.current.textColor)
)
const backgroundImage = useTransform(sweepPos, (pos) => buildGradient(pos, optsRef.current.colors, optsRef.current.textColor));
const isInView = useInView(spanRef, { once, amount: 0.1 })
const isInView = useInView(spanRef, { once, amount: 0.1 });
useEffect(() => {
const el = spanRef.current
if (!el || !isMulti) return
setMeasuredWidths(measureWidths(el, texts))
}, [Array.isArray(text) ? text.join("\0") : text])
useEffect(() => {
const el = spanRef.current;
if (!el || !isMulti) return;
setMeasuredWidths(measureWidths(el, texts));
}, [Array.isArray(text) ? text.join("\0") : text]);
playRef.current = () => {
const { duration, delay, repeat, repeatDelay, texts } = optsRef.current
playRef.current = () => {
const { duration, delay, repeat, repeatDelay, texts } = optsRef.current;
sweepPos.set(SWEEP_START)
sweepPos.set(SWEEP_START);
const controls = animate(sweepPos, SWEEP_END, {
duration,
delay,
ease: sweepEase,
onComplete() {
if (!repeat) return
timerRef.current = setTimeout(() => {
const next = (indexRef.current + 1) % texts.length
indexRef.current = next
setActiveIndex(next)
playRef.current()
}, repeatDelay * 1000)
},
})
const controls = animate(sweepPos, SWEEP_END, {
duration,
delay,
ease: sweepEase,
onComplete() {
if (!repeat) return;
timerRef.current = setTimeout(() => {
const next = (indexRef.current + 1) % texts.length;
indexRef.current = next;
setActiveIndex(next);
playRef.current();
}, repeatDelay * 1000);
},
});
stopRef.current = () => controls.stop()
}
stopRef.current = () => controls.stop();
};
useEffect(() => {
if (prefersReducedMotion) {
sweepPos.set(SWEEP_END)
return
}
if (startOnView && !isInView) return
if (once && hasPlayedRef.current) return
hasPlayedRef.current = true
playRef.current()
useEffect(() => {
if (prefersReducedMotion) {
sweepPos.set(SWEEP_END);
return;
}
if (startOnView && !isInView) return;
if (once && hasPlayedRef.current) return;
hasPlayedRef.current = true;
playRef.current();
return () => {
stopRef.current?.()
clearTimeout(timerRef.current)
}
}, [isInView, startOnView, once, prefersReducedMotion, sweepPos])
return () => {
stopRef.current?.();
clearTimeout(timerRef.current);
};
}, [isInView, startOnView, once, prefersReducedMotion, sweepPos]);
const fixedW =
isMulti && fixedWidth && measuredWidths.length > 0
? Math.max(...measuredWidths)
: undefined
const fixedW = isMulti && fixedWidth && measuredWidths.length > 0 ? Math.max(...measuredWidths) : undefined;
const animatedW =
isMulti && !fixedWidth && measuredWidths[activeIndex] != null
? measuredWidths[activeIndex]
: undefined
const animatedW = isMulti && !fixedWidth && measuredWidths[activeIndex] != null ? measuredWidths[activeIndex] : undefined;
return (
<motion.span
ref={spanRef}
className={cn("align-bottom leading-[100%] text-inherit", className)}
style={{
transform: "translateY(-2px)",
color: "transparent",
backgroundClip: "text",
WebkitBackgroundClip: "text",
backgroundSize: "100% 100%",
backgroundImage,
...(isMulti && {
display: "inline-block",
overflow: "hidden",
whiteSpace: "nowrap",
verticalAlign: "text-center",
...(fixedW != null && { width: fixedW }),
}),
}}
animate={animatedW != null ? { width: animatedW } : undefined}
transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
{...props}
>
{texts[activeIndex]}
</motion.span>
)
return (
<motion.span
ref={spanRef}
className={cn("align-bottom leading-[100%] text-inherit", className)}
style={{
transform: "translateY(-2px)",
color: "transparent",
backgroundClip: "text",
WebkitBackgroundClip: "text",
backgroundSize: "100% 100%",
backgroundImage,
...(isMulti && {
display: "inline-block",
overflow: "hidden",
whiteSpace: "nowrap",
verticalAlign: "text-center",
...(fixedW != null && { width: fixedW }),
}),
}}
animate={animatedW != null ? { width: animatedW } : undefined}
transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
{...props}
>
{texts[activeIndex]}
</motion.span>
);
}