@scrabble-solver/scrabble-solver 2.11.1 → 2.11.3

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 (127) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/210.js +109 -0
  16. package/.next/server/chunks/277.js +380 -258
  17. package/.next/server/chunks/44.js +47 -0
  18. package/.next/server/chunks/987.js +91 -0
  19. package/.next/server/middleware-build-manifest.js +1 -1
  20. package/.next/server/pages/404.html +2 -2
  21. package/.next/server/pages/404.js.nft.json +1 -1
  22. package/.next/server/pages/500.html +1 -1
  23. package/.next/server/pages/_app.js +1 -73
  24. package/.next/server/pages/_app.js.nft.json +1 -1
  25. package/.next/server/pages/_document.js.nft.json +1 -1
  26. package/.next/server/pages/api/dictionary/[locale]/[word].js +3 -4
  27. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  28. package/.next/server/pages/api/dictionary/[locale].js +3 -4
  29. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  30. package/.next/server/pages/api/solve.js +38 -13
  31. package/.next/server/pages/api/solve.js.nft.json +1 -1
  32. package/.next/server/pages/api/verify.js +5 -5
  33. package/.next/server/pages/api/verify.js.nft.json +1 -1
  34. package/.next/server/pages/api/visit.js +3 -4
  35. package/.next/server/pages/api/visit.js.nft.json +1 -1
  36. package/.next/server/pages/index.html +1 -1
  37. package/.next/server/pages/index.js +141 -237
  38. package/.next/server/pages/index.js.nft.json +1 -1
  39. package/.next/server/pages/index.json +1 -1
  40. package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_buildManifest.js +1 -1
  41. package/.next/static/chunks/pages/_app-21c83ddb81fc09d0.js +28 -0
  42. package/.next/static/chunks/pages/index-0858deea02b2a417.js +1 -0
  43. package/.next/static/css/885da289cec275b3.css +1 -0
  44. package/.next/static/css/ea1c8134fe9a143e.css +2 -0
  45. package/.next/trace +53 -53
  46. package/package.json +9 -9
  47. package/src/api/index.ts +1 -0
  48. package/src/api/isCellValid.ts +3 -2
  49. package/src/api/isCharacterValid.ts +13 -0
  50. package/src/components/Board/components/Cell/Cell.module.scss +10 -2
  51. package/src/components/Board/hooks/useGrid.ts +1 -2
  52. package/src/components/Board/lib/getPositionInGrid.ts +1 -1
  53. package/src/components/Button/Button.module.scss +14 -1
  54. package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
  55. package/src/components/Modal/Modal.module.scss +21 -1
  56. package/src/components/Modal/Modal.tsx +4 -1
  57. package/src/components/NotFound/NotFound.module.scss +13 -4
  58. package/src/components/NotFound/NotFound.tsx +4 -7
  59. package/src/components/Rack/Rack.tsx +3 -1
  60. package/src/components/Results/HeaderButton.tsx +5 -12
  61. package/src/components/Results/Result.tsx +5 -3
  62. package/src/components/Results/Results.module.scss +13 -1
  63. package/src/components/Results/Results.tsx +29 -43
  64. package/src/components/Results/types.ts +1 -1
  65. package/src/components/Solver/Solver.module.scss +4 -0
  66. package/src/components/Solver/Solver.tsx +9 -12
  67. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
  68. package/src/components/Tile/Tile.module.scss +53 -79
  69. package/src/components/Tile/Tile.tsx +1 -3
  70. package/src/components/Tile/TilePure.tsx +4 -14
  71. package/src/hooks/useAppLayout.ts +3 -1
  72. package/src/i18n/constants.ts +65 -0
  73. package/src/i18n/de.json +1 -1
  74. package/src/i18n/en.json +1 -1
  75. package/src/i18n/es.json +1 -1
  76. package/src/i18n/fa.json +1 -1
  77. package/src/i18n/fr.json +1 -1
  78. package/src/i18n/i18n.module.scss +27 -0
  79. package/src/i18n/pl.json +1 -1
  80. package/src/icons/DashCircleFill.svg +1 -0
  81. package/src/icons/EyeFill.svg +5 -0
  82. package/src/icons/index.ts +3 -2
  83. package/src/lib/createRegExp.ts +13 -0
  84. package/src/lib/groupResults.ts +38 -0
  85. package/src/lib/guessLocale.ts +22 -0
  86. package/src/lib/index.ts +4 -1
  87. package/src/lib/sortResults.ts +6 -10
  88. package/src/modals/DictionaryModal/DictionaryModal.module.scss +0 -1
  89. package/src/modals/MenuModal/MenuModal.module.scss +23 -0
  90. package/src/modals/MenuModal/MenuModal.tsx +8 -2
  91. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -1
  92. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +8 -0
  93. package/src/modals/ResultsModal/ResultsModal.module.scss +2 -3
  94. package/src/modals/ResultsModal/ResultsModal.tsx +47 -11
  95. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +3 -44
  96. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +4 -2
  97. package/src/pages/api/dictionary/[locale]/[word].ts +0 -1
  98. package/src/pages/api/dictionary/[locale]/index.ts +0 -1
  99. package/src/pages/api/solve.ts +3 -3
  100. package/src/pages/api/verify.ts +0 -1
  101. package/src/pages/api/visit.ts +0 -1
  102. package/src/pages/index.tsx +13 -15
  103. package/src/parameters/index.ts +2 -2
  104. package/src/state/sagas.ts +1 -0
  105. package/src/state/selectors.ts +37 -37
  106. package/src/state/slices/boardInitialState.ts +3 -1
  107. package/src/state/slices/cellFilterInitialState.ts +3 -3
  108. package/src/state/slices/cellFilterSlice.ts +3 -1
  109. package/src/state/slices/dictionaryInitialState.ts +2 -2
  110. package/src/state/slices/rackInitialState.ts +3 -1
  111. package/src/state/slices/resultsInitialState.ts +11 -4
  112. package/src/state/slices/settingsInitialState.ts +10 -21
  113. package/src/state/slices/solveInitialState.ts +2 -2
  114. package/src/state/slices/solveSlice.ts +2 -0
  115. package/src/state/slices/verifyInitialState.ts +13 -4
  116. package/src/styles/mixins.scss +5 -1
  117. package/src/styles/variables.scss +13 -0
  118. package/src/types/index.ts +11 -2
  119. package/.next/server/chunks/417.js +0 -221
  120. package/.next/server/chunks/664.js +0 -621
  121. package/.next/static/chunks/pages/_app-270526803bc274eb.js +0 -28
  122. package/.next/static/chunks/pages/index-c6e7754ccf3532df.js +0 -1
  123. package/.next/static/css/ad39b36eab07e613.css +0 -1
  124. package/.next/static/css/e5803e581e4c0451.css +0 -2
  125. package/src/components/Board/types/index.ts +0 -4
  126. package/src/modals/SettingsModal/components/LocaleSetting/options.ts +0 -68
  127. /package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.11.1",
