feat: 登录状态机

This commit is contained in:
bietiaop
2025-01-31 18:48:46 +08:00
parent 28182cac64
commit 3cb3135235
12 changed files with 470 additions and 65 deletions

View File

@@ -0,0 +1,146 @@
import { motion, useMotionValue, useSpring } from 'motion/react'
import { useRef, useState } from 'react'
const springValues = {
damping: 30,
stiffness: 100,
mass: 2
}
export interface HoverTiltedCardProps {
imageSrc: string
altText?: string
captionText?: string
containerHeight?: string
containerWidth?: string
imageHeight?: string
imageWidth?: string
scaleOnHover?: number
rotateAmplitude?: number
showTooltip?: boolean
overlayContent?: React.ReactNode
displayOverlayContent?: boolean
}
export default function HoverTiltedCard({
imageSrc,
altText = 'NapCat',
captionText = 'NapCat',
containerHeight = '200px',
containerWidth = '100%',
imageHeight = '200px',
imageWidth = '200px',
scaleOnHover = 1.1,
rotateAmplitude = 14,
showTooltip = false,
overlayContent = (
<div className="text-center font-ubuntu mt-6 px-4 py-0.5 shadow-lg rounded-full bg-danger-600 text-default-100 bg-opacity-80">
NapCat
</div>
),
displayOverlayContent = true
}: HoverTiltedCardProps) {
const ref = useRef<HTMLDivElement>(null)
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotateX = useSpring(useMotionValue(0), springValues)
const rotateY = useSpring(useMotionValue(0), springValues)
const scale = useSpring(1, springValues)
const opacity = useSpring(0)
const rotateFigcaption = useSpring(0, {
stiffness: 350,
damping: 30,
mass: 1
})
const [lastY, setLastY] = useState(0)
function handleMouse(e: React.MouseEvent) {
if (!ref.current) return
const rect = ref.current.getBoundingClientRect()
const offsetX = e.clientX - rect.left - rect.width / 2
const offsetY = e.clientY - rect.top - rect.height / 2
const rotationX = (offsetY / (rect.height / 2)) * -rotateAmplitude
const rotationY = (offsetX / (rect.width / 2)) * rotateAmplitude
rotateX.set(rotationX)
rotateY.set(rotationY)
x.set(e.clientX - rect.left)
y.set(e.clientY - rect.top)
const velocityY = offsetY - lastY
rotateFigcaption.set(-velocityY * 0.6)
setLastY(offsetY)
}
function handleMouseEnter() {
scale.set(scaleOnHover)
opacity.set(1)
}
function handleMouseLeave() {
opacity.set(0)
scale.set(1)
rotateX.set(0)
rotateY.set(0)
rotateFigcaption.set(0)
}
return (
<figure
ref={ref}
className="relative w-full h-full [perspective:800px] flex flex-col items-center justify-center"
style={{
height: containerHeight,
width: containerWidth
}}
onMouseMove={handleMouse}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<motion.div
className="relative [transform-style:preserve-3d]"
style={{
width: imageWidth,
height: imageHeight,
rotateX,
rotateY,
scale
}}
>
<motion.img
src={imageSrc}
alt={altText}
className="absolute top-0 left-0 object-cover rounded-md will-change-transform [transform:translateZ(0)] pointer-events-none select-none"
style={{
width: imageWidth,
height: imageHeight
}}
/>
{displayOverlayContent && overlayContent && (
<motion.div className="absolute top-0 left-0 right-0 z-10 flex justify-center will-change-transform [transform:translateZ(30px)]">
{overlayContent}
</motion.div>
)}
</motion.div>
{showTooltip && (
<motion.figcaption
className="pointer-events-none absolute left-0 top-0 rounded-md bg-white px-2 py-1 text-sm text-default-900 opacity-0 z-10 hidden sm:block"
style={{
x,
y,
opacity,
rotate: rotateFigcaption
}}
>
{captionText}
</motion.figcaption>
)}
</figure>
)
}

View File

