summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2025-06-13 23:24:49 -0700
committerKai Stevenson <kai@kaistevenson.com>2025-06-13 23:24:49 -0700
commit6475f534e7bf457113dcc82d94bfb7ac413f71c4 (patch)
treece029883066b1cc13ab5b4fc5f71b8547e0f3cce /frontend/src
parent87f134c9f2714593890a530221df429122c4398b (diff)
grouping
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.css72
-rw-r--r--frontend/src/App.tsx46
-rw-r--r--frontend/src/Grid.tsx50
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>
+ );
+};