From 51f020e944ca360594b73b5f8491a46e68870af7 Mon Sep 17 00:00:00 2001 From: e_bloss Date: Thu, 25 Sep 2025 12:45:09 -0400 Subject: [PATCH 1/2] Alteratinos to include new file for color setting dropdown menu in navbar. Some minor layout changes and default color theme changes also. --- package-lock.json | 31 ++++++ package.json | 1 + src/App.jsx | 3 + src/AppProvider.jsx | 8 +- src/components/Navbar.jsx | 28 ++++-- src/components/PuzzleFormContainer.jsx | 23 +++-- src/components/SettingsDropdown.jsx | 54 ++++++++++ src/scripts/language-handler.js | 10 +- src/styles/main.css | 130 ++++++++++++++++++------- 9 files changed, 225 insertions(+), 63 deletions(-) create mode 100644 src/components/SettingsDropdown.jsx diff --git a/package-lock.json b/package-lock.json index c99bebc..29bd72a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "crucigrama", "version": "0.0.0", "dependencies": { + "bootstrap": "^5.3.8", "react": "^18.3.1", "react-dom": "^18.3.1", "react-to-print": "^3.0.4" @@ -1006,6 +1007,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.32.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz", @@ -1611,6 +1623,25 @@ "dev": true, "license": "MIT" }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 5d97b29..c61314e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "bootstrap": "^5.3.8", "react": "^18.3.1", "react-dom": "^18.3.1", "react-to-print": "^3.0.4" diff --git a/src/App.jsx b/src/App.jsx index 8091aa8..cff9ab5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,7 @@ import { useContext, useEffect, useState } from 'react' import './styles/main.css' +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; import Navbar from './components/Navbar.jsx' import PuzzleFormContainer from './components/PuzzleFormContainer.jsx' @@ -8,6 +10,7 @@ import Footer from './components/Footer.jsx' import CrosswordContainer from './components/CrosswordContainer.jsx' import ColorConfiguration from './components/ColorConfiguration.jsx' import { AppContext } from './AppProvider.jsx' +import SettingsDropdown from './components/SettingsDropdown.jsx' // Main App component that renders the entire application. diff --git a/src/AppProvider.jsx b/src/AppProvider.jsx index f88b5f6..f3e6393 100644 --- a/src/AppProvider.jsx +++ b/src/AppProvider.jsx @@ -38,10 +38,10 @@ export const AppProvider = ({ children }) => { // {Object} original_colors - Ref object containing original color variables. const original_colors = useRef({ - '--c': '#3C096C', - '--d': '#5A189A', - '--e': '#7B2CBF', - '--f': '#9D4EDD', + '--c': '#4c65877f', + '--d': '#6b826e9e', + '--e': 'rgba(255, 255, 255, 0.728)', + '--f': '#6b826e7d', }); // {Object} colors - Object containing color variables. diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 37963ca..b7ac7b5 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect } from 'react' import { updateContent, translation } from '../scripts/language-handler' import { AppContext } from '../AppProvider' +import SettingsDropdown from './SettingsDropdown' // Navbar component that provides language selection functionality and displays the brand logo. @@ -13,22 +14,29 @@ const Navbar = () => { updateContent(translation[lang]); }, [lang]) + // ADD settings and customization options here *** return (
diff --git a/src/components/PuzzleFormContainer.jsx b/src/components/PuzzleFormContainer.jsx index ea00306..e599919 100644 --- a/src/components/PuzzleFormContainer.jsx +++ b/src/components/PuzzleFormContainer.jsx @@ -35,8 +35,16 @@ const PuzzleFormContainer = ({ restartCrossword }) => { setShowAnswers(true)} /> + {/* Buttons to control the timer */} + {!timerRef && } + {timerRef && } +
- Time Left: + Time Remaining: {formatTime(timeLeft)}
@@ -46,18 +54,9 @@ const PuzzleFormContainer = ({ restartCrossword }) => { {/* Your crossword will go here */} - {/* Buttons to control the timer */} - {!timerRef && } - {timerRef && } + - +
diff --git a/src/components/SettingsDropdown.jsx b/src/components/SettingsDropdown.jsx new file mode 100644 index 0000000..e031983 --- /dev/null +++ b/src/components/SettingsDropdown.jsx @@ -0,0 +1,54 @@ +import React, { useContext, useState, useRef } from 'react'; +import { AppContext } from '../AppProvider'; + +// A dropdown menu to change color settings for the crossword app +const SettingsDropdown = () => { + const [open, setOpen] = useState(false); + const { setColors, colors, original_colors } = useContext(AppContext); + + const toggleDropdown = () => setOpen(!open); + + //handle the dropdown selection + const handleColorChange = (e) => { + const { name, value } = e.target; + setColors(prev => ({ + ...prev, + [name]: value + })); + }; + + const resetColors = () => { + setColors(original_colors.current); + }; + + return ( +
+ + + {open && ( +
+ + + + + + + + + + +
+ )} +
+ ); +}; + +export default SettingsDropdown \ No newline at end of file diff --git a/src/scripts/language-handler.js b/src/scripts/language-handler.js index d7410ac..bc2e637 100644 --- a/src/scripts/language-handler.js +++ b/src/scripts/language-handler.js @@ -39,17 +39,18 @@ export const translation = { en: { personalized_puzzle_title: 'Personalized Crossword Puzzle', personalized_puzzle_message: 'Next, you can generate your own crossword puzzle, with the words you choose.
First, enter which word will be displayed vertically:', - configuration_button_text: '🔩 Settings', + configuration_button_text: '⚙️ Settings', print_button: '🖨️ Print', view_answers_button: '🔎 View Answers', restart_button: '♻️ Restart', references_title: 'References', start_button: '🚀 Start!', + start_timer_button: '⏱️ Start Timer', generate_button: 'Ready! Generate crossword', json_mode_button: 'I prefer to generate it by inserting a JSON', example_message: 'In the example crossword puzzle, the vertical word is "FREUD".', load_json_button: '🚀 Load', - config_modal_title: '🔩 Settings', + config_modal_title: '⚙️ Settings', config_vertical_setting: 'Vertical word:', config_border_setting: 'Cell border:', config_empty_cell_setting: 'Empty cells:', @@ -70,17 +71,18 @@ export const translation = { es: { personalized_puzzle_title: 'Crucigrama personalizado', personalized_puzzle_message: 'A continuación, podés generar tu propio crucigrama, con las palabras que vos elijas.
Primero, ingresá cuál va a ser la palabra a mostrar de manera vertical:', - configuration_button_text: '🔩 Configuración', + configuration_button_text: '⚙️ Configuración', print_button: '🖨️ Imprimir', view_answers_button: '🔎 Ver Respuestas', restart_button: '♻️ Reiniciar', references_title: 'Referencias', start_button: '🚀 ¡Vamos!', + start_timer_button: '⏱️ Iniciar Temporizador', generate_button: '¡Listo! Generar crucigrama', json_mode_button: 'Prefiero generarlo insertando un JSON', example_message: 'En el crucigrama de ejemplo, la palabra vertical es "FREUD".', load_json_button: '🚀 Cargar', - config_modal_title: '🔩 Configuración', + config_modal_title: '⚙️ Configuración', config_vertical_setting: 'Palabra vertical:', config_border_setting: 'Borde de las celdas:', config_empty_cell_setting: 'Celdas vacías:', diff --git a/src/styles/main.css b/src/styles/main.css index dc37f86..50bf060 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -7,16 +7,16 @@ margin: 0; padding: 0; - --wrong: #9f3647; - --correct: rgb(60, 179, 113); - --a: #10002B; - --b: #240046; - --c: #3C096C; - --d: #5A189A; - --e: #7B2CBF; - --f: #9D4EDD; - --g: #C77DFF; - --h: #E0AAFF; + --wrong: #9f364881; + --correct: rgba(60, 179, 114, 0.427); + --a: #133356; + --b: #dbc6ac; + --c: #c0c4b3; + --d: #6b826e; + --e: #638a75; + --f: #4c6587ed; + --g: #a9acb1; + --h: #84adbc79; } body { @@ -33,14 +33,6 @@ body { padding: 0; } -.mb-3 { - margin-bottom: 3px; -} - -.col-md-6 > * { - margin: 6px 6px; -} - /* ==================================== MAINNAV ==================================== */ #mainNav { @@ -56,7 +48,7 @@ body { } .navbar-brand { - color: white; + color: var(--c); font-size: xx-large; } @@ -80,12 +72,14 @@ body { } .wrong-answer { - border: 1px solid var(--wrong); + border-color: var(--wrong); + border-width: 3px; } .correct-answer { font-weight: bold; - border: none; + border-color: var(--correct); + border-width: 3px; } #cpuzzle { @@ -100,15 +94,16 @@ body { } .puzzle-cell { - background-color: white; + background-color: rgb(255, 255, 255); border: 0.5px solid black; - width: 39px; + width: 30px; text-align: center; text-transform: uppercase; color: black; font-weight: 400; font-size: medium; - padding: 5px; + padding: 6px; + border-radius: 8px; } #cpuzzle-generator-container input.text { @@ -118,7 +113,7 @@ body { .actual .puzzle-cell { background-color: var(--e); border: 0.5px solid var(--d); - color: white; + color: rgb(34, 28, 67); } .actual .puzzle-cell:hover, .actual .correct-answer { @@ -137,15 +132,21 @@ body { color: var(--h); width: 300px; height: 300px; + border-radius: 8px; } /* ==================================== BUTTONS ===================================== */ .btn.btn-primary { - color: var(--h); + color: var(--c); background-color: var(--a); border: 1px solid var(--c); margin: 1px 3px; font-size: medium; + border-radius: 8px; + width: 150px; + height: 30px; + justify-content: center; + cursor: pointer; } .btn.btn-primary:hover { @@ -188,21 +189,84 @@ footer a { /* ==================================== NAVBAR LANGUAGE CONTAINER =================================== */ -.langauge-selection { - margin-right: 20px; +/* ================= NAVBAR BUTTON SHARED STYLES ================= */ + +.language-selection { + margin-right: 10px; } -.language-btn { - background-color: white; +.language-btn, +.personalize-btn { + background-color: var(--c); color: black; - padding: 10px 10px; + padding: 10px; + margin-left: 5px; text-align: center; text-decoration: none; display: inline-block; + border-radius: 8px; + cursor: pointer; + border: none; + font-size: 14px; + font-weight: bold; } +.language-btn:hover, +.personalize-btn:hover { + background-color: var(--d); + color: white; +} + +/* ================= ACTIVE BUTTON STYLE ================= */ + .active-btn { background-color: var(--d); color: white; - font-weight: bold; -} \ No newline at end of file +} + +/* ================= DROPDOWN MENU STYLING ================= */ + +.dropdown-menu { + position: absolute; + top: 110%; + right: 0; + background-color: #eeeef0; + border: 1px solid #ccc; + min-width: 180px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; + display: flex; + flex-direction: column; + border-radius: 8px; + + opacity: 0; + transform: translateY(-10px); + pointer-events: none; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +/* Dropdown visible state (toggle via React conditional) */ +.dropdown-menu.show { + opacity: 1; + transform: translateY(0); + pointer-events: auto; +} + +/* ================= DROPDOWN ITEM STYLING ================= */ + +.dropdown-item { + background-color: var(--c); + color: black; + padding: 10px; + text-align: left; + border: none; + font-size: 14px; + cursor: pointer; + border-radius: 0; + transition: background 0.2s ease; +} + +.dropdown-item:hover { + background-color: var(--d); + color: white; +} From c69318c5939db9f2f409ad928d3170ba81d6b80d Mon Sep 17 00:00:00 2001 From: e_bloss Date: Fri, 26 Sep 2025 16:35:43 -0400 Subject: [PATCH 2/2] Created the language dropdown and updated the translation to include start/restart timer. --- package-lock.json | 86 ++++++++++++++++++++++++++ package.json | 2 + src/components/LanguageDropdown.jsx | 52 ++++++++++++++++ src/components/Navbar.jsx | 21 +------ src/components/PuzzleFormContainer.jsx | 12 ++-- src/scripts/language-handler.js | 2 + src/styles/main.css | 44 +------------ 7 files changed, 154 insertions(+), 65 deletions(-) create mode 100644 src/components/LanguageDropdown.jsx diff --git a/package-lock.json b/package-lock.json index 29bd72a..7288ee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0", "dependencies": { "bootstrap": "^5.3.8", + "i18next": "^25.5.2", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-i18next": "^16.0.0", "react-to-print": "^3.0.4" }, "devDependencies": { @@ -264,6 +266,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -2796,6 +2807,46 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz", + "integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3780,6 +3831,32 @@ "react": "^18.3.1" } }, + "node_modules/react-i18next": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.0.tgz", + "integrity": "sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.5.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4509,6 +4586,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c61314e..ba398b7 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ }, "dependencies": { "bootstrap": "^5.3.8", + "i18next": "^25.5.2", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-i18next": "^16.0.0", "react-to-print": "^3.0.4" }, "devDependencies": { diff --git a/src/components/LanguageDropdown.jsx b/src/components/LanguageDropdown.jsx new file mode 100644 index 0000000..96ba149 --- /dev/null +++ b/src/components/LanguageDropdown.jsx @@ -0,0 +1,52 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { AppContext } from '../AppProvider'; +import { updateContent, translation } from '../scripts/language-handler'; + +// A dropdown menu to change color settings for the crossword app +const LanguageDropdown = () => { + const [open, setOpen] = useState(false); + const { lang, setLang } = useContext(AppContext); + + const toggleDropdown = () => setOpen(!open); + + //handle the dropdown selection + const handleLanguageChange = (e) => { + setLang(e); + setOpen(false); + }; + + // It updates the local storage and the content based on the selected language. + useEffect(() => { + localStorage.setItem('language', lang); + updateContent(translation[lang]); + }, [lang]) + + return ( +
+ + + {open && ( +
+ + + + +
+ )} +
+ ); +}; + +export default LanguageDropdown \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index b7ac7b5..8e67891 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,18 +1,10 @@ -import React, { useContext, useEffect } from 'react' -import { updateContent, translation } from '../scripts/language-handler' -import { AppContext } from '../AppProvider' + import SettingsDropdown from './SettingsDropdown' +import LanguageDropdown from './LanguageDropdown' // Navbar component that provides language selection functionality and displays the brand logo. const Navbar = () => { - const { lang, setLang } = useContext(AppContext) - - // It updates the local storage and the content based on the selected language. - useEffect(() => { - localStorage.setItem('language', lang); - updateContent(translation[lang]); - }, [lang]) // ADD settings and customization options here *** return ( @@ -26,14 +18,7 @@ const Navbar = () => {
{/* Left: Language Buttons */} -
- - -
+ {/* Right: Settings Dropdown */} diff --git a/src/components/PuzzleFormContainer.jsx b/src/components/PuzzleFormContainer.jsx index e599919..9b33c61 100644 --- a/src/components/PuzzleFormContainer.jsx +++ b/src/components/PuzzleFormContainer.jsx @@ -31,15 +31,17 @@ const PuzzleFormContainer = ({ restartCrossword }) => { return ( <>
- restartCrossword()} /> - setShowAnswers(true)} /> - + restartCrossword()} /> + + setShowAnswers(true)} /> + + {/* Buttons to control the timer */} - {!timerRef && } - {timerRef && } diff --git a/src/scripts/language-handler.js b/src/scripts/language-handler.js index bc2e637..792ec23 100644 --- a/src/scripts/language-handler.js +++ b/src/scripts/language-handler.js @@ -46,6 +46,7 @@ export const translation = { references_title: 'References', start_button: '🚀 Start!', start_timer_button: '⏱️ Start Timer', + restart_timer_button: '🔁 Restart Timer', generate_button: 'Ready! Generate crossword', json_mode_button: 'I prefer to generate it by inserting a JSON', example_message: 'In the example crossword puzzle, the vertical word is "FREUD".', @@ -78,6 +79,7 @@ export const translation = { references_title: 'Referencias', start_button: '🚀 ¡Vamos!', start_timer_button: '⏱️ Iniciar Temporizador', + restart_timer_button: '🔁 Reiniciar Temporizador', generate_button: '¡Listo! Generar crucigrama', json_mode_button: 'Prefiero generarlo insertando un JSON', example_message: 'En el crucigrama de ejemplo, la palabra vertical es "FREUD".', diff --git a/src/styles/main.css b/src/styles/main.css index 50bf060..c30be44 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -224,49 +224,9 @@ footer a { color: white; } -/* ================= DROPDOWN MENU STYLING ================= */ - -.dropdown-menu { - position: absolute; - top: 110%; - right: 0; - background-color: #eeeef0; - border: 1px solid #ccc; - min-width: 180px; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); - z-index: 1000; - display: flex; - flex-direction: column; - border-radius: 8px; - - opacity: 0; - transform: translateY(-10px); - pointer-events: none; - transition: opacity 0.3s ease, transform 0.3s ease; -} - -/* Dropdown visible state (toggle via React conditional) */ -.dropdown-menu.show { - opacity: 1; - transform: translateY(0); - pointer-events: auto; -} - -/* ================= DROPDOWN ITEM STYLING ================= */ +/* ================== DROPDOWN ===========================*/ .dropdown-item { - background-color: var(--c); - color: black; - padding: 10px; - text-align: left; - border: none; - font-size: 14px; - cursor: pointer; - border-radius: 0; - transition: background 0.2s ease; + padding: 12px 16px; } -.dropdown-item:hover { - background-color: var(--d); - color: white; -}