@scrabble-solver/scrabble-solver 2.10.7 → 2.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +15 -21
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/images-manifest.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/176.js +4603 -395
- package/.next/server/chunks/664.js +27 -2414
- package/.next/server/chunks/859.js +29 -1
- package/.next/server/font-loader-manifest.js +1 -0
- package/.next/server/font-loader-manifest.json +6 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -12
- package/.next/server/pages/_app.js +176 -13
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +133 -17
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +16 -0
- package/.next/server/pages/index.html +1 -9
- package/.next/server/pages/index.js +52 -165
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/main-74c4d6b2b5c362f3.js +1 -0
- package/.next/static/chunks/pages/{404-67383848027ec49b.js → 404-d5ff00df1c687977.js} +1 -1
- package/.next/static/chunks/pages/_app-52cb288dc680bdfe.js +28 -0
- package/.next/static/chunks/pages/index-5c2544930e46c5ce.js +1 -0
- package/.next/static/chunks/webpack-6ef43a8d4a395f49.js +1 -0
- package/.next/static/css/ec4e47a6b1866fe5.css +1 -0
- package/.next/static/css/f65b7b2a74f57c1c.css +2 -0
- package/.next/static/fZRsz4P0gQ8Wgb9jP8eap/_buildManifest.js +1 -0
- package/.next/trace +55 -55
- package/package.json +12 -10
- package/src/components/Badge/Badge.module.scss +1 -1
- package/src/components/Board/Board.module.scss +14 -0
- package/src/components/Board/Board.tsx +117 -19
- package/src/components/Board/BoardPure.tsx +7 -15
- package/src/components/Board/components/Actions/Actions.module.scss +64 -0
- package/src/components/Board/components/Actions/Actions.tsx +68 -0
- package/src/components/Board/components/Actions/index.ts +1 -0
- package/src/components/Board/components/Cell/Cell.module.scss +22 -165
- package/src/components/Board/components/Cell/Cell.tsx +0 -37
- package/src/components/Board/components/Cell/CellPure.tsx +5 -75
- package/src/components/Board/components/index.ts +1 -0
- package/src/components/Board/hooks/useGrid.ts +16 -16
- package/src/components/Button/Button.module.scss +3 -3
- package/src/components/Checkbox/Checkbox.tsx +1 -4
- package/src/components/Dictionary/Dictionary.tsx +28 -30
- package/src/components/DictionaryInput/DictionaryInput.tsx +3 -0
- package/src/components/Key/Key.module.scss +1 -1
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +1 -1
- package/src/components/Modal/Modal.module.scss +4 -2
- package/src/components/Rack/Rack.module.scss +5 -0
- package/src/components/Radio/Radio.tsx +1 -4
- package/src/components/Results/Results.module.scss +2 -2
- package/src/components/SeoMessage/SeoMessage.tsx +19 -0
- package/src/components/SeoMessage/index.ts +1 -0
- package/src/components/Solver/Solver.module.scss +0 -5
- package/src/components/Solver/Solver.tsx +1 -1
- package/src/components/Solver/components/EmptyState/EmptyState.module.scss +1 -1
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +9 -2
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -1
- package/src/components/Tile/Tile.module.scss +23 -11
- package/src/components/Tile/Tile.tsx +26 -9
- package/src/components/Tile/TilePure.tsx +9 -4
- package/src/components/Tooltip/Tooltip.module.scss +7 -7
- package/src/components/index.ts +1 -0
- package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageRack.ts +6 -3
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fa.json +1 -0
- package/src/i18n/fr.json +1 -0
- package/src/i18n/pl.json +1 -0
- package/src/icons/Flag.svg +2 -2
- package/src/icons/FlagFill.svg +4 -0
- package/src/icons/Square.svg +4 -0
- package/src/icons/SquareFill.svg +4 -0
- package/src/icons/index.ts +3 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
- package/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
- package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -2
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +1 -1
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -2
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +14 -24
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +1 -2
- package/src/pages/_app.tsx +9 -5
- package/src/pages/index.module.scss +1 -2
- package/src/pages/index.tsx +10 -8
- package/src/parameters/index.ts +12 -0
- package/src/state/slices/boardSlice.ts +5 -5
- package/src/styles/animations.scss +10 -0
- package/src/styles/global.scss +2 -2
- package/src/styles/mixins.scss +60 -3
- package/src/styles/variables.scss +43 -33
- package/src/types/index.ts +1 -0
- package/.next/server/chunks/210.js +0 -122
- package/.next/server/chunks/579.js +0 -3925
- package/.next/server/chunks/676.js +0 -32
- package/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +0 -1
- package/.next/static/chunks/490-d29992f1c264d70e.js +0 -5
- package/.next/static/chunks/509-6ad4482d4351452c.js +0 -1
- package/.next/static/chunks/main-f11614d8aa7ee555.js +0 -1
- package/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +0 -24
- package/.next/static/chunks/pages/index-146039f501e49c08.js +0 -1
- package/.next/static/chunks/webpack-59c5c889f52620d6.js +0 -1
- package/.next/static/css/4482c4a0064d3807.css +0 -1
- package/.next/static/css/78e42ad01f580f64.css +0 -1
- package/.next/static/css/9d1013ec684361b9.css +0 -1
- package/src/components/Board/components/Cell/Button.tsx +0 -32
- /package/.next/static/{6RggBFm8kHrh-k1-CG3um → fZRsz4P0gQ8Wgb9jP8eap}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,10 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
|
27
27
|
>
|
|
28
28
|
{typeof error !== 'undefined' && <EmptyState variant="error">{error.message}</EmptyState>}
|
|
29
29
|
|
|
30
|
+
{results.length === 0 && (
|
|
31
|
+
<EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
|
|
32
|
+
)}
|
|
33
|
+
|
|
30
34
|
{results.map(({ definitions, exists, isAllowed, word }) => (
|
|
31
35
|
<div
|
|
32
36
|
className={classNames(styles.result, {
|
|
@@ -35,40 +39,34 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
|
35
39
|
})}
|
|
36
40
|
key={word}
|
|
37
41
|
>
|
|
38
|
-
{
|
|
39
|
-
<
|
|
40
|
-
)}
|
|
41
|
-
|
|
42
|
-
{typeof word !== 'undefined' && (
|
|
43
|
-
<div className={styles.content}>
|
|
44
|
-
{word && <h2 className={styles.word}>{word}</h2>}
|
|
42
|
+
<div className={styles.content}>
|
|
43
|
+
{word && <h2 className={styles.word}>{word}</h2>}
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
{isAllowed === true && (
|
|
48
|
+
<>
|
|
49
|
+
{definitions.length === 0 && (
|
|
50
|
+
<>
|
|
51
|
+
{exists && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
|
|
52
|
+
{!exists && <div>{translate('dictionary.empty-state.no-results')}</div>}
|
|
53
|
+
</>
|
|
54
|
+
)}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
{definitions.length > 0 && (
|
|
57
|
+
<ul className={styles.definitions}>
|
|
58
|
+
{definitions.map((result, index) => (
|
|
59
|
+
<li key={index} className={styles.definition}>
|
|
60
|
+
{result}
|
|
61
|
+
</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
)}
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)}
|
|
68
|
+
{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
|
|
69
|
+
</div>
|
|
72
70
|
</div>
|
|
73
71
|
))}
|
|
74
72
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
|
|
1
2
|
import classNames from 'classnames';
|
|
2
3
|
import { ChangeEvent, FormEvent, FunctionComponent } from 'react';
|
|
3
4
|
import { useDispatch } from 'react-redux';
|
|
@@ -28,8 +29,10 @@ const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
28
29
|
<form className={classNames(styles.dictionaryInput, className)} onSubmit={handleSubmit}>
|
|
29
30
|
<input
|
|
30
31
|
className={styles.input}
|
|
32
|
+
pattern={`.*[^\\s${COMMA_ARABIC}${COMMA_LATIN}].*`}
|
|
31
33
|
placeholder={translate('dictionary.input.placeholder')}
|
|
32
34
|
required
|
|
35
|
+
title={translate('dictionary.input.title')}
|
|
33
36
|
type="text"
|
|
34
37
|
value={input}
|
|
35
38
|
onChange={handleChange}
|
|
@@ -8,7 +8,7 @@ $icon-size: 15px;
|
|
|
8
8
|
min-width: $key-size;
|
|
9
9
|
height: $key-size;
|
|
10
10
|
padding: var(--spacing--s) var(--spacing--m);
|
|
11
|
-
background-color: white;
|
|
11
|
+
background-color: var(--color--white);
|
|
12
12
|
border: var(--border);
|
|
13
13
|
border-radius: var(--border--radius);
|
|
14
14
|
box-shadow: var(--box-shadow);
|
|
@@ -60,12 +60,14 @@
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
.container {
|
|
63
|
+
--box-shadow--offset: 1px;
|
|
64
|
+
--box-shadow--offset--negative: calc(-1 * var(--box-shadow--offset));
|
|
65
|
+
|
|
63
66
|
display: flex;
|
|
64
67
|
flex-direction: column;
|
|
65
68
|
height: 100%;
|
|
66
69
|
background-color: var(--color--background);
|
|
67
|
-
transition: var(--transition);
|
|
68
|
-
transition-duration: var(--transition--duration--long);
|
|
70
|
+
transition: var(--transition--long);
|
|
69
71
|
transform: translateX(var(--modal--width));
|
|
70
72
|
opacity: 0;
|
|
71
73
|
|
|
@@ -8,24 +8,21 @@ interface Props {
|
|
|
8
8
|
children?: ReactNode;
|
|
9
9
|
className?: string;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
-
id: string;
|
|
12
11
|
name: string;
|
|
13
12
|
value: string;
|
|
14
13
|
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled,
|
|
16
|
+
const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled, name, value, onChange }) => (
|
|
18
17
|
<label
|
|
19
18
|
className={classNames(styles.radio, className, {
|
|
20
19
|
[styles.checked]: checked,
|
|
21
20
|
})}
|
|
22
|
-
htmlFor={id}
|
|
23
21
|
>
|
|
24
22
|
<input
|
|
25
23
|
checked={checked}
|
|
26
24
|
className={styles.input}
|
|
27
25
|
disabled={disabled}
|
|
28
|
-
id={id}
|
|
29
26
|
name={name}
|
|
30
27
|
type="radio"
|
|
31
28
|
value={value}
|
|
@@ -45,7 +45,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
45
45
|
&:focus,
|
|
46
46
|
&:hover {
|
|
47
47
|
background-color: var(--color--primary);
|
|
48
|
-
color:
|
|
48
|
+
color: var(--color--primary--opposite);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -73,7 +73,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
73
73
|
&.highlighted {
|
|
74
74
|
&:not(:disabled) {
|
|
75
75
|
background-color: var(--color--primary);
|
|
76
|
-
color:
|
|
76
|
+
color: var(--color--primary--opposite);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CSSProperties, FunctionComponent } from 'react';
|
|
2
|
+
|
|
3
|
+
const INVISIBLE_STYLE: CSSProperties = {
|
|
4
|
+
color: 'transparent',
|
|
5
|
+
pointerEvents: 'none',
|
|
6
|
+
position: 'absolute',
|
|
7
|
+
userSelect: 'none',
|
|
8
|
+
transform: 'translateY(-9999px)',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const SeoMessage: FunctionComponent = () => (
|
|
12
|
+
<p style={INVISIBLE_STYLE}>
|
|
13
|
+
Scrabble Solver 2 is a free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words
|
|
14
|
+
using given letters and board state. Available in English, French, German, Polish & Spanish. Source code is
|
|
15
|
+
available on GitHub - contributions are welcome!
|
|
16
|
+
</p>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default SeoMessage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './SeoMessage';
|
|
@@ -157,7 +157,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
157
157
|
|
|
158
158
|
<div className={styles.bottomContainer} ref={bottomContainerRef}>
|
|
159
159
|
<div className={styles.bottomContent}>
|
|
160
|
-
<form
|
|
160
|
+
<form onSubmit={handleSubmit}>
|
|
161
161
|
<Rack className={styles.rack} tileSize={tileSize} />
|
|
162
162
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
163
163
|
</form>
|
package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
.resultCandidatePicker {
|
|
4
4
|
display: flex;
|
|
5
|
-
align-items: center;
|
|
6
5
|
gap: var(--spacing--l);
|
|
7
6
|
|
|
8
7
|
@include media('<xs') {
|
|
@@ -14,6 +13,14 @@
|
|
|
14
13
|
flex: 0 0 auto;
|
|
15
14
|
display: flex;
|
|
16
15
|
border-radius: var(--border--radius);
|
|
16
|
+
|
|
17
|
+
&.bothEnabled {
|
|
18
|
+
box-shadow: var(--box-shadow);
|
|
19
|
+
|
|
20
|
+
.button {
|
|
21
|
+
box-shadow: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
.button {
|
|
@@ -50,7 +57,7 @@
|
|
|
50
57
|
width: 100%;
|
|
51
58
|
border: var(--border);
|
|
52
59
|
border-radius: var(--border--radius);
|
|
53
|
-
background-color: white;
|
|
60
|
+
background-color: var(--color--white);
|
|
54
61
|
box-shadow: var(--box-shadow);
|
|
55
62
|
cursor: pointer;
|
|
56
63
|
|
|
@@ -34,6 +34,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
34
34
|
const disabled = isOutdated || !resultCandidate;
|
|
35
35
|
const isPreviousDisabled = index <= 0 || disabled;
|
|
36
36
|
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
37
|
+
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
37
38
|
|
|
38
39
|
const handleNextClick = () => {
|
|
39
40
|
if (!isNextDisabled) {
|
|
@@ -51,7 +52,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
51
52
|
|
|
52
53
|
return (
|
|
53
54
|
<div className={classNames(styles.resultCandidatePicker, className)} {...props}>
|
|
54
|
-
<div className={styles.buttons}>
|
|
55
|
+
<div className={classNames(styles.buttons, { [styles.bothEnabled]: bothEnabled })}>
|
|
55
56
|
<Button
|
|
56
57
|
aria-label={translate('common.previous')}
|
|
57
58
|
className={styles.button}
|
|
@@ -24,18 +24,20 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
&.blank {
|
|
27
|
-
--background-color: white;
|
|
27
|
+
--background-color: var(--color--white);
|
|
28
28
|
|
|
29
|
+
.placeholder,
|
|
29
30
|
.character {
|
|
30
|
-
color:
|
|
31
|
+
color: var(--color--foreground);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
&.highlighted {
|
|
35
36
|
--background-color: var(--color--primary);
|
|
36
37
|
|
|
37
|
-
color:
|
|
38
|
+
color: var(--color--primary--opposite);
|
|
38
39
|
|
|
40
|
+
.placeholder,
|
|
39
41
|
.character {
|
|
40
42
|
color: inherit;
|
|
41
43
|
}
|
|
@@ -45,12 +47,6 @@
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
&.invalid {
|
|
49
|
-
--background-color: var(--color--red--light);
|
|
50
|
-
|
|
51
|
-
color: var(--color--error);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
50
|
&:not(.disabled) {
|
|
55
51
|
.input::selection {
|
|
56
52
|
--background--color: transparent;
|
|
@@ -59,7 +55,8 @@
|
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
.input,
|
|
62
|
-
.character
|
|
58
|
+
.character,
|
|
59
|
+
.placeholder {
|
|
63
60
|
padding: 0;
|
|
64
61
|
font-weight: bold;
|
|
65
62
|
text-transform: uppercase;
|
|
@@ -77,7 +74,8 @@
|
|
|
77
74
|
font-size: 16px; // prevent iOS from automatically zooming in on focus
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
.character
|
|
77
|
+
.character,
|
|
78
|
+
.placeholder {
|
|
81
79
|
position: absolute;
|
|
82
80
|
top: 0;
|
|
83
81
|
right: 0;
|
|
@@ -97,6 +95,18 @@
|
|
|
97
95
|
|
|
98
96
|
.raised & {
|
|
99
97
|
box-shadow: inset -2px -2px 2px -1px rgba(34, 34, 34, 0.8);
|
|
98
|
+
|
|
99
|
+
@include media('<xs') {
|
|
100
|
+
box-shadow: inset -2px -2px 1px -1px rgba(34, 34, 34, 0.8);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.character {
|
|
106
|
+
opacity: 1;
|
|
107
|
+
|
|
108
|
+
.empty & {
|
|
109
|
+
opacity: 0;
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
|
|
@@ -130,6 +140,8 @@
|
|
|
130
140
|
position: absolute;
|
|
131
141
|
width: $size;
|
|
132
142
|
height: $size;
|
|
143
|
+
background-color: var(--color--error--opposite);
|
|
144
|
+
color: var(--color--error);
|
|
133
145
|
|
|
134
146
|
[dir='ltr'] & {
|
|
135
147
|
top: 0;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
2
|
+
import mergeRefs from 'merge-refs';
|
|
2
3
|
import {
|
|
3
4
|
ChangeEventHandler,
|
|
4
|
-
createRef,
|
|
5
5
|
FocusEventHandler,
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
8
|
RefObject,
|
|
9
9
|
useEffect,
|
|
10
10
|
useMemo,
|
|
11
|
+
useRef,
|
|
11
12
|
} from 'react';
|
|
12
13
|
|
|
14
|
+
import { useMediaQuery } from 'hooks';
|
|
13
15
|
import { getTileSizes, noop } from 'lib';
|
|
16
|
+
import { EASE_OUT_CUBIC, TILE_APPEAR_DURATION, TILE_APPEAR_KEYFRAMES } from 'parameters';
|
|
14
17
|
import { selectLocale, useTypedSelector } from 'state';
|
|
15
18
|
|
|
16
19
|
import TilePure from './TilePure';
|
|
@@ -42,7 +45,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
42
45
|
character = '',
|
|
43
46
|
disabled,
|
|
44
47
|
highlighted,
|
|
45
|
-
inputRef
|
|
48
|
+
inputRef,
|
|
46
49
|
isBlank,
|
|
47
50
|
isValid,
|
|
48
51
|
placeholder,
|
|
@@ -59,21 +62,35 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
59
62
|
const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
|
|
60
63
|
const characterStyle = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
|
|
61
64
|
const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
|
|
62
|
-
const
|
|
65
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
66
|
+
const mergedRef = inputRef ? mergeRefs(ref, inputRef) : ref;
|
|
63
67
|
const isEmpty = !character || character === EMPTY_CELL;
|
|
64
|
-
const
|
|
68
|
+
const isLessThanXs = useMediaQuery('<xs');
|
|
69
|
+
const canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined' && !isLessThanXs;
|
|
65
70
|
const pointsFormatted = typeof points === 'number' ? points.toLocaleString(locale) : '';
|
|
66
71
|
|
|
67
72
|
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
68
|
-
|
|
73
|
+
ref.current?.select();
|
|
69
74
|
onKeyDown(event);
|
|
70
75
|
};
|
|
71
76
|
|
|
72
77
|
useEffect(() => {
|
|
73
|
-
if (autoFocus &&
|
|
74
|
-
|
|
78
|
+
if (autoFocus && ref.current) {
|
|
79
|
+
ref.current.focus();
|
|
75
80
|
}
|
|
76
|
-
}, [autoFocus,
|
|
81
|
+
}, [autoFocus, ref]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!ref.current?.parentElement || !character) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ref.current.parentElement.animate(TILE_APPEAR_KEYFRAMES, {
|
|
89
|
+
duration: TILE_APPEAR_DURATION,
|
|
90
|
+
easing: EASE_OUT_CUBIC,
|
|
91
|
+
fill: 'forwards',
|
|
92
|
+
});
|
|
93
|
+
}, [character]);
|
|
77
94
|
|
|
78
95
|
return (
|
|
79
96
|
<TilePure
|
|
@@ -85,7 +102,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
85
102
|
className={className}
|
|
86
103
|
disabled={disabled}
|
|
87
104
|
highlighted={highlighted}
|
|
88
|
-
inputRef={
|
|
105
|
+
inputRef={mergedRef}
|
|
89
106
|
isBlank={isBlank}
|
|
90
107
|
isValid={isValid}
|
|
91
108
|
placeholder={placeholder}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
8
|
memo,
|
|
9
|
-
|
|
9
|
+
Ref,
|
|
10
10
|
} from 'react';
|
|
11
11
|
|
|
12
12
|
import { ExclamationSquareFill } from 'icons';
|
|
@@ -22,7 +22,7 @@ interface Props {
|
|
|
22
22
|
className?: string;
|
|
23
23
|
disabled?: boolean;
|
|
24
24
|
highlighted?: boolean;
|
|
25
|
-
inputRef:
|
|
25
|
+
inputRef: Ref<HTMLInputElement>;
|
|
26
26
|
isBlank?: boolean;
|
|
27
27
|
isValid?: boolean;
|
|
28
28
|
placeholder?: string;
|
|
@@ -66,7 +66,6 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
66
66
|
[styles.disabled]: disabled,
|
|
67
67
|
[styles.empty]: !character,
|
|
68
68
|
[styles.highlighted]: highlighted,
|
|
69
|
-
[styles.invalid]: !isValid,
|
|
70
69
|
[styles.raised]: raised,
|
|
71
70
|
[styles.points1]: points === 1,
|
|
72
71
|
[styles.points2]: points === 2,
|
|
@@ -93,8 +92,14 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
93
92
|
onKeyDown={onKeyDown}
|
|
94
93
|
/>
|
|
95
94
|
|
|
95
|
+
{placeholder && (
|
|
96
|
+
<div className={styles.placeholder} style={characterStyle} tabIndex={-1}>
|
|
97
|
+
{placeholder}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
|
|
96
101
|
<div className={styles.character} style={characterStyle} tabIndex={-1}>
|
|
97
|
-
{character
|
|
102
|
+
{character}
|
|
98
103
|
</div>
|
|
99
104
|
|
|
100
105
|
{canShowPoints && (
|
|
@@ -10,9 +10,9 @@ $arrow-size: 4px;
|
|
|
10
10
|
padding: var(--spacing--s) var(--spacing--m);
|
|
11
11
|
box-shadow: var(--box-shadow);
|
|
12
12
|
border-radius: var(--border--radius);
|
|
13
|
-
background-color: var(--tooltip--background);
|
|
14
|
-
color: var(--tooltip--foreground);
|
|
15
|
-
z-index:
|
|
13
|
+
background-color: var(--color--tooltip--background);
|
|
14
|
+
color: var(--color--tooltip--foreground);
|
|
15
|
+
z-index: var(--z-index--tooltip);
|
|
16
16
|
|
|
17
17
|
&.top {
|
|
18
18
|
.arrow {
|
|
@@ -21,7 +21,7 @@ $arrow-size: 4px;
|
|
|
21
21
|
&::after {
|
|
22
22
|
left: 0;
|
|
23
23
|
bottom: 0;
|
|
24
|
-
border-top-color: var(--tooltip--background);
|
|
24
|
+
border-top-color: var(--color--tooltip--background);
|
|
25
25
|
border-bottom: none;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -34,7 +34,7 @@ $arrow-size: 4px;
|
|
|
34
34
|
&::after {
|
|
35
35
|
left: 0;
|
|
36
36
|
top: 0;
|
|
37
|
-
border-right-color: var(--tooltip--background);
|
|
37
|
+
border-right-color: var(--color--tooltip--background);
|
|
38
38
|
border-left: none;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -47,7 +47,7 @@ $arrow-size: 4px;
|
|
|
47
47
|
&::after {
|
|
48
48
|
top: 0;
|
|
49
49
|
left: 0;
|
|
50
|
-
border-bottom-color: var(--tooltip--background);
|
|
50
|
+
border-bottom-color: var(--color--tooltip--background);
|
|
51
51
|
border-top: none;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -60,7 +60,7 @@ $arrow-size: 4px;
|
|
|
60
60
|
&::after {
|
|
61
61
|
right: 0;
|
|
62
62
|
top: 0;
|
|
63
|
-
border-left-color: var(--tooltip--background);
|
|
63
|
+
border-left-color: var(--color--tooltip--background);
|
|
64
64
|
border-right: none;
|
|
65
65
|
}
|
|
66
66
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { default as Rack } from './Rack';
|
|
|
19
19
|
export { default as Radio } from './Radio';
|
|
20
20
|
export { default as Results } from './Results';
|
|
21
21
|
export { default as ResultsInput } from './ResultsInput';
|
|
22
|
+
export { default as SeoMessage } from './SeoMessage';
|
|
22
23
|
export { default as Sizer } from './Sizer';
|
|
23
24
|
export { default as Solver } from './Solver';
|
|
24
25
|
export { default as SplashScreen } from './SplashScreen';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { boardSlice, localStorage, selectBoard, useTypedSelector } from 'state';
|
|
|
7
7
|
const useLocalStorageBoard = (): void => {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
const board = useTypedSelector(selectBoard);
|
|
10
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
10
11
|
|
|
11
12
|
useEffectOnce(() => {
|
|
12
13
|
const persistedBoard = localStorage.getBoard();
|
|
@@ -14,13 +15,15 @@ const useLocalStorageBoard = (): void => {
|
|
|
14
15
|
if (persistedBoard) {
|
|
15
16
|
dispatch(boardSlice.actions.init(persistedBoard));
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
setIsLoaded(true);
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
useEffect(() => {
|
|
20
|
-
if (board) {
|
|
23
|
+
if (board && isLoaded) {
|
|
21
24
|
localStorage.setBoard(board);
|
|
22
25
|
}
|
|
23
|
-
}, [board]);
|
|
26
|
+
}, [board, isLoaded]);
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export default useLocalStorageBoard;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { localStorage, selectConfigId, settingsSlice, useTypedSelector } from 's
|
|
|
7
7
|
const useLocalStorageConfigId = (): void => {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
const configId = useTypedSelector(selectConfigId);
|
|
10
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
10
11
|
|
|
11
12
|
useEffectOnce(() => {
|
|
12
13
|
const persistedConfigId = localStorage.getConfigId();
|
|
@@ -14,13 +15,15 @@ const useLocalStorageConfigId = (): void => {
|
|
|
14
15
|
if (persistedConfigId) {
|
|
15
16
|
dispatch(settingsSlice.actions.init({ configId: persistedConfigId }));
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
setIsLoaded(true);
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
useEffect(() => {
|
|
20
|
-
if (configId) {
|
|
23
|
+
if (configId && isLoaded) {
|
|
21
24
|
localStorage.setConfigId(configId);
|
|
22
25
|
}
|
|
23
|
-
}, [configId]);
|
|
26
|
+
}, [configId, isLoaded]);
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export default useLocalStorageConfigId;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -8,6 +8,7 @@ import { localStorage, selectLocale, settingsSlice, useTypedSelector } from 'sta
|
|
|
8
8
|
const useLocalStorageLocale = (): void => {
|
|
9
9
|
const dispatch = useDispatch();
|
|
10
10
|
const locale = useTypedSelector(selectLocale);
|
|
11
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
11
12
|
|
|
12
13
|
useEffectOnce(() => {
|
|
13
14
|
const persistedLocale = localStorage.getLocale();
|
|
@@ -17,13 +18,15 @@ const useLocalStorageLocale = (): void => {
|
|
|
17
18
|
} else {
|
|
18
19
|
dispatch(settingsSlice.actions.init({ locale: detectLocale() }));
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
setIsLoaded(true);
|
|
20
23
|
});
|
|
21
24
|
|
|
22
25
|
useEffect(() => {
|
|
23
|
-
if (locale) {
|
|
26
|
+
if (locale && isLoaded) {
|
|
24
27
|
localStorage.setLocale(locale);
|
|
25
28
|
}
|
|
26
|
-
}, [locale]);
|
|
29
|
+
}, [locale, isLoaded]);
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
export default useLocalStorageLocale;
|