diff options
author | Kai Stevenson <kai@kaistevenson.com> | 2025-06-13 23:24:49 -0700 |
---|---|---|
committer | Kai Stevenson <kai@kaistevenson.com> | 2025-06-13 23:24:49 -0700 |
commit | 6475f534e7bf457113dcc82d94bfb7ac413f71c4 (patch) | |
tree | ce029883066b1cc13ab5b4fc5f71b8547e0f3cce /frontend/src | |
parent | 87f134c9f2714593890a530221df429122c4398b (diff) |
grouping
Diffstat (limited to 'frontend/src')
-rw-r--r-- | frontend/src/App.css | 72 | ||||
-rw-r--r-- | frontend/src/App.tsx | 46 | ||||
-rw-r--r-- | frontend/src/Grid.tsx | 50 |
3 files changed, 142 insertions, 26 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css index 4f77d97..6566fca 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,11 @@ +:root { + --bg-main: #1f2526; + --bg-secondary: #364446; + --primary-element: #56d0b8; + --secondary-element: #5dd6e3; + --highlight-element: #ffec69; +} + .App { text-align: center; } @@ -28,7 +36,7 @@ } .App-bg { - background-color: #282c34; + background-color: var(--bg-main); display: flex; flex-direction: column; align-items: center; @@ -42,32 +50,61 @@ align-items: center; justify-content: center; font-size: calc(10px + 2vmin); - color: white; } -.Grid { - background: #5262816f; +.Grid-container { + background: var(--bg-main); border-radius: 20px; width: 80vmin; height: 80vmin; display: grid; + gap: 0.8vmin; /* exactly the same as the grid gap */ +} + +.Grid { + display: grid; + width: 100%; + height: auto; grid-template-columns: repeat(4, 1fr); - grid-template-rows: repeat(4, 1fr); - gap: 1%; + gap: 0.8vmin; } .Fit-text { + overflow: hidden; + text-overflow: clip; +} +.Grid-square .Fit-text { font-size: calc(10px + 1vmin); overflow: hidden; text-overflow: clip; } +.Completed-group .Fit-text { + font-size: calc(10px + 2vmin); + font-style: bold; + overflow: hidden; + text-overflow: clip; +} .Grid-square { - background: #acc1eb9d; + background: var(--secondary-element); + border: none; + border-radius: 10px; + width: 19.4vmin; + height: 19.4vmin; + font-size: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.Completed-group { + background: var(--secondary-element); border: none; border-radius: 10px; width: 100%; - height: 100%; + height: 19.4vmin; font-size: 50%; overflow: hidden; display: flex; @@ -76,7 +113,24 @@ } .Selected { - background: #d8deea9d; + background: var(--highlight-element); +} + +.Removed { + background: var(--bg-secondary); + cursor: default; +} + +.Submit-button { + background: var(--primary-element); + border-radius: 10px; + margin-top: 2vmin; + font-size: calc(10px + 1vmin); + font-weight: bold; + width: 100%; + height: 5vh; + border: none; + cursor: pointer; } @keyframes Logo-rotate-in { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f6f5296..4eb26bd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,11 +6,17 @@ import { Grid } from "./Grid"; import { getWords } from "./serverHelper"; export type GameState = { - words: { word: string; selected: boolean }[]; + words: { word: string; selected: boolean; used: boolean }[]; + groups: { title: string; words: string[] }[]; }; const initializeGameState = async (): Promise<GameState> => ({ - words: (await getWords()).map((word) => ({ word, selected: false })), + words: (await getWords()).map((word) => ({ + word, + selected: false, + used: false, + })), + groups: [], }); const Game = () => { @@ -39,12 +45,44 @@ const Game = () => { //FIXME don't mutate state candidateState.words[idx].selected = !candidateState.words[idx].selected; setGameState(candidateState); - console.log(`selected ${idx}`); + }; + const submitSelectionHandler = () => { + const candidateState = { ...gameState! }; + + const selectedWords = candidateState.words + .filter(({ selected }) => selected) + .map(({ word }) => word); + + if (selectedWords.length !== 4) { + return; + } + + candidateState.words = candidateState.words.map( + ({ selected, word, used }) => ({ + used: used || selected, + selected: false, + word, + }) + ); + + //mock the server response for this selection + const response = "Four letter verbs"; + const newGroup: GameState["groups"][number] = { + title: response, + words: selectedWords, + }; + candidateState.groups = [...candidateState.groups, newGroup]; + + setGameState(candidateState); }; //display logic return gameState ? ( - <Grid selectWordHandler={selectWordHandler} words={gameState.words!} /> + <Grid + selectWordHandler={selectWordHandler} + submitSelectionHandler={submitSelectionHandler} + gameState={gameState!} + /> ) : ( <h1>Loading...</h1> ); diff --git a/frontend/src/Grid.tsx b/frontend/src/Grid.tsx index b35f757..f6df3a8 100644 --- a/frontend/src/Grid.tsx +++ b/frontend/src/Grid.tsx @@ -9,28 +9,52 @@ const Tile = ({ word: GameState["words"][number]; }) => ( <button - onClick={selectWordHandler} - className={`Grid-square ${word.selected ? "Selected" : ""}`} - style={{ cursor: "pointer" }} + onClick={!word.used ? selectWordHandler : undefined} + className={`Grid-square ${ + word.selected ? "Selected" : word.used ? "Removed" : "" + }`} > - <h1 className="Fit-text">{word.word.toUpperCase()}</h1> + <pre> + <h1 className="Fit-text">{word.word.toUpperCase()}</h1> + </pre> </button> ); export const Grid = ({ selectWordHandler, - words, + submitSelectionHandler, + gameState, }: { selectWordHandler: (idx: number) => void; - words: GameState["words"]; -}) => ( - <div className="Grid"> - {new Array(16).fill(undefined).map((_, i) => ( + submitSelectionHandler: () => void; + gameState: GameState; +}) => { + const groups = gameState.groups.map(({ title, words }) => ( + <div className="Completed-group"> + <pre> + <h1 className="Fit-text">{title.toUpperCase()}</h1> + <h2 className="Fit-text">{words.join(", ")}</h2> + </pre> + </div> + )); + const tiles = gameState.words.map((word, i) => + !word.used ? ( <Tile selectWordHandler={() => selectWordHandler(i)} - word={words[i]} + word={word} key={i} /> - ))} - </div> -); + ) : undefined + ); + return ( + <div className="Grid-container"> + {groups} + <div className="Grid">{tiles}</div> + {gameState.words.filter(({ selected }) => selected).length === 4 ? ( + <button className="Submit-button" onClick={submitSelectionHandler}> + SUBMIT + </button> + ) : undefined} + </div> + ); +}; |