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.
Files changed (148) hide show
  1. package/babel.config.js +1 -1
  2. package/lerna.json +1 -1
  3. package/package.json +3 -3
  4. package/packages/configs/package-lock.json +4 -4
  5. package/packages/configs/package.json +3 -3
  6. package/packages/constants/package-lock.json +2 -2
  7. package/packages/constants/package.json +1 -1
  8. package/packages/dictionaries/package-lock.json +2 -2
  9. package/packages/dictionaries/package.json +4 -4
  10. package/packages/logger/package-lock.json +2 -2
  11. package/packages/logger/package.json +1 -1
  12. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  13. package/packages/scrabble-solver/.next/build-manifest.json +15 -21
  14. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  15. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  16. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  17. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  18. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  19. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  20. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  22. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  23. package/packages/scrabble-solver/.next/images-manifest.json +1 -1
  24. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  25. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  26. package/packages/scrabble-solver/.next/required-server-files.json +1 -1
  27. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  28. package/packages/scrabble-solver/.next/server/chunks/176.js +4603 -395
  29. package/packages/scrabble-solver/.next/server/chunks/664.js +27 -2414
  30. package/packages/scrabble-solver/.next/server/chunks/859.js +29 -1
  31. package/packages/scrabble-solver/.next/server/font-loader-manifest.js +1 -0
  32. package/packages/scrabble-solver/.next/server/font-loader-manifest.json +6 -0
  33. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  35. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  36. package/packages/scrabble-solver/.next/server/pages/500.html +1 -12
  37. package/packages/scrabble-solver/.next/server/pages/_app.js +176 -13
  38. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  39. package/packages/scrabble-solver/.next/server/pages/_document.js +1 -1
  40. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  41. package/packages/scrabble-solver/.next/server/pages/_error.js +133 -17
  42. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  43. package/packages/scrabble-solver/.next/server/pages/api/solve.js +16 -0
  44. package/packages/scrabble-solver/.next/server/pages/index.html +1 -9
  45. package/packages/scrabble-solver/.next/server/pages/index.js +52 -165
  46. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  47. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  48. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  49. package/packages/scrabble-solver/.next/static/chunks/main-74c4d6b2b5c362f3.js +1 -0
  50. package/packages/scrabble-solver/.next/static/chunks/pages/{404-67383848027ec49b.js → 404-d5ff00df1c687977.js} +1 -1
  51. package/packages/scrabble-solver/.next/static/chunks/pages/_app-52cb288dc680bdfe.js +28 -0
  52. package/packages/scrabble-solver/.next/static/chunks/pages/index-5c2544930e46c5ce.js +1 -0
  53. package/packages/scrabble-solver/.next/static/chunks/webpack-6ef43a8d4a395f49.js +1 -0
  54. package/packages/scrabble-solver/.next/static/css/ec4e47a6b1866fe5.css +1 -0
  55. package/packages/scrabble-solver/.next/static/css/f65b7b2a74f57c1c.css +2 -0
  56. package/packages/scrabble-solver/.next/static/fZRsz4P0gQ8Wgb9jP8eap/_buildManifest.js +1 -0
  57. package/packages/scrabble-solver/.next/trace +55 -55
  58. package/packages/scrabble-solver/package-lock.json +255 -122
  59. package/packages/scrabble-solver/package.json +11 -9
  60. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss +1 -1
  61. package/packages/scrabble-solver/src/components/Board/Board.module.scss +14 -0
  62. package/packages/scrabble-solver/src/components/Board/Board.tsx +117 -19
  63. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +7 -15
  64. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +64 -0
  65. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +68 -0
  66. package/packages/scrabble-solver/src/components/Board/components/Actions/index.ts +1 -0
  67. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +22 -165
  68. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +0 -37
  69. package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +5 -75
  70. package/packages/scrabble-solver/src/components/Board/components/index.ts +1 -0
  71. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +16 -16
  72. package/packages/scrabble-solver/src/components/Button/Button.module.scss +3 -3
  73. package/packages/scrabble-solver/src/components/Checkbox/Checkbox.tsx +1 -4
  74. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +28 -30
  75. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +3 -0
  76. package/packages/scrabble-solver/src/components/Key/Key.module.scss +1 -1
  77. package/packages/scrabble-solver/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +1 -1
  78. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +4 -2
  79. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +5 -0
  80. package/packages/scrabble-solver/src/components/Radio/Radio.tsx +1 -4
  81. package/packages/scrabble-solver/src/components/Results/Results.module.scss +2 -2
  82. package/packages/scrabble-solver/src/components/SeoMessage/SeoMessage.tsx +19 -0
  83. package/packages/scrabble-solver/src/components/SeoMessage/index.ts +1 -0
  84. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +0 -5
  85. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +1 -1
  86. package/packages/scrabble-solver/src/components/Solver/components/EmptyState/EmptyState.module.scss +1 -1
  87. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +9 -2
  88. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -1
  89. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +23 -11
  90. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +26 -9
  91. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +9 -4
  92. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss +7 -7
  93. package/packages/scrabble-solver/src/components/index.ts +1 -0
  94. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageBoard.ts +6 -3
  95. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +6 -3
  96. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageLocale.ts +6 -3
  97. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageRack.ts +6 -3
  98. package/packages/scrabble-solver/src/i18n/de.json +1 -0
  99. package/packages/scrabble-solver/src/i18n/en.json +1 -0
  100. package/packages/scrabble-solver/src/i18n/es.json +1 -0
  101. package/packages/scrabble-solver/src/i18n/fa.json +1 -0
  102. package/packages/scrabble-solver/src/i18n/fr.json +1 -0
  103. package/packages/scrabble-solver/src/i18n/pl.json +1 -0
  104. package/packages/scrabble-solver/src/icons/Flag.svg +2 -2
  105. package/packages/scrabble-solver/src/icons/FlagFill.svg +4 -0
  106. package/packages/scrabble-solver/src/icons/Square.svg +4 -0
  107. package/packages/scrabble-solver/src/icons/SquareFill.svg +4 -0
  108. package/packages/scrabble-solver/src/icons/index.ts +3 -0
  109. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
  110. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
  111. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -2
  112. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +1 -1
  113. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -2
  114. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +14 -24
  115. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +1 -2
  116. package/packages/scrabble-solver/src/pages/_app.tsx +9 -5
  117. package/packages/scrabble-solver/src/pages/index.module.scss +1 -2
  118. package/packages/scrabble-solver/src/pages/index.tsx +10 -8
  119. package/packages/scrabble-solver/src/parameters/index.ts +12 -0
  120. package/packages/scrabble-solver/src/state/slices/boardSlice.ts +5 -5
  121. package/packages/scrabble-solver/src/styles/animations.scss +10 -0
  122. package/packages/scrabble-solver/src/styles/global.scss +2 -2
  123. package/packages/scrabble-solver/src/styles/mixins.scss +60 -3
  124. package/packages/scrabble-solver/src/styles/variables.scss +43 -33
  125. package/packages/scrabble-solver/src/types/index.ts +1 -0
  126. package/packages/solver/package-lock.json +2 -2
  127. package/packages/solver/package.json +5 -5
  128. package/packages/types/package-lock.json +3 -3
  129. package/packages/types/package.json +2 -2
  130. package/packages/word-definitions/package-lock.json +2 -2
  131. package/packages/word-definitions/package.json +2 -2
  132. package/packages/word-lists/package-lock.json +2 -2
  133. package/packages/word-lists/package.json +2 -2
  134. package/packages/scrabble-solver/.next/server/chunks/210.js +0 -122
  135. package/packages/scrabble-solver/.next/server/chunks/579.js +0 -3925
  136. package/packages/scrabble-solver/.next/server/chunks/676.js +0 -32
  137. package/packages/scrabble-solver/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +0 -1
  138. package/packages/scrabble-solver/.next/static/chunks/490-d29992f1c264d70e.js +0 -5
  139. package/packages/scrabble-solver/.next/static/chunks/509-6ad4482d4351452c.js +0 -1
  140. package/packages/scrabble-solver/.next/static/chunks/main-f11614d8aa7ee555.js +0 -1
  141. package/packages/scrabble-solver/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +0 -24
  142. package/packages/scrabble-solver/.next/static/chunks/pages/index-146039f501e49c08.js +0 -1
  143. package/packages/scrabble-solver/.next/static/chunks/webpack-59c5c889f52620d6.js +0 -1
  144. package/packages/scrabble-solver/.next/static/css/4482c4a0064d3807.css +0 -1
  145. package/packages/scrabble-solver/.next/static/css/78e42ad01f580f64.css +0 -1
  146. package/packages/scrabble-solver/.next/static/css/9d1013ec684361b9.css +0 -1
  147. package/packages/scrabble-solver/src/components/Board/components/Cell/Button.tsx +0 -32
  148. /package/packages/scrabble-solver/.next/static/{6RggBFm8kHrh-k1-CG3um → fZRsz4P0gQ8Wgb9jP8eap}/_ssgManifest.js +0 -0
