refactor: replace preset detail modal layoutId morph with bottom sheet slide-up + category crossfade

- Remove layoutId from preset card in game-page-client.tsx and modal in preset-detail-modal.tsx to eliminate text smearing during morph animation.
- Replace modal scale/opacity spring animation with bottom sheet y-slide spring (y: 100% → 0).
- Wrap settings table content in AnimatePresence mode='wait' with keyed motion.div for crossfade when switching categories.
This commit is contained in:
2026-04-28 09:24:08 +08:00
parent 1e41531834
commit f502d120a7
2 changed files with 66 additions and 59 deletions
-1
View File
@@ -751,7 +751,6 @@ export function GamePageClient({
return ( return (
<motion.div <motion.div
key={preset.id} key={preset.id}
layoutId={preset.id}
onClick={() => setSelectedPresetId(preset.id)} onClick={() => setSelectedPresetId(preset.id)}
className={`shrink-0 w-72 flex flex-col gap-3 p-4 rounded-xl border transition-colors cursor-pointer hover:border-primary/30 ${ className={`shrink-0 w-72 flex flex-col gap-3 p-4 rounded-xl border transition-colors cursor-pointer hover:border-primary/30 ${
raw raw
+15 -7
View File
@@ -164,14 +164,13 @@ export function PresetDetailModal({
> >
{/* Modal card */} {/* Modal card */}
<motion.div <motion.div
layoutId={preset.id}
className="relative w-full max-w-4xl max-h-[90vh] overflow-hidden rounded-xl border border-border bg-background flex flex-col" className="relative w-full max-w-4xl max-h-[90vh] overflow-hidden rounded-xl border border-border bg-background flex flex-col"
initial={{ scale: 0.95, opacity: 0 }} initial={{ y: "100%", opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }} exit={{ y: "100%", opacity: 0 }}
transition={{ transition={{
type: "spring", type: "spring",
damping: 25, damping: 30,
stiffness: 300, stiffness: 300,
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
@@ -427,7 +426,15 @@ export function PresetDetailModal({
{/* Right panel */} {/* Right panel */}
<div className="flex-1 overflow-y-auto p-5"> <div className="flex-1 overflow-y-auto p-5">
{hasCategories ? ( {hasCategories ? (
<div className="flex flex-col gap-4"> <AnimatePresence mode="wait">
<motion.div
key={currentCategory?.category ?? "empty"}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="flex flex-col gap-4"
>
{/* Category navigation */} {/* Category navigation */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<button <button
@@ -482,7 +489,8 @@ export function PresetDetailModal({
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </motion.div>
</AnimatePresence>
) : ( ) : (
<div className="flex flex-col items-center justify-center py-16 gap-3 rounded-lg border border-border bg-text/2"> <div className="flex flex-col items-center justify-center py-16 gap-3 rounded-lg border border-border bg-text/2">
<p className="text-sm text-text/40"> <p className="text-sm text-text/40">