3
+ "version": "2.11.3",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,13 +31,13 @@
31
31
  "@floating-ui/react": "^0.20.1",
32
32
  "@kamilmielnik/trie": "^2.0.1",
33
33
  "@reduxjs/toolkit": "^1.9.3",
34
- "@scrabble-solver/configs": "^2.11.1",
35
- "@scrabble-solver/constants": "^2.11.1",
36
- "@scrabble-solver/dictionaries": "^2.11.1",
37
- "@scrabble-solver/logger": "^2.11.1",
38
- "@scrabble-solver/solver": "^2.11.1",
39
- "@scrabble-solver/types": "^2.11.1",
40
- "@scrabble-solver/word-definitions": "^2.11.1",
34
+ "@scrabble-solver/configs": "^2.11.3",
35
+ "@scrabble-solver/constants": "^2.11.3",
36
+ "@scrabble-solver/dictionaries": "^2.11.3",
37
+ "@scrabble-solver/logger": "^2.11.3",
38
+ "@scrabble-solver/solver": "^2.11.3",
39
+ "@scrabble-solver/types": "^2.11.3",
40
+ "@scrabble-solver/word-definitions": "^2.11.3",
41
41
  "classnames": "^2.3.2",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
@@ -72,5 +72,5 @@
72
72
  "sass": "^1.59.2",
73
73
  "workbox-webpack-plugin": "^6.5.4"
74
74
  },
75
- "gitHead": "8dea3cee4e6f902ab8d1ae2339dd1e48b11db39d"
75
+ "gitHead": "693e06a0f1375268d31d12c1a24924bf5ca3c267"
76
76
  }
package/src/api/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as getServerLoggingData } from './getServerLoggingData';
2
2
  export { default as isBoardValid } from './isBoardValid';