@@ -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: black;
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: white;
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: ref,
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 inputRef = useMemo<RefObject<HTMLInputElement>>(() => ref || createRef(), [ref]);
65
+ const ref = useRef<HTMLInputElement>(null);
66
+ const mergedRef = inputRef ? mergeRefs(ref, inputRef) : ref;
63
67
  const isEmpty = !character || character === EMPTY_CELL;
64
- const canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined';
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
- inputRef.current?.select();
73
+ ref.current?.select();
69
74
  onKeyDown(event);
70
75
  };
71
76
 
72
77
  useEffect(() => {
73
- if (autoFocus && inputRef.current) {
74
- inputRef.current.focus();
78
+ if (autoFocus && ref.current) {
79
+ ref.current.focus();
75
80
  }
76
- }, [autoFocus, inputRef]);
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={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
- RefObject,
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: RefObject<HTMLInputElement>;
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 || placeholder}
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: 200;
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
  }
@@ -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;
@@ -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, rackSlice, selectRack, useTypedSelector } from 'state';
7
7
  const useLocalStorageRack = (): void => {
8
8
  const dispatch = useDispatch();
9
9
  const rack = useTypedSelector(selectRack);
10
+ const [isLoaded, setIsLoaded] = useState(false);
10
11
 
11
12
  useEffectOnce(() => {
12
13
  const persistedRack = localStorage.getRack();
@@ -14,13 +15,15 @@ const useLocalStorageRack = (): void => {
14
15
  if (persistedRack) {
15
16
  dispatch(rackSlice.actions.init(persistedRack));
16
17
  }
18
+
19
+ setIsLoaded(true);
17
20
  });
18
21
 
19
22
  useEffect(() => {
20
- if (rack) {
23
+ if (rack && isLoaded) {
21
24
  localStorage.setRack(rack);
22
25
  }
23
- }, [rack]);
26
+ }, [isLoaded, rack]);
24
27
  };
