@scrabble-solver/scrabble-solver 2.10.4 → 2.10.6

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 (71) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +9 -9
  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/176.js +433 -228
  16. package/.next/server/middleware-build-manifest.js +1 -1
  17. package/.next/server/pages/404.html +2 -2
  18. package/.next/server/pages/404.js.nft.json +1 -1
  19. package/.next/server/pages/500.html +2 -2
  20. package/.next/server/pages/_app.js.nft.json +1 -1
  21. package/.next/server/pages/index.html +2 -2
  22. package/.next/server/pages/index.js +20 -50
  23. package/.next/server/pages/index.js.nft.json +1 -1
  24. package/.next/server/pages/index.json +1 -1
  25. package/.next/server/pages-manifest.json +3 -3
  26. package/.next/static/chunks/791-93aa8b8c22e488ac.js +1 -0
  27. package/.next/static/chunks/pages/{404-6c99a0c91257c60b.js → 404-ff35a4cf7f1ec85a.js} +1 -1
  28. package/.next/static/chunks/pages/index-ded620fd5df96be0.js +1 -0
  29. package/.next/static/css/4482c4a0064d3807.css +1 -0
  30. package/.next/static/css/a943dd97164732d4.css +1 -0
  31. package/.next/static/iL0av55MV28b0MXfhKKt2/_buildManifest.js +1 -0
  32. package/.next/trace +55 -55
  33. package/package.json +14 -14
  34. package/src/components/Board/components/Cell/Cell.module.scss +11 -11
  35. package/src/components/Button/Button.module.scss +29 -10
  36. package/src/components/Button/Button.tsx +24 -5
  37. package/src/components/Button/Link.tsx +44 -0
  38. package/src/components/Radio/Radio.module.scss +2 -2
  39. package/src/components/Results/SolveButton.tsx +1 -0
  40. package/src/components/Solver/Solver.module.scss +9 -21
  41. package/src/components/Solver/Solver.tsx +20 -56
  42. package/src/components/Solver/components/ApplyButton/ApplyButton.tsx +1 -0
  43. package/src/components/Solver/components/MobileControls/MobileControls.tsx +62 -0
  44. package/src/components/Solver/components/MobileControls/index.ts +1 -0
  45. package/src/components/{ResultCandidatePicker → Solver/components/ResultCandidatePicker}/ResultCandidatePicker.module.scss +47 -3
  46. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +75 -0
  47. package/src/components/Solver/components/SolveButton/SolveButton.tsx +1 -0
  48. package/src/components/Solver/components/index.ts +2 -0
  49. package/src/components/SquareButton/SquareButton.module.scss +1 -2
  50. package/src/components/SquareButton/SquareButton.tsx +2 -2
  51. package/src/components/Tile/Tile.module.scss +35 -18
  52. package/src/components/Tile/Tile.tsx +2 -2
  53. package/src/components/Tile/TilePure.tsx +9 -5
  54. package/src/components/Tooltip/Tooltip.module.scss +5 -5
  55. package/src/components/index.ts +0 -1
  56. package/src/icons/ChevronLeft.svg +4 -0
  57. package/src/icons/ChevronRight.svg +4 -0
  58. package/src/icons/index.ts +2 -0
  59. package/src/modals/MenuModal/MenuModal.module.scss +3 -39
  60. package/src/modals/MenuModal/MenuModal.tsx +22 -20
  61. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
  62. package/src/modals/ResultsModal/ResultsModal.tsx +3 -3
  63. package/src/styles/mixins.scss +8 -2
  64. package/.next/static/P7XhuDLmwJJqC8kgPjX42/_buildManifest.js +0 -1
  65. package/.next/static/chunks/528-9942ddad0031ff79.js +0 -1
  66. package/.next/static/chunks/pages/index-d761f0af070273d2.js +0 -1
  67. package/.next/static/css/97eb6ee0c4300c83.css +0 -1
  68. package/.next/static/css/dcca0c1a39cf5ef5.css +0 -1
  69. package/src/components/ResultCandidatePicker/ResultCandidatePicker.tsx +0 -38
  70. /package/.next/static/{P7XhuDLmwJJqC8kgPjX42 → iL0av55MV28b0MXfhKKt2}/_ssgManifest.js +0 -0
  71. /package/src/components/{ResultCandidatePicker → Solver/components/ResultCandidatePicker}/index.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.10.4",
