Skip to content

Commit 0ce051a

Browse files
authored
Added several interactive React components to enhance the project's component library (#2)
## 1. Theme Switcher Component - Implements a theme preview system with Light/Dark/System modes - Real-time theme switching with smooth transitions - Preview card that demonstrates theme changes - System theme detection and synchronization - Responsive design for all screen sizes ## 2. Form Validator Component - Real-time form validation with immediate feedback - Password strength validation with regex pattern - Show/Hide password functionality - Error states and success indicators - Responsive form layout with proper spacing - Custom error messages for each validation rule ## 3. Progress Bar Component - Interactive progress bar with animations - Increment/decrement controls - Dynamic status messages based on progress - Reset functionality - Disabled states for buttons at min/max - Smooth progress transitions ## 4. Search Filter Component - Real-time search functionality - Category filtering system - Sort options (by name and difficulty) - Grid layout for results - Hover effects and animations - Responsive grid system - Uses useMemo for performance optimization ## 5. Toast Notification Component - Multiple toast types (success, error, warning) - Animated entrance/exit - Auto-dismiss functionality - Icon integration - Fixed positioning - Stacking context handling # Testing - Tested all components across different screen sizes - Verified theme switching functionality - Tested form validation rules - Checked search and filter functionality - Verified toast notifications - Tested progress bar interactions # Checklist - [x] Components follow project structure - [x] CSS uses project variables - [x] Components are responsive - [x] Added proper documentation - [x] Tested all features - [x] Added Author component to each component - [x] Code follows project style guide - [x] No console errors/warnings - [x] All components are reusable # Author Victor Ezeanyika GitHub: https://github.com/VictorEZCodes
2 parents ce6b7ae + 0549173 commit 0ce051a

File tree

11 files changed

+1028
-3
lines changed

11 files changed

+1028
-3
lines changed

src/App.jsx

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,72 @@
1-
1+
import { useState } from 'react';
2+
import Hero from './Hero';
23
import Details from './components/kelixirr/Details';
3-
import Hero from './Hero'
4+
import Toast from './components/VictorEZCodes/Toast';
5+
import ProgressBar from './components/VictorEZCodes/ProgressBar';
6+
import ThemeSwitcher from './components/VictorEZCodes/ThemeSwitcher';
7+
import SearchFilter from './components/VictorEZCodes/SearchFilter';
8+
import FormValidator from './components/VictorEZCodes/FormValidator';
49

510
function App() {
11+
const [showToast, setShowToast] = useState(false);
12+
const [progress] = useState(0);
13+
614
return (
715
<>
816
<Hero />
917
<Details />
18+
19+
{/* Toast Demo Section */}
20+
<div style={{ padding: '40px 20px', borderTop: '1px solid var(--border-color)', textAlign: 'center' }}>
21+
<h2>Toast Notifications Demo</h2>
22+
<button
23+
onClick={() => setShowToast(true)}
24+
style={{ padding: '10px 20px', margin: '10px' }}
25+
>
26+
Show Toast
27+
</button>
28+
{showToast && (
29+
<Toast
30+
message="Operation successful!"
31+
type="success"
32+
onClose={() => setShowToast(false)}
33+
/>
34+
)}
35+
</div>
36+
37+
{/* Progress Bar Demo Section */}
38+
<div style={{ padding: '40px 20px', borderTop: '1px solid var(--border-color)' }}>
39+
<ProgressBar progress={progress} />
40+
</div>
41+
42+
{/* Theme Switcher Section */}
43+
<div style={{
44+
padding: '40px 20px',
45+
borderTop: '1px solid var(--border-color)',
46+
background: 'var(--background-color)'
47+
}}>
48+
<ThemeSwitcher />
49+
</div>
50+
51+
{/* Search Filter Section */}
52+
<div style={{
53+
padding: '40px 20px',
54+
background: 'var(--background-alt)',
55+
borderTop: '1px solid var(--border-color)'
56+
}}>
57+
<SearchFilter />
58+
</div>
59+
60+
{/* Form Validator Section */}
61+
<div style={{
62+
padding: '40px 20px',
63+
borderTop: '1px solid var(--border-color)',
64+
background: 'var(--background-color)'
65+
}}>
66+
<FormValidator />
67+
</div>
1068
</>
1169
);
1270
}
1371