25
28
 
26
29
  export default useLocalStorageRack;
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "Dieses Wort ist nicht erlaubt.",
23
23
  "dictionary.empty-state.uninitialized": "Die Wörterbuchdefinition des letzten markierten Wortes wird hier angezeigt.",
24
24
  "dictionary.input.placeholder": "Durchsuche Wörterbuch...",
25
+ "dictionary.input.title": "Durch Kommas getrennte Wörter",
25
26
  "empty-state.error": "Fehler",
26
27
  "empty-state.info": "Info",
27
28
  "empty-state.success": "Juhuu!",
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "This word is not allowed.",
23
23
  "dictionary.empty-state.uninitialized": "Dictionary definition of the most recently highlighted word will be shown here.",
24
24
  "dictionary.input.placeholder": "Search dictionary...",
25
+ "dictionary.input.title": "Comma-separated words",
25
26
  "empty-state.error": "Error",
26
27
  "empty-state.info": "Info",
27
28
  "empty-state.success": "Yeah!",
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "Esta palabra no es aceptable.",
23
23
  "dictionary.empty-state.uninitialized": "Aquí se mostrará la definición del diccionario de la última palabra resaltada.",
24
24
  "dictionary.input.placeholder": "Busca el diccionario...",
25
+ "dictionary.input.title": "Palabras separadas por comas",
25
26
  "empty-state.error": "Error",
26
27
  "empty-state.info": "Info",
27
28
  "empty-state.success": "Sí!",
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "این کلمه مجاز نیست.",
23
23
  "dictionary.empty-state.uninitialized": "معنی لغت اینجا نمایش داده خواهد شد.",
