From cf5041e14f06f882dc92e135eeecafb4d3eea07b Mon Sep 17 00:00:00 2001
From: Priyam Srivastava
Date: Wed, 4 Feb 2026 14:39:09 +0530
Subject: [PATCH 1/9] darkmode/css_animation
---
app/components/DarkModeToggle/index.js | 92 ++++++++++++++++++
.../DarkModeToggle/tests/index.test.js | 70 ++++++++++++++
.../tests/__snapshots__/index.test.js.snap | 27 ++++--
app/containers/App/index.js | 68 ++++++++------
.../tests/__snapshots__/index.test.js.snap | 29 ++++++
app/containers/HomeContainer/index.js | 71 ++++++++++++--
.../tests/__snapshots__/index.test.js.snap | 27 ++++--
app/contexts/themeContext.js | 93 +++++++++++++++++++
app/global-styles.js | 65 ++++++++++++-
app/themes/colors.js | 30 +++++-
app/themes/tests/colors.test.js | 30 +++++-
app/utils/testUtils.js | 5 +-
yarn.lock | 84 ++++++++---------
13 files changed, 579 insertions(+), 112 deletions(-)
create mode 100644 app/components/DarkModeToggle/index.js
create mode 100644 app/components/DarkModeToggle/tests/index.test.js
create mode 100644 app/contexts/themeContext.js
diff --git a/app/components/DarkModeToggle/index.js b/app/components/DarkModeToggle/index.js
new file mode 100644
index 00000000..927c3d22
--- /dev/null
+++ b/app/components/DarkModeToggle/index.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import styled from '@emotion/styled';
+import { IconButton, Tooltip } from '@mui/material';
+import { Brightness4, Brightness7 } from '@mui/icons-material';
+import { useTheme } from '@app/contexts/themeContext';
+
+const ToggleButton = styled(IconButton)`
+ && {
+ position: fixed;
+ bottom: 2rem;
+ right: 2rem;
+ width: 56px;
+ height: 56px;
+ background: ${(props) => props.bgcolor};
+ color: ${(props) => props.color};
+ box-shadow: 0 4px 20px ${(props) => props.shadowcolor};
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 1000;
+
+ &:hover {
+ background: ${(props) => props.hoverbg};
+ transform: translateY(-4px) rotate(15deg);
+ box-shadow: 0 8px 30px ${(props) => props.shadowcolor};
+ }
+
+ &:active {
+ transform: translateY(-2px) rotate(0deg);
+ }
+
+ @media (max-width: 768px) {
+ width: 48px;
+ height: 48px;
+ bottom: 1.5rem;
+ right: 1.5rem;
+ }
+ }
+`;
+
+const IconWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: ${(props) => (props.animate ? 'rotate 0.5s ease-in-out' : 'none')};
+
+ @keyframes rotate {
+ from {
+ transform: rotate(0deg) scale(0.8);
+ opacity: 0.5;
+ }
+ to {
+ transform: rotate(360deg) scale(1);
+ opacity: 1;
+ }
+ }
+`;
+
+// eslint-disable-next-line complexity
+export const DarkModeToggle = () => {
+ const { isDarkMode, toggleTheme, colors } = useTheme();
+ const [isAnimating, setIsAnimating] = React.useState(false);
+
+ const handleToggle = () => {
+ setIsAnimating(true);
+ toggleTheme();
+ setTimeout(() => setIsAnimating(false), 500);
+ };
+
+ const tooltipTitle = isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode';
+ const bgColor = isDarkMode ? colors.surface : colors.primary;
+ const textColor = isDarkMode ? colors.accent : colors.text;
+ const hoverBgColor = isDarkMode ? colors.hover : colors.secondary;
+ const Icon = isDarkMode ? Brightness7 : Brightness4;
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default DarkModeToggle;
diff --git a/app/components/DarkModeToggle/tests/index.test.js b/app/components/DarkModeToggle/tests/index.test.js
new file mode 100644
index 00000000..17db72ca
--- /dev/null
+++ b/app/components/DarkModeToggle/tests/index.test.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { DarkModeToggle } from '../index';
+import { ThemeProvider } from '@app/contexts/themeContext';
+
+describe('DarkModeToggle', () => {
+ const renderWithTheme = (initialMode = 'light') => {
+ if (initialMode === 'dark') {
+ localStorage.setItem('theme', 'dark');
+ } else {
+ localStorage.setItem('theme', 'light');
+ }
+
+ return render(
+
+
+
+ );
+ };
+
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it('should render the toggle button', () => {
+ renderWithTheme();
+ const button = screen.getByRole('button', { name: /toggle dark mode/i });
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should show sun icon in dark mode', () => {
+ renderWithTheme('dark');
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should show moon icon in light mode', () => {
+ renderWithTheme('light');
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should toggle theme when clicked', () => {
+ renderWithTheme();
+ const button = screen.getByRole('button', { name: /toggle dark mode/i });
+
+ // Click to switch to dark mode
+ fireEvent.click(button);
+ expect(localStorage.getItem('theme')).toBe('dark');
+
+ // Click to switch back to light mode
+ fireEvent.click(button);
+ expect(localStorage.getItem('theme')).toBe('light');
+ });
+
+ it('should have fixed positioning', () => {
+ renderWithTheme();
+ const button = screen.getByRole('button');
+ const styles = window.getComputedStyle(button);
+ expect(styles.position).toBe('fixed');
+ });
+
+ it('should show tooltip on hover', () => {
+ renderWithTheme();
+ const button = screen.getByRole('button');
+ fireEvent.mouseOver(button);
+ // Material-UI tooltips appear after a delay
+ });
+});
diff --git a/app/components/ProtectedRoute/tests/__snapshots__/index.test.js.snap b/app/components/ProtectedRoute/tests/__snapshots__/index.test.js.snap
index 8f25242d..77f1be60 100644
--- a/app/components/ProtectedRoute/tests/__snapshots__/index.test.js.snap
+++ b/app/components/ProtectedRoute/tests/__snapshots__/index.test.js.snap
@@ -10,18 +10,23 @@ exports[` should render and match the snapshot 1`] = `
class="css-1cnh6jp e666d1v2"
>
Go to Storybook
should render and match the snapshot 1`] = `
Get details of repositories
should render and match the snapshot 1`] = `
>
should render and match the snapshot 1`] = `
tests should render and match the snapshot 1`] = `
Get details of repositories
tests should render and match the snapshot 1`] = `
>
tests should render and match the snapshot 1`] = `