Skip to content

Commit 8988821

Browse files
committed
feat: custom toggle and settings
1 parent efbb468 commit 8988821

File tree

3 files changed

+138
-46
lines changed

3 files changed

+138
-46
lines changed

src/components/CustomToggle.js

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,109 @@
11
import React from 'react';
2+
import { motion } from 'framer-motion';
3+
import { Check, X } from '@phosphor-icons/react';
4+
5+
const CustomToggle = ({ id, checked, onChange, label, disabled, colorTheme = 'rose' }) => {
6+
const themes = {
7+
rose: {
8+
track: 'bg-rose-500/20 border-rose-500/50 shadow-[0_0_15px_rgba(244,63,94,0.2)]',
9+
knob: 'bg-rose-500 border-rose-400 shadow-[0_0_10px_rgba(244,63,94,0.4)]',
10+
},
11+
blue: {
12+
track: 'bg-blue-500/20 border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]',
13+
knob: 'bg-blue-500 border-blue-400 shadow-[0_0_10px_rgba(59,130,246,0.4)]',
14+
},
15+
green: {
16+
track: 'bg-emerald-500/20 border-emerald-500/50 shadow-[0_0_15px_rgba(16,185,129,0.2)]',
17+
knob: 'bg-emerald-500 border-emerald-400 shadow-[0_0_10px_rgba(16,185,129,0.4)]',
18+
},
19+
amber: {
20+
track: 'bg-amber-500/20 border-amber-500/50 shadow-[0_0_15px_rgba(245,158,11,0.2)]',
21+
knob: 'bg-amber-500 border-amber-400 shadow-[0_0_10px_rgba(245,158,11,0.4)]',
22+
},
23+
purple: {
24+
track: 'bg-purple-500/20 border-purple-500/50 shadow-[0_0_15px_rgba(168,85,247,0.2)]',
25+
knob: 'bg-purple-500 border-purple-400 shadow-[0_0_10px_rgba(168,85,247,0.4)]',
26+
},
27+
cyan: {
28+
track: 'bg-cyan-500/20 border-cyan-500/50 shadow-[0_0_15px_rgba(6,182,212,0.2)]',
29+
knob: 'bg-cyan-500 border-cyan-400 shadow-[0_0_10px_rgba(6,182,212,0.4)]',
30+
},
31+
indigo: {
32+
track: 'bg-indigo-500/20 border-indigo-500/50 shadow-[0_0_15px_rgba(99,102,241,0.2)]',
33+
knob: 'bg-indigo-500 border-indigo-400 shadow-[0_0_10px_rgba(99,102,241,0.4)]',
34+
},
35+
};
36+
37+
const activeTheme = themes[colorTheme] || themes.rose;
238

3-
const CustomToggle = ({ id, checked, onChange, label, disabled }) => {
439
return (
5-
<div className="flex items-center justify-between w-full">
40+
<div
41+
className={`flex items-center justify-between w-full group py-2 ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
42+
onClick={() => !disabled && onChange({ target: { checked: !checked } })}
43+
>
644
<label
745
htmlFor={id}
8-
className={`text-lg cursor-pointer ${disabled ? 'text-gray-500' : 'text-white'}`}
46+
className="text-base sm:text-lg text-gray-300 font-medium cursor-pointer select-none group-hover:text-white transition-colors duration-300"
47+
onClick={(e) => e.stopPropagation()} // Prevent double toggle if label is clicked directly (though container click handles it)
948
>
1049
{label}
1150
</label>
12-
<label className="relative inline-flex items-center cursor-pointer">
51+
52+
<div className="relative inline-flex items-center cursor-pointer">
1353
<input
1454
type="checkbox"
1555
id={id}
16-
className="sr-only peer"
56+
className="sr-only"
1757
checked={checked}
1858
onChange={onChange}
1959
disabled={disabled}
2060
/>
61+
62+
{/* Track */}
2163
<div
22-
className={`w-11 h-6 bg-gray-600 rounded-full peer peer-focus:ring-4 peer-focus:ring-primary-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600 ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
23-
></div>
24-
</label>
64+
className={`
65+
w-14 h-8 rounded-full transition-all duration-300 ease-out
66+
border border-transparent shadow-inner relative overflow-hidden
67+
${checked
68+
? activeTheme.track
69+
: 'bg-gray-900/50 border-white/10 hover:border-white/20'
70+
}
71+
`}
72+
>
73+
{/* Background sheen effect */}
74+
<div className={`absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -skew-x-12 translate-x-[-100%] transition-transform duration-1000 ${checked ? 'group-hover:translate-x-[100%]' : ''}`} />
75+
</div>
76+
77+
{/* Knob */}
78+
<motion.div
79+
className={`
80+
absolute top-1 left-1 w-6 h-6 rounded-full shadow-lg
81+
flex items-center justify-center
82+
backdrop-blur-sm border
83+
${checked
84+
? activeTheme.knob
85+
: 'bg-gray-700 border-gray-600'
86+
}
87+
`}
88+
animate={{
89+
x: checked ? 24 : 0,
90+
rotate: checked ? 360 : 0
91+
}}
92+
transition={{
93+
type: "spring",
94+
stiffness: 500,
95+
damping: 30
96+
}}
97+
>
98+
{checked ? (
99+
<Check size={12} weight="bold" className="text-white" />
100+
) : (
101+
<X size={12} weight="bold" className="text-gray-400" />
102+
)}
103+
</motion.div>
104+
</div>
25105
</div>
26106
);
27107
};
28108

29-
export default CustomToggle;
109+
export default CustomToggle;

src/components/Toast.test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ describe('Toast Component', () => {
4545
removeToast: mockRemoveToast,
4646
});
4747

48-
expect(screen.getByText('Internal Link')).toBeInTheDocument();
49-
expect(screen.getByText('Internal Link').closest('a')).toHaveAttribute('href', '/internal');
48+
expect(screen.getByRole('link', { name: 'Internal Link' })).toHaveAttribute('href', '/internal');
5049

5150
expect(screen.getByText('External Link')).toBeInTheDocument();
52-
expect(screen.getByText('External Link').closest('a')).toHaveAttribute('href', 'https://example.com');
51+
expect(screen.getByRole('link', { name: 'External Link' })).toHaveAttribute('href', 'https://example.com');
5352

5453
expect(screen.getByText('Action Button')).toBeInTheDocument();
5554
const actionButton = screen.getByText('Action Button');

src/pages/SettingsPage.js

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ArrowLeftIcon,
66
Trophy,
77
Layout,
8-
Database,
8+
DatabaseIcon,
99
FilmStrip,
1010
Warning,
1111
Trash,
@@ -158,7 +158,7 @@ const SettingsPage = () => {
158158

159159
{/* Client-Side Badge */}
160160
<div className="hidden md:flex items-center gap-2 px-4 py-2 rounded-full bg-gray-800/50 border border-gray-700 text-xs font-mono text-gray-400">
161-
<Database size={14} />
161+
<DatabaseIcon size={14} />
162162
<span>CLIENT-SIDE STORAGE ONLY</span>
163163
</div>
164164
</div>
@@ -175,24 +175,36 @@ const SettingsPage = () => {
175175
label="Enable System Animations"
176176
checked={isAnimationEnabled}
177177
onChange={toggleAnimation}
178+
colorTheme="blue"
178179
/>
179-
</div>
180180

181-
<div className={`pl-4 border-l-2 border-gray-700 space-y-4 transition-all duration-300 ${!isAnimationEnabled ? 'opacity-50 grayscale' : ''}`}>
182-
<CustomToggle
183-
id="show-animations-homepage"
184-
label="Show animations on Homepage"
185-
checked={showAnimationsHomepage}
186-
onChange={toggleShowAnimationsHomepage}
187-
disabled={!isAnimationEnabled}
188-
/>
189-
<CustomToggle
190-
id="show-animations-inner-pages"
191-
label="Show animations on Inner Pages"
192-
checked={showAnimationsInnerPages}
193-
onChange={toggleShowAnimationsInnerPages}
194-
disabled={!isAnimationEnabled}
195-
/>
181+
<motion.div
182+
animate={{
183+
height: isAnimationEnabled ? 'auto' : 0,
184+
opacity: isAnimationEnabled ? 1 : 0,
185+
}}
186+
className="overflow-hidden"
187+
>
188+
<div className="my-4 border-t border-white/5"></div>
189+
<div className="space-y-4 pl-2">
190+
<CustomToggle
191+
id="show-animations-homepage"
192+
label="Show animations on Homepage"
193+
checked={showAnimationsHomepage}
194+
onChange={toggleShowAnimationsHomepage}
195+
disabled={!isAnimationEnabled}
196+
colorTheme="blue"
197+
/>
198+
<CustomToggle
199+
id="show-animations-inner-pages"
200+
label="Show animations on Inner Pages"
201+
checked={showAnimationsInnerPages}
202+
onChange={toggleShowAnimationsInnerPages}
203+
disabled={!isAnimationEnabled}
204+
colorTheme="blue"
205+
/>
206+
</div>
207+
</motion.div>
196208
</div>
197209

198210
{!isAnimationEnabled && (
@@ -212,6 +224,7 @@ const SettingsPage = () => {
212224
label="Show Achievement Popups"
213225
checked={showAchievementToast}
214226
onChange={toggleAchievementToast}
227+
colorTheme="rose"
215228
/>
216229
<p className="mt-2 text-sm text-gray-400 ml-1">
217230
When enabled, you'll receive a toast notification whenever you unlock a new achievement.
@@ -225,21 +238,21 @@ const SettingsPage = () => {
225238
Apply experimental visual filters to the entire application. Combine them at your own risk!
226239
</p>
227240
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4">
228-
<CustomToggle id="fx-invert" label="Invert Colors" checked={isInverted} onChange={toggleInvert} />
229-
<CustomToggle id="fx-retro" label="Retro CRT" checked={isRetro} onChange={toggleRetro} />
230-
<CustomToggle id="fx-party" label="Party Mode" checked={isParty} onChange={toggleParty} />
231-
<CustomToggle id="fx-mirror" label="Mirror World" checked={isMirror} onChange={toggleMirror} />
232-
<CustomToggle id="fx-noir" label="Film Noir" checked={isNoir} onChange={toggleNoir} />
233-
<CustomToggle id="fx-terminal" label="Terminal Green" checked={isTerminal} onChange={toggleTerminal} />
234-
<CustomToggle id="fx-blueprint" label="Blueprint" checked={isBlueprint} onChange={toggleBlueprint} />
235-
<CustomToggle id="fx-sepia" label="Sepia Tone" checked={isSepia} onChange={toggleSepia} />
236-
<CustomToggle id="fx-vaporwave" label="Vaporwave" checked={isVaporwave} onChange={toggleVaporwave} />
237-
<CustomToggle id="fx-cyberpunk" label="Cyberpunk" checked={isCyberpunk} onChange={toggleCyberpunk} />
238-
<CustomToggle id="fx-gameboy" label="Game Boy" checked={isGameboy} onChange={toggleGameboy} />
239-
<CustomToggle id="fx-comic" label="Comic Book" checked={isComic} onChange={toggleComic} />
240-
<CustomToggle id="fx-sketchbook" label="Sketchbook" checked={isSketchbook} onChange={toggleSketchbook} />
241-
<CustomToggle id="fx-hellenic" label="Hellenic Statue" checked={isHellenic} onChange={toggleHellenic} />
242-
<CustomToggle id="fx-glitch" label="System Glitch" checked={isGlitch} onChange={toggleGlitch} />
241+
<CustomToggle id="fx-invert" label="Invert Colors" checked={isInverted} onChange={toggleInvert} colorTheme="purple" />
242+
<CustomToggle id="fx-retro" label="Retro CRT" checked={isRetro} onChange={toggleRetro} colorTheme="amber" />
243+
<CustomToggle id="fx-party" label="Party Mode" checked={isParty} onChange={toggleParty} colorTheme="rose" />
244+
<CustomToggle id="fx-mirror" label="Mirror World" checked={isMirror} onChange={toggleMirror} colorTheme="cyan" />
245+
<CustomToggle id="fx-noir" label="Film Noir" checked={isNoir} onChange={toggleNoir} colorTheme="indigo" />
246+
<CustomToggle id="fx-terminal" label="Terminal Green" checked={isTerminal} onChange={toggleTerminal} colorTheme="green" />
247+
<CustomToggle id="fx-blueprint" label="Blueprint" checked={isBlueprint} onChange={toggleBlueprint} colorTheme="blue" />
248+
<CustomToggle id="fx-sepia" label="Sepia Tone" checked={isSepia} onChange={toggleSepia} colorTheme="amber" />
249+
<CustomToggle id="fx-vaporwave" label="Vaporwave" checked={isVaporwave} onChange={toggleVaporwave} colorTheme="cyan" />
250+
<CustomToggle id="fx-cyberpunk" label="Cyberpunk" checked={isCyberpunk} onChange={toggleCyberpunk} colorTheme="purple" />
251+
<CustomToggle id="fx-gameboy" label="Game Boy" checked={isGameboy} onChange={toggleGameboy} colorTheme="green" />
252+
<CustomToggle id="fx-comic" label="Comic Book" checked={isComic} onChange={toggleComic} colorTheme="amber" />
253+
<CustomToggle id="fx-sketchbook" label="Sketchbook" checked={isSketchbook} onChange={toggleSketchbook} colorTheme="indigo" />
254+
<CustomToggle id="fx-hellenic" label="Hellenic Statue" checked={isHellenic} onChange={toggleHellenic} colorTheme="blue" />
255+
<CustomToggle id="fx-glitch" label="System Glitch" checked={isGlitch} onChange={toggleGlitch} colorTheme="rose" />
243256
</div>
244257
</Section>
245258

@@ -287,7 +300,7 @@ const SettingsPage = () => {
287300
</Section>
288301

289302
{/* Data Zone */}
290-
<Section title="Data Management" icon={<Database />} delay={0.5}>
303+
<Section title="Data Management" icon={<DatabaseIcon />} delay={0.5}>
291304
<div className="bg-red-900/10 border border-red-500/20 rounded-xl p-6">
292305
<div className="flex items-start gap-4">
293306
<div className="p-3 bg-red-500/10 rounded-full text-red-400 shrink-0">

0 commit comments

Comments
 (0)