14-
export default App
72+
export default App;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useState } from "react";
2+
import styles from "./FormValidator.module.css";
3+
import { FaEye, FaEyeSlash } from "react-icons/fa";
4+
import Author from "../../Author";
5+
6+
export default function FormValidator() {
7+
const [formData, setFormData] = useState({
8+
username: "",
9+
email: "",
10+
password: "",
11+
confirmPassword: ""
12+
});
13+
const [showPassword, setShowPassword] = useState(false);
14+
const [errors, setErrors] = useState({});
15+
const [touched, setTouched] = useState({});
16+
17+
const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d).{8,}$/;
18+
19+
const validateField = (name, value) => {
20+
switch (name) {
21+
case "username":
22+
return value.length < 3 ? "Username must be at least 3 characters" : "";
23+
case "email":
24+
return !/\S+@\S+\.\S+/.test(value) ? "Invalid email address" : "";
25+
case "password":
26+
return !PASSWORD_REGEX.test(value)
27+
? "Password must be 8+ characters with at least one letter and one number"
28+
: "";
29+
case "confirmPassword":
30+
return value !== formData.password ? "Passwords do not match" : "";
31+
default:
32+
return "";
33+
}
34+
};
35+
36+
const handleChange = (e) => {
37+
const { name, value } = e.target;
38+
setFormData(prev => ({ ...prev, [name]: value }));
39+
setErrors(prev => ({ ...prev, [name]: validateField(name, value) }));
40+
};
41+
42+
const handleBlur = (e) => {
43+
const { name } = e.target;
44+
setTouched(prev => ({ ...prev, [name]: true }));
45+
};
46+
47+
const handleSubmit = (e) => {
48+
e.preventDefault();
49+
const newErrors = {};
50+
Object.keys(formData).forEach(key => {
51+
newErrors[key] = validateField(key, formData[key]);
52+
});
53+
setErrors(newErrors);
54+
setTouched(Object.keys(formData).reduce((acc, key) => ({ ...acc, [key]: true }), {}));
55+
56+
if (Object.values(newErrors).every(error => !error)) {
57+
alert("Form submitted successfully!");
58+
}
59+
};
60+
61+
return (
62+
<section className={styles.section}>
63+
<h2 className={styles.description}>Form Validator</h2>
64+
<p className={styles.description}>
65+
Real-time form validation with detailed feedback
66+
</p>
67+
68+
<form onSubmit={handleSubmit} className={styles.form}>
69+
<div className={styles.formGroup}>
70+
<label>Username</label>
71+
<input
72+
type="text"
73+
name="username"
74+
value={formData.username}
75+
onChange={handleChange}
76+
onBlur={handleBlur}
77+
className={touched.username && (errors.username ? styles.error : styles.valid)}
78+
/>
79+
{touched.username && errors.username && (
80+
<span className={styles.errorMessage}>{errors.username}</span>
81+
)}
82+
</div>
83+
84+
<div className={styles.formGroup}>
85+
<label>Password</label>
86+
<div className={styles.passwordInput}>
87+
<input
88+
type={showPassword ? "text" : "password"}
89+
name="password"
90+
value={formData.password}
91+
onChange={handleChange}
92+
onBlur={handleBlur}
93+
className={touched.password && (errors.password ? styles.error : styles.valid)}
94+
/>
95+
<button
96+
type="button"
97+
onClick={() => setShowPassword(!showPassword)}
98+
className={styles.togglePassword}
99+
>
100+
{showPassword ? <FaEyeSlash /> : <FaEye />}
101+
</button>
102+
</div>
103+
{touched.password && errors.password && (
104+
<span className={styles.errorMessage}>{errors.password}</span>
105+
)}
106+
</div>
107+
108+
<button type="submit" className={styles.submitButton}>
109+
Submit
110+
</button>
111+
</form>
112+
113+
<Author name="Victor Ezeanyika" githubLink="https://github.com/VictorEZCodes" />
114+
</section>
115+
);
116+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
.section {
2+
max-width: var(--max-width-content);
3+
margin: var(--spacing-xl-5) auto;
4+
padding: var(--spacing-lg);
5+
}
6+
7+
.description {
8+
margin-bottom: var(--spacing-xl);
9+
text-align: center;
10+
color: var(--text-color);
11+
}
12+
13+
.form {
14+
max-width: 500px;
15+
margin: 0 auto;
16+
padding: var(--spacing-xl);
17+
background: var(--background-color);
18+
border: 1px solid var(--border-color);
19+
border-radius: var(--border-radius-lg);
20+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
21+
}
22+
23+
.formGroup {
24+
margin-bottom: var(--spacing-lg);
25+
}
26+
27+
.formGroup label {
28+
display: block;
29+
margin-bottom: var(--spacing-sm);
30+
color: var(--text-color);
31+
font-weight: 500;
32+
}
33+
34+
.formGroup input {
35+
width: 100%;
36+
padding: var(--spacing-md);
37+
border: 2px solid var(--border-color);
38+
border-radius: var(--border-radius-md);
39+
font-size: var(--font-size-base);
40+
transition: border-color var(--transition-base);
41+
}
42+
43+
.formGroup input.error {
44+
border-color: #f44336;
45+
}
46+
47+
.formGroup input.valid {
48+
border-color: #4caf50;
49+
}
50+
51+
.errorMessage {
52+
color: #f44336;
53+
font-size: var(--font-size-sm);
54+
margin-top: var(--spacing-xs);
55+
display: block;
56+
}
57+
58+
.passwordInput {
59+
position: relative;
60+
}
61+
62+
.togglePassword {
63+
position: absolute;
64+
right: var(--spacing-md);
65+
top: 50%;
66+
transform: translateY(-50%);
67+
background: none;
68+
border: none;
69+
color: var(--text-color);
70+
cursor: pointer;
71+
padding: var(--spacing-xs);
72+
}
73+
74+
.submitButton {
75+
width: 100%;
76+
padding: var(--spacing-md);
77+
background: var(--primary-color);
78+
color: white;
79+
border: none;
80+
border-radius: var(--border-radius-md);
81+
font-size: var(--font-size-base);
82+
cursor: pointer;
83+
transition: transform var(--transition-base);
84+
}
85+
86+
.submitButton:hover {
87+
transform: translateY(-2px);
88+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
89+
}
90+
91+
.strengthIndicator {
92+
display: flex;
93+
gap: var(--spacing-xs);
94+
margin-top: var(--spacing-sm);
95+
}
96+
97+
.strengthBar {
98+
height: 4px;
99+
flex: 1;
100+
background: var(--border-color);
101+
border-radius: var(--border-radius-sm);
102+
}
103+
104+
.strengthBar.weak { background: #f44336; }
105+
.strengthBar.medium { background: #ff9800; }
106+
.strengthBar.strong { background: #4caf50; }
107+
108+
@media (max-width: 48rem) {
109+
.form {
110+
padding: var(--spacing-md);
111+
}
112+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useState } from "react";
2+
import styles from "./ProgressBar.module.css";
3+
import { FaChartLine } from "react-icons/fa";
4+
import Author from "../../Author";
5+
6+
export default function ProgressBar() {
7+
const [progress, setProgress] = useState(0);
8+
9+
const handleProgress = (amount) => {
10+
setProgress(prev => {
11+
const newValue = prev + amount;
12+
return Math.min(100, Math.max(0, newValue));
13+
});
14+
};
15+
16+
const getProgressMessage = () => {
17+
if (progress === 100) return "Complete!";
18+
if (progress >= 75) return "Almost there!";
19+
if (progress >= 50) return "Halfway there!";
20+
if (progress > 0) return "Keep going!";
21+
return "Let's start!";
22+
};
23+
24+
return (
25+
<>
26+
<section className={styles.section}>
27+
<h2>Progress Bar Component</h2>
28+
<p>
29+
An interactive progress bar with animations and status messages.
30+
Use the buttons below to control the progress:
31+
</p>
32+
33+
<div className={styles.progressContainer}>
34+
<div className={styles.progressBar}>
35+
<div
36+
className={styles.progressFill}
37+
style={{ width: `${progress}%` }}
38+
>
39+
<span className={styles.progressText}>
40+
{progress}%
41+
</span>
42+
</div>
43+
</div>
44+
45+
<p className={styles.progressMessage}>
46+
<FaChartLine className={styles.progressIcon} />
47+
{getProgressMessage()}
48+
</p>
49+
50+
<div className={styles.buttonContainer}>
51+
<button
52+
className={styles.progressButton}
53+
onClick={() => handleProgress(-10)}
54+
disabled={progress === 0}
55+
>
56+
-10%
57+
</button>
58+
<button
59+
className={styles.progressButton}
60+
onClick={() => handleProgress(10)}
61+
disabled={progress === 100}
62+
>
63+
+10%
64+
</button>
65+
<button
66+
className={styles.progressButton}
67+
onClick={() => setProgress(0)}
68+
disabled={progress === 0}
69+
>
70+
Reset
71+
</button>
72+
</div>
73+
</div>
74+
75+
<Author name="Victor Ezeanyika" githubLink="https://github.com/VictorEZCodes" />
76+
</section>
77+
</>
78+
);
79+
}

0 commit comments

Comments
 (0)