3
3
  export { default as isCellValid } from './isCellValid';
4
+ export { default as isCharacterValid } from './isCharacterValid';
4
5
  export { default as isRowValid } from './isRowValid';
@@ -1,6 +1,7 @@
1
- import { BLANK } from '@scrabble-solver/constants';
2
1
  import { CellJson, Config } from '@scrabble-solver/types';
3
2
 
3
+ import isCharacterValid from './isCharacterValid';
4
+
4
5
  const isCellValid = (cell: CellJson, config: Config): boolean => {
5
6
  const { isEmpty, tile, x, y } = cell;
6
7
 
@@ -16,7 +17,7 @@ const isCellValid = (cell: CellJson, config: Config): boolean => {
16
17
  return false;
17
18
  }
18
19
 
19
- if (tile !== null && !config.hasCharacter(tile.character) && tile.character !== BLANK) {
20
+ if (tile !== null && !isCharacterValid(tile.character)) {
20
21
  return false;
21
22
  }
22
23
 
@@ -0,0 +1,13 @@
1
+ const TWO_TILE_CHARACTER_LENGTH = 2;
2
+ const MAX_CHARACTER_LENGTH = TWO_TILE_CHARACTER_LENGTH;
3
+
4
+ const isCharacterValid = (character: string): boolean => {
5
+ /*
6
+ * We could be strict here and check whether config.hasCharacter(character) || character === BLANK
7
+ * but since this case won't really affect/break solving, we don't need to worry about it.
8
+ * It's better to display an empty state than error state in UI, so this is a sanity check only.
9
+ */
10
+ return character.length !== 0 && character.length <= MAX_CHARACTER_LENGTH;
11
+ };
12
+
13
+ export default isCharacterValid;
@@ -6,6 +6,7 @@
6
6
 
7
7
  position: relative;
8
8
  display: table-cell;
9
+ vertical-align: middle;
9
10
  background-color: var(--color--white);
10
11
  border-bottom: var(--border--width) dotted var(--border--color--light);
11
12
  transition: var(--transition);
@@ -35,42 +36,49 @@
35
36
 
36
37
  &.bonusStart {
37
38
  &::before {
39
+ content: '';
38
40
  background-color: var(--color--bonus--start);
39
41
  }
40
42
  }
41
43
 
42
44
  &.bonusCharacter1 {
43
45
  &::before {
46
+ content: '';
44
47
  background-color: var(--color--bonus--character--1);
45
48
  }
46
49
  }
47
50
 
48
51
  &.bonusCharacter2 {
49
52
  &::before {
53
+ content: '';
50
54
  background-color: var(--color--bonus--character--2);
51
55
  }
52
56
  }
53
57
 
54
58
  &.bonusCharacter3 {
55
59
  &::before {
60
+ content: '';
56
61
  background-color: var(--color--bonus--character--3);
57
62
  }
58
63
  }
59
64
 
60
65
  &.bonusCharacter5 {
61
66
  &::before {
67
+ content: '';
62
68
  background-color: var(--color--bonus--character--5);
63
69
  }
64
70
  }
65
71
 
66
72
  &.bonusCharacterMultiplier2 {
67
73
  &::before {
74
+ content: '';
68
75
  background-color: var(--color--bonus--character-multiplier--2);
69
76
  }
70
77
  }
71
78
 
72
79
  &.bonusCharacterMultiplier3 {
73
80
  &::before {
81
+ content: '';
74
82
  background-color: var(--color--bonus--character-multiplier--3);
75
83
  }
76
84
  }
@@ -91,7 +99,7 @@
91
99
 
92
100
  &.filtered {
93
101
  &::before {
94
- content: ' ';
102
+ content: '';
95
103
  background-color: var(--color--foreground--secondary);
96
104
  }
97
105
  }
@@ -113,7 +121,6 @@
113
121
  font-weight: bold;
114
122
  color: var(--color--white);
115
123
  transition: var(--transition);
116
- content: ' ';
117
124
 
118
125
  [lang='fa-IR'] & {
119
126
  font-family: var(--font--family--latin);
@@ -166,6 +173,7 @@
166
173
  }
167
174
  }
168
175
  }
176
+
169
177
  .icon {
170
178
  $size: 40%;
171
179
 
@@ -19,10 +19,9 @@ import { useLatest } from 'react-use';
19
19
  import { LOCALE_FEATURES } from 'i18n';
20
20
  import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue } from 'lib';
21
21
  import { boardSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
22
- import { Direction } from 'types';
22
+ import { Direction, Point } from 'types';
23
23
 
24
24
  import { getPositionInGrid } from '../lib';
25
- import { Point } from '../types';
26
25
 
27
26
  const toggleDirection = (direction: Direction) => (direction === 'vertical' ? 'horizontal' : 'vertical');
28
27
 
@@ -1,4 +1,4 @@
1
- import { Point } from '../types';
1
+ import { Point } from 'types';
2
2
 
3
3
  const getPositionInGrid = <T>(grid: T[][], constraint: (value: T) => boolean): Point | undefined => {
4
4
  for (let y = 0; y < grid.length; ++y) {
@@ -60,16 +60,29 @@
60
60
  .content {
61
61
  display: flex;
62
62
  align-items: center;
63
- gap: 20px;
63
+ gap: var(--spacing--l);
64
+
64
65
  }
65
66
 
66
67
  .icon {
68
+ flex: 0 0 auto;
67
69
  width: var(--button--icon--size);
68
70
  height: var(--button--icon--size);
69
71
  transition: var(--transition);
70
72
  }
71
73
 
72
74
  .label {
75
+ @include ellipsis;
76
+
77
+ flex: 1;
73
78
  font-size: var(--font--size--h3);
74
79
  transition: var(--transition);
80
+
81
+ [dir='ltr'] & {
82
+ text-align: left;
83
+ }
84
+
85
+ [dir='rtl'] & {
86
+ text-align: right;
87
+ }
75
88
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  $loading-duration: 250ms;
4
4
  $loaded-duration: 100ms;
5
- $hiding-duration: 200ms;
5
+ $hiding-duration: 150ms;
6
6
 
7
7
  .logoSplashScreen {
8
8
  display: flex;
@@ -16,18 +16,21 @@ $hiding-duration: 200ms;
16
16
  animation-delay: $loading-duration + $loaded-duration;
17
17
  animation-duration: $hiding-duration;
18
18
  animation-fill-mode: forwards;
19
+ animation-timing-function: linear;
19
20
 
20
21
  .logos {
21
22
  animation: pulse;
22
23
  animation-delay: $loading-duration + $loaded-duration;
23
24
  animation-duration: $hiding-duration;
24
25
  animation-fill-mode: forwards;
26
+ animation-timing-function: linear;
25
27
  }
26
28
 
27
29
  .logoColor {
28
30
  animation: progress;
29
31
  animation-duration: $loading-duration;
30
32
  animation-fill-mode: forwards;
33
+ animation-timing-function: linear;
31
34
  }
32
35
  }
33
36
  }
@@ -89,6 +89,7 @@
89
89
  z-index: 1;
90
90
  flex: 0 0 auto;
91
91
  padding: var(--spacing--l);
92
+ padding-bottom: 0;
92
93
  background-color: var(--color--background);
93
94
  }
94
95
 
@@ -117,6 +118,25 @@
117
118
  position: relative;
118
119
  flex: 1;
119
120
  min-height: 0;
120
- padding: var(--spacing--l);
121
121
  overflow: auto;
122
+ padding: var(--spacing--l);
123
+ }
124
+
125
+ .footer {
126
+ flex: 0 0 auto;
127
+ display: flex;
128
+ flex-direction: row-reverse;
129
+ justify-content: space-between;
130
+ gap: var(--spacing--l);
131
+ padding: var(--spacing--l);
132
+ padding-top: 0;
133
+
134
+ @include media('>s') {
135
+ display: none;
136
+ }
137
+
138
+ > * {
139
+ min-width: 0;
140
+ flex: 1;
141
+ }
122
142
  }
@@ -15,12 +15,13 @@ import styles from './Modal.module.scss';
15
15
  export interface Props {
16
16
  children: ReactNode;
17
17
  className?: string;
18
+ footer?: ReactNode;
18
19
  isOpen: boolean;
19
20
  title: string;
20
21
  onClose: () => void;
21
22
  }
22
23
 
23
- const Modal: FunctionComponent<Props> = ({ children, className, isOpen, title, onClose }) => {
24
+ const Modal: FunctionComponent<Props> = ({ children, className, footer, isOpen, title, onClose }) => {
24
25
  const translate = useTranslate();
25
26
  const [shouldReturnFocusAfterClose, setShouldReturnFocusAfterClose] = useState(true);
26
27
 
@@ -68,6 +69,8 @@ const Modal: FunctionComponent<Props> = ({ children, className, isOpen, title, o
68
69
  </div>
69
70
 
70
71
  <div className={styles.content}>{children}</div>
72
+
73
+ {footer && <div className={styles.footer}>{footer}</div>}
71
74
  </div>
72
75
  </ReactModal>
73
76
  );
@@ -1,3 +1,5 @@
1
+ @import 'styles/mixins';
2
+
1
3
  .notFound {
2
4
  display: flex;
3
5
  align-items: center;
@@ -11,18 +13,25 @@
11
13
  align-items: center;
12
14
  justify-content: center;
13
15
  flex-direction: column;
14
- padding: var(--spacing--xxl);
16
+ padding: var(--spacing--l);
15
17
  }
16
18
 
17
19
  .icon {
18
- $size: 204px;
20
+ --size: 200px;
19
21
 
20
- height: $size;
21
- width: $size;
22
+ height: var(--size);
23
+ width: var(--size);
22
24
  margin-bottom: var(--spacing--xxl);
23
25
  color: var(--color--red);
26
+
27
+ @include media('<xs') {
28
+ --size: 50%;
29
+
30
+ margin-bottom: var(--spacing--xl);
31
+ }
24
32
  }
25
33
 
26
34
  .tiles {
35
+ max-width: 100%;
27
36
  height: 60px;
28
37
  }
@@ -1,4 +1,3 @@
1
- import Link from 'next/link';
2
1
  import { FunctionComponent } from 'react';
3
2
 
4
3
  import { DashCircleFill } from 'icons';
@@ -11,12 +10,10 @@ const CONTENT = [['HTTP', '404']];
11
10
 
12
11
  const NotFound: FunctionComponent = () => (
13
12
  <div className={styles.notFound}>
14
- <Link href="/">
15
- <a className={styles.link}>
16
- <DashCircleFill className={styles.icon} />
17
- <PlainTiles className={styles.tiles} content={CONTENT} />
18
- </a>
19
- </Link>
13
+ <a className={styles.link} href="/">
14
+ <DashCircleFill className={styles.icon} />
15
+ <PlainTiles className={styles.tiles} content={CONTENT} />
16
+ </a>
20
17
  </div>
21
18
  );
22
19
 
@@ -8,6 +8,7 @@ import {
8
8
  createKeyboardNavigation,
9
9
  extractCharacters,
10
10
  extractInputValue,
11
+ getTileSizes,
11
12
  zipCharactersAndTiles,
12
13
  } from 'lib';
13
14
  import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
@@ -31,6 +32,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
31
32
  const tilesRefs = useMemo(() => createArray(tilesCount).map(() => createRef<HTMLInputElement>()), [tilesCount]);
32
33
  const activeIndexRef = useRef<number>();
33
34
  const { direction } = LOCALE_FEATURES[locale];
35
+ const { tileFontSize } = getTileSizes(tileSize);
34
36
 
35
37
  const changeActiveIndex = useCallback(
36
38
  (offset: number) => {
@@ -110,7 +112,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
110
112
  }, [changeActiveIndex, config, direction]);
111
113
 
112
114
  return (
113
- <div className={classNames(styles.rack, className)} onPaste={handlePaste}>
115
+ <div className={classNames(styles.rack, className)} style={{ fontSize: tileFontSize }} onPaste={handlePaste}>
114
116
  {tiles.map(({ character, tile }, index) => (
115
117
  <RackTile
116
118
  activeIndexRef={activeIndexRef}
@@ -3,13 +3,7 @@ import { ReactElement } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
5
  import { SortDown, SortUp } from 'icons';
6
- import {
7
- resultsSlice,
8
- selectResultsSortColumn,
9
- selectResultsSortDirection,
10
- useTranslate,
11
- useTypedSelector,
12
- } from 'state';
6
+ import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
13
7
  import { ResultColumn, SortDirection } from 'types';
14
8
 
15
9
  import { useTooltip } from '../Tooltip';
@@ -24,8 +18,7 @@ interface Props {
24
18
  const HeaderButton = ({ column }: Props): ReactElement => {
25
19
  const dispatch = useDispatch();
26
20
  const translate = useTranslate();
27
- const sortColumn = useTypedSelector(selectResultsSortColumn);
28
- const sortDirection = useTypedSelector(selectResultsSortDirection);
21
+ const sort = useTypedSelector(selectResultsSort);
29
22
  const triggerProps = useTooltip(translate(column.translationKey));
30
23
 
31
24
  const handleOrderChange = (columnId: ResultColumn) => {
@@ -44,10 +37,10 @@ const HeaderButton = ({ column }: Props): ReactElement => {
44
37
  <span className={styles.cell}>
45
38
  <span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
46
39
 
47
- {sortColumn === column.id && (
40
+ {sort.column === column.id && (
48
41
  <>
49
- {sortDirection === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
50
- {sortDirection === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
42
+ {sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
43
+ {sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
51
44
  </>
52
45
  )}
53
46
  </span>
@@ -3,7 +3,7 @@ import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useR
3
3
 
4
4
  import { LOCALE_FEATURES } from 'i18n';
5
5
  import { noop } from 'lib';
6
- import { selectLocale, useTypedSelector } from 'state';
6
+ import { selectIsResultMatching, selectLocale, useTypedSelector } from 'state';
7
7
  import { ResultColumn } from 'types';
8
8
 
9
9
  import Cell from './Cell';
@@ -20,7 +20,7 @@ interface Props {
20
20
  const Result = ({ data, index, style }: Props): ReactElement => {
21
21
  const {
22
22
  highlightedIndex,
23
- results,
23
+ results = [],
24
24
  onBlur = noop,
25
25
  onClick = noop,
26
26
  onFocus = noop,
@@ -28,11 +28,12 @@ const Result = ({ data, index, style }: Props): ReactElement => {
28
28
  onMouseLeave = noop,
29
29
  } = data;
30
30
  const ref = useRef<HTMLButtonElement>(null);
31
+ const columns = useColumns();
31
32
  const locale = useTypedSelector(selectLocale);
32
33
  const { consonants, vowels } = LOCALE_FEATURES[locale];
33
34
  const result = results[index];
35
+ const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
34
36
  const otherWords = result.words.slice(1).join(' / ').toLocaleUpperCase();
35
- const columns = useColumns();
36
37
  const enabledColumns = Object.fromEntries(columns.map((column) => [column.id, true]));
37
38
 
38
39
  const handleClick: MouseEventHandler = (event) => onClick(result, event);
@@ -46,6 +47,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
46
47
  aria-label={result.word}
47
48
  className={classNames(styles.result, {
48
49
  [styles.highlighted]: index === highlightedIndex,
50
+ [styles.notMatching]: !isMatching,
49
51
  })}
50
52
  ref={ref}
51
53
  style={style}
@@ -10,7 +10,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
10
10
  background: var(--color--background--element);
11
11
  border: var(--border);
12
12
  border-radius: var(--border--radius);
13
- box-shadow: var(--box-shadow);
14
13
  font-family: var(--font--family--title);
15
14
  }
16
15
 
@@ -115,6 +114,19 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
115
114
  color: var(--color--primary--opposite);
116
115
  }
117
116
  }
117
+
118
+ &.notMatching {
119
+ color: var(--color--inactive);
120
+
121
+ &:focus,
122
+ &:hover,
123
+ &.highlighted {
124
+ &:not(:disabled) {
125
+ background-color: var(--color--primary--light);
126
+ color: var(--color--primary--opposite);
127
+ }
128
+ }
129
+ }
118
130
  }
119
131
 
120
132
  .resultContent {
@@ -9,9 +9,8 @@ import {
9
9
  selectAreResultsOutdated,
10
10
  selectIsLoading,
11
11
  selectLocale,
12
+ selectResults,
12
13
  selectSolveError,
13
- selectSortedFilteredResults,
14
- selectSortedResults,
15
14
  useTranslate,
16
15
  useTypedSelector,
17
16
  } from 'state';
@@ -38,9 +37,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
38
37
  const translate = useTranslate();
39
38
  const locale = useTypedSelector(selectLocale);
40
39
  const { direction } = LOCALE_FEATURES[locale];
41
- const allResults = useTypedSelector(selectSortedResults);
42
- const filteredResults = useTypedSelector(selectSortedFilteredResults);
43
- const results = filteredResults || [];
40
+ const results = useTypedSelector(selectResults);
44
41
  const isLoading = useTypedSelector(selectIsLoading);
45
42
  const isOutdated = useTypedSelector(selectAreResultsOutdated);
46
43
  const error = useTypedSelector(selectSolveError);
@@ -50,8 +47,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
50
47
  const columns = useColumns();
51
48
  const scrollToIndex = typeof highlightedIndex === 'number' ? highlightedIndex : 0;
52
49
  const scrollToIndexRef = useLatest(scrollToIndex);
53
- const hasResults =
54
- typeof error === 'undefined' && typeof filteredResults !== 'undefined' && typeof allResults !== 'undefined';
50
+ const hasResults = typeof error === 'undefined' && typeof results !== 'undefined';
55
51
 
56
52
  useEffect(() => {
57
53
  // without setTimeout, the initial scrolling offset is calculated
@@ -65,7 +61,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
65
61
  return () => {
66
62
  globalThis.clearTimeout(timeout);
67
63
  };
68
- }, [allResults, listRef, scrollToIndexRef]);
64
+ }, [results, listRef, scrollToIndexRef]);
69
65
 
70
66
  return (
71
67
  <div className={classNames(styles.results, className)}>
@@ -84,7 +80,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
84
80
  </EmptyState>
85
81
  )}
86
82
 
87
- {typeof error === 'undefined' && typeof filteredResults === 'undefined' && (
83
+ {typeof error === 'undefined' && typeof results === 'undefined' && (
88
84
  <EmptyState className={styles.emptyState} variant="info">
89
85
  {translate('results.empty-state.uninitialized')}
90
86
 
@@ -102,45 +98,35 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
102
98
  </EmptyState>
103
99
  )}
104
100
 
105
- {!isOutdated && (
106
- <>
107
- {allResults.length === 0 && (
108
- <EmptyState className={styles.emptyState} variant="warning">
109
- {translate('results.empty-state.no-results')}
110
- </EmptyState>
111
- )}
112
-
113
- {allResults.length > 0 && filteredResults.length === 0 && (
114
- <EmptyState className={styles.emptyState} variant="info">
115
- {translate('results.empty-state.no-filtered-results')}
116
- </EmptyState>
117
- )}
118
-
119
- {allResults.length > 0 && filteredResults.length > 0 && (
120
- <div className={styles.listContainer}>
121
- <FixedSizeList
122
- className={classNames(styles.list, {
123
- [styles.outdated]: isOutdated,
124
- })}
125
- direction={direction}
126
- height={height}
127
- itemCount={filteredResults.length}
128
- itemData={itemData}
129
- itemSize={RESULTS_ITEM_HEIGHT}
130
- ref={setListRef}
131
- width={width}
132
- >
133
- {Result}
134
- </FixedSizeList>
135
- </div>
136
- )}
137
- </>
101
+ {!isOutdated && results.length === 0 && (
102
+ <EmptyState className={styles.emptyState} variant="warning">
103
+ {translate('results.empty-state.no-results')}
104
+ </EmptyState>
105
+ )}
106
+
107
+ {!isOutdated && results.length > 0 && (
108
+ <div className={styles.listContainer}>
109
+ <FixedSizeList
110
+ className={classNames(styles.list, {
111
+ [styles.outdated]: isOutdated,
112
+ })}
113
+ direction={direction}
114
+ height={height}
115
+ itemCount={results.length}
116
+ itemData={itemData}
117
+ itemSize={RESULTS_ITEM_HEIGHT}
118
+ ref={setListRef}
119
+ width={width}
120
+ >
121
+ {Result}
122
+ </FixedSizeList>
123
+ </div>
138
124
  )}
139
125
  </>
140
126
  )}
141
127
  </div>
142
128
 
143
- {hasResults && allResults.length > 0 && !isOutdated && <ResultsInput className={styles.input} />}
129
+ {hasResults && results.length > 0 && !isOutdated && <ResultsInput className={styles.input} />}
144
130
 
145
131
  {isLoading && <Loading />}
146
132
  </div>
@@ -19,5 +19,5 @@ export interface ResultCallbacks {
19
19
 
20
20
  export interface ResultData extends ResultCallbacks {
21
21
  highlightedIndex?: number;
22
- results: Result[];
22
+ results: Result[] | undefined;
23
23
  }
@@ -63,6 +63,10 @@
63
63
  }
64
64
  }
65
65
 
66
+ .results {
67
+ box-shadow: var(--box-shadow);
68
+ }
69
+
66
70
  .dictionaryContainer {
67
71
  flex: 0 0 auto;
68
72
  height: var(--dictionary--height);