3
+ "version": "2.10.6",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -30,14 +30,14 @@
30
30
  "dependencies": {
31
31
  "@kamilmielnik/trie": "^2.0.1",
32
32
  "@popperjs/core": "^2.11.6",
33
- "@reduxjs/toolkit": "^1.9.2",
34
- "@scrabble-solver/configs": "^2.10.4",
35
- "@scrabble-solver/constants": "^2.10.4",
36
- "@scrabble-solver/dictionaries": "^2.10.4",
37
- "@scrabble-solver/logger": "^2.10.4",
38
- "@scrabble-solver/solver": "^2.10.4",
39
- "@scrabble-solver/types": "^2.10.4",
40
- "@scrabble-solver/word-definitions": "^2.10.4",
33
+ "@reduxjs/toolkit": "^1.9.3",
34
+ "@scrabble-solver/configs": "^2.10.6",
35
+ "@scrabble-solver/constants": "^2.10.6",
36
+ "@scrabble-solver/dictionaries": "^2.10.6",
37
+ "@scrabble-solver/logger": "^2.10.6",
38
+ "@scrabble-solver/solver": "^2.10.6",
39
+ "@scrabble-solver/types": "^2.10.6",
40
+ "@scrabble-solver/word-definitions": "^2.10.6",
41
41
  "classnames": "^2.3.2",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
@@ -63,18 +63,18 @@
63
63
  "devDependencies": {
64
64
  "@svgr/webpack": "^6.5.1",
65
65
  "@types/classnames": "^2.3.0",
66
- "@types/react": "^18.0.27",
67
- "@types/react-dom": "^18.0.10",
66
+ "@types/react": "^18.0.28",
67
+ "@types/react-dom": "^18.0.11",
68
68
  "@types/react-modal": "^3.13.1",
69
69
  "@types/react-portal": "^4.0.4",
70
70
  "@types/react-redux": "^7.1.25",
71
71
  "@types/react-window": "^1.8.5",
72
72
  "@types/redux": "^3.6.31",
73
73
  "@types/redux-saga": "^0.10.5",
74
- "@types/uuid": "^9.0.0",
74
+ "@types/uuid": "^9.0.1",
75
75
  "env-cmd": "^10.1.0",
76
- "sass": "^1.57.1",
76
+ "sass": "^1.58.3",
77
77
  "workbox-webpack-plugin": "^6.5.4"
78
78
  },
79
- "gitHead": "7cce9fa637f3c346b1772d0a2b401d3e271b8e67"
79
+ "gitHead": "188bb77933a4e88af88b05063a4977c50c788071"
80
80
  }
@@ -35,69 +35,69 @@ $icon-size: 16px;
35
35
  }
36
36
 
37
37
  &.bonusStart {
38
- &:before {
38
+ &::before {
39
39
  background-color: var(--color--violet--light);
40
40
  }
41
41
  }
42
42
 
43
43
  &.bonusCharacter1 {
44
- &:before {
44
+ &::before {
45
45
  background-color: var(--color--yellow--light);
46
46
  }
47
47
  }
48
48
 
49
49
  &.bonusCharacter2 {
50
- &:before {
50
+ &::before {
51
51
  background-color: var(--color--green--light);
52
52
  }
53
53
  }
54
54
 
55
55
  &.bonusCharacter3 {
56
- &:before {
56
+ &::before {
57
57
  background-color: var(--color--blue--light);
58
58
  }
59
59
  }
60
60
 
61
61
  &.bonusCharacter5 {
62
- &:before {
62
+ &::before {
63
63
  background-color: var(--color--red--light);
64
64
  }
65
65
  }
66
66
 
67
67
  &.bonusCharacterMultiplier2 {
68
- &:before {
68
+ &::before {
69
69
  background-color: var(--color--light-blue--light);
70
70
  }
71
71
  }
72
72
 
73
73
  &.bonusCharacterMultiplier3 {
74
- &:before {
74
+ &::before {
75
75
  background-color: var(--color--dark-blue--light);
76
76
  }
77
77
  }
78
78
 
79
79
  &.bonusWord2 {
80
- &:before {
80
+ &::before {
81
81
  content: 'x2';
82
82
  background-color: var(--color--orange);
83
83
  }
84
84
  }
85
85
 
86
86
  &.bonusWord3 {
87
- &:before {
87
+ &::before {
88
88
  content: 'x3';
89
89
  background-color: var(--color--pink);
90
90
  }
91
91
  }
92
92
 
93
93
  &.candidate {
94
- &:before {
94
+ &::before {
95
95
  content: ' ';
96
96
  background-color: var(--color--primary);
97
97
  }
98
98
  }
99
99
 
100
- &:before {
100
+ &::before {
101
101
  $size: 80%;
102
102
 
103
103
  display: flex;
@@ -8,8 +8,7 @@
8
8
  padding: var(--spacing--m) var(--spacing--l);
9
9
  border: var(--border);
10
10
  border-radius: var(--border--radius);
11
- box-shadow: none;
12
- color: var(--color--inactive);
11
+ text-transform: uppercase;
13
12
  transition: var(--transition);
14
13
  cursor: pointer;
15
14
 
@@ -17,10 +16,31 @@
17
16
  &:active,
18
17
  &:focus,
19
18
  &:hover {
20
- text-decoration: none;
21
- background-color: var(--color--primary);
22
19
  box-shadow: var(--box-shadow);
23
- color: white;
20
+ text-decoration: none;
21
+
22
+ &.primary {
23
+ background-color: var(--color--primary);
24
+ color: white;
25
+ }
26
+
27
+ &.default {
28
+ background-color: var(--color--background--overlay);
29
+ color: var(--color--foreground);
30
+
31
+ .icon {
32
+ color: var(--color--inactive);
33
+ }
34
+ }
35
+ }
36
+
37
+ &:focus,
38
+ &:hover {
39
+ &.default:not(:disabled) {
40
+ .icon {
41
+ color: var(--color--foreground--secondary);
42
+ }
43
+ }
24
44
  }
25
45
 
26
46
  &:active {
@@ -33,24 +53,23 @@
33
53
  }
34
54
 
35
55
  &[disabled] {
36
- opacity: var(--opacity--disabled);
37
- box-shadow: none;
38
- cursor: not-allowed;
56
+ @include disabled;
39
57
  }
40
58
  }
41
59
 
42
60
  .content {
43
61
  display: flex;
44
62
  align-items: center;
45
- gap: var(--spacing--m);
63
+ gap: 20px;
46
64
  }
47
65
 
48
66
  .icon {
49
67
  width: var(--button--icon--size);
50
68
  height: var(--button--icon--size);
69
+ transition: var(--transition);
51
70
  }
52
71
 
53
72
  .label {
54
73
  font-size: var(--font--size--h3);
55
- text-transform: uppercase;
74
+ transition: var(--transition);
56
75
  }
@@ -1,22 +1,39 @@
1
1
  import classNames from 'classnames';
2
- import { ButtonHTMLAttributes, FunctionComponent, MouseEventHandler, ReactNode, SVGAttributes } from 'react';
2
+ import { ButtonHTMLAttributes, FunctionComponent, ReactNode, SVGAttributes } from 'react';
3
3
 
4
4
  import { useTooltip } from '../Tooltip';
5
5
 
6
6
  import styles from './Button.module.scss';
7
+ import Link from './Link';
7
8
 
8
9
  interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
9
10
  Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
10
11
  iconClassName?: string;
11
12
  tooltip?: ReactNode;
12
- onClick: MouseEventHandler<HTMLButtonElement>;
13
+ variant?: 'default' | 'primary';
13
14
  }
14
15
 
15
- const Button: FunctionComponent<Props> = ({ children, className, Icon, iconClassName, tooltip, ...props }) => {
16
+ const Button: FunctionComponent<Props> = ({
17
+ children,
18
+ className,
19
+ Icon,
20
+ iconClassName,
21
+ tooltip,
22
+ variant = 'default',
23
+ ...props
24
+ }) => {
16
25
  const triggerProps = useTooltip(tooltip, props);
17
26
 
18
27
  return (
19
- <button className={classNames(styles.button, className)} type="button" {...props} {...triggerProps}>
28
+ <button
29
+ className={classNames(styles.button, className, {
30
+ [styles.default]: variant === 'default',
31
+ [styles.primary]: variant === 'primary',
32
+ })}
33
+ type="button"
34
+ {...props}
35
+ {...triggerProps}
36
+ >
20
37
  <span className={styles.content}>
21
38
  {Icon && <Icon className={classNames(styles.icon, iconClassName)} />}
22
39
  {children && <span className={styles.label}>{children}</span>}
@@ -25,4 +42,6 @@ const Button: FunctionComponent<Props> = ({ children, className, Icon, iconClass
25
42
  );
26
43
  };
27
44
 
28
- export default Button;
45
+ export default Object.assign(Button, {
46
+ Link,
47
+ });
@@ -0,0 +1,44 @@
1
+ import classNames from 'classnames';
2
+ import { AnchorHTMLAttributes, FunctionComponent, ReactNode, SVGAttributes } from 'react';
3
+
4
+ import { useTooltip } from '../Tooltip';
5
+
6
+ import styles from './Button.module.scss';
7
+
8
+ interface Props extends AnchorHTMLAttributes<HTMLAnchorElement> {
9
+ href: string;
10
+ Icon: FunctionComponent<SVGAttributes<SVGElement>>;
11
+ iconClassName?: string;
12
+ tooltip?: ReactNode;
13
+ variant?: 'default' | 'primary';
14
+ }
15
+
16
+ const Link: FunctionComponent<Props> = ({
17
+ children,
18
+ className,
19
+ Icon,
20
+ iconClassName,
21
+ tooltip,
22
+ variant = 'default',
23
+ ...props
24
+ }) => {
25
+ const triggerProps = useTooltip(tooltip, props);
26
+
27
+ return (
28
+ <a
29
+ className={classNames(styles.button, className, {
30
+ [styles.default]: variant === 'default',
31
+ [styles.primary]: variant === 'primary',
32
+ })}
33
+ {...props}
34
+ {...triggerProps}
35
+ >
36
+ <span className={styles.content}>
37
+ {Icon && <Icon className={classNames(styles.icon, iconClassName)} />}
38
+ {children && <span className={styles.label}>{children}</span>}
39
+ </span>
40
+ </a>
41
+ );
42
+ };
43
+
44
+ export default Link;
@@ -24,7 +24,7 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
24
24
 
25
25
  &.checked {
26
26
  .icon {
27
- &:after {
27
+ &::after {
28
28
  background-color: var(--color--primary);
29
29
  }
30
30
  }
@@ -51,7 +51,7 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
51
51
  box-shadow: var(--box-shadow--null);
52
52
  pointer-events: none;
53
53
 
54
- &:after {
54
+ &::after {
55
55
  content: ' ';
56
56
  position: absolute;
57
57
  top: $radio-inner-border;
@@ -35,6 +35,7 @@ const SolveButton: FunctionComponent<Props> = ({ className }) => {
35
35
  disabled={isLoading || !isOutdated || !hasTiles}
36
36
  Icon={Search}
37
37
  type="submit"
38
+ variant="primary"
38
39
  onClick={handleClick}
39
40
  >
40
41
  {translate('results.solve')}
@@ -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,8 @@
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
- }
119
-
120
- .apply {
121
- flex: 0 0 auto;
122
110
  }
123
111
 
124
112
  .solve {
@@ -1,6 +1,6 @@
1
1
  import { Result } from '@scrabble-solver/types';
2
2
  import classNames from 'classnames';
3
- import { FormEvent, FunctionComponent, useEffect, useMemo } from 'react';
3
+ import { FunctionComponent, SyntheticEvent, useEffect, useMemo } from 'react';
4
4
  import { useDispatch } from 'react-redux';
5
5
  import { useMeasure } from 'react-use';
6
6
 
@@ -17,14 +17,10 @@ import {
17
17
  } from 'parameters';
18
18
  import {
19
19
  resultsSlice,
20
- selectAreResultsOutdated,
21
20
  selectConfig,
22
21
  selectResultCandidate,
23
- selectSolveError,
24
22
  selectSortedFilteredResults,
25
- selectSortedResults,
26
23
  solveSlice,
27
- useTranslate,
28
24
  useTypedSelector,
29
25
  } from 'state';
30
26
 
@@ -32,11 +28,10 @@ import Board from '../Board';
32
28
  import Dictionary from '../Dictionary';
33
29
  import DictionaryInput from '../DictionaryInput';
34
30
  import Rack from '../Rack';
35
- import ResultCandidatePicker from '../ResultCandidatePicker';
36
31
  import Results from '../Results';
37
32
  import Well from '../Well';
38
33
 
39
- import { ApplyButton, EmptyState, SolveButton } from './components';
34
+ import { MobileControls, SolveButton } from './components';
40
35
  import styles from './Solver.module.scss';
41
36
 
42
37
  interface Props {
@@ -49,20 +44,21 @@ interface Props {
49
44
  // eslint-disable-next-line max-statements
50
45
  const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResults }) => {
51
46
  const dispatch = useDispatch();
52
- const translate = useTranslate();
53
47
  const isTouchDevice = useIsTouchDevice();
54
48
  const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
55
49
  const [resultsContainerRef, { width: resultsContainerWidth }] = useMeasure<HTMLDivElement>();
56
50
  const isLessThanXl = useMediaQuery('<xl');
57
51
  const isLessThanL = useMediaQuery('<l');
52
+ const isLessThanM = useMediaQuery('<m');
58
53
  const componentsSpacing = isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING;
59
54
  const maxBoardWidth = width - resultsContainerWidth - (isLessThanL ? 0 : componentsSpacing) - 2 * componentsSpacing;
60
- const maxBoardHeight = Math.max(height - bottomContainerHeight, isLessThanL ? 0 : COLUMN_MIN_HEIGHT);
55
+ const maxBoardHeight = Math.max(
56
+ height - bottomContainerHeight,
57
+ isLessThanL ? 0 : COLUMN_MIN_HEIGHT,
58
+ isLessThanM ? Number.POSITIVE_INFINITY : 0,
59
+ );
61
60
  const config = useTypedSelector(selectConfig);
62
61
  const resultCandidate = useTypedSelector(selectResultCandidate);
63
- const isOutdated = useTypedSelector(selectAreResultsOutdated);
64
- const allResults = useTypedSelector(selectSortedResults);
65
- const error = useTypedSelector(selectSolveError);
66
62
  const results = useTypedSelector(selectSortedFilteredResults);
67
63
  const [bestResult] = results || [];
68
64
  const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
@@ -109,9 +105,13 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
109
105
  );
110
106
  const callbacks = isTouchDevice ? touchCallbacks : mouseCallbacks;
111
107
 
112
- const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
108
+ const handleSubmit = (event: SyntheticEvent) => {
113
109
  event.preventDefault();
114
- onShowResults();
110
+
111
+ if (isLessThanL) {
112
+ onShowResults();
113
+ }
114
+
115
115
  dispatch(solveSlice.actions.submit());
116
116
  };
117
117
 
@@ -155,52 +155,16 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
155
155
  </form>
156
156
 
157
157
  {isLessThanL && (
158
- <div className={styles.controls} style={{ maxWidth: maxControlsWidth }}>
159
- {typeof error !== 'undefined' && (
160
- <EmptyState variant="error" onClick={onShowResults}>
161
- {error.message}
162
- </EmptyState>
163
- )}
164
-
165
- {typeof error === 'undefined' && typeof results === 'undefined' && (
166
- <EmptyState variant="info" onClick={onShowResults}>
167
- {translate('results.empty-state.uninitialized')}
168
- </EmptyState>
169
- )}
170
-
171
- {typeof error === 'undefined' && typeof results !== 'undefined' && typeof allResults !== 'undefined' && (
172
- <>
173
- {isOutdated && (
174
- <EmptyState variant="info" onClick={onShowResults}>
175
- {translate('results.empty-state.outdated')}
176
- </EmptyState>
177
- )}
178
-
179
- {!isOutdated && (
180
- <>
181
- {allResults.length === 0 && (
182
- <EmptyState variant="warning" onClick={onShowResults}>
183
- {translate('results.empty-state.no-results')}
184
- </EmptyState>
185
- )}
186
-
187
- {allResults.length > 0 && resultCandidate && (
188
- <ResultCandidatePicker className={styles.resultCandidatePicker} onClick={onShowResults} />
189
- )}
190
- </>
191
- )}
192
- </>
193
- )}
194
-
195
- {allResults && allResults.length > 0 && !isOutdated && (
196
- <ApplyButton className={classNames(styles.submit, styles.apply)} />
197
- )}
198
- </div>
158
+ <MobileControls
159
+ className={styles.controls}
160
+ style={{ maxWidth: maxControlsWidth }}
161
+ onShowResults={onShowResults}
162
+ />
199
163
  )}
200
164
  </div>
201
165
  </div>
202
166
 
203
- {isTouchDevice && <SolveButton className={styles.solve} onClick={onShowResults} />}
167
+ {isTouchDevice && <SolveButton className={styles.solve} onClick={handleSubmit} />}
204
168
  </div>
205
169
  );
206
170
  };
@@ -29,6 +29,7 @@ const ApplyButton: FunctionComponent<Props> = ({ className }) => {
29
29
  Icon={Check}
30
30
  iconClassName={styles.icon}
31
31
  type="submit"
32
+ variant="primary"
32
33
  onClick={handleClick}
33
34
  />
34
35
  );
@@ -0,0 +1,62 @@
1
+ import { FunctionComponent, HTMLProps } from 'react';
2
+
3
+ import {
4
+ selectAreResultsOutdated,
5
+ selectResultCandidate,
6
+ selectSolveError,
7
+ selectSortedResults,
8
+ useTranslate,
9
+ useTypedSelector,
10
+ } from 'state';
11
+
12
+ import EmptyState from '../EmptyState';
13
+ import ResultCandidatePicker from '../ResultCandidatePicker';
14
+
15
+ interface Props extends HTMLProps<HTMLDivElement> {
16
+ onShowResults: () => void;
17
+ }
18
+
19
+ // eslint-disable-next-line max-statements
20
+ const MobileControls: FunctionComponent<Props> = ({ onShowResults, ...props }) => {
21
+ const translate = useTranslate();
22
+ const resultCandidate = useTypedSelector(selectResultCandidate);
23
+ const isOutdated = useTypedSelector(selectAreResultsOutdated);
24
+ const allResults = useTypedSelector(selectSortedResults);
25
+ const error = useTypedSelector(selectSolveError);
26
+
27
+ return (
28
+ <div {...props}>
29
+ {typeof error !== 'undefined' && (
30
+ <EmptyState variant="error" onClick={onShowResults}>
31
+ {error.message}
32
+ </EmptyState>
33
+ )}
34
+
35
+ {typeof error === 'undefined' && typeof allResults === 'undefined' && (
36
+ <EmptyState variant="info" onClick={onShowResults}>
37
+ {translate('results.empty-state.uninitialized')}
38
+ </EmptyState>
39
+ )}
40
+
41
+ {typeof error === 'undefined' && typeof allResults !== 'undefined' && (
42
+ <>
43
+ {(isOutdated || !resultCandidate) && (
44
+ <EmptyState variant="info" onClick={onShowResults}>
45
+ {translate('results.empty-state.outdated')}
46
+ </EmptyState>
47
+ )}
48
+
49
+ {!isOutdated && allResults.length === 0 && (
50
+ <EmptyState variant="warning" onClick={onShowResults}>
51
+ {translate('results.empty-state.no-results')}
52
+ </EmptyState>
53
+ )}
54
+
55
+ {!isOutdated && allResults.length > 0 && resultCandidate && <ResultCandidatePicker onClick={onShowResults} />}
56
+ </>
57
+ )}
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default MobileControls;
@@ -0,0 +1 @@
1
+ export { default } from './MobileControls';
@@ -1,9 +1,51 @@
1
1
  @import 'styles/mixins';
2
2
 
3
3
  .resultCandidatePicker {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: var(--spacing--l);
7
+ }
8
+
9
+ .buttons {
10
+ flex: 0 0 auto;
11
+ display: flex;
12
+ border-radius: var(--border--radius);
13
+ box-shadow: var(--box-shadow);
14
+
15
+ @include media('<xs') {
16
+ display: none;
17
+ }
18
+ }
19
+
20
+ .button {
21
+ padding: var(--spacing--m);
22
+ box-shadow: none;
23
+
24
+ &:focus-within {
25
+ z-index: 1;
26
+ }
27
+
28
+ &:first-child {
29
+ border-right: none;
30
+ border-top-right-radius: 0;
31
+ border-bottom-right-radius: 0;
32
+ }
33
+
34
+ &:last-child {
35
+ border-top-left-radius: 0;
36
+ border-bottom-left-radius: 0;
37
+ }
38
+
39
+ [dir='rtl'] & {
40
+ transform: rotate(180deg);
41
+ }
42
+ }
43
+
44
+ .resultCandidate {
4
45
  @include button-reset;
5
46
  @include focus-effect;
6
47
 
48
+ flex: 1;
7
49
  display: flex;
8
50
  align-items: center;
9
51
  min-width: 0;
@@ -15,9 +57,7 @@
15
57
  cursor: pointer;
16
58
 
17
59
  &[disabled] {
18
- opacity: var(--opacity--disabled);
19
- box-shadow: none;
20
- cursor: not-allowed;
60
+ @include disabled;
21
61
  }
22
62
  }
23
63
 
@@ -74,3 +114,7 @@
74
114
  height: $size;
75
115
  color: var(--color--inactive);
76
116
  }
117
+
118
+ .apply {
119
+ flex: 0 0 auto;
120
+ }