24
24
  "dictionary.input.placeholder": "جستجو در فرهنگ لغت ...",
25
+ "dictionary.input.title": "کلمات جدا شده با کاما",
25
26
  "empty-state.error": "خطا",
26
27
  "empty-state.info": "اطلاعات",
27
28
  "empty-state.success": "حله!",
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
23
23
  "dictionary.empty-state.uninitialized": "La définition dictionaire du dernier mot surligné sera affichée ici.",
24
24
  "dictionary.input.placeholder": "Rechercher dans le dictionnaire...",
25
+ "dictionary.input.title": "Mots séparées par des virgules",
25
26
  "empty-state.error": "Erreur",
26
27
  "empty-state.info": "Info",
27
28
  "empty-state.success": "Ouais!",
@@ -22,6 +22,7 @@
22
22
  "dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
23
23
  "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona słownikowa definicja ostatnio podświetlonego słowa.",
24
24
  "dictionary.input.placeholder": "Szukaj w słowniku...",
25
+ "dictionary.input.title": "Słowa rozdzielone przecinkiem",
25
26
  "empty-state.error": "Błąd",
26
27
  "empty-state.info": "Info",
27
28
  "empty-state.success": "Hurra!",
@@ -1,4 +1,4 @@
1
- <!-- https://icons.getbootstrap.com/icons/flag-fill/ -->
1
+ <!-- https://icons.getbootstrap.com/icons/flag/ -->
2
2
  <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
- <path d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12.435 12.435 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A19.626 19.626 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a19.587 19.587 0 0 0 1.349-.476l.019-.007.004-.002h.001" fill="currentColor" />
3
+ <path d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12.435 12.435 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A19.626 19.626 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a19.587 19.587 0 0 0 1.349-.476l.019-.007.004-.002h.001M14 1.221c-.22.078-.48.167-.766.255-.81.252-1.872.523-2.734.523-.886 0-1.592-.286-2.203-.534l-.008-.003C7.662 1.21 7.139 1 6.5 1c-.669 0-1.606.229-2.415.478A21.294 21.294 0 0 0 3 1.845v6.433c.22-.078.48-.167.766-.255C4.576 7.77 5.638 7.5 6.5 7.5c.847 0 1.548.28 2.158.525l.028.01C9.32 8.29 9.86 8.5 10.5 8.5c.668 0 1.606-.229 2.415-.478A21.317 21.317 0 0 0 14 7.655V1.222z" fill="currentColor" />
4
4
  </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/flag-fill/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12.435 12.435 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A19.626 19.626 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a19.587 19.587 0 0 0 1.349-.476l.019-.007.004-.002h.001" fill="currentColor" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/square/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z" fill="currentColor" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/square-fill/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2z" fill="currentColor" />
4
+ </svg>
@@ -19,6 +19,7 @@ export { default as Eraser } from './Eraser.svg';
19
19
  export { default as ExclamationSquareFill } from './ExclamationSquareFill.svg';
20
20
  export { default as ExclamationTriangleFill } from './ExclamationTriangleFill.svg';
21
21
  export { default as Flag } from './Flag.svg';
22
+ export { default as FlagFill } from './FlagFill.svg';
22
23
  export { default as FlagEs } from './FlagEs.svg';
23
24
  export { default as FlagFa } from './FlagFa.svg';
24
25
  export { default as FlagFr } from './FlagFr.svg';
@@ -34,4 +35,6 @@ export { default as Sack } from './Sack.svg';
34
35
  export { default as Search } from './Search.svg';
35
36
  export { default as SortDown } from './SortDown.svg';
36
37
  export { default as SortUp } from './SortUp.svg';
38
+ export { default as Square } from './Square.svg';
39
+ export { default as SquareFill } from './SquareFill.svg';
37
40
  export { default as Star } from './Star.svg';
@@ -5,7 +5,7 @@
5
5
  transition: var(--transition);
6
6
 
7
7
  &.finished {
8
- opacity: 0.2;
8
+ opacity: var(--opacity--disabled);
9
9
  }
10
10
 
11
11
  &.overused {
@@ -3,5 +3,5 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  height: 100%;
6
- background-color: white;
6
+ background-color: var(--color--white);
7
7
  }
