- {grid}
-
-
+
);
};
diff --git a/src/Game/Grid/Guess/Guess.module.css b/src/Game/Grid/Guess/Guess.module.css
index e969189..3c01e37 100644
--- a/src/Game/Grid/Guess/Guess.module.css
+++ b/src/Game/Grid/Guess/Guess.module.css
@@ -10,23 +10,13 @@
text-decoration: none;
}
-.spacer {
- flex: 1;
-
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding-left: 8px;
- font-size: 16pt;
-}
-
-.guess .spacer {
+.guess + svg {
color: var(--dictionary-color);
transition: all 0.2s;
}
-.guess:hover .spacer,
-.guess:focus-visible .spacer,
-.guess:active .spacer {
+.guess:hover + svg,
+.guess:focus-visible + svg,
+.guess:active + svg {
color: var(--link-color);
}
diff --git a/src/Game/Grid/Guess/Guess.tsx b/src/Game/Grid/Guess/Guess.tsx
index 5d21d40..0bac4d2 100644
--- a/src/Game/Grid/Guess/Guess.tsx
+++ b/src/Game/Grid/Guess/Guess.tsx
@@ -20,10 +20,9 @@ const Guess = ({ guess, Icon = MdOpenInNew, onClick, length }: Props) => {
const letters = word
.split("")
- .reduce
>(
- (acc, l) => ({ ...acc, [l]: (acc[l] || 0) + 1 }),
- {}
- );
+ .reduce<
+ Record
+ >((acc, l) => ({ ...acc, [l]: (acc[l] || 0) + 1 }), {});
// Find everything that's correct.
for (let i = 0; i < N; i += 1) {
@@ -82,13 +81,11 @@ const Guess = ({ guess, Icon = MdOpenInNew, onClick, length }: Props) => {
};
return (
-
-
- {letters}
-
-
-
-
+ <>
+
+ {letters}
+
+ >
);
};
diff --git a/src/Game/Grid/Input/Input.tsx b/src/Game/Grid/Input/Input.tsx
index 08b9281..5df7bc8 100644
--- a/src/Game/Grid/Input/Input.tsx
+++ b/src/Game/Grid/Input/Input.tsx
@@ -16,7 +16,13 @@ const Input = () => {
return out;
}, [word, solution.length]);
- return {letters}
;
+ return (
+ <>
+
+ {letters}
+
+ >
+ );
};
export default Input;
diff --git a/src/Game/Grid/Remainder/Remainder.module.css b/src/Game/Grid/Remainder/Remainder.module.css
index 3c6245a..56a1adc 100644
--- a/src/Game/Grid/Remainder/Remainder.module.css
+++ b/src/Game/Grid/Remainder/Remainder.module.css
@@ -1,8 +1,16 @@
-.message {
- padding: 8px;
- color: #999;
-}
+.container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-self: stretch;
+
+ .message {
+ padding: 8px;
+ color: #999;
+ }
-.count {
- color: var(--letter-color)
-}
\ No newline at end of file
+ .count {
+ color: var(--letter-color);
+ }
+}
diff --git a/src/Game/Grid/Remainder/Remainder.tsx b/src/Game/Grid/Remainder/Remainder.tsx
index c63e440..7afef39 100644
--- a/src/Game/Grid/Remainder/Remainder.tsx
+++ b/src/Game/Grid/Remainder/Remainder.tsx
@@ -1,3 +1,4 @@
+import { useGameNumber } from "../../../App/Setup/GameLoader";
import { useGuesses } from "../../guess";
import classes from "./Remainder.module.css";
import { usePossibilities } from "./usePossibilities";
@@ -6,14 +7,24 @@ const Remainder = () => {
const [guessMap] = useGuesses();
const guesses = Object.keys(guessMap);
const { remainders, formattedCount } = usePossibilities(guesses);
- if (!remainders || remainders.length === 0) {
- return null;
- }
+ const gameNumber = useGameNumber();
return (
-
- mulige ord: {formattedCount}
-
+ <>
+ {" "}
+
+
+ {remainders?.length > 0 ? (
+ <>
+ mulige ord:{" "}
+ {formattedCount}
+ >
+ ) : null}
+
+
# {gameNumber}
+
+
+ >
);
};
diff --git a/src/Game/Grid/Remaining/Remaining.tsx b/src/Game/Grid/Remaining/Remaining.tsx
index 9a5b33c..bd780d0 100644
--- a/src/Game/Grid/Remaining/Remaining.tsx
+++ b/src/Game/Grid/Remaining/Remaining.tsx
@@ -14,7 +14,13 @@ const Remaining = () => {
return out;
}, [word.length]);
- return {children}
;
+ return (
+ <>
+
+ {children}
+
+ >
+ );
};
export default Remaining;
diff --git a/src/Game/Header/Header.module.css b/src/Game/Header/Header.module.css
deleted file mode 100644
index 37b25fd..0000000
--- a/src/Game/Header/Header.module.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.header {
- display: flex;
- flex-direction: row;
- border-bottom: 1px solid #999a;
- width: 100%;
- justify-content: center;
-
- color: #999;
-}
diff --git a/src/Game/Header/Header.tsx b/src/Game/Header/Header.tsx
deleted file mode 100644
index 1b7d8b0..0000000
--- a/src/Game/Header/Header.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import Help from "./Help";
-import Random from "../../Random";
-import Title from "./Title";
-import Language from "./Language";
-import Settings from "./Settings";
-import classes from "./Header.module.css";
-
-type Props = {} & Pick<
- React.HTMLAttributes,
- "style" | "className"
->;
-
-export const Header = (props: Props) => {
- return (
-
-
-
-
-
-
-
- );
-};
diff --git a/src/Game/Header/Help/Help.tsx b/src/Game/Header/Help/Help.tsx
index d0fb3de..d9526ec 100644
--- a/src/Game/Header/Help/Help.tsx
+++ b/src/Game/Header/Help/Help.tsx
@@ -1,12 +1,11 @@
-import { useState, useMemo, useCallback } from "react";
-import { createPortal } from "react-dom";
+import { useState, useCallback } from "react";
import { MdOutlineHelpOutline } from "react-icons/md";
import classes from "./Help.module.css";
-import Dialog from "../../../Dialog";
import Letter from "../../Grid/Letter";
import { useParams } from "react-router";
import Accordion from "../../../Accordion";
import Segment from "../../../Accordion/Segment";
+import { Modal } from "../../../spa-components/Modal";
const Help = () => {
const { lang } = useParams();
@@ -21,12 +20,12 @@ const Help = () => {
localStorage.setItem(key, String(Date.now()));
}, [key]);
- const modal = useMemo(() => {
- if (!showing) {
- return null;
- }
- return (
-
- );
- }, [showing, onClose]);
-
- const portal = createPortal(showing ? modal : null, document.body);
-
- return (
- setShowing(true)}
- >
-
- {portal}
-
+
+ >
);
};
diff --git a/src/Game/Header/Language/Language.module.css b/src/Game/Header/Language/Language.module.css
deleted file mode 100644
index 4b9816c..0000000
--- a/src/Game/Header/Language/Language.module.css
+++ /dev/null
@@ -1,22 +0,0 @@
-.flag {
- font-size: 2em;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- width: 50px;
- height: 100%;
- cursor: pointer;
-
- filter: saturate(0);
- transition: all 0.2s;
-
- text-decoration: none;
-}
-
-.flag:hover,
-.flag:active,
-.flag:focus-visible {
- filter: saturate(1);
- text-decoration: none;
-}
diff --git a/src/Game/Header/Language/Language.tsx b/src/Game/Header/Language/Language.tsx
deleted file mode 100644
index 5db087a..0000000
--- a/src/Game/Header/Language/Language.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useParams } from "react-router";
-import { Link } from "react-router-dom";
-import classes from "./Language.module.css";
-
-type Info = {
- name: string;
- flag: string;
- code: string;
-};
-
-const LANGUAGES = [
- { name: "bokmål", flag: "🇳🇴", code: "nb-no" },
- { name: "nynorsk", flag: "🇳🇴", code: "nn-no" },
-] as const;
-
-const LOOKUP: Record = LANGUAGES.reduce(
- (acc, lang) => ({ ...acc, [lang.code]: lang }),
- {}
-);
-
-const Language = () => {
- const { lang } = useParams();
-
- if (!lang) {
- return null;
- }
-
- const record = LOOKUP[lang];
- if (!record) {
- return null;
- }
-
- return (
-
- {record.flag}
-
- );
-};
-
-export default Language;
diff --git a/src/Game/Header/Language/index.ts b/src/Game/Header/Language/index.ts
deleted file mode 100644
index 7ab0eab..0000000
--- a/src/Game/Header/Language/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./Language";
diff --git a/src/Game/Header/Settings/Settings.tsx b/src/Game/Header/Settings/Settings.tsx
index 04d3c5a..d616e61 100644
--- a/src/Game/Header/Settings/Settings.tsx
+++ b/src/Game/Header/Settings/Settings.tsx
@@ -1,7 +1,5 @@
-import { useCallback, useMemo, useState } from "react";
-import { createPortal } from "react-dom";
+import { useCallback, useState } from "react";
import { MdOutlineSettings } from "react-icons/md";
-import classes from "./Settings.module.css";
import UserDialog from "../../../SettingsDialog";
const Settings = () => {
@@ -10,25 +8,13 @@ const Settings = () => {
setShowing(false);
}, []);
- const modal = useMemo(() => {
- if (!showing) {
- return null;
- }
-
- return ;
- }, [onClose, showing]);
-
- const portal = createPortal(modal, document.body);
-
return (
- setShowing(true)}
- >
-
- {portal}
-
+ <>
+
+
+ >
);
};
diff --git a/src/Game/Header/Title/Title.module.css b/src/Game/Header/Title/Title.module.css
deleted file mode 100644
index 3f8b5c3..0000000
--- a/src/Game/Header/Title/Title.module.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.container {
- flex: 1;
-
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.container h1 {
- padding-bottom: 0;
- margin-bottom: 0;
-}
-
-.gameNumber {
- color: #999;
- font-size: 0.9em;
-}
diff --git a/src/Game/Header/Title/Title.tsx b/src/Game/Header/Title/Title.tsx
deleted file mode 100644
index 3f3a9a3..0000000
--- a/src/Game/Header/Title/Title.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Link, useParams } from "react-router-dom";
-import { useGameNumber } from "../../../App/Setup/GameLoader";
-import classes from "./Title.module.css";
-
-const Title = () => {
- const { lang } = useParams();
- const gameNumber = useGameNumber();
- return (
-
-
- Ordle
-
- # {gameNumber}
-
- );
-};
-
-export default Title;
diff --git a/src/Game/Header/Title/index.ts b/src/Game/Header/Title/index.ts
deleted file mode 100644
index c3ebe86..0000000
--- a/src/Game/Header/Title/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./Title";
diff --git a/src/Game/Header/index.ts b/src/Game/Header/index.ts
deleted file mode 100644
index 1bbd07d..0000000
--- a/src/Game/Header/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-import { Header } from "./Header";
-export default Header;
diff --git a/src/Random/Random.tsx b/src/Random/Random.tsx
index 51df031..d43776c 100644
--- a/src/Random/Random.tsx
+++ b/src/Random/Random.tsx
@@ -1,14 +1,13 @@
import { MdOutlineAutorenew } from "react-icons/md";
import { useNewGame } from "../Game/control";
-import classes from "./Random.module.css";
const Random = () => {
const newGame = useNewGame();
return (
-
+
+
);
};
diff --git a/src/SettingsDialog/SettingsDialog.module.css b/src/SettingsDialog/SettingsDialog.module.css
index b9543a1..39691e1 100644
--- a/src/SettingsDialog/SettingsDialog.module.css
+++ b/src/SettingsDialog/SettingsDialog.module.css
@@ -56,3 +56,21 @@
border-top: 1px solid #999a;
padding: 4px;
}
+
+.languages {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ padding: 16px;
+
+ a {
+ padding: 0.5em 1em;
+ border: 2px solid rgba(from var(--link-color) r g b / 0.8);
+ border-radius: 0.5em;
+ }
+
+ a.active {
+ background-color: var(--link-color);
+ color: #fff;
+ }
+}
diff --git a/src/SettingsDialog/SettingsDialog.tsx b/src/SettingsDialog/SettingsDialog.tsx
index c766eae..a940e84 100644
--- a/src/SettingsDialog/SettingsDialog.tsx
+++ b/src/SettingsDialog/SettingsDialog.tsx
@@ -1,33 +1,49 @@
-import { useMemo } from "react";
-import { createPortal } from "react-dom";
-import Dialog from "../Dialog";
import Accordion from "../Accordion";
import UserSegment from "./UserSegment";
import SettingsSegment from "./SettingsSegment";
import classes from "./SettingsDialog.module.css";
+import { Modal } from "../spa-components/Modal";
+import Segment from "../Accordion/Segment";
+import { NavLink } from "react-router";
+import { ComponentProps } from "react";
type Props = {
onClose: () => void;
+ open: boolean;
};
-const SettingsDialog = ({ onClose }: Props) => {
- const modal = useMemo(() => {
- return (
-
- );
- }, [onClose]);
+const className: ComponentProps["className"] = ({
+ isActive,
+}) => (isActive ? classes.active : undefined);
- const portal = createPortal(modal, document.body);
+const LanguageSegment = () => {
+ return (
+
+
+
+ bokmål
+
+
+ nynorsk
+
+
+
+ );
+};
- return <>{portal}>;
+const SettingsDialog = ({ open, onClose }: Props) => {
+ return (
+
+
+
+
+
+
+
+ versjon {import.meta.env.VITE_RELEASE ?? "development"}
+
+
+ );
};
export default SettingsDialog;
diff --git a/src/index.css b/src/index.css
index 2d4938b..ce27de7 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,4 +1,10 @@
:root {
+ --theme-color: #0aacf2;
+
+ --header-background-color: #000;
+ --header-foreground-color: var(--theme-color);
+ --header-button-color: var(--theme-color);
+
--letter-color: #000;
--letter-color-rgb: 0, 0, 0;
--letter-border: #000a;
@@ -14,6 +20,10 @@
--dictionary-color: #999a;
--secondary-text: #999a;
--primary-color: #000;
+
+ font-family: "Roboto", sans-serif;
+ color: var(--letter-color);
+ background-color: var(--background-color);
}
@media (prefers-color-scheme: dark) {
@@ -31,14 +41,11 @@
}
}
-body,
-html {
- color: var(--letter-color);
- background-color: var(--background-color);
+body {
+ display: flex;
+ flex-direction: column;
margin: 0;
- padding: 0;
- font-family: "Roboto", sans-serif;
- height: 100%;
+ height: 100dvh;
}
a,
@@ -53,5 +60,7 @@ a:focus-visible {
}
#root {
- height: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
}
diff --git a/src/spa-components b/src/spa-components
new file mode 160000
index 0000000..5202f55
--- /dev/null
+++ b/src/spa-components
@@ -0,0 +1 @@
+Subproject commit 5202f55188f17e95964094cb0bb7e6a69ce02af5
diff --git a/tsconfig.app.json b/tsconfig.app.json
index f0a2350..584f0f1 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -18,7 +18,9 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ "types": ["vite-plugin-pwa/client", "jest"]
},
"include": ["src"]
}
diff --git a/vite.config.ts b/vite.config.ts
index d9a7fa0..bc40986 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,51 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
+import { VitePWA } from "vite-plugin-pwa";
-// https://vitejs.dev/config/
+// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [
+ react(),
+ VitePWA({
+ base: "/",
+ manifest: {
+ name: "Ordle",
+ short_name: "Ordle",
+ theme_color: "#000",
+ icons: [
+ {
+ src: "/logo-64.png",
+ sizes: "64x64 32x32",
+ type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "/logo-192.png",
+ type: "image/png",
+ sizes: "192x192",
+ purpose: "any",
+ },
+ {
+ src: "/logo-512.png",
+ type: "image/png",
+ sizes: "512x512",
+ purpose: "any",
+ },
+ ],
+ start_url: ".",
+ },
+
+ devOptions: { enabled: true, navigateFallback: "index.html" },
+ workbox: {
+ cacheId: "ordle",
+ runtimeCaching: [
+ {
+ urlPattern: /\.(?:json|svg|png|css)$/i,
+ handler: "StaleWhileRevalidate",
+ },
+ ],
+ },
+ }),
+ ],
base: "/",
});