fix: use single scrolling practice track
This commit is contained in:
@@ -93,6 +93,7 @@ export default function ProcessPurposePracticePage() {
|
|||||||
const latestInputRef = useRef<string[]>([])
|
const latestInputRef = useRef<string[]>([])
|
||||||
const answerTimerRef = useRef<number | null>(null)
|
const answerTimerRef = useRef<number | null>(null)
|
||||||
const deckScrollRef = useRef<HTMLDivElement | null>(null)
|
const deckScrollRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const currentScrollLimitRef = useRef(0)
|
||||||
const { practiceMode } = useAppStore()
|
const { practiceMode } = useAppStore()
|
||||||
|
|
||||||
const itemMap = useMemo(
|
const itemMap = useMemo(
|
||||||
@@ -103,17 +104,9 @@ export default function ProcessPurposePracticePage() {
|
|||||||
const totalCount = progress.order.length
|
const totalCount = progress.order.length
|
||||||
const completedCount = progress.completedIds.length
|
const completedCount = progress.completedIds.length
|
||||||
const isFinished = completedCount >= totalCount
|
const isFinished = completedCount >= totalCount
|
||||||
const visibleQuestionCards = progress.order
|
const deckCards = progress.order
|
||||||
.slice(0, progress.currentIndex + 1)
|
|
||||||
.map((id, index) => ({ item: itemMap.get(id), index }))
|
.map((id, index) => ({ item: itemMap.get(id), index }))
|
||||||
.filter((card): card is { item: NonNullable<typeof currentItem>; index: number } => Boolean(card.item))
|
.filter((card): card is { item: NonNullable<typeof currentItem>; index: number } => Boolean(card.item))
|
||||||
const nextPreviewItem = itemMap.get(progress.order[progress.currentIndex + 1])
|
|
||||||
const deckCards = [
|
|
||||||
...visibleQuestionCards.map((card) => ({ ...card, isPreview: false })),
|
|
||||||
...(nextPreviewItem
|
|
||||||
? [{ item: nextPreviewItem, index: progress.currentIndex + 1, isPreview: true }]
|
|
||||||
: []),
|
|
||||||
]
|
|
||||||
|
|
||||||
const focusFirstEmptyInput = useCallback(() => {
|
const focusFirstEmptyInput = useCallback(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -236,6 +229,7 @@ export default function ProcessPurposePracticePage() {
|
|||||||
const deckTop = deck.getBoundingClientRect().top
|
const deckTop = deck.getBoundingClientRect().top
|
||||||
const cardTop = currentCard.getBoundingClientRect().top
|
const cardTop = currentCard.getBoundingClientRect().top
|
||||||
const targetTop = Math.max(deck.scrollTop + cardTop - deckTop - 12, 0)
|
const targetTop = Math.max(deck.scrollTop + cardTop - deckTop - 12, 0)
|
||||||
|
currentScrollLimitRef.current = targetTop
|
||||||
deck.scrollTo({ top: targetTop, behavior: 'smooth' })
|
deck.scrollTo({ top: targetTop, behavior: 'smooth' })
|
||||||
})
|
})
|
||||||
}, [progress.currentIndex])
|
}, [progress.currentIndex])
|
||||||
@@ -437,6 +431,14 @@ export default function ProcessPurposePracticePage() {
|
|||||||
focusFirstEmptyInput()
|
focusFirstEmptyInput()
|
||||||
}, [focusFirstEmptyInput])
|
}, [focusFirstEmptyInput])
|
||||||
|
|
||||||
|
const handleDeckScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
const deck = event.currentTarget
|
||||||
|
const maxScrollTop = currentScrollLimitRef.current
|
||||||
|
if (deck.scrollTop > maxScrollTop + 2) {
|
||||||
|
deck.scrollTop = maxScrollTop
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
if (!currentItem) return null
|
if (!currentItem) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -479,19 +481,21 @@ export default function ProcessPurposePracticePage() {
|
|||||||
<main className="mx-auto flex min-h-0 w-full max-w-5xl flex-1 flex-col gap-3 overflow-hidden px-4 pb-4 pt-3">
|
<main className="mx-auto flex min-h-0 w-full max-w-5xl flex-1 flex-col gap-3 overflow-hidden px-4 pb-4 pt-3">
|
||||||
<div
|
<div
|
||||||
ref={deckScrollRef}
|
ref={deckScrollRef}
|
||||||
|
onScroll={handleDeckScroll}
|
||||||
className="min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain pr-1 scroll-smooth"
|
className="min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain pr-1 scroll-smooth"
|
||||||
>
|
>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{deckCards.map(({ item, index, isPreview }) => {
|
{deckCards.map(({ item, index }) => {
|
||||||
const isCurrent = index === progress.currentIndex
|
const isCurrent = index === progress.currentIndex
|
||||||
const isAnsweredHistory = index < progress.currentIndex
|
const isAnsweredHistory = index < progress.currentIndex
|
||||||
|
const isFuture = index > progress.currentIndex
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.section
|
<motion.section
|
||||||
key={item.id}
|
key={item.id}
|
||||||
initial={{ opacity: 0, y: 24, scale: 0.98 }}
|
initial={{ opacity: 0, y: 24, scale: 0.98 }}
|
||||||
animate={{
|
animate={{
|
||||||
opacity: isCurrent ? 1 : isPreview ? 0.52 : 0.62,
|
opacity: isCurrent ? 1 : isFuture ? 0.42 : 0.62,
|
||||||
y: 0,
|
y: 0,
|
||||||
scale: isCurrent ? 1 : 0.975,
|
scale: isCurrent ? 1 : 0.975,
|
||||||
}}
|
}}
|
||||||
@@ -503,8 +507,8 @@ export default function ProcessPurposePracticePage() {
|
|||||||
? 'border-green-300 shadow-lg shadow-green-100/70 ring-2 ring-green-100 dark:border-green-700 dark:shadow-none dark:ring-green-900/40'
|
? 'border-green-300 shadow-lg shadow-green-100/70 ring-2 ring-green-100 dark:border-green-700 dark:shadow-none dark:ring-green-900/40'
|
||||||
: isCurrent
|
: isCurrent
|
||||||
? 'border-blue-200 shadow-xl shadow-blue-100/80 ring-2 ring-blue-100 dark:border-blue-800 dark:shadow-none dark:ring-blue-900/30'
|
? 'border-blue-200 shadow-xl shadow-blue-100/80 ring-2 ring-blue-100 dark:border-blue-800 dark:shadow-none dark:ring-blue-900/30'
|
||||||
: isPreview
|
: isFuture
|
||||||
? 'pointer-events-none border-dashed border-gray-200 bg-white/60 shadow-sm blur-[0.4px] dark:border-gray-700 dark:bg-gray-800/50'
|
? 'pointer-events-none border-dashed border-gray-200 bg-white/55 shadow-sm blur-[0.4px] dark:border-gray-700 dark:bg-gray-800/45'
|
||||||
: 'border-gray-100 shadow-sm dark:border-gray-800'
|
: 'border-gray-100 shadow-sm dark:border-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -546,7 +550,7 @@ export default function ProcessPurposePracticePage() {
|
|||||||
|
|
||||||
<p
|
<p
|
||||||
className={`leading-9 text-gray-900 dark:text-gray-100 ${
|
className={`leading-9 text-gray-900 dark:text-gray-100 ${
|
||||||
isCurrent ? 'text-xl' : isPreview ? 'line-clamp-2 select-none text-base text-gray-500 blur-[1.5px] dark:text-gray-400' : 'text-base'
|
isCurrent ? 'text-xl' : isFuture ? 'line-clamp-2 select-none text-base text-gray-500 blur-[1.5px] dark:text-gray-400' : 'text-base'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.purpose}
|
{item.purpose}
|
||||||
|
|||||||
Reference in New Issue
Block a user