@@ -40,12 +40,11 @@ const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, disabled }
40
40
 
41
41
  return (
42
42
  <div className={className}>
43
- {options.map((option, index) => (
43
+ {options.map((option) => (
44
44
  <Radio
45
45
  checked={value === parseValue(option.value)}
46
46
  className={styles.option}
47
47
  disabled={disabled}
48
- id={`autoGroupTiles-${index}`}
49
48
  key={option.value}
50
49
  name="autoGroupTiles"
51
50
  value={option.value}
@@ -9,5 +9,5 @@
9
9
  .label {
10
10
  display: flex;
11
11
  align-items: center;
12
- font-family: 'Open Sans', sans-serif;
12
+ font-family: var(--font--family--latin);
13
13
  }
@@ -22,12 +22,11 @@ const ConfigSetting: FunctionComponent<Props> = ({ className, disabled }) => {
22
22
 
23
23
  return (
24
24
  <div className={className}>
25
- {options.map((option, index) => (
25
+ {options.map((option) => (
26
26
  <Radio
27
27
  checked={configId === option.value}
28
28
  className={styles.option}
29
29
  disabled={disabled}
30
- id={`configId-${index}`}
31
30
  key={option.value}
32
31
  name="configId"
33
32
  value={option.value}
@@ -1,17 +1,8 @@
1
1
  @import 'styles/mixins';
2
2
 
3
3
  .option {
4
- margin-bottom: var(--spacing--m);
5
-
6
- &:last-child {
7
- margin-bottom: 0;
8
- }
9
-
10
- &:hover,
11
- &.checked {
12
- .flag {
13
- box-shadow: var(--box-shadow);
14
- }
4
+ & + & {
5
+ margin-top: var(--spacing--m);
15
6
  }
16
7
  }
17
8
 
@@ -19,10 +10,10 @@
19
10
  display: flex;
20
11
  align-items: center;
21
12
  gap: var(--spacing--l);
22
- font-family: 'Open Sans', sans-serif;
13
+ font-family: var(--font--family--latin);
23
14
 
24
15
  &.fa {
25
- font-family: 'Vazirmatn', 'Open Sans', sans-serif;
16
+ font-family: var(--font--family--arabic);
26
17
  }
27
18
  }
28
19
 
@@ -30,17 +21,16 @@
30
21
  $height: 32px;
31
22
 
32
23
  height: $height;
33
- box-shadow: var(--box-shadow--null);
34
24
  transition: var(--transition);
35
25
 
36
- &.es {
37
- $aspect-ratio: 1.5;
26
+ &.de {
27
+ $aspect-ratio: 1.6;
38
28
 
39
29
  width: $height * $aspect-ratio;
40
30
  }
41
31
 
42
- &.fr {
43
- $aspect-ratio: 1.6;
32
+ &.es {
33
+ $aspect-ratio: 1.5;
44
34
 
45
35
  width: $height * $aspect-ratio;
46
36
  }
@@ -51,6 +41,12 @@
51
41
  width: $height * $aspect-ratio;
52
42
  }
53
43
 
44
+ &.fr {
45
+ $aspect-ratio: 1.6;
46
+
47
+ width: $height * $aspect-ratio;
48
+ }
49
+
54
50
  &.gb {
55
51
  $aspect-ratio: 2;
56
52
 
@@ -68,10 +64,4 @@
68
64
 
69
65
  width: $height * $aspect-ratio;
70
66
  }
71
-
72
- &.de {
73
- $aspect-ratio: 1.6;
74
-
75
- width: $height * $aspect-ratio;
76
- }
77
67
  }
@@ -25,14 +25,13 @@ const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
25
25
 
26
26
  return (
27
27
  <div className={className}>
28
- {options.map(({ Icon, ...option }, index) => (
28
+ {options.map(({ Icon, ...option }) => (
29
29
  <Radio
30
30
  checked={locale === option.value}
31
31
  className={classNames(styles.option, className, {
32
32
  [styles.checked]: locale === option.value,
33
33
  })}
34
34
  disabled={disabled}
35
- id={`locale-${index}`}
36
35
  key={option.value}
37
36
  name="locale"
38
37
  value={option.value}