@@ -13,7 +13,6 @@ import { useTheme } from '@/hooks/use-theme'
import logo from '@/assets/images/logo.png'
import type { MenuItem } from '@/config/site'
import { title } from '../primitives'
import Menus from './menus'
interface SideBarProps {
@@ -49,18 +48,14 @@ const SideBar: React.FC<SideBarProps> = (props) => {
>
<motion.div className="w-64 flex flex-col items-stretch h-full transition-transform duration-300 ease-in-out z-30 relative float-right">
<div className="flex justify-center items-center mt-2 gap-2">
<Image height={40} src={logo} className="mb-2" />
<Image radius="none" height={40} src={logo} className="mb-2" />
<div
className={clsx(
'flex items-center hm-medium',
title({
shadow: true,
color: isDark ? 'violet' : 'pink'
}),
'!text-2xl'
'!text-2xl shiny-text'
)}
>
WebUI
NapCat
</div>
</div>
<div className="overflow-y-auto flex flex-col flex-1 px-4">

View File

@@ -19,7 +19,7 @@ const TailwindMarkdown: React.FC<{ content: string }> = ({ content }) => {
p: ({ node, ...props }) => <p className="m-0" {...props} />,
a: ({ node, ...props }) => (
<a
className="text-blue-500 hover:underline"
className="text-primary-500 inline-block hover:underline"
target="_blank"
{...props}
/>
@@ -32,12 +32,12 @@ const TailwindMarkdown: React.FC<{ content: string }> = ({ content }) => {
),
blockquote: ({ node, ...props }) => (
<blockquote
className="border-l-4 border-gray-300 pl-4 italic"
className="border-l-4 border-default-300 pl-4 italic"
{...props}
/>
),
code: ({ node, ...props }) => (
<code className="bg-gray-100 p-1 rounded" {...props} />
<code className="bg-default-100 p-1 rounded text-xs" {...props} />
)
}}
>

View File

@@ -4,28 +4,17 @@ import { Spinner } from '@heroui/spinner'
import { useRequest } from 'ahooks'
import clsx from 'clsx'
import { BietiaopIcon, WebUIIcon } from '@/components/icons'
import HoverTiltedCard from '@/components/hover_titled_card'
import NapCatRepoInfo from '@/components/napcat_repo_info'
import { title } from '@/components/primitives'
import logo from '@/assets/images/logo.png'
import WebUIManager from '@/controllers/webui_manager'
import packageJson from '../../../package.json'
function VersionInfo() {
const { data, loading, error } = useRequest(WebUIManager.getPackageInfo)
return (
<div className="flex items-center gap-2 mb-5">
<Chip
startContent={
<Chip color="danger" size="sm" className="-ml-0.5 select-none">
WebUI
</Chip>
}
>
{packageJson.version}
</Chip>
<Chip
startContent={
<Chip color="warning" size="sm" className="-ml-0.5 select-none">
@@ -51,21 +40,8 @@ export default function AboutPage() {
<title> NapCat WebUI</title>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="max-w-full w-[1000px] px-5 flex flex-col items-center">
<div className="flex flex-col md:flex-row items-center">
<Image
alt="logo"
className="flex-shrink-0 w-52 md:w-48 mr-2"
src={logo}
/>
<div className="flex -mt-9 md:mt-0">
<WebUIIcon />
</div>
</div>
<div className="flex opacity-60 items-center gap-2 mb-2 font-ubuntu">
Created By
<div className="flex scale-80 -ml-5 -mr-5">
<BietiaopIcon />
</div>
<div className="flex flex-col md:flex-row items-center mb-6">
<HoverTiltedCard imageSrc={logo} />
</div>
<VersionInfo />
<div className="mb-6 flex flex-col items-center gap-4">

View File

@@ -1,5 +1,5 @@
@import url('./fonts.css');
@import url('./text.css');
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,34 @@
@layer base {
.shiny-text {
@apply text-pink-400 text-opacity-60;
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
animation: shine 5s linear infinite;
}
.shiny-text {
background-image: linear-gradient(
120deg,
rgba(255, 50, 50, 0) 40%,
rgba(255, 76, 76, 0.8) 50%,
rgba(255, 50, 50, 0) 60%
);
}
.dark .shiny-text {
background-image: linear-gradient(
120deg,
rgba(255, 255, 255, 0) 40%,
rgba(206, 21, 21, 0.8) 50%,
rgba(255, 255, 255, 0) 60%
);
}
@keyframes shine {
0% {
background-position: 100%;
}
100% {
background-position: -100%;
}
}
}