histogram width divide by mode, not total #2
12 changed files with 83 additions and 64 deletions
39
src/App.css
39
src/App.css
|
@ -1,38 +1,3 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
html.dark {
|
||||
background-color: rgb(15, 23, 42);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,22 @@ import React from 'react'
|
|||
import { render, screen } from '@testing-library/react'
|
||||
import App from './App'
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
})
|
||||
})
|
||||
|
||||
test('renders App component', () => {
|
||||
render(<App />)
|
||||
const linkElement = screen.getByText(/Not Wordle/)
|
||||
|
|
43
src/App.tsx
43
src/App.tsx
|
@ -1,5 +1,8 @@
|
|||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import { ChartBarIcon } from '@heroicons/react/outline'
|
||||
import {
|
||||
InformationCircleIcon,
|
||||
ChartBarIcon,
|
||||
SunIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Alert } from './components/alerts/Alert'
|
||||
import { Grid } from './components/grid/Grid'
|
||||
|
@ -15,9 +18,15 @@ import {
|
|||
saveGameStateToLocalStorage,
|
||||
} from './lib/localStorage'
|
||||
|
||||
import './App.css'
|
||||
|
||||
const ALERT_TIME_MS = 2000
|
||||
|
||||
function App() {
|
||||
const prefersDarkMode = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)'
|
||||
).matches
|
||||
|
||||
const [currentGuess, setCurrentGuess] = useState('')
|
||||
const [isGameWon, setIsGameWon] = useState(false)
|
||||
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false)
|
||||
|
@ -26,6 +35,13 @@ function App() {
|
|||
const [isStatsModalOpen, setIsStatsModalOpen] = useState(false)
|
||||
const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false)
|
||||
const [isGameLost, setIsGameLost] = useState(false)
|
||||
const [isDarkMode, setIsDarkMode] = useState(
|
||||
localStorage.getItem('theme')
|
||||
? localStorage.getItem('theme') === 'dark'
|
||||
: prefersDarkMode
|
||||
? true
|
||||
: false
|
||||
)
|
||||
const [successAlert, setSuccessAlert] = useState('')
|
||||
const [guesses, setGuesses] = useState<string[]>(() => {
|
||||
const loaded = loadGameStateFromLocalStorage()
|
||||
|
@ -44,6 +60,19 @@ function App() {
|
|||
|
||||
const [stats, setStats] = useState(() => loadStats())
|
||||
|
||||
useEffect(() => {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}, [isDarkMode])
|
||||
|
||||
const handleDarkMode = (isDark: boolean) => {
|
||||
setIsDarkMode(isDark)
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
saveGameStateToLocalStorage({ guesses, solution })
|
||||
}, [guesses])
|
||||
|
@ -114,13 +143,17 @@ function App() {
|
|||
return (
|
||||
<div className="py-8 max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="flex w-80 mx-auto items-center mb-8">
|
||||
<h1 className="text-xl grow font-bold">Not Wordle</h1>
|
||||
<h1 className="text-xl grow font-bold dark:text-white">Not Wordle</h1>
|
||||
<SunIcon
|
||||
className="h-6 w-6 cursor-pointer dark:stroke-white"
|
||||
onClick={() => handleDarkMode(!isDarkMode)}
|
||||
/>
|
||||
<InformationCircleIcon
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
className="h-6 w-6 cursor-pointer dark:stroke-white"
|
||||
onClick={() => setIsInfoModalOpen(true)}
|
||||
/>
|
||||
<ChartBarIcon
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
className="h-6 w-6 cursor-pointer dark:stroke-white"
|
||||
onClick={() => setIsStatsModalOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -8,13 +8,16 @@ type Props = {
|
|||
|
||||
export const Cell = ({ value, status }: Props) => {
|
||||
const classes = classnames(
|
||||
'w-14 h-14 border-solid border-2 flex items-center justify-center mx-0.5 text-lg font-bold rounded',
|
||||
'w-14 h-14 border-solid border-2 flex items-center justify-center mx-0.5 text-lg font-bold rounded dark:text-white',
|
||||
{
|
||||
'bg-white border-slate-200': !status,
|
||||
'border-black': value && !status,
|
||||
'bg-slate-400 text-white border-slate-400': status === 'absent',
|
||||
'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-600':
|
||||
!status,
|
||||
'border-black dark:border-slate-100': value && !status,
|
||||
'bg-slate-400 dark:bg-slate-700 text-white border-slate-400 dark:border-slate-700':
|
||||
status === 'absent',
|
||||
'bg-green-500 text-white border-green-500': status === 'correct',
|
||||
'bg-yellow-500 text-white border-yellow-500': status === 'present',
|
||||
'bg-yellow-500 dark:bg-yellow-700 text-white border-yellow-500 dark:border-yellow-700':
|
||||
status === 'present',
|
||||
'cell-animation': !!value,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -19,13 +19,14 @@ export const Key = ({
|
|||
onClick,
|
||||
}: Props) => {
|
||||
const classes = classnames(
|
||||
'flex items-center justify-center rounded mx-0.5 text-xs font-bold cursor-pointer select-none',
|
||||
'flex items-center justify-center rounded mx-0.5 text-xs font-bold cursor-pointer select-none dark:text-white',
|
||||
{
|
||||
'bg-slate-200 hover:bg-slate-300 active:bg-slate-400': !status,
|
||||
'bg-slate-200 dark:bg-slate-600 hover:bg-slate-300 active:bg-slate-400':
|
||||
!status,
|
||||
'bg-slate-400 text-white': status === 'absent',
|
||||
'bg-green-500 hover:bg-green-600 active:bg-green-700 text-white':
|
||||
status === 'correct',
|
||||
'bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 text-white':
|
||||
'bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 dark:bg-yellow-700 text-white':
|
||||
status === 'present',
|
||||
}
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ type Props = {
|
|||
export const AboutModal = ({ isOpen, handleClose }: Props) => {
|
||||
return (
|
||||
<BaseModal title="About" isOpen={isOpen} handleClose={handleClose}>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
This is an open source clone of the game Wordle -{' '}
|
||||
<a
|
||||
href="https://github.com/hannahcode/wordle"
|
||||
|
|
|
@ -46,10 +46,10 @@ export const BaseModal = ({ title, children, isOpen, handleClose }: Props) => {
|
|||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6 dark:bg-gray-800">
|
||||
<div className="absolute right-4 top-4">
|
||||
<XCircleIcon
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
className="h-6 w-6 cursor-pointer dark:stroke-white"
|
||||
onClick={() => handleClose()}
|
||||
/>
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@ export const BaseModal = ({ title, children, isOpen, handleClose }: Props) => {
|
|||
<div className="text-center">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg leading-6 font-medium text-gray-900"
|
||||
className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
|
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||
export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
||||
return (
|
||||
<BaseModal title="How to play" isOpen={isOpen} handleClose={handleClose}>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
Guess the WORDLE in 6 tries. After each guess, the color of the tiles
|
||||
will change to show how close your guess was to the word.
|
||||
</p>
|
||||
|
@ -21,7 +21,7 @@ export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
|||
<Cell value="R" />
|
||||
<Cell value="Y" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
The letter W is in the word and in the correct spot.
|
||||
</p>
|
||||
|
||||
|
@ -32,7 +32,7 @@ export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
|||
<Cell value="O" />
|
||||
<Cell value="T" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
The letter L is in the word but in the wrong spot.
|
||||
</p>
|
||||
|
||||
|
@ -43,7 +43,7 @@ export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
|||
<Cell value="U" status="absent" />
|
||||
<Cell value="E" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
The letter U is not in the word in any spot.
|
||||
</p>
|
||||
</BaseModal>
|
||||
|
|
|
@ -35,16 +35,16 @@ export const StatsModal = ({
|
|||
return (
|
||||
<BaseModal title="Statistics" isOpen={isOpen} handleClose={handleClose}>
|
||||
<StatBar gameStats={gameStats} />
|
||||
<h4 className="text-lg leading-6 font-medium text-gray-900">
|
||||
<h4 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Guess Distribution
|
||||
</h4>
|
||||
<Histogram gameStats={gameStats} />
|
||||
{(isGameLost || isGameWon) && (
|
||||
<div className="mt-5 sm:mt-6 columns-2">
|
||||
<div className="mt-5 sm:mt-6 columns-2 dark:text-white">
|
||||
<div>
|
||||
<h5>New word in</h5>
|
||||
<Countdown
|
||||
className="text-lg font-medium text-gray-900"
|
||||
className="text-lg font-medium text-gray-900 dark:text-gray-100"
|
||||
date={tomorrow}
|
||||
daysInHours={true}
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,7 @@ export const Histogram = ({ gameStats }: Props) => {
|
|||
const maxValue = Math.max(...winDistribution)
|
||||
|
||||
return (
|
||||
<div className="columns-1 justify-left m-2 text-sm">
|
||||
<div className="columns-1 justify-left m-2 text-sm dark:text-white">
|
||||
{winDistribution.map((value, i) => (
|
||||
<Progress
|
||||
key={i}
|
||||
|
|
|
@ -12,7 +12,7 @@ const StatItem = ({
|
|||
value: string | number
|
||||
}) => {
|
||||
return (
|
||||
<div className="items-center justify-center m-1 w-1/4">
|
||||
<div className="items-center justify-center m-1 w-1/4 dark:text-white">
|
||||
<div className="text-3xl font-bold">{value}</div>
|
||||
<div className="text-xs">{label}</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
|
|
Loading…
Add table
Reference in a new issue