Last active
February 17, 2025 21:53
-
-
Save nickstamas/41a61ab155c467d41e8d9a6640caec41 to your computer and use it in GitHub Desktop.
Onboarding Animation Reference
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useState, useEffect } from 'react'; | |
| import { ArrowRight, Trophy } from 'lucide-react'; | |
| const OnboardingQuiz = () => { | |
| const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); | |
| const [isAnimating, setIsAnimating] = useState(false); | |
| const [selectedAnswer, setSelectedAnswer] = useState(null); | |
| const [showXPAlert, setShowXPAlert] = useState(false); | |
| const [displayedXP, setDisplayedXP] = useState(0); | |
| const questions = [ | |
| { | |
| question: "What brings you here today?", | |
| answers: [ | |
| "I want to learn new skills", | |
| "I'm looking for career growth", | |
| "I'm exploring new interests", | |
| "I need specific knowledge" | |
| ], | |
| xp: 50 | |
| }, | |
| { | |
| question: "How much time can you dedicate?", | |
| answers: [ | |
| "Less than 1 hour per week", | |
| "2-3 hours per week", | |
| "4-6 hours per week", | |
| "More than 6 hours per week" | |
| ], | |
| xp: 75 | |
| }, | |
| { | |
| question: "What's your preferred learning style?", | |
| answers: [ | |
| "Visual learning", | |
| "Reading and writing", | |
| "Interactive exercises", | |
| "Audio and video content" | |
| ], | |
| xp: 100 | |
| } | |
| ]; | |
| const handleAnswerClick = (answerIndex) => { | |
| if (isAnimating) return; | |
| setSelectedAnswer(answerIndex); | |
| setIsAnimating(true); | |
| setShowXPAlert(true); | |
| // Hide XP alert after delay | |
| setTimeout(() => { | |
| setShowXPAlert(false); | |
| // Update XP value only after alert is hidden | |
| setTimeout(() => { | |
| if (currentQuestionIndex < questions.length - 1) { | |
| setDisplayedXP(questions[currentQuestionIndex + 1].xp); | |
| } | |
| }, 300); // Wait for fade out animation | |
| }, 2500); | |
| // Move to next question after delay | |
| setTimeout(() => { | |
| if (currentQuestionIndex < questions.length - 1) { | |
| setCurrentQuestionIndex(prev => prev + 1); | |
| setSelectedAnswer(null); | |
| } | |
| setIsAnimating(false); | |
| }, 1000); | |
| }; | |
| // Set initial XP value | |
| useEffect(() => { | |
| setDisplayedXP(questions[0].xp); | |
| }, []); | |
| // Reset animations when question changes | |
| useEffect(() => { | |
| setIsAnimating(false); | |
| }, [currentQuestionIndex]); | |
| const currentQuestion = questions[currentQuestionIndex]; | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-b from-blue-50 to-blue-100 p-6 flex items-center justify-center"> | |
| {/* XP Alert */} | |
| <div | |
| className={` | |
| absolute top-12 left-1/2 -translate-x-1/2 z-50 | |
| transition-all duration-300 | |
| ${showXPAlert ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-8'} | |
| `} | |
| > | |
| <div className="bg-white text-gray-800 px-6 py-3 rounded-full shadow-lg flex items-center gap-3"> | |
| <div className="bg-green-100 p-1.5 rounded-full"> | |
| <Trophy size={16} className="text-green-600" /> | |
| </div> | |
| <span className="font-medium pr-2">+{displayedXP} XP</span> | |
| </div> | |
| </div> | |
| <div className="w-full max-w-md"> | |
| {/* Progress bar */} | |
| <div className="h-1 bg-gray-200 rounded-full mb-8"> | |
| <div | |
| className="h-1 bg-blue-500 rounded-full transition-all duration-300" | |
| style={{ width: `${((currentQuestionIndex + 1) / questions.length) * 100}%` }} | |
| /> | |
| </div> | |
| {/* Question */} | |
| <div className="mb-8"> | |
| <h2 className="text-2xl font-semibold text-gray-800 mb-2 animate-slide-up"> | |
| {currentQuestion.question} | |
| </h2> | |
| <p className="text-sm text-gray-500 animate-slide-up" style={{ animationDelay: '100ms' }}> | |
| Question {currentQuestionIndex + 1} of {questions.length} | |
| </p> | |
| </div> | |
| {/* Answers */} | |
| <div className="space-y-3"> | |
| {currentQuestion.answers.map((answer, index) => { | |
| const delay = (index + 1) * 100; | |
| const isSelected = selectedAnswer === index; | |
| return ( | |
| <button | |
| key={index} | |
| onClick={() => handleAnswerClick(index)} | |
| disabled={isAnimating} | |
| className={` | |
| w-full p-4 rounded-lg border-2 text-left relative | |
| transition-all duration-300 transform | |
| ${isAnimating && !isSelected ? 'opacity-0 translate-y-4' : 'opacity-100 translate-y-0'} | |
| ${isSelected | |
| ? 'border-blue-500 bg-blue-50 text-blue-700 animate-pulse-once' | |
| : 'border-gray-200 hover:border-blue-200 hover:bg-blue-50' | |
| } | |
| `} | |
| style={{ | |
| transitionDelay: isAnimating ? '0ms' : `${delay}ms`, | |
| animation: !isAnimating && `slideUp 500ms ${delay}ms both` | |
| }} | |
| > | |
| <span className="block text-sm sm:text-base">{answer}</span> | |
| {isSelected && ( | |
| <ArrowRight className="absolute right-4 top-1/2 transform -translate-y-1/2 text-blue-500" size={20} /> | |
| )} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| <style>{` | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes pulseOnce { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.02); } | |
| 100% { transform: scale(1); } | |
| } | |
| .animate-slide-up { | |
| animation: slideUp 500ms ease-out forwards; | |
| } | |
| .animate-pulse-once { | |
| animation: pulseOnce 300ms ease-in-out; | |
| } | |
| `}</style> | |
| </div> | |
| ); | |
| }; | |
| export default OnboardingQuiz; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment