scrabble-solver 2.10.5 → 2.10.7

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 (132) hide show
  1. package/README.md +12 -12
  2. package/lerna.json +1 -1
  3. package/package.json +10 -10
  4. package/packages/configs/package-lock.json +4 -4
  5. package/packages/configs/package.json +3 -3
  6. package/packages/constants/build/index.d.ts +2 -0
  7. package/packages/constants/build/index.js +3 -1
  8. package/packages/constants/package-lock.json +2 -2
  9. package/packages/constants/package.json +1 -1
  10. package/packages/constants/src/index.ts +4 -0
  11. package/packages/dictionaries/package-lock.json +2 -2
  12. package/packages/dictionaries/package.json +4 -4
  13. package/packages/logger/package-lock.json +2 -2
  14. package/packages/logger/package.json +1 -1
  15. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  16. package/packages/scrabble-solver/.next/build-manifest.json +10 -10
  17. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  18. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  19. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  20. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  22. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  23. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  24. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  25. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  26. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  27. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  28. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  29. package/packages/scrabble-solver/.next/server/chunks/176.js +551 -447
  30. package/packages/scrabble-solver/.next/server/chunks/290.js +3 -1
  31. package/packages/scrabble-solver/.next/server/chunks/579.js +24 -16
  32. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  33. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  34. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  35. package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
  36. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  37. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +18 -16
  38. package/packages/scrabble-solver/.next/server/pages/index.html +2 -2
  39. package/packages/scrabble-solver/.next/server/pages/index.js +18 -11
  40. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  41. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  42. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  43. package/packages/scrabble-solver/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +1 -0
  44. package/packages/scrabble-solver/.next/static/chunks/509-6ad4482d4351452c.js +1 -0
  45. package/packages/scrabble-solver/.next/static/chunks/pages/{404-7619583a9e7188b1.js → 404-67383848027ec49b.js} +1 -1
  46. package/packages/scrabble-solver/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +24 -0
  47. package/packages/scrabble-solver/.next/static/chunks/pages/index-146039f501e49c08.js +1 -0
  48. package/packages/scrabble-solver/.next/static/css/4482c4a0064d3807.css +1 -0
  49. package/packages/scrabble-solver/.next/static/css/9d1013ec684361b9.css +1 -0
  50. package/packages/scrabble-solver/.next/trace +55 -55
  51. package/packages/scrabble-solver/package-lock.json +37 -37
  52. package/packages/scrabble-solver/package.json +13 -13
  53. package/packages/scrabble-solver/src/components/Board/components/Cell/Button.tsx +1 -0
  54. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +6 -0
  55. package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +10 -1
  56. package/packages/scrabble-solver/src/components/Button/Button.module.scss +2 -5
  57. package/packages/scrabble-solver/src/components/Button/Button.tsx +1 -0
  58. package/packages/scrabble-solver/src/components/Button/Link.tsx +1 -0
  59. package/packages/scrabble-solver/src/components/{SquareButton/SquareButton.module.scss → IconButton/IconButton.module.scss} +2 -3
  60. package/packages/scrabble-solver/src/components/{SquareButton/SquareButton.tsx → IconButton/IconButton.tsx} +7 -6
  61. package/packages/scrabble-solver/src/components/{SquareButton → IconButton}/Link.tsx +3 -2
  62. package/packages/scrabble-solver/src/components/IconButton/index.ts +1 -0
  63. package/packages/scrabble-solver/src/components/Logo/Logo.svg +44 -15
  64. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +3 -2
  65. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +37 -9
  66. package/packages/scrabble-solver/src/components/Rack/RackTile.tsx +5 -0
  67. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +1 -0
  68. package/packages/scrabble-solver/src/components/Results/Result.tsx +1 -0
  69. package/packages/scrabble-solver/src/components/Results/Results.module.scss +0 -1
  70. package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -0
  71. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +11 -19
  72. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +18 -36
  73. package/packages/scrabble-solver/src/components/Solver/components/EmptyState/EmptyState.module.scss +0 -2
  74. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss +15 -0
  75. package/packages/scrabble-solver/src/components/Solver/components/{ApplyButton/ApplyButton.tsx → InsertButton/InsertButton.tsx} +10 -6
  76. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/index.ts +1 -0
  77. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +143 -0
  78. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +98 -0
  79. package/packages/scrabble-solver/src/components/Solver/components/SolveButton/SolveButton.tsx +11 -1
  80. package/packages/scrabble-solver/src/components/Solver/components/index.ts +2 -1
  81. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +3 -0
  82. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +3 -0
  83. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.tsx +14 -2
  84. package/packages/scrabble-solver/src/components/index.ts +1 -2
  85. package/packages/scrabble-solver/src/i18n/de.json +5 -0
  86. package/packages/scrabble-solver/src/i18n/en.json +5 -0
  87. package/packages/scrabble-solver/src/i18n/es.json +5 -0
  88. package/packages/scrabble-solver/src/i18n/fa.json +5 -0
  89. package/packages/scrabble-solver/src/i18n/fr.json +5 -0
  90. package/packages/scrabble-solver/src/i18n/pl.json +5 -0
  91. package/packages/scrabble-solver/src/icons/ArrowDown.svg +1 -1
  92. package/packages/scrabble-solver/src/icons/ArrowLeft.svg +1 -1
  93. package/packages/scrabble-solver/src/icons/ArrowRight.svg +1 -1
  94. package/packages/scrabble-solver/src/icons/ArrowUp.svg +1 -1
  95. package/packages/scrabble-solver/src/icons/ChevronDown.svg +1 -1
  96. package/packages/scrabble-solver/src/icons/ChevronLeft.svg +4 -0
  97. package/packages/scrabble-solver/src/icons/ChevronRight.svg +4 -0
  98. package/packages/scrabble-solver/src/icons/List.svg +1 -1
  99. package/packages/scrabble-solver/src/icons/index.ts +2 -0
  100. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +2 -3
  101. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +9 -3
  102. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +1 -0
  103. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +2 -2
  104. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +2 -2
  105. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +2 -2
  106. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +3 -1
  107. package/packages/scrabble-solver/src/pages/index.tsx +10 -6
  108. package/packages/scrabble-solver/src/state/sagas.ts +5 -2
  109. package/packages/scrabble-solver/src/state/useTranslate.ts +5 -2
  110. package/packages/scrabble-solver/src/styles/mixins.scss +6 -0
  111. package/packages/scrabble-solver/src/types/index.ts +6 -1
  112. package/packages/solver/package-lock.json +2 -2
  113. package/packages/solver/package.json +5 -5
  114. package/packages/types/package-lock.json +3 -3
  115. package/packages/types/package.json +2 -2
  116. package/packages/word-definitions/package-lock.json +8 -8
  117. package/packages/word-definitions/package.json +2 -2
  118. package/packages/word-lists/package-lock.json +2 -2
  119. package/packages/word-lists/package.json +2 -2
  120. package/packages/scrabble-solver/.next/static/WILX-RgqlLTd4ZoPs8C_E/_buildManifest.js +0 -1
  121. package/packages/scrabble-solver/.next/static/chunks/144-6768fe9a92865ec8.js +0 -1
  122. package/packages/scrabble-solver/.next/static/chunks/pages/_app-fa0661b072fc6af9.js +0 -24
  123. package/packages/scrabble-solver/.next/static/chunks/pages/index-1a6bbb880f28606a.js +0 -1
  124. package/packages/scrabble-solver/.next/static/css/d80ffccf2315791a.css +0 -1
  125. package/packages/scrabble-solver/.next/static/css/e737d5d7fbed2434.css +0 -1
  126. package/packages/scrabble-solver/src/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +0 -76
  127. package/packages/scrabble-solver/src/components/ResultCandidatePicker/ResultCandidatePicker.tsx +0 -38
  128. package/packages/scrabble-solver/src/components/Solver/components/ApplyButton/ApplyButton.module.scss +0 -5
  129. package/packages/scrabble-solver/src/components/Solver/components/ApplyButton/index.ts +0 -1
  130. package/packages/scrabble-solver/src/components/SquareButton/index.ts +0 -1
  131. /package/packages/scrabble-solver/.next/static/{WILX-RgqlLTd4ZoPs8C_E → 6RggBFm8kHrh-k1-CG3um}/_ssgManifest.js +0 -0
  132. /package/packages/scrabble-solver/src/components/{ResultCandidatePicker → Solver/components/ResultCandidatePicker}/index.ts +0 -0
@@ -6,7 +6,7 @@ import { CardChecklist, Cog, Eraser, Github, Keyboard, List, Sack } from 'icons'
6
6
  import { GITHUB_PROJECT_URL } from 'parameters';
7
7
  import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
8
8
 
9
- import SquareButton from '../SquareButton';
9
+ import IconButton from '../IconButton';
10
10
 
11
11
  import styles from './NavButtons.module.scss';
12
12
 
@@ -37,13 +37,25 @@ const NavButtons: FunctionComponent<Props> = ({
37
37
  return (
38
38
  <div className={styles.navButtons}>
39
39
  <div className={styles.group}>
40
- <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
40
+ <IconButton
41
+ aria-label={translate('common.clear')}
42
+ className={styles.button}
43
+ Icon={Eraser}
44
+ tooltip={translate('common.clear')}
45
+ onClick={onClear}
46
+ />
41
47
  </div>
42
48
 
43
49
  <div className={styles.separator} />
44
50
 
45
51
  <div className={styles.group}>
46
- <SquareButton className={styles.button} Icon={List} tooltip={translate('menu')} onClick={onShowMenu} />
52
+ <IconButton
53
+ aria-label={translate('menu')}
54
+ className={styles.button}
55
+ Icon={List}
56
+ tooltip={translate('menu')}
57
+ onClick={onShowMenu}
58
+ />
47
59
  </div>
48
60
  </div>
49
61
  );
@@ -52,13 +64,20 @@ const NavButtons: FunctionComponent<Props> = ({
52
64
  return (
53
65
  <div className={styles.navButtons}>
54
66
  <div className={styles.group}>
55
- <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
67
+ <IconButton
68
+ aria-label={translate('common.clear')}
69
+ className={styles.button}
70
+ Icon={Eraser}
71
+ tooltip={translate('common.clear')}
72
+ onClick={onClear}
73
+ />
56
74
  </div>
57
75
 
58
76
  <div className={styles.separator} />
59
77
 
60
78
  <div className={styles.group}>
61
- <SquareButton
79
+ <IconButton
80
+ aria-label={translate('remaining-tiles')}
62
81
  className={classNames(styles.button, {
63
82
  [styles.error]: hasOverusedTiles,
64
83
  })}
@@ -67,7 +86,8 @@ const NavButtons: FunctionComponent<Props> = ({
67
86
  onClick={onShowRemainingTiles}
68
87
  />
69
88
 
70
- <SquareButton
89
+ <IconButton
90
+ aria-label={translate('words')}
71
91
  className={classNames(styles.button, {
72
92
  [styles.error]: hasInvalidWords,
73
93
  })}
@@ -80,7 +100,8 @@ const NavButtons: FunctionComponent<Props> = ({
80
100
  <div className={styles.separator} />
81
101
 
82
102
  <div className={styles.group}>
83
- <SquareButton.Link
103
+ <IconButton.Link
104
+ aria-label={translate('github')}
84
105
  className={styles.button}
85
106
  href={GITHUB_PROJECT_URL}
86
107
  Icon={Github}
@@ -94,7 +115,8 @@ const NavButtons: FunctionComponent<Props> = ({
94
115
 
95
116
  <div className={styles.group}>
96
117
  {!isTouchDevice && (
97
- <SquareButton
118
+ <IconButton
119
+ aria-label={translate('keyMap')}
98
120
  className={styles.button}
99
121
  Icon={Keyboard}
100
122
  tooltip={translate('keyMap')}
@@ -102,7 +124,13 @@ const NavButtons: FunctionComponent<Props> = ({
102
124
  />
103
125
  )}
104
126
 
105
- <SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
127
+ <IconButton
128
+ aria-label={translate('settings')}
129
+ className={styles.button}
130
+ Icon={Cog}
131
+ tooltip={translate('settings')}
132
+ onClick={onShowSettings}
133
+ />
106
134
  </div>
107
135
  </div>
108
136
  );
@@ -18,6 +18,7 @@ import {
18
18
  selectCharacterIsValid,
19
19
  selectCharacterPoints,
20
20
  selectConfig,
21
+ selectLocale,
21
22
  useTranslate,
22
23
  useTypedSelector,
23
24
  } from 'state';
@@ -49,6 +50,7 @@ const RackTile: FunctionComponent<Props> = ({
49
50
  }) => {
50
51
  const dispatch = useDispatch();
51
52
  const translate = useTranslate();
53
+ const locale = useTypedSelector(selectLocale);
52
54
  const config = useTypedSelector(selectConfig);
53
55
  const points = useTypedSelector((state) => selectCharacterPoints(state, character));
54
56
  const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
@@ -91,6 +93,9 @@ const RackTile: FunctionComponent<Props> = ({
91
93
 
92
94
  return (
93
95
  <Tile
96
+ aria-label={translate('rack.tile.location', {
97
+ index: (index + 1).toLocaleString(locale),
98
+ })}
94
99
  autoFocus={index === 0}
95
100
  className={styles.tile}
96
101
  character={character === null ? undefined : character}
@@ -34,6 +34,7 @@ const HeaderButton = ({ column }: Props): ReactElement => {
34
34
 
35
35
  return (
36
36
  <button
37
+ aria-label={translate(column.translationKey)}
37
38
  className={classNames(styles.headerButton, column.className)}
38
39
  key={column.id}
39
40
  type="button"
@@ -43,6 +43,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
43
43
 
44
44
  return (
45
45
  <button
46
+ aria-label={result.word}
46
47
  className={classNames(styles.result, {
47
48
  [styles.highlighted]: index === highlightedIndex,
48
49
  })}
@@ -31,7 +31,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
31
31
  justify-content: space-between;
32
32
  border-bottom: var(--border);
33
33
  font-weight: 700;
34
- overflow: auto;
35
34
  }
36
35
 
37
36
  .headerButton {
@@ -31,6 +31,7 @@ const SolveButton: FunctionComponent<Props> = ({ className }) => {
31
31
 
32
32
  return (
33
33
  <Button
34
+ aria-label={translate('results.solve')}
34
35
  className={className}
35
36
  disabled={isLoading || !isOutdated || !hasTiles}
36
37
  Icon={Search}
@@ -47,6 +47,10 @@
47
47
  margin: 0 auto;
48
48
  }
49
49
 
50
+ .submitInput {
51
+ display: none;
52
+ }
53
+
50
54
  .column {
51
55
  display: flex;
52
56
  flex-direction: column;
@@ -58,6 +62,11 @@
58
62
  }
59
63
  }
60
64
 
65
+ .resultsContainer {
66
+ flex: 1;
67
+ position: relative;
68
+ }
69
+
61
70
  .dictionary {
62
71
  display: flex;
63
72
  flex-direction: column;
@@ -72,11 +81,6 @@
72
81
  flex: 0 0 auto;
73
82
  }
74
83
 
75
- .resultsContainer {
76
- flex: 1;
77
- position: relative;
78
- }
79
-
80
84
  .bottomContainer {
81
85
  flex: 0 0 auto;
82
86
  display: flex;
@@ -101,24 +105,12 @@
101
105
  border: var(--border);
102
106
  }
103
107
 
104
- .submitInput {
105
- display: none;
106
- }
107
-
108
108
  .controls {
109
109
  width: 100%;
110
- display: flex;
111
- align-items: flex-start;
112
- justify-content: center;
113
- gap: var(--spacing--l);
114
- }
115
-
116
- .resultCandidatePicker {
117
- flex: 1;
118
110
  }
119
111
 
120
- .apply {
121
- flex: 0 0 auto;
112
+ .emptyState {
113
+ margin-top: var(--spacing);
122
114
  }
123
115
 
124
116
  .solve {
@@ -32,11 +32,10 @@ import Board from '../Board';
32
32
  import Dictionary from '../Dictionary';
33
33
  import DictionaryInput from '../DictionaryInput';
34
34
  import Rack from '../Rack';
35
- import ResultCandidatePicker from '../ResultCandidatePicker';
36
35
  import Results from '../Results';
37
36
  import Well from '../Well';
38
37
 
39
- import { ApplyButton, EmptyState, SolveButton } from './components';
38
+ import { EmptyState, ResultCandidatePicker, SolveButton } from './components';
40
39
  import styles from './Solver.module.scss';
41
40
 
42
41
  interface Props {
@@ -55,14 +54,19 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
55
54
  const [resultsContainerRef, { width: resultsContainerWidth }] = useMeasure<HTMLDivElement>();
56
55
  const isLessThanXl = useMediaQuery('<xl');
57
56
  const isLessThanL = useMediaQuery('<l');
57
+ const isLessThanM = useMediaQuery('<m');
58
58
  const componentsSpacing = isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING;
59
59
  const maxBoardWidth = width - resultsContainerWidth - (isLessThanL ? 0 : componentsSpacing) - 2 * componentsSpacing;
60
- const maxBoardHeight = Math.max(height - bottomContainerHeight, isLessThanL ? 0 : COLUMN_MIN_HEIGHT);
60
+ const maxBoardHeight = Math.max(
61
+ height - bottomContainerHeight,
62
+ isLessThanL ? 0 : COLUMN_MIN_HEIGHT,
63
+ isLessThanM ? Number.POSITIVE_INFINITY : 0,
64
+ );
61
65
  const config = useTypedSelector(selectConfig);
62
- const resultCandidate = useTypedSelector(selectResultCandidate);
66
+ const error = useTypedSelector(selectSolveError);
63
67
  const isOutdated = useTypedSelector(selectAreResultsOutdated);
68
+ const resultCandidate = useTypedSelector(selectResultCandidate);
64
69
  const allResults = useTypedSelector(selectSortedResults);
65
- const error = useTypedSelector(selectSolveError);
66
70
  const results = useTypedSelector(selectSortedFilteredResults);
67
71
  const [bestResult] = results || [];
68
72
  const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
@@ -120,10 +124,10 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
120
124
  };
121
125
 
122
126
  useEffect(() => {
123
- if (bestResult) {
127
+ if (isLessThanL && bestResult && !isOutdated) {
124
128
  dispatch(resultsSlice.actions.changeResultCandidate(bestResult));
125
129
  }
126
- }, [bestResult, dispatch]);
130
+ }, [bestResult, dispatch, isLessThanL, isOutdated]);
127
131
 
128
132
  return (
129
133
  <div className={classNames(styles.solver, className)}>
@@ -160,41 +164,19 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
160
164
 
161
165
  {isLessThanL && (
162
166
  <div className={styles.controls} style={{ maxWidth: maxControlsWidth }}>
163
- {typeof error !== 'undefined' && (
164
- <EmptyState variant="error" onClick={onShowResults}>
167
+ <ResultCandidatePicker onResultClick={onShowResults} />
168
+
169
+ {error && (
170
+ <EmptyState className={styles.emptyState} variant="error">
165
171
  {error.message}
166
172
  </EmptyState>
167
173
  )}
168
174
 
169
- {typeof error === 'undefined' && typeof results === 'undefined' && (
170
- <EmptyState variant="info" onClick={onShowResults}>
171
- {translate('results.empty-state.uninitialized')}
175
+ {allResults && allResults.length === 0 && !isOutdated && (
176
+ <EmptyState className={styles.emptyState} variant="warning">
177
+ {translate('results.empty-state.no-results')}
172
178
  </EmptyState>
173
179
  )}
174
-
175
- {typeof error === 'undefined' && typeof results !== 'undefined' && typeof allResults !== 'undefined' && (
176
- <>
177
- {(isOutdated || !resultCandidate) && (
178
- <EmptyState variant="info" onClick={onShowResults}>
179
- {translate('results.empty-state.outdated')}
180
- </EmptyState>
181
- )}
182
-
183
- {!isOutdated && allResults.length === 0 && (
184
- <EmptyState variant="warning" onClick={onShowResults}>
185
- {translate('results.empty-state.no-results')}
186
- </EmptyState>
187
- )}
188
-
189
- {!isOutdated && allResults.length > 0 && resultCandidate && (
190
- <ResultCandidatePicker className={styles.resultCandidatePicker} onClick={onShowResults} />
191
- )}
192
- </>
193
- )}
194
-
195
- {allResults && allResults.length > 0 && !isOutdated && resultCandidate && (
196
- <ApplyButton className={classNames(styles.submit, styles.apply)} />
197
- )}
198
180
  </div>
199
181
  )}
200
182
  </div>
@@ -5,8 +5,6 @@
5
5
  border: var(--border);
6
6
  border-radius: var(--border--radius);
7
7
  background-color: white;
8
- box-shadow: var(--box-shadow);
9
- cursor: pointer;
10
8
  text-align: center;
11
9
  }
12
10
 
@@ -0,0 +1,15 @@
1
+ @import 'styles/mixins';
2
+
3
+ .insertButton {
4
+ @include media('>=s') {
5
+ padding: var(--spacing--m) var(--spacing--xl);
6
+ }
7
+
8
+ @include media('<xs') {
9
+ padding: var(--spacing--m);
10
+ }
11
+ }
12
+
13
+ .icon {
14
+ transform: scale(1.5);
15
+ }
@@ -1,20 +1,23 @@
1
+ import classNames from 'classnames';
1
2
  import { FunctionComponent } from 'react';
2
3
  import { useDispatch } from 'react-redux';
3
4
 
4
5
  import { Check } from 'icons';
5
- import { resultsSlice, selectResultCandidate, useTypedSelector } from 'state';
6
+ import { resultsSlice, selectAreResultsOutdated, selectResultCandidate, useTranslate, useTypedSelector } from 'state';
6
7
 
7
8
  import Button from '../../../Button';
8
9
 
9
- import styles from './ApplyButton.module.scss';
10
+ import styles from './InsertButton.module.scss';
10
11
 
11
12
  interface Props {
12
13
  className?: string;
13
14
  }
14
15
 
15
- const ApplyButton: FunctionComponent<Props> = ({ className }) => {
16
+ const InsertButton: FunctionComponent<Props> = ({ className }) => {
16
17
  const dispatch = useDispatch();
18
+ const translate = useTranslate();
17
19
  const resultCandidate = useTypedSelector(selectResultCandidate);
20
+ const isOutdated = useTypedSelector(selectAreResultsOutdated);
18
21
 
19
22
  const handleClick = () => {
20
23
  if (resultCandidate) {
@@ -24,8 +27,9 @@ const ApplyButton: FunctionComponent<Props> = ({ className }) => {
24
27
 
25
28
  return (
26
29
  <Button
27
- className={className}
28
- disabled={!resultCandidate}
30
+ aria-label={translate('results.insert')}
31
+ className={classNames(styles.insertButton, className)}
32
+ disabled={isOutdated || !resultCandidate}
29
33
  Icon={Check}
30
34
  iconClassName={styles.icon}
31
35
  type="submit"
@@ -35,4 +39,4 @@ const ApplyButton: FunctionComponent<Props> = ({ className }) => {
35
39
  );
36
40
  };
37
41
 
38
- export default ApplyButton;
42
+ export default InsertButton;
@@ -0,0 +1 @@
1
+ export { default } from './InsertButton';
@@ -0,0 +1,143 @@
1
+ @import 'styles/mixins';
2
+
3
+ .resultCandidatePicker {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: var(--spacing--l);
7
+
8
+ @include media('<xs') {
9
+ gap: var(--spacing--m);
10
+ }
11
+ }
12
+
13
+ .buttons {
14
+ flex: 0 0 auto;
15
+ display: flex;
16
+ border-radius: var(--border--radius);
17
+ }
18
+
19
+ .button {
20
+ padding: var(--spacing--m);
21
+
22
+ &:focus-within {
23
+ z-index: 1;
24
+ }
25
+
26
+ &:first-child {
27
+ border-right: none;
28
+ border-top-right-radius: 0;
29
+ border-bottom-right-radius: 0;
30
+ }
31
+
32
+ &:last-child {
33
+ border-top-left-radius: 0;
34
+ border-bottom-left-radius: 0;
35
+ }
36
+
37
+ [dir='rtl'] & {
38
+ transform: rotate(180deg);
39
+ }
40
+ }
41
+
42
+ .resultCandidate {
43
+ @include button-reset;
44
+ @include focus-effect;
45
+
46
+ flex: 1;
47
+ display: flex;
48
+ align-items: center;
49
+ min-width: 0;
50
+ width: 100%;
51
+ border: var(--border);
52
+ border-radius: var(--border--radius);
53
+ background-color: white;
54
+ box-shadow: var(--box-shadow);
55
+ cursor: pointer;
56
+
57
+ &[disabled] {
58
+ @include disabled;
59
+ }
60
+ }
61
+
62
+ .content {
63
+ display: flex;
64
+ align-items: center;
65
+ width: 100%;
66
+ overflow: hidden;
67
+ }
68
+
69
+ .points,
70
+ .word {
71
+ padding: var(--spacing--m) var(--spacing--l);
72
+ }
73
+
74
+ .points {
75
+ flex: 0 0 auto;
76
+ font-weight: bold;
77
+
78
+ [dir='ltr'] & {
79
+ border-right: var(--border);
80
+
81
+ @include media('<xs') {
82
+ border-right: none;
83
+ padding-right: var(--spacing--xs);
84
+ }
85
+ }
86
+
87
+ [dir='rtl'] & {
88
+ border-left: var(--border);
89
+
90
+ @include media('<xs') {
91
+ border-left: none;
92
+ padding-left: var(--spacing--xs);
93
+ }
94
+ }
95
+ }
96
+
97
+ .word {
98
+ @include ellipsis;
99
+
100
+ flex: 1;
101
+ text-transform: uppercase;
102
+ text-align: center;
103
+ white-space: pre-wrap;
104
+
105
+ [dir='ltr'] & {
106
+ @include media('<xs') {
107
+ padding: var(--spacing--m);
108
+ text-align: left;
109
+ }
110
+ }
111
+
112
+ [dir='rtl'] & {
113
+ @include media('<xs') {
114
+ padding: var(--spacing--m);
115
+ text-align: right;
116
+ }
117
+ }
118
+ }
119
+
120
+ .iconContainer {
121
+ flex: 0 0 auto;
122
+ display: flex;
123
+
124
+ [dir='ltr'] & {
125
+ padding-right: var(--spacing--l);
126
+ }
127
+
128
+ [dir='rtl'] & {
129
+ padding-left: var(--spacing--l);
130
+ }
131
+ }
132
+
133
+ .icon {
134
+ $size: 20px;
135
+
136
+ width: $size;
137
+ height: $size;
138
+ color: var(--color--inactive);
139
+ }
140
+
141
+ .insert {
142
+ flex: 0 0 auto;
143
+ }
@@ -0,0 +1,98 @@
1
+ import classNames from 'classnames';
2
+ import { FunctionComponent, HTMLProps, MouseEventHandler } from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+
5
+ import { ChevronDown, ChevronLeft, ChevronRight } from 'icons';
6
+ import {
7
+ resultsSlice,
8
+ selectAreResultsOutdated,
9
+ selectLocale,
10
+ selectResultCandidate,
11
+ selectSortedResults,
12
+ useTranslate,
13
+ useTypedSelector,
14
+ } from 'state';
15
+
16
+ import Button from '../../../Button';
17
+ import InsertButton from '../InsertButton';
18
+
19
+ import styles from './ResultCandidatePicker.module.scss';
20
+
21
+ interface Props extends HTMLProps<HTMLDivElement> {
22
+ onResultClick: MouseEventHandler<HTMLButtonElement>;
23
+ }
24
+
25
+ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultClick, ...props }) => {
26
+ const dispatch = useDispatch();
27
+ const translate = useTranslate();
28
+ const locale = useTypedSelector(selectLocale);
29
+ const isOutdated = useTypedSelector(selectAreResultsOutdated);
30
+ const sortedResults = useTypedSelector(selectSortedResults);
31
+ const results = sortedResults || [];
32
+ const resultCandidate = useTypedSelector(selectResultCandidate);
33
+ const index = resultCandidate ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
34
+ const disabled = isOutdated || !resultCandidate;
35
+ const isPreviousDisabled = index <= 0 || disabled;
36
+ const isNextDisabled = index >= results.length - 1 || disabled;
37
+
38
+ const handleNextClick = () => {
39
+ if (!isNextDisabled) {
40
+ const nextResult = results[index + 1];
41
+ dispatch(resultsSlice.actions.changeResultCandidate(nextResult));
42
+ }
43
+ };
44
+
45
+ const handlePreviousClick = () => {
46
+ if (!isPreviousDisabled) {
47
+ const previousResult = results[index - 1];
48
+ dispatch(resultsSlice.actions.changeResultCandidate(previousResult));
49
+ }
50
+ };
51
+
52
+ return (
53
+ <div className={classNames(styles.resultCandidatePicker, className)} {...props}>
54
+ <div className={styles.buttons}>
55
+ <Button
56
+ aria-label={translate('common.previous')}
57
+ className={styles.button}
58
+ disabled={isPreviousDisabled}
59
+ Icon={ChevronLeft}
60
+ onClick={handlePreviousClick}
61
+ />
62
+
63
+ <Button
64
+ aria-label={translate('common.next')}
65
+ className={styles.button}
66
+ disabled={isNextDisabled}
67
+ Icon={ChevronRight}
68
+ onClick={handleNextClick}
69
+ />
70
+ </div>
71
+
72
+ <button
73
+ aria-label={translate('results')}
74
+ className={styles.resultCandidate}
75
+ disabled={disabled}
76
+ type="button"
77
+ onClick={onResultClick}
78
+ >
79
+ {resultCandidate && (
80
+ <>
81
+ <div className={styles.points}>{resultCandidate.points.toLocaleString(locale)}</div>
82
+ <div className={styles.word}>{resultCandidate.word}</div>
83
+ </>
84
+ )}
85
+
86
+ {!resultCandidate && <div className={styles.word}> </div>}
87
+
88
+ <div className={styles.iconContainer}>
89
+ <ChevronDown className={styles.icon} />
90
+ </div>
91
+ </button>
92
+
93
+ <InsertButton className={styles.insert} />
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export default ResultCandidatePicker;
@@ -4,7 +4,14 @@ import { useDispatch } from 'react-redux';
4
4
 
5
5
  import { Search } from 'icons';
6
6
  import { noop } from 'lib';
7
- import { selectAreResultsOutdated, selectIsLoading, selectRack, solveSlice, useTypedSelector } from 'state';
7
+ import {
8
+ selectAreResultsOutdated,
9
+ selectIsLoading,
10
+ selectRack,
11
+ solveSlice,
12
+ useTranslate,
13
+ useTypedSelector,
14
+ } from 'state';
8
15
 
9
16
  import Button from '../../../Button';
10
17
 
@@ -17,6 +24,7 @@ interface Props {
17
24
 
18
25
  const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) => {
19
26
  const dispatch = useDispatch();
27
+ const translate = useTranslate();
20
28
  const isLoading = useTypedSelector(selectIsLoading);
21
29
  const isOutdated = useTypedSelector(selectAreResultsOutdated);
22
30
  const rack = useTypedSelector(selectRack);
@@ -29,10 +37,12 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
29
37
 
30
38
  return (
31
39
  <Button
40
+ aria-label={translate('results.solve')}
32
41
  className={classNames(styles.solveButton, className)}
33
42
  disabled={isLoading || !isOutdated || !hasTiles}
34
43
  Icon={Search}
35
44
  iconClassName={styles.icon}
45
+ tooltip={translate('results.solve')}
36
46
  type="submit"
37
47
  variant="primary"
38
48
  onClick={handleClick}
@@ -1,3 +1,4 @@
1
- export { default as ApplyButton } from './ApplyButton';
2
1
  export { default as EmptyState } from './EmptyState';
2
+ export { default as InsertButton } from './InsertButton';
3
+ export { default as ResultCandidatePicker } from './ResultCandidatePicker';
3
4
  export { default as SolveButton } from './SolveButton';