33import Image from "next/image" ;
44import Link from "next/link" ;
55import React , { useCallback , useEffect , useMemo , useState } from "react" ;
6- import { ChevronLeft , ChevronRight } from "lucide-react" ;
6+ import { ChevronLeft , ChevronRight , X } from "lucide-react" ;
77import {
88 activityEventsConfig ,
99 type ActivityEvent ,
@@ -44,19 +44,11 @@ export function ActivityTicker({ className }: ActivityTickerProps) {
4444 const [ activeIndex , setActiveIndex ] = useState ( 0 ) ;
4545 const totalEvents = events . length ;
4646
47- // 拖拽坐标
48- const [ offset , setOffset ] = useState ( { x : 0 , y : 0 } ) ;
49- const [ dragStart , setDragStart ] = useState < { x : number ; y : number } | null > (
50- null ,
51- ) ;
52-
53- const handlePointerDown = ( event : React . PointerEvent < HTMLDivElement > ) => {
54- event . currentTarget . setPointerCapture ( event . pointerId ) ;
55- setDragStart ( { x : event . clientX - offset . x , y : event . clientY - offset . y } ) ;
56- } ;
47+ // 控制浮窗显隐
48+ const [ isClosed , setIsClosed ] = useState ( false ) ;
5749
5850 useEffect ( ( ) => {
59- if ( totalEvents <= 1 ) {
51+ if ( isClosed || totalEvents <= 1 ) {
6052 return ;
6153 }
6254
@@ -66,7 +58,7 @@ export function ActivityTicker({ className }: ActivityTickerProps) {
6658 } , ROTATION_INTERVAL_MS ) ;
6759
6860 return ( ) => window . clearInterval ( timer ) ;
69- } , [ totalEvents , activeIndex ] ) ;
61+ } , [ totalEvents , activeIndex , isClosed ] ) ;
7062
7163 const handlePrev = useCallback ( ( ) => {
7264 if ( totalEvents <= 1 ) {
@@ -86,90 +78,104 @@ export function ActivityTicker({ className }: ActivityTickerProps) {
8678 return null ;
8779 }
8880
81+ if ( isClosed ) {
82+ return null ;
83+ }
84+
8985 const activeEvent = events [ activeIndex ] ;
9086 const coverSrc = activeEvent . coverUrl ;
9187 const showPlayback = activeEvent . deprecated && Boolean ( activeEvent . playback ) ;
9288
9389 return (
94- < aside
95- className = { cn (
96- "relative w-full overflow-hidden rounded-2xl border border-border bg-background/70 text-left shadow-md backdrop-blur supports-[backdrop-filter]:bg-background/50" ,
97- className ,
98- ) }
99- >
100- < div className = "group relative aspect-[5/4] w-full overflow-hidden" >
101- < Image
102- src = { coverSrc }
103- alt = { activeEvent . name }
104- fill
105- sizes = "(min-width: 1024px) 320px, (min-width: 640px) 288px, 90vw"
106- priority
107- className = "object-contain object-top"
108- />
109- { /* 下半透明渐变,用于保证文字与按钮对比度 */ }
110- < div className = "absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
111- { events . length > 1 && (
112- < >
113- { /* 多条活动时显示手动切换指示器 */ }
114- < div className = "absolute inset-x-0 top-0 flex justify-end gap-1 p-3" >
115- { events . map ( ( event , idx ) => (
116- < button
117- key = { `${ event . name } -${ idx } ` }
118- type = "button"
119- onClick = { ( ) => setActiveIndex ( idx ) }
120- aria-label = { `切换到 ${ event . name } ` }
121- className = { cn (
122- "h-1.5 w-6 rounded-full transition-opacity" ,
123- idx === activeIndex
124- ? "bg-white/90 opacity-100"
125- : "bg-white/40 opacity-60 hover:opacity-85" ,
126- ) }
127- />
128- ) ) }
129- </ div >
130- < button
131- type = "button"
132- aria-label = "上一条活动"
133- onClick = { handlePrev }
134- className = "absolute left-3 top-1/2 z-30 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-black/35 text-white shadow-sm transition hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
135- >
136- < ChevronLeft className = "h-4 w-4" />
137- </ button >
138- < button
139- type = "button"
140- aria-label = "下一条活动"
141- onClick = { handleNext }
142- className = "absolute right-3 top-1/2 z-30 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-black/35 text-white shadow-sm transition hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
143- >
144- < ChevronRight className = "h-4 w-4" />
145- </ button >
146- </ >
90+ < div className = "group relative" >
91+ < button
92+ type = "button"
93+ aria-label = "关闭活动轮播"
94+ onClick = { ( ) => setIsClosed ( true ) }
95+ className = "pointer-events-none absolute -top-3 -right-3 z-40 flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background text-foreground opacity-0 shadow transition group-hover:pointer-events-auto group-hover:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
96+ >
97+ < X className = "h-4 w-4" />
98+ </ button >
99+ < aside
100+ className = { cn (
101+ "relative w-full overflow-hidden rounded-2xl border border-border bg-background/70 text-left shadow-md backdrop-blur supports-[backdrop-filter]:bg-background/50" ,
102+ className ,
147103 ) }
148- { /* 底部毛玻璃按钮,根据 deprecated 控制回放按钮可见性 */ }
149- < div
150- className = { cn (
151- "absolute inset-x-0 bottom-0 top-3/4 z-10 grid border-t border-white/15 bg-white/20 text-sm font-medium text-white shadow-lg backdrop-blur-md" ,
152- showPlayback ? "grid-cols-2" : "grid-cols-1" ,
104+ >
105+ < div className = "relative aspect-[5/4] w-full overflow-hidden" >
106+ < Image
107+ src = { coverSrc }
108+ alt = { activeEvent . name }
109+ fill
110+ sizes = "(min-width: 1024px) 320px, (min-width: 640px) 288px, 90vw"
111+ priority
112+ className = "object-contain object-top"
113+ />
114+ { /* 下半透明渐变,用于保证文字与按钮对比度 */ }
115+ < div className = "absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
116+ { events . length > 1 && (
117+ < >
118+ { /* 多条活动时显示手动切换指示器 */ }
119+ < div className = "absolute inset-x-0 top-0 flex justify-end gap-1 p-3" >
120+ { events . map ( ( event , idx ) => (
121+ < button
122+ key = { `${ event . name } -${ idx } ` }
123+ type = "button"
124+ onClick = { ( ) => setActiveIndex ( idx ) }
125+ aria-label = { `切换到 ${ event . name } ` }
126+ className = { cn (
127+ "h-1.5 w-6 rounded-full transition-opacity" ,
128+ idx === activeIndex
129+ ? "bg-white/90 opacity-100"
130+ : "bg-white/40 opacity-60 hover:opacity-85" ,
131+ ) }
132+ />
133+ ) ) }
134+ </ div >
135+ < button
136+ type = "button"
137+ aria-label = "上一条活动"
138+ onClick = { handlePrev }
139+ className = "absolute left-3 top-1/2 z-30 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-black/35 text-white shadow-sm transition hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
140+ >
141+ < ChevronLeft className = "h-4 w-4" />
142+ </ button >
143+ < button
144+ type = "button"
145+ aria-label = "下一条活动"
146+ onClick = { handleNext }
147+ className = "absolute right-3 top-1/2 z-30 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-black/35 text-white shadow-sm transition hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
148+ >
149+ < ChevronRight className = "h-4 w-4" />
150+ </ button >
151+ </ >
153152 ) }
154- >
155- < Link
156- href = { activeEvent . discord }
157- prefetch = { false }
158- className = "flex h-full items-center justify-center px-3 text-white transition-colors hover:bg-white/25 hover:text-white"
153+ { /* 底部毛玻璃按钮,根据 deprecated 控制回放按钮可见性 */ }
154+ < div
155+ className = { cn (
156+ "absolute inset-x-0 bottom-0 top-3/4 z-10 grid border-t border-white/15 bg-white/20 text-sm font-medium text-white shadow-lg backdrop-blur-md" ,
157+ showPlayback ? "grid-cols-2" : "grid-cols-1" ,
158+ ) }
159159 >
160- Discord
161- </ Link >
162- { showPlayback && (
163160 < Link
164- href = { activeEvent . playback as string }
161+ href = { activeEvent . discord }
165162 prefetch = { false }
166- className = "flex h-full items-center justify-center border-l border-white/15 px-3 text-white transition-colors hover:bg-white/25 hover:text-white"
163+ className = "flex h-full items-center justify-center px-3 text-white transition-colors hover:bg-white/25 hover:text-white"
167164 >
168- Playback
165+ Discord
169166 </ Link >
170- ) }
167+ { showPlayback && (
168+ < Link
169+ href = { activeEvent . playback as string }
170+ prefetch = { false }
171+ className = "flex h-full items-center justify-center border-l border-white/15 px-3 text-white transition-colors hover:bg-white/25 hover:text-white"
172+ >
173+ Playback
174+ </ Link >
175+ ) }
176+ </ div >
171177 </ div >
172- </ div >
173- </ aside >
178+ </ aside >
179+ </ div >
174180 ) ;
175181}
0 commit comments