Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/components/Header/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { Menu, Button } from 'semantic-ui-react';
import { useTheme } from '../ThemeProvider';

const Header = () => {
const [promptEvent, setPromptEvent] = useState(null);
Expand Down Expand Up @@ -28,8 +29,10 @@ const Header = () => {
});
};

const { theme, toggleTheme } = useTheme();

return (
<Menu stackable inverted>
<Menu stackable inverted={theme === 'dark'}>
<Menu.Item header>
<h1>QuizApp</h1>
</Menu.Item>
Expand All @@ -44,6 +47,17 @@ const Header = () => {
/>
</Menu.Item>
)}

<Menu.Item position="right">
<Button
toggle
basic={theme !== 'dark'}
color={theme === 'dark' ? 'grey' : 'yellow'}
icon={theme === 'dark' ? 'sun' : 'moon'}
onClick={toggleTheme}
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
/>
</Menu.Item>
</Menu>
);
};
Expand Down
9 changes: 6 additions & 3 deletions src/components/Quiz/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import he from 'he';

import Countdown from '../Countdown';
import { useTheme } from '../ThemeProvider';
import { getLetter } from '../../utils';

const Quiz = ({ data, countdownTime, endQuiz }) => {
Expand Down Expand Up @@ -69,6 +70,8 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
});
};

const { theme } = useTheme();

return (
<Item.Header>
<Container>
Expand All @@ -77,7 +80,7 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
<Item>
<Item.Content>
<Item.Extra>
<Header as="h1" block floated="left">
<Header as="h1" block floated="left" inverted={theme === 'dark'}>
<Icon name="info circle" />
<Header.Content>
{`Question No.${questionIndex + 1} of ${data.length}`}
Expand All @@ -91,15 +94,15 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
</Item.Extra>
<br />
<Item.Meta>
<Message size="huge" floating>
<Message size="huge" floating inverted={theme === 'dark'}>
<b>{`Q. ${he.decode(data[questionIndex].question)}`}</b>
</Message>
<br />
<Item.Description>
<h3>Please choose one of the following answers:</h3>
</Item.Description>
<Divider />
<Menu vertical fluid size="massive">
<Menu vertical fluid size="massive" inverted={theme === 'dark'}>
{data[questionIndex].options.map((option, i) => {
const letter = getLetter(i);
const decodedOption = he.decode(option);
Expand Down
10 changes: 9 additions & 1 deletion src/components/Result/QNA.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Table } from 'semantic-ui-react';
import { useTheme } from '../ThemeProvider';

const QNA = ({ questionsAndAnswers }) => {
const { theme } = useTheme();
return (
<Table celled striped selectable size="large">
<Table
celled
striped
selectable
size="large"
inverted={theme === 'dark'}
>
<Table.Header>
<Table.Row>
<Table.HeaderCell>No.</Table.HeaderCell>
Expand Down
23 changes: 13 additions & 10 deletions src/components/Result/Stats.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Segment, Header, Button } from 'semantic-ui-react';
import { useTheme } from '../ThemeProvider';

import ShareButton from '../ShareButton';
import { calculateScore, calculateGrade, timeConverter } from '../../utils';
Expand All @@ -12,37 +13,39 @@ const Stats = ({
replayQuiz,
resetQuiz,
}) => {
const { theme } = useTheme();
const score = calculateScore(totalQuestions, correctAnswers);
const { grade, remarks } = calculateGrade(score);
const { hours, minutes, seconds } = timeConverter(timeTaken);

return (
<Segment>
<Header as="h1" textAlign="center" block>
<Segment inverted={theme === 'dark'}>
<Header as="h1" textAlign="center" block inverted={theme === 'dark'}>
{remarks}
</Header>
<Header as="h2" textAlign="center" block>
<Header as="h2" textAlign="center" block inverted={theme === 'dark'}>
Grade: {grade}
</Header>
<Header as="h3" textAlign="center" block>
<Header as="h3" textAlign="center" block inverted={theme === 'dark'}>
Total Questions: {totalQuestions}
</Header>
<Header as="h3" textAlign="center" block>
<Header as="h3" textAlign="center" block inverted={theme === 'dark'}>
Correct Answers: {correctAnswers}
</Header>
<Header as="h3" textAlign="center" block>
<Header as="h3" textAlign="center" block inverted={theme === 'dark'}>
Your Score: {score}%
</Header>
<Header as="h3" textAlign="center" block>
<Header as="h3" textAlign="center" block inverted={theme === 'dark'}>
Passing Score: 60%
</Header>
<Header as="h3" textAlign="center" block>
<Header as="h3" textAlign="center" block inverted={theme === 'dark'}>
Time Taken:{' '}
{`${Number(hours)}h ${Number(minutes)}m ${Number(seconds)}s`}
</Header>
<div style={{ marginTop: 35 }}>
<Button
primary
primary={theme !== 'dark'}
basic={theme === 'dark'}
content="Play Again"
onClick={replayQuiz}
size="big"
Expand All @@ -51,7 +54,7 @@ const Stats = ({
style={{ marginRight: 15, marginBottom: 8 }}
/>
<Button
color="teal"
color={theme === 'dark' ? 'grey' : 'teal'}
content="Back to Home"
onClick={resetQuiz}
size="big"
Expand Down
4 changes: 3 additions & 1 deletion src/components/Result/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Container, Menu } from 'semantic-ui-react';
import { useTheme } from '../ThemeProvider';

import Stats from './Stats';
import QNA from './QNA';
Expand All @@ -14,14 +15,15 @@ const Result = ({
resetQuiz,
}) => {
const [activeTab, setActiveTab] = useState('Stats');
const { theme } = useTheme();

const handleTabClick = (e, { name }) => {
setActiveTab(name);
};

return (
<Container>
<Menu fluid widths={2}>
<Menu fluid widths={2} inverted={theme === 'dark'}>
<Menu.Item
name="Stats"
active={activeTab === 'Stats'}
Expand Down
39 changes: 39 additions & 0 deletions src/components/ThemeProvider/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { createContext, useContext, useEffect, useState } from 'react';

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');

useEffect(() => {
// initialize theme from localStorage or OS preference
const stored = localStorage.getItem('quizapp-theme');
if (stored) {
setTheme(stored);
document.documentElement.setAttribute('data-theme', stored);
return;
}

const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const initial = prefersDark ? 'dark' : 'light';
setTheme(initial);
document.documentElement.setAttribute('data-theme', initial);
}, []);

useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('quizapp-theme', theme);
}, [theme]);

const toggleTheme = () => setTheme(prev => (prev === 'dark' ? 'light' : 'dark'));

return (
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};

export const useTheme = () => useContext(ThemeContext);

export default ThemeContext;
166 changes: 166 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1 +1,167 @@
@import url(../node_modules/semantic-ui-css/semantic.min.css);

/* Theme variables */
:root {
--bg-color: #ffffff;
--surface-color: #ffffff;
--text-color: #1f2937; /* gray-800 */
--muted-color: #6b7280; /* gray-500 */
--primary-color: #21ab9d; /* teal */
--card-bg: #ffffff;
--button-bg: var(--primary-color);
--button-text: #ffffff;
--option-active-bg: #21ab9d;
--option-active-text: #ffffff;
--option-hover-bg: rgba(33,171,157,0.1);
--option-hover-text: #0f172a;
}

/* Dark theme variables */
[data-theme='dark'] {
--bg-color: #0f172a; /* slate-900 */
--surface-color: #0b1220; /* slightly lighter */
--text-color: #e6edf3; /* near white */
--muted-color: #94a3b8; /* slate-400 */
--primary-color: #21ab9d; /* keep teal */
--card-bg: #071022;
--button-bg: #1f2937;
--button-text: #e6edf3;
--option-active-bg: #163e3a; /* darker teal */
--option-active-text: #e6edf3;
--option-hover-bg: rgba(255,255,255,0.04);
--option-hover-text: #e6edf3;
}

/* global */
html, body, #root {
height: 100%;
}
body {
background-color: var(--bg-color) !important;
color: var(--text-color) !important;
transition: background-color 200ms ease, color 200ms ease;
}

main {
padding: 1.25rem;
}

/* General semantic UI overrides to match theme variables */
.ui.menu,
.ui.menu .item,
.ui.menu .header,
.ui.menu .item .button {
background: transparent !important;
color: var(--text-color) !important;
}

/* Menu item hover & active states for both light and dark */
.ui.menu .item:hover {
background: var(--option-hover-bg) !important;
color: var(--option-hover-text) !important;
}

.ui.menu .item.active {
background: var(--option-active-bg) !important;
color: var(--option-active-text) !important;
}

/* Support Menu.Inverted in semantic UI for dark theme */
.ui.inverted.menu .item.active {
background: var(--option-active-bg) !important;
color: var(--option-active-text) !important;
}

.ui.inverted.menu {
background: var(--surface-color) !important;
color: var(--text-color) !important;
}

.ui.segment,
.ui.container,
.ui.segment .content,
.ui.card,
.ui.grid > .row > .column {
background: var(--card-bg) !important;
color: var(--text-color) !important;
}

/* Table styles */
.ui.table thead th {
background: var(--surface-color) !important;
color: var(--text-color) !important;
}
.ui.table tbody td {
background: var(--card-bg) !important;
color: var(--text-color) !important;
}

/* support striped tables - alternate rows in dark mode */
.ui.table.striped tbody tr:nth-child(odd) td {
background: var(--surface-color) !important;
}
.ui.table.striped tbody tr:nth-child(even) td {
background: var(--card-bg) !important;
}

.ui.header h1,
.ui.header h2,
.ui.header h3,
h1, h2, h3, p {
color: var(--text-color) !important;
}

/* header block override - adjust background and text color for both themes */
.ui.header.block,
.ui.header.block .content {
background: var(--card-bg) !important;
color: var(--text-color) !important;
padding: 0.85rem 1rem !important;
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(0,0,0,0.04) inset;
}

/* ensure heading elements inside block headers inherit proper color */
.ui.header.block h1,
.ui.header.block h2,
.ui.header.block h3 {
color: var(--text-color) !important;
}

/* floating message should respect theme */
.ui.message.floating {
background: var(--card-bg) !important;
color: var(--text-color) !important;
border: 1px solid rgba(255,255,255,0.03) !important;
}

.ui.button,
.ui.buttons .ui.button {
background-color: var(--button-bg) !important;
color: var(--button-text) !important;
border: 1px solid rgba(0,0,0,0.08) !important;
}

.ui.button.basic {
background: transparent !important;
color: var(--text-color) !important;
border-color: rgba(255,255,255,0.06) !important;
}

/* Muted text helper */
.muted {
color: var(--muted-color) !important;
}

/* add nice transition to colors */
* {
transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}

/* small responsive layout tweaks if needed */
@media (max-width: 600px) {
main {
padding: .5rem;
}
}

Loading