@zomako/elearning-components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FlashcardDeck/FlashcardDeck.css +134 -0
- package/FlashcardDeck/FlashcardDeck.jsx +87 -0
- package/FlashcardDeck/FlashcardDeck.tsx +96 -0
- package/FlashcardDeck/README.md +313 -0
- package/FlashcardDeck/index.js +1 -0
- package/README.md +53 -0
- package/dist/elearning-components.es.js +688 -0
- package/dist/elearning-components.umd.js +30 -0
- package/dist/style.css +1 -0
- package/index.html +12 -0
- package/package.json +30 -0
- package/rollup.config.js +20 -0
- package/src/components/FlashcardDeck/FlashcardDeck.css +134 -0
- package/src/components/FlashcardDeck/FlashcardDeck.tsx +96 -0
- package/src/components/FlashcardDeck/index.ts +1 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +15 -0
- package/vite.config.js +24 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
.flashcard-deck-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 2rem;
|
|
6
|
+
padding: 2rem;
|
|
7
|
+
max-width: 600px;
|
|
8
|
+
margin: 0 auto;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.flashcard-deck-header {
|
|
12
|
+
width: 100%;
|
|
13
|
+
text-align: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.flashcard-counter {
|
|
17
|
+
font-size: 1rem;
|
|
18
|
+
color: #666;
|
|
19
|
+
font-weight: 500;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.flashcard-wrapper {
|
|
23
|
+
perspective: 1000px;
|
|
24
|
+
width: 100%;
|
|
25
|
+
min-height: 300px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.flashcard {
|
|
29
|
+
position: relative;
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 300px;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transform-style: preserve-3d;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.flashcard-face {
|
|
37
|
+
position: absolute;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
backface-visibility: hidden;
|
|
41
|
+
border-radius: 12px;
|
|
42
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
padding: 2rem;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.flashcard-front {
|
|
51
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
52
|
+
color: white;
|
|
53
|
+
transform: rotateY(0deg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.flashcard-back {
|
|
57
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
58
|
+
color: white;
|
|
59
|
+
transform: rotateY(180deg);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.flashcard-content {
|
|
63
|
+
font-size: 1.25rem;
|
|
64
|
+
text-align: center;
|
|
65
|
+
word-wrap: break-word;
|
|
66
|
+
overflow-wrap: break-word;
|
|
67
|
+
line-height: 1.6;
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.flashcard-controls {
|
|
72
|
+
display: flex;
|
|
73
|
+
gap: 1rem;
|
|
74
|
+
width: 100%;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.flashcard-button {
|
|
79
|
+
padding: 0.75rem 2rem;
|
|
80
|
+
font-size: 1rem;
|
|
81
|
+
font-weight: 600;
|
|
82
|
+
border: none;
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
transition: all 0.3s ease;
|
|
86
|
+
background-color: #667eea;
|
|
87
|
+
color: white;
|
|
88
|
+
min-width: 120px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.flashcard-button:hover:not(:disabled) {
|
|
92
|
+
background-color: #5568d3;
|
|
93
|
+
transform: translateY(-2px);
|
|
94
|
+
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.flashcard-button:active:not(:disabled) {
|
|
98
|
+
transform: translateY(0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.flashcard-button:disabled {
|
|
102
|
+
background-color: #e0e0e0;
|
|
103
|
+
color: #9e9e9e;
|
|
104
|
+
cursor: not-allowed;
|
|
105
|
+
opacity: 0.6;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.flashcard-empty-message {
|
|
109
|
+
text-align: center;
|
|
110
|
+
color: #666;
|
|
111
|
+
font-size: 1.1rem;
|
|
112
|
+
padding: 2rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Responsive design */
|
|
116
|
+
@media (max-width: 768px) {
|
|
117
|
+
.flashcard-deck-container {
|
|
118
|
+
padding: 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.flashcard {
|
|
122
|
+
height: 250px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.flashcard-content {
|
|
126
|
+
font-size: 1.1rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.flashcard-button {
|
|
130
|
+
padding: 0.625rem 1.5rem;
|
|
131
|
+
font-size: 0.9rem;
|
|
132
|
+
min-width: 100px;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import './FlashcardDeck.css';
|
|
4
|
+
|
|
5
|
+
const FlashcardDeck = ({ cards = [] }) => {
|
|
6
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
7
|
+
const [isFlipped, setIsFlipped] = useState(false);
|
|
8
|
+
|
|
9
|
+
if (!cards || cards.length === 0) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flashcard-deck-container">
|
|
12
|
+
<p className="flashcard-empty-message">No cards available</p>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const currentCard = cards[currentIndex];
|
|
18
|
+
const canGoPrevious = currentIndex > 0;
|
|
19
|
+
const canGoNext = currentIndex < cards.length - 1;
|
|
20
|
+
|
|
21
|
+
const handlePrevious = () => {
|
|
22
|
+
if (canGoPrevious) {
|
|
23
|
+
setCurrentIndex(currentIndex - 1);
|
|
24
|
+
setIsFlipped(false);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleNext = () => {
|
|
29
|
+
if (canGoNext) {
|
|
30
|
+
setCurrentIndex(currentIndex + 1);
|
|
31
|
+
setIsFlipped(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleCardClick = () => {
|
|
36
|
+
setIsFlipped(!isFlipped);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="flashcard-deck-container">
|
|
41
|
+
<div className="flashcard-deck-header">
|
|
42
|
+
<span className="flashcard-counter">
|
|
43
|
+
Card {currentIndex + 1} of {cards.length}
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div className="flashcard-wrapper">
|
|
48
|
+
<motion.div
|
|
49
|
+
key={currentIndex}
|
|
50
|
+
className="flashcard"
|
|
51
|
+
onClick={handleCardClick}
|
|
52
|
+
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
|
53
|
+
transition={{ duration: 0.6, type: 'spring', stiffness: 100 }}
|
|
54
|
+
style={{ transformStyle: 'preserve-3d' }}
|
|
55
|
+
>
|
|
56
|
+
<div className="flashcard-face flashcard-front">
|
|
57
|
+
<div className="flashcard-content">{currentCard.front}</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="flashcard-face flashcard-back">
|
|
60
|
+
<div className="flashcard-content">{currentCard.back}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</motion.div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="flashcard-controls">
|
|
66
|
+
<button
|
|
67
|
+
className="flashcard-button flashcard-button-previous"
|
|
68
|
+
onClick={handlePrevious}
|
|
69
|
+
disabled={!canGoPrevious}
|
|
70
|
+
aria-label="Previous card"
|
|
71
|
+
>
|
|
72
|
+
Previous
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
className="flashcard-button flashcard-button-next"
|
|
76
|
+
onClick={handleNext}
|
|
77
|
+
disabled={!canGoNext}
|
|
78
|
+
aria-label="Next card"
|
|
79
|
+
>
|
|
80
|
+
Next
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default FlashcardDeck;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import './FlashcardDeck.css';
|
|
4
|
+
|
|
5
|
+
export interface Flashcard {
|
|
6
|
+
front: string;
|
|
7
|
+
back: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FlashcardDeckProps {
|
|
11
|
+
cards?: Flashcard[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FlashcardDeck: React.FC<FlashcardDeckProps> = ({ cards = [] }) => {
|
|
15
|
+
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
|
16
|
+
const [isFlipped, setIsFlipped] = useState<boolean>(false);
|
|
17
|
+
|
|
18
|
+
if (!cards || cards.length === 0) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="flashcard-deck-container">
|
|
21
|
+
<p className="flashcard-empty-message">No cards available</p>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const currentCard = cards[currentIndex];
|
|
27
|
+
const canGoPrevious = currentIndex > 0;
|
|
28
|
+
const canGoNext = currentIndex < cards.length - 1;
|
|
29
|
+
|
|
30
|
+
const handlePrevious = (): void => {
|
|
31
|
+
if (canGoPrevious) {
|
|
32
|
+
setCurrentIndex(currentIndex - 1);
|
|
33
|
+
setIsFlipped(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleNext = (): void => {
|
|
38
|
+
if (canGoNext) {
|
|
39
|
+
setCurrentIndex(currentIndex + 1);
|
|
40
|
+
setIsFlipped(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleCardClick = (): void => {
|
|
45
|
+
setIsFlipped(!isFlipped);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="flashcard-deck-container">
|
|
50
|
+
<div className="flashcard-deck-header">
|
|
51
|
+
<span className="flashcard-counter">
|
|
52
|
+
Card {currentIndex + 1} of {cards.length}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className="flashcard-wrapper">
|
|
57
|
+
<motion.div
|
|
58
|
+
key={currentIndex}
|
|
59
|
+
className="flashcard"
|
|
60
|
+
onClick={handleCardClick}
|
|
61
|
+
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
|
62
|
+
transition={{ duration: 0.6, type: 'spring', stiffness: 100 }}
|
|
63
|
+
style={{ transformStyle: 'preserve-3d' }}
|
|
64
|
+
>
|
|
65
|
+
<div className="flashcard-face flashcard-front">
|
|
66
|
+
<div className="flashcard-content">{currentCard.front}</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="flashcard-face flashcard-back">
|
|
69
|
+
<div className="flashcard-content">{currentCard.back}</div>
|
|
70
|
+
</div>
|
|
71
|
+
</motion.div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="flashcard-controls">
|
|
75
|
+
<button
|
|
76
|
+
className="flashcard-button flashcard-button-previous"
|
|
77
|
+
onClick={handlePrevious}
|
|
78
|
+
disabled={!canGoPrevious}
|
|
79
|
+
aria-label="Previous card"
|
|
80
|
+
>
|
|
81
|
+
Previous
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
className="flashcard-button flashcard-button-next"
|
|
85
|
+
onClick={handleNext}
|
|
86
|
+
disabled={!canGoNext}
|
|
87
|
+
aria-label="Next card"
|
|
88
|
+
>
|
|
89
|
+
Next
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default FlashcardDeck;
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# FlashcardDeck Component
|
|
2
|
+
|
|
3
|
+
A fully-featured, reusable React component for displaying interactive flashcards with smooth 3D flip animations. Perfect for e-learning applications, study tools, quiz systems, and any scenario where you need to present information in a card-based format with front and back sides.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The `FlashcardDeck` component provides an engaging way to display educational content, study materials, or any paired information. It allows users to:
|
|
8
|
+
|
|
9
|
+
- View one card at a time from a deck of flashcards
|
|
10
|
+
- Click on cards to flip them and reveal the back side with a smooth 3D animation
|
|
11
|
+
- Navigate through the deck using Previous and Next buttons
|
|
12
|
+
- Track their progress with a card counter
|
|
13
|
+
|
|
14
|
+
The component is built with React and uses Framer Motion for smooth, performant animations. It's fully self-contained, responsive, and accessible.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **3D Flip Animation**: Smooth, spring-based 3D rotation animation powered by Framer Motion
|
|
19
|
+
- **Card Navigation**: Previous and Next buttons to move through the deck
|
|
20
|
+
- **Progress Tracking**: Displays current card position (e.g., "Card 1 of 10")
|
|
21
|
+
- **Responsive Design**: Optimized for both desktop and mobile devices
|
|
22
|
+
- **Accessibility**: Includes ARIA labels for screen readers
|
|
23
|
+
- **Empty State Handling**: Gracefully handles empty or missing card arrays
|
|
24
|
+
- **Auto-reset**: Automatically resets flip state when navigating to a new card
|
|
25
|
+
- **Disabled States**: Navigation buttons are properly disabled at deck boundaries
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
- React 16.8+ (hooks support required)
|
|
32
|
+
- Node.js and npm (or yarn/pnpm)
|
|
33
|
+
|
|
34
|
+
### Install Dependencies
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install react react-dom framer-motion
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or with yarn:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
yarn add react react-dom framer-motion
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or with pnpm:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm add react react-dom framer-motion
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Required | Default | Description |
|
|
55
|
+
|------|------|----------|---------|-------------|
|
|
56
|
+
| `cards` | `Array<{front: string, back: string}>` | No | `[]` | An array of flashcard objects. Each object must have a `front` property (string) representing the front side text and a `back` property (string) representing the back side text. If not provided or empty, the component displays an empty state message. |
|
|
57
|
+
|
|
58
|
+
### TypeScript Interface
|
|
59
|
+
|
|
60
|
+
If you're using TypeScript, you can import the type definitions:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import FlashcardDeck, { Flashcard } from './FlashcardDeck/FlashcardDeck';
|
|
64
|
+
|
|
65
|
+
// Flashcard interface
|
|
66
|
+
interface Flashcard {
|
|
67
|
+
front: string;
|
|
68
|
+
back: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Component props interface
|
|
72
|
+
interface FlashcardDeckProps {
|
|
73
|
+
cards?: Flashcard[];
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
### Basic Example
|
|
80
|
+
|
|
81
|
+
```jsx
|
|
82
|
+
import React from 'react';
|
|
83
|
+
import FlashcardDeck from './FlashcardDeck/FlashcardDeck';
|
|
84
|
+
|
|
85
|
+
const App = () => {
|
|
86
|
+
const cards = [
|
|
87
|
+
{
|
|
88
|
+
front: 'What is React?',
|
|
89
|
+
back: 'React is a JavaScript library for building user interfaces, particularly web applications. It allows developers to create reusable UI components.',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
front: 'What is JSX?',
|
|
93
|
+
back: 'JSX is a syntax extension for JavaScript that looks similar to HTML. It allows you to write HTML-like code in your JavaScript files.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
front: 'What is a component?',
|
|
97
|
+
back: 'A component is a reusable piece of code that returns JSX. Components can be functional or class-based and help organize your UI into manageable pieces.',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
return <FlashcardDeck cards={cards} />;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default App;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### TypeScript Example
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import React from 'react';
|
|
111
|
+
import FlashcardDeck, { Flashcard } from './FlashcardDeck/FlashcardDeck';
|
|
112
|
+
|
|
113
|
+
const App: React.FC = () => {
|
|
114
|
+
const cards: Flashcard[] = [
|
|
115
|
+
{
|
|
116
|
+
front: 'Capital of France?',
|
|
117
|
+
back: 'Paris',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
front: 'Capital of Japan?',
|
|
121
|
+
back: 'Tokyo',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
front: 'Capital of Australia?',
|
|
125
|
+
back: 'Canberra',
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
return <FlashcardDeck cards={cards} />;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default App;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Dynamic Card Loading Example
|
|
136
|
+
|
|
137
|
+
```jsx
|
|
138
|
+
import React, { useState, useEffect } from 'react';
|
|
139
|
+
import FlashcardDeck from './FlashcardDeck/FlashcardDeck';
|
|
140
|
+
|
|
141
|
+
const App = () => {
|
|
142
|
+
const [cards, setCards] = useState([]);
|
|
143
|
+
const [loading, setLoading] = useState(true);
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
// Simulate API call
|
|
147
|
+
fetch('/api/flashcards')
|
|
148
|
+
.then((res) => res.json())
|
|
149
|
+
.then((data) => {
|
|
150
|
+
setCards(data);
|
|
151
|
+
setLoading(false);
|
|
152
|
+
})
|
|
153
|
+
.catch((error) => {
|
|
154
|
+
console.error('Error loading cards:', error);
|
|
155
|
+
setLoading(false);
|
|
156
|
+
});
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
if (loading) {
|
|
160
|
+
return <div>Loading flashcards...</div>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return <FlashcardDeck cards={cards} />;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default App;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Empty State Example
|
|
170
|
+
|
|
171
|
+
```jsx
|
|
172
|
+
import React from 'react';
|
|
173
|
+
import FlashcardDeck from './FlashcardDeck/FlashcardDeck';
|
|
174
|
+
|
|
175
|
+
const App = () => {
|
|
176
|
+
// Component will display "No cards available" message
|
|
177
|
+
return <FlashcardDeck cards={[]} />;
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Component Structure
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
FlashcardDeck/
|
|
185
|
+
├── FlashcardDeck.jsx # Main component file (JavaScript)
|
|
186
|
+
├── FlashcardDeck.tsx # Main component file (TypeScript)
|
|
187
|
+
├── FlashcardDeck.css # Component styles
|
|
188
|
+
├── index.js # Export file
|
|
189
|
+
└── README.md # This documentation
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Styling
|
|
193
|
+
|
|
194
|
+
The component includes its own CSS file with customizable styles. You can override the default styles by targeting the following CSS classes:
|
|
195
|
+
|
|
196
|
+
### Available CSS Classes
|
|
197
|
+
|
|
198
|
+
- `.flashcard-deck-container` - Main container wrapper
|
|
199
|
+
- `.flashcard-deck-header` - Header section containing the card counter
|
|
200
|
+
- `.flashcard-counter` - Card position counter text
|
|
201
|
+
- `.flashcard-wrapper` - Wrapper with 3D perspective
|
|
202
|
+
- `.flashcard` - Card container with 3D transform
|
|
203
|
+
- `.flashcard-face` - Base class for both card faces
|
|
204
|
+
- `.flashcard-front` - Front side of the card (purple gradient)
|
|
205
|
+
- `.flashcard-back` - Back side of the card (pink gradient)
|
|
206
|
+
- `.flashcard-content` - Content wrapper inside each card face
|
|
207
|
+
- `.flashcard-controls` - Container for navigation buttons
|
|
208
|
+
- `.flashcard-button` - Base button styles
|
|
209
|
+
- `.flashcard-button-previous` - Previous button
|
|
210
|
+
- `.flashcard-button-next` - Next button
|
|
211
|
+
- `.flashcard-empty-message` - Empty state message
|
|
212
|
+
|
|
213
|
+
### Custom Styling Example
|
|
214
|
+
|
|
215
|
+
```css
|
|
216
|
+
/* Override card colors */
|
|
217
|
+
.flashcard-front {
|
|
218
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.flashcard-back {
|
|
222
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Customize button styles */
|
|
226
|
+
.flashcard-button {
|
|
227
|
+
background-color: #4CAF50;
|
|
228
|
+
border-radius: 20px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.flashcard-button:hover:not(:disabled) {
|
|
232
|
+
background-color: #45a049;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Animation Details
|
|
237
|
+
|
|
238
|
+
The component uses Framer Motion for animations:
|
|
239
|
+
|
|
240
|
+
- **Flip Animation**: Spring-based rotation on the Y-axis (180 degrees)
|
|
241
|
+
- **Duration**: 0.6 seconds
|
|
242
|
+
- **Animation Type**: Spring with stiffness of 100
|
|
243
|
+
- **3D Transform**: Uses CSS `transform-style: preserve-3d` for proper 3D rendering
|
|
244
|
+
- **Backface Visibility**: Hidden to ensure only the visible side is shown during rotation
|
|
245
|
+
|
|
246
|
+
## Browser Support
|
|
247
|
+
|
|
248
|
+
Works in all modern browsers that support:
|
|
249
|
+
- CSS 3D Transforms
|
|
250
|
+
- ES6+ JavaScript
|
|
251
|
+
- React 16.8+ (hooks)
|
|
252
|
+
|
|
253
|
+
Tested and working in:
|
|
254
|
+
- Chrome/Edge (latest)
|
|
255
|
+
- Firefox (latest)
|
|
256
|
+
- Safari (latest)
|
|
257
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
258
|
+
|
|
259
|
+
## Accessibility
|
|
260
|
+
|
|
261
|
+
The component includes accessibility features:
|
|
262
|
+
|
|
263
|
+
- ARIA labels on navigation buttons (`aria-label="Previous card"` and `aria-label="Next card"`)
|
|
264
|
+
- Semantic HTML structure
|
|
265
|
+
- Keyboard navigation support (buttons are focusable)
|
|
266
|
+
- Disabled state indicators for navigation buttons
|
|
267
|
+
|
|
268
|
+
## Performance Considerations
|
|
269
|
+
|
|
270
|
+
- Uses React hooks for efficient state management
|
|
271
|
+
- Framer Motion handles GPU-accelerated animations
|
|
272
|
+
- Minimal re-renders (only updates when necessary)
|
|
273
|
+
- CSS transforms for smooth animations without layout shifts
|
|
274
|
+
|
|
275
|
+
## Common Use Cases
|
|
276
|
+
|
|
277
|
+
1. **Language Learning**: Vocabulary flashcards with words and translations
|
|
278
|
+
2. **Study Tools**: Question and answer flashcards for exam preparation
|
|
279
|
+
3. **Training Materials**: Concept explanations with detailed descriptions
|
|
280
|
+
4. **Quiz Systems**: Interactive quiz questions with answers
|
|
281
|
+
5. **Memory Games**: Matching games with clues and solutions
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
### Cards not flipping
|
|
286
|
+
|
|
287
|
+
- Ensure Framer Motion is properly installed: `npm install framer-motion`
|
|
288
|
+
- Check that the CSS file is imported correctly
|
|
289
|
+
- Verify that both `front` and `back` properties exist on each card object
|
|
290
|
+
|
|
291
|
+
### Navigation buttons not working
|
|
292
|
+
|
|
293
|
+
- Ensure cards array is not empty
|
|
294
|
+
- Check browser console for any JavaScript errors
|
|
295
|
+
- Verify that cards prop is passed correctly
|
|
296
|
+
|
|
297
|
+
### Styling issues
|
|
298
|
+
|
|
299
|
+
- Make sure `FlashcardDeck.css` is imported in your component
|
|
300
|
+
- Check for CSS conflicts with your global styles
|
|
301
|
+
- Verify that CSS classes are not being overridden unintentionally
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
This component is part of the e-learning-components project. Use it freely in your projects.
|
|
306
|
+
|
|
307
|
+
## Contributing
|
|
308
|
+
|
|
309
|
+
Contributions are welcome! Please ensure that:
|
|
310
|
+
- Code follows React best practices
|
|
311
|
+
- Animations remain smooth and performant
|
|
312
|
+
- Accessibility features are maintained
|
|
313
|
+
- Documentation is updated accordingly
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FlashcardDeck';
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# E-learning Components
|
|
2
|
+
|
|
3
|
+
A collection of reusable React components for e-learning applications.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### FlashcardDeck
|
|
8
|
+
|
|
9
|
+
A fully-featured flashcard component with 3D flip animations. See `FlashcardDeck/README.md` for detailed documentation.
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Build
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Project Structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
elearning-components/
|
|
35
|
+
├── FlashcardDeck/ # FlashcardDeck component
|
|
36
|
+
│ ├── FlashcardDeck.jsx # Component (JavaScript)
|
|
37
|
+
│ ├── FlashcardDeck.tsx # Component (TypeScript)
|
|
38
|
+
│ ├── FlashcardDeck.css # Styles
|
|
39
|
+
│ ├── index.js # Export
|
|
40
|
+
│ └── README.md # Documentation
|
|
41
|
+
├── src/ # Application source
|
|
42
|
+
│ ├── App.jsx # Demo app
|
|
43
|
+
│ └── main.jsx # Entry point
|
|
44
|
+
├── package.json # Dependencies
|
|
45
|
+
├── vite.config.js # Vite configuration
|
|
46
|
+
└── README.md # This file
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Technologies
|
|
50
|
+
|
|
51
|
+
- React 18
|
|
52
|
+
- Framer Motion
|
|
53
|
+
- Vite
|