@scrabble-solver/scrabble-solver 2.18.1 → 2.18.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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +14 -14
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/diagnostics/framework.json +1 -1
- package/.next/dynamic-css-manifest.json +2 -2
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +37 -37
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/309.js +1 -1
- package/.next/server/chunks/344.js +1 -1
- package/.next/server/chunks/371.js +1 -1
- package/.next/server/chunks/392.js +1 -1
- package/.next/server/chunks/465.js +1 -1
- package/.next/server/chunks/546.js +1 -1
- package/.next/server/chunks/641.js +1 -1
- package/.next/server/chunks/67.js +1 -0
- package/.next/server/chunks/712.js +1 -1
- package/.next/server/chunks/812.js +1 -1
- package/.next/server/chunks/846.js +1 -1
- package/.next/server/chunks/886.js +1 -1
- package/.next/server/chunks/942.js +1 -0
- package/.next/server/chunks/953.js +1 -1
- package/.next/server/chunks/961.js +1 -1
- package/.next/server/dynamic-css-manifest.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +3 -3
- package/.next/server/pages/500.html +3 -3
- package/.next/server/pages/_app.js +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +1 -1
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +1 -1
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +1 -1
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/index.html +3 -3
- package/.next/server/pages/index.js +4 -4
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages/not-found.html +3 -3
- package/.next/server/pages/not-found.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/{5KOWzYqfY1Z2rjrzptr8D → 8WnIUTe-yJvPD6stltvYI}/_buildManifest.js +1 -1
- package/.next/static/chunks/100.305bdf9c75b1758f.js +1 -0
- package/.next/static/chunks/256.6a20b23a766cfebd.js +1 -0
- package/.next/static/chunks/{307.67f52f13e3d60d4e.js → 307.89e6100f58443679.js} +1 -1
- package/.next/static/chunks/{363.01eed3233f92b6a4.js → 363.cec65b1f9668a75e.js} +1 -1
- package/.next/static/chunks/{422.1559ae6756912ca5.js → 422.3e55b4c94e328701.js} +1 -1
- package/.next/static/chunks/{663.d2c9ca36b26695b8.js → 663.bd2c45413fbca518.js} +1 -1
- package/.next/static/chunks/693.677c88e0eb1340b5.js +1 -0
- package/.next/static/chunks/{820.97b82f751ca49579.js → 820.823e9e885ebb66af.js} +1 -1
- package/.next/static/chunks/827.c81b92b383a748c9.js +1 -0
- package/.next/static/chunks/{865.a9b563a45848a83c.js → 865.59baba8296c9b116.js} +1 -1
- package/.next/static/chunks/912.077603e4f0bc6cca.js +1 -0
- package/.next/static/chunks/{939.19b33a3d3c351d73.js → 939.2ad86b50405c7897.js} +1 -1
- package/.next/static/chunks/980.7ce17464fc56648a.js +1 -0
- package/.next/static/chunks/{main-b92e08f3fc96dd8d.js → main-7e52da35a4beea0b.js} +1 -1
- package/.next/static/chunks/pages/_app-e3fe880e24c024ab.js +1 -0
- package/.next/static/chunks/pages/index-b073a1686818dd26.js +1 -0
- package/.next/static/chunks/webpack-d7138f1ef6b8c9d6.js +1 -0
- package/.next/static/css/65bcc9440e067113.css +1 -0
- package/.next/static/css/{4dbcacc1122ac6b9.css → 92f85ce358a0a468.css} +1 -1
- package/.next/static/css/a691348b788f8bc0.css +1 -0
- package/.next/static/css/cd4d7f2458487399.css +1 -0
- package/.next/trace +22 -21
- package/.next/trace-build +1 -1
- package/AGENTS.md +9 -0
- package/CLAUDE.md +1 -0
- package/package.json +14 -12
- package/public/llms.txt +1 -1
- package/src/components/Board/Board.tsx +3 -1
- package/src/components/Board/BoardPure.tsx +26 -20
- package/src/components/Board/components/Cell/Cell.tsx +4 -4
- package/src/components/Board/lib.test.ts +106 -0
- package/src/components/Board/lib.ts +34 -3
- package/src/components/Board/selectors.ts +17 -5
- package/src/components/Button/Button.module.scss +1 -1
- package/src/components/Modal/Modal.module.scss +0 -4
- package/src/components/Results/Header.tsx +30 -5
- package/src/components/Results/Result.tsx +45 -55
- package/src/components/Results/Results.module.scss +0 -126
- package/src/components/Results/Results.module.scss.d.ts +0 -10
- package/src/components/Results/Results.tsx +25 -5
- package/src/components/Solver/Solver.module.scss +4 -4
- package/src/components/Solver/Solver.tsx +5 -3
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +5 -1
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +1 -0
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -2
- package/src/components/Table/Cell.tsx +42 -0
- package/src/components/Table/Header.tsx +11 -0
- package/src/components/{Results → Table}/HeaderButton.tsx +22 -14
- package/src/components/Table/Row.tsx +59 -0
- package/src/components/{ResultsInput/ResultsInput.tsx → Table/Search.tsx} +9 -11
- package/src/components/Table/Table.module.scss +147 -0
- package/src/components/Table/Table.module.scss.d.ts +19 -0
- package/src/components/Table/index.ts +5 -0
- package/src/components/Tile/Tile.module.scss +2 -1
- package/src/hooks/useColumns.ts +7 -5
- package/src/hooks/useIsCompactLayout.ts +14 -0
- package/src/i18n/languages/english.json +6 -1
- package/src/i18n/languages/french.json +6 -1
- package/src/i18n/languages/german.json +6 -1
- package/src/i18n/languages/persian.json +6 -1
- package/src/i18n/languages/polish.json +6 -1
- package/src/i18n/languages/romanian.json +6 -1
- package/src/i18n/languages/spanish.json +6 -1
- package/src/i18n/languages/turkish.json +6 -1
- package/src/icons/QuestionSquare.svg +5 -0
- package/src/lib/createCoordinatesComparator.ts +23 -0
- package/src/lib/createKeyComparator.ts +1 -4
- package/src/lib/createSortComparator.ts +22 -0
- package/src/lib/getCoordinates.test.ts +21 -0
- package/src/lib/getCoordinates.ts +16 -0
- package/src/lib/getNextSort.ts +12 -0
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +3 -1
- package/src/modals/RemainingTilesModal/components/Character/Character.tsx +8 -9
- package/src/modals/ResultsModal/ResultsModal.tsx +14 -10
- package/src/modals/WordsModal/WordsModal.module.scss +16 -27
- package/src/modals/WordsModal/WordsModal.module.scss.d.ts +3 -6
- package/src/modals/WordsModal/WordsModal.tsx +56 -42
- package/src/modals/WordsModal/components/WordsTable/WordRow.tsx +92 -0
- package/src/modals/WordsModal/components/WordsTable/WordsTable.module.scss +69 -0
- package/src/modals/WordsModal/components/WordsTable/WordsTable.module.scss.d.ts +16 -0
- package/src/modals/WordsModal/components/WordsTable/WordsTable.tsx +137 -0
- package/src/modals/WordsModal/components/WordsTable/index.ts +1 -0
- package/src/modals/WordsModal/components/index.ts +1 -0
- package/src/pages/api/verify.ts +7 -6
- package/src/pages/index.tsx +29 -4
- package/src/parameters/index.ts +2 -0
- package/src/sdk/verify.ts +3 -2
- package/src/solver-worker/client.test.ts +10 -3
- package/src/solver-worker/index.worker.ts +7 -7
- package/src/solver-worker/messages.ts +3 -3
- package/src/state/hoveredWord/index.ts +4 -0
- package/src/state/hoveredWord/initialState.ts +5 -0
- package/src/state/hoveredWord/selectors.ts +5 -0
- package/src/state/hoveredWord/slice.ts +18 -0
- package/src/state/hoveredWord/types.ts +5 -0
- package/src/state/index.ts +1 -0
- package/src/state/localStorage.ts +13 -2
- package/src/state/results/lib.ts +9 -26
- package/src/state/results/selectors.ts +9 -1
- package/src/state/results/slice.ts +4 -16
- package/src/state/sagas.ts +26 -1
- package/src/state/selectors.ts +18 -1
- package/src/state/store.ts +2 -0
- package/src/state/types.ts +2 -0
- package/src/state/verify/initialState.ts +7 -0
- package/src/state/verify/lib.test.ts +55 -0
- package/src/state/verify/lib.ts +49 -0
- package/src/state/verify/selectors.ts +37 -0
- package/src/state/verify/slice.ts +13 -2
- package/src/state/verify/types.ts +6 -2
- package/src/styles/_tokens.scss +2 -0
- package/src/styles/mixins.scss +5 -0
- package/src/styles/variables.module.scss +1 -0
- package/src/styles/variables.module.scss.d.ts +1 -0
- package/src/styles/variables.scss +1 -1
- package/src/types/index.ts +24 -11
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/server/chunks/316.js +0 -1
- package/.next/server/chunks/916.js +0 -1
- package/.next/static/chunks/106.e414e88694add27f.js +0 -1
- package/.next/static/chunks/127.125d896a19061d71.js +0 -1
- package/.next/static/chunks/18.8b91e137d239e15c.js +0 -1
- package/.next/static/chunks/583.7a46807be14c377e.js +0 -1
- package/.next/static/chunks/711.ba7b8f900dfd40d0.js +0 -1
- package/.next/static/chunks/912.ff8037ba9d4db5c8.js +0 -1
- package/.next/static/chunks/pages/_app-2e607dcd4135f340.js +0 -1
- package/.next/static/chunks/pages/index-e0087247c995328d.js +0 -1
- package/.next/static/chunks/webpack-cfb34d175e081981.js +0 -1
- package/.next/static/css/b74cd7de82dabcdd.css +0 -1
- package/.next/static/css/c403ce739a9c0d83.css +0 -1
- package/.next/static/css/fbfdce70731d0d2b.css +0 -1
- package/src/components/Results/Cell.tsx +0 -32
- package/src/components/ResultsInput/ResultsInput.module.scss +0 -12
- package/src/components/ResultsInput/ResultsInput.module.scss.d.ts +0 -7
- package/src/components/ResultsInput/index.ts +0 -1
- /package/.next/static/{5KOWzYqfY1Z2rjrzptr8D → 8WnIUTe-yJvPD6stltvYI}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type FocusEventHandler, type MouseEventHandler, type ReactElement, useRef } from 'react';
|
|
1
|
+
import { type FocusEventHandler, type MouseEventHandler, type ReactElement } from 'react';
|
|
3
2
|
import Highlighter from 'react-highlight-words';
|
|
4
3
|
import { type RowComponentProps } from 'react-window';
|
|
5
4
|
|
|
5
|
+
import { Cell, Row } from '@/components/Table';
|
|
6
6
|
import { useColumns } from '@/hooks/useColumns';
|
|
7
7
|
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
8
|
-
import { noop } from '@/lib/noop';
|
|
9
8
|
import {
|
|
10
9
|
selectIsResultMatching,
|
|
11
10
|
selectLocale,
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
} from '@/state';
|
|
16
15
|
import { ResultColumnId } from '@/types';
|
|
17
16
|
|
|
18
|
-
import { Cell } from './Cell';
|
|
19
17
|
import styles from './Results.module.scss';
|
|
20
18
|
import { type ResultData } from './types';
|
|
21
19
|
|
|
@@ -23,13 +21,12 @@ export const Result = ({
|
|
|
23
21
|
index,
|
|
24
22
|
highlightedIndex,
|
|
25
23
|
results = [],
|
|
26
|
-
onBlur
|
|
27
|
-
onClick
|
|
28
|
-
onFocus
|
|
29
|
-
onMouseEnter
|
|
24
|
+
onBlur,
|
|
25
|
+
onClick,
|
|
26
|
+
onFocus,
|
|
27
|
+
onMouseEnter,
|
|
30
28
|
style,
|
|
31
29
|
}: RowComponentProps<ResultData>): ReactElement => {
|
|
32
|
-
const ref = useRef<HTMLButtonElement>(null);
|
|
33
30
|
const columns = useColumns();
|
|
34
31
|
const locale = useTypedSelector(selectLocale);
|
|
35
32
|
const query = useTypedSelector(selectResultsQuery);
|
|
@@ -39,68 +36,61 @@ export const Result = ({
|
|
|
39
36
|
const words = direction === 'rtl' ? [...result.words].reverse() : result.words;
|
|
40
37
|
const coordinates = useTypedSelector((state) => selectResultCoordinates(state, index));
|
|
41
38
|
|
|
42
|
-
const handleClick: MouseEventHandler = (event) => onClick(result, event);
|
|
43
|
-
const handleMouseEnter: MouseEventHandler = (event) => onMouseEnter(result, event);
|
|
44
|
-
const handleBlur: FocusEventHandler = (event) => onBlur(result, event);
|
|
45
|
-
const handleFocus: FocusEventHandler = (event) => onFocus(result, event);
|
|
39
|
+
const handleClick: MouseEventHandler | undefined = onClick && ((event) => onClick(result, event));
|
|
40
|
+
const handleMouseEnter: MouseEventHandler | undefined = onMouseEnter && ((event) => onMouseEnter(result, event));
|
|
41
|
+
const handleBlur: FocusEventHandler | undefined = onBlur && ((event) => onBlur(result, event));
|
|
42
|
+
const handleFocus: FocusEventHandler | undefined = onFocus && ((event) => onFocus(result, event));
|
|
46
43
|
|
|
47
44
|
return (
|
|
48
|
-
<
|
|
49
|
-
aria-current={index === highlightedIndex ? 'true' : undefined}
|
|
50
|
-
aria-hidden={isMatching ? undefined : 'true'}
|
|
45
|
+
<Row
|
|
51
46
|
aria-label={result.word}
|
|
52
|
-
className={classNames(styles.result, {
|
|
53
|
-
[styles.highlighted]: index === highlightedIndex,
|
|
54
|
-
})}
|
|
55
47
|
data-testid="result"
|
|
56
|
-
|
|
48
|
+
highlighted={index === highlightedIndex}
|
|
49
|
+
inactive={!isMatching}
|
|
57
50
|
style={style}
|
|
58
|
-
type="button"
|
|
59
51
|
onBlur={handleBlur}
|
|
60
52
|
onClick={handleClick}
|
|
61
53
|
onFocus={handleFocus}
|
|
62
54
|
onMouseEnter={handleMouseEnter}
|
|
63
55
|
>
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
)}
|
|
56
|
+
{columns[ResultColumnId.Coordinates] && (
|
|
57
|
+
<Cell className={styles.coordinates} translationKey="settings.showCoordinates" value={coordinates} />
|
|
58
|
+
)}
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
{columns[ResultColumnId.Word] && (
|
|
61
|
+
<Cell primary translationKey="common.word" value={result.word}>
|
|
62
|
+
<Highlighter highlightClassName={styles.highlight} searchWords={[query]} textToHighlight={result.word} />
|
|
63
|
+
</Cell>
|
|
64
|
+
)}
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
{columns[ResultColumnId.TilesCount] && (
|
|
67
|
+
<Cell className={styles.stat} translationKey="common.tiles" value={result.tilesCount} />
|
|
68
|
+
)}
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
{columns[ResultColumnId.VowelsCount] && (
|
|
71
|
+
<Cell className={styles.stat} translationKey="common.vowels" value={result.vowelsCount} />
|
|
72
|
+
)}
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
{columns[ResultColumnId.ConsonantsCount] && (
|
|
75
|
+
<Cell className={styles.stat} translationKey="common.consonants" value={result.consonantsCount} />
|
|
76
|
+
)}
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
{columns[ResultColumnId.BlanksCount] && (
|
|
79
|
+
<Cell className={styles.stat} translationKey="common.blanks" value={result.blanksCount} />
|
|
80
|
+
)}
|
|
90
81
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
{columns[ResultColumnId.WordsCount] && (
|
|
83
|
+
<Cell
|
|
84
|
+
className={styles.stat}
|
|
85
|
+
translationKey="common.words"
|
|
86
|
+
tooltip={`${result.wordsCount.toLocaleString(locale)} (${words.join(separator)})`}
|
|
87
|
+
value={result.wordsCount}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</button>
|
|
91
|
+
{columns[ResultColumnId.Points] && (
|
|
92
|
+
<Cell className={styles.points} data-testid="points" translationKey="common.points" value={result.points} />
|
|
93
|
+
)}
|
|
94
|
+
</Row>
|
|
105
95
|
);
|
|
106
96
|
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
@use 'styles/mixins' as *;
|
|
2
2
|
|
|
3
|
-
$row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
4
|
-
|
|
5
3
|
.results {
|
|
6
4
|
position: relative;
|
|
7
5
|
display: flex;
|
|
@@ -37,122 +35,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
.header {
|
|
41
|
-
position: relative;
|
|
42
|
-
z-index: 1;
|
|
43
|
-
display: flex;
|
|
44
|
-
align-items: center;
|
|
45
|
-
justify-content: space-between;
|
|
46
|
-
border-bottom: var(--border);
|
|
47
|
-
border-top-left-radius: inherit;
|
|
48
|
-
border-top-right-radius: inherit;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.headerButton {
|
|
52
|
-
@include button-reset;
|
|
53
|
-
@include focus-effect;
|
|
54
|
-
|
|
55
|
-
cursor: pointer;
|
|
56
|
-
|
|
57
|
-
text-transform: uppercase;
|
|
58
|
-
transition:
|
|
59
|
-
background-color var(--transition--duration) var(--transition--easing),
|
|
60
|
-
color var(--transition--duration) var(--transition--easing);
|
|
61
|
-
background-color: var(--color--background);
|
|
62
|
-
height: 100%;
|
|
63
|
-
|
|
64
|
-
&:first-child {
|
|
65
|
-
border-start-start-radius: inherit;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
&:last-child {
|
|
69
|
-
border-start-end-radius: inherit;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
&:focus,
|
|
73
|
-
&:hover {
|
|
74
|
-
background-color: var(--color--primary);
|
|
75
|
-
color: var(--color--primary--opposite);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.headerButtonLabel {
|
|
80
|
-
@include ellipsis;
|
|
81
|
-
|
|
82
|
-
flex: 0 1 auto;
|
|
83
|
-
text-align: start;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.headerButtonIcon {
|
|
87
|
-
flex: 0 0 auto;
|
|
88
|
-
width: var(--results--icon--size);
|
|
89
|
-
height: var(--results--icon--size);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.result {
|
|
93
|
-
@include button-reset;
|
|
94
|
-
|
|
95
|
-
user-select: none;
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
font-weight: 400;
|
|
98
|
-
|
|
99
|
-
&:focus,
|
|
100
|
-
&:hover,
|
|
101
|
-
&.highlighted {
|
|
102
|
-
&:not(:disabled) {
|
|
103
|
-
background-color: var(--color--primary);
|
|
104
|
-
color: var(--color--primary--opposite);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&[aria-hidden='true'] {
|
|
109
|
-
color: var(--color--inactive);
|
|
110
|
-
|
|
111
|
-
&:focus,
|
|
112
|
-
&:hover,
|
|
113
|
-
&.highlighted {
|
|
114
|
-
&:not(:disabled) {
|
|
115
|
-
background-color: var(--color--primary--light);
|
|
116
|
-
color: var(--color--primary--opposite);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.resultContent {
|
|
123
|
-
display: flex;
|
|
124
|
-
align-items: center;
|
|
125
|
-
justify-content: space-between;
|
|
126
|
-
height: 100%;
|
|
127
|
-
|
|
128
|
-
.word {
|
|
129
|
-
@include ellipsis;
|
|
130
|
-
|
|
131
|
-
display: flex;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.cell {
|
|
136
|
-
display: flex;
|
|
137
|
-
align-items: center;
|
|
138
|
-
justify-content: center;
|
|
139
|
-
height: 100%;
|
|
140
|
-
gap: var(--spacing--s);
|
|
141
|
-
line-height: var(--results--item--height);
|
|
142
|
-
|
|
143
|
-
.result &.word,
|
|
144
|
-
.headerButton.word & {
|
|
145
|
-
justify-content: flex-start;
|
|
146
|
-
padding-inline-start: $row-padding-horizontal;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.word {
|
|
151
|
-
flex: 1 1 0;
|
|
152
|
-
min-width: 0;
|
|
153
|
-
text-transform: uppercase;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
38
|
.stat {
|
|
157
39
|
$width: 55px;
|
|
158
40
|
|
|
@@ -181,14 +63,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
181
63
|
margin: var(--spacing--xl) auto 0;
|
|
182
64
|
}
|
|
183
65
|
|
|
184
|
-
.sortIcon {
|
|
185
|
-
$size: 19px;
|
|
186
|
-
|
|
187
|
-
flex: 0 0 auto;
|
|
188
|
-
width: $size;
|
|
189
|
-
height: $size;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
66
|
.input {
|
|
193
67
|
border-top: var(--border);
|
|
194
68
|
border-bottom-left-radius: var(--border--radius);
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
// Generated by scripts/generate-scss-types.ts - do not edit.
|
|
2
2
|
declare const styles: {
|
|
3
|
-
readonly cell: string;
|
|
4
3
|
readonly content: string;
|
|
5
4
|
readonly coordinates: string;
|
|
6
5
|
readonly emptyState: string;
|
|
7
|
-
readonly header: string;
|
|
8
|
-
readonly headerButton: string;
|
|
9
|
-
readonly headerButtonIcon: string;
|
|
10
|
-
readonly headerButtonLabel: string;
|
|
11
6
|
readonly highlight: string;
|
|
12
|
-
readonly highlighted: string;
|
|
13
7
|
readonly input: string;
|
|
14
8
|
readonly list: string;
|
|
15
9
|
readonly listContainer: string;
|
|
16
10
|
readonly outdated: string;
|
|
17
11
|
readonly points: string;
|
|
18
|
-
readonly result: string;
|
|
19
|
-
readonly resultContent: string;
|
|
20
12
|
readonly results: string;
|
|
21
13
|
readonly solveButton: string;
|
|
22
|
-
readonly sortIcon: string;
|
|
23
14
|
readonly stat: string;
|
|
24
|
-
readonly word: string;
|
|
25
15
|
};
|
|
26
16
|
|
|
27
17
|
export default styles;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { useEffect, useMemo, type FunctionComponent } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, type FunctionComponent } from 'react';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
3
4
|
import { List, useListRef } from 'react-window';
|
|
4
5
|
import { useDebounce } from 'use-debounce';
|
|
5
6
|
|
|
@@ -7,9 +8,11 @@ import { useLatest } from '@/hooks/useLatest';
|
|
|
7
8
|
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
8
9
|
import { RESULTS_ITEM_HEIGHT, RESULTS_OVERSCAN_COUNT } from '@/parameters';
|
|
9
10
|
import {
|
|
11
|
+
resultsSlice,
|
|
10
12
|
selectAreResultsOutdated,
|
|
11
13
|
selectLocale,
|
|
12
14
|
selectProcessedResults,
|
|
15
|
+
selectResultsQuery,
|
|
13
16
|
selectSolveError,
|
|
14
17
|
selectSolveIsLoading,
|
|
15
18
|
useTranslate,
|
|
@@ -18,7 +21,7 @@ import {
|
|
|
18
21
|
|
|
19
22
|
import { EmptyState } from '../EmptyState';
|
|
20
23
|
import { Loading } from '../Loading';
|
|
21
|
-
import {
|
|
24
|
+
import { Search } from '../Table';
|
|
22
25
|
|
|
23
26
|
import { Header } from './Header';
|
|
24
27
|
import { Result } from './Result';
|
|
@@ -35,10 +38,12 @@ interface Props {
|
|
|
35
38
|
const IS_LOADING_DEBOUNCE = 100;
|
|
36
39
|
|
|
37
40
|
export const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIndex }) => {
|
|
41
|
+
const dispatch = useDispatch();
|
|
38
42
|
const translate = useTranslate();
|
|
39
43
|
const locale = useTypedSelector(selectLocale);
|
|
40
44
|
const { direction } = LOCALE_FEATURES[locale];
|
|
41
45
|
const results = useTypedSelector(selectProcessedResults);
|
|
46
|
+
const query = useTypedSelector(selectResultsQuery);
|
|
42
47
|
const isLoading = useTypedSelector(selectSolveIsLoading);
|
|
43
48
|
const [isLoadingDebounced] = useDebounce(isLoading, IS_LOADING_DEBOUNCE);
|
|
44
49
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
@@ -58,7 +63,14 @@ export const Results: FunctionComponent<Props> = ({ callbacks, className, highli
|
|
|
58
63
|
const scrollToIndex = typeof highlightedIndex === 'number' ? highlightedIndex : 0;
|
|
59
64
|
const scrollToIndexRef = useLatest(scrollToIndex);
|
|
60
65
|
const hasResults = typeof error === 'undefined' && typeof results !== 'undefined';
|
|
61
|
-
const
|
|
66
|
+
const showSearch = hasResults && results.length > 0 && !isOutdated;
|
|
67
|
+
|
|
68
|
+
const handleQueryChange = useCallback(
|
|
69
|
+
(newQuery: string) => {
|
|
70
|
+
dispatch(resultsSlice.actions.changeQuery(newQuery));
|
|
71
|
+
},
|
|
72
|
+
[dispatch],
|
|
73
|
+
);
|
|
62
74
|
|
|
63
75
|
useEffect(() => {
|
|
64
76
|
// without setTimeout, the initial scrolling offset is calculated
|
|
@@ -119,7 +131,7 @@ export const Results: FunctionComponent<Props> = ({ callbacks, className, highli
|
|
|
119
131
|
)}
|
|
120
132
|
|
|
121
133
|
{!isOutdated && results.length > 0 && (
|
|
122
|
-
<div className={styles.listContainer}
|
|
134
|
+
<div className={styles.listContainer}>
|
|
123
135
|
<List
|
|
124
136
|
className={classNames(styles.list, {
|
|
125
137
|
[styles.outdated]: isOutdated,
|
|
@@ -131,6 +143,7 @@ export const Results: FunctionComponent<Props> = ({ callbacks, className, highli
|
|
|
131
143
|
rowCount={results.length}
|
|
132
144
|
rowHeight={RESULTS_ITEM_HEIGHT}
|
|
133
145
|
rowProps={itemData}
|
|
146
|
+
onMouseLeave={callbacks.onMouseLeave}
|
|
134
147
|
/>
|
|
135
148
|
</div>
|
|
136
149
|
)}
|
|
@@ -138,7 +151,14 @@ export const Results: FunctionComponent<Props> = ({ callbacks, className, highli
|
|
|
138
151
|
)}
|
|
139
152
|
</div>
|
|
140
153
|
|
|
141
|
-
{
|
|
154
|
+
{showSearch && (
|
|
155
|
+
<Search
|
|
156
|
+
className={styles.input}
|
|
157
|
+
placeholder={translate('results.input.placeholder')}
|
|
158
|
+
value={query}
|
|
159
|
+
onChange={handleQueryChange}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
142
162
|
|
|
143
163
|
{isLoadingDebounced && <Loading />}
|
|
144
164
|
</div>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
|
|
11
|
-
@include media('
|
|
11
|
+
@include media('compact', 'touch') {
|
|
12
12
|
margin-bottom: calc(3 * var(--spacing--l) + var(--button--icon--size));
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
height: 100%;
|
|
32
32
|
margin: 0 auto;
|
|
33
33
|
|
|
34
|
-
@include media('
|
|
34
|
+
@include media('compact') {
|
|
35
35
|
grid-template-columns: 1fr;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
flex: 0 0 var(--solver-column--width);
|
|
55
55
|
gap: var(--spacing);
|
|
56
56
|
|
|
57
|
-
@include media('
|
|
57
|
+
@include media('compact') {
|
|
58
58
|
display: none;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
width: 100%;
|
|
107
107
|
max-width: var(--controls-max-width);
|
|
108
108
|
|
|
109
|
-
@include media('
|
|
109
|
+
@include media('desktop') {
|
|
110
110
|
display: none;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -3,13 +3,14 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { type FunctionComponent, memo, type SyntheticEvent, useEffect, useMemo } from 'react';
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
|
|
6
|
+
import { useIsCompactLayout } from '@/hooks/useIsCompactLayout';
|
|
6
7
|
import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
|
|
7
|
-
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
8
8
|
import {
|
|
9
9
|
resultsSlice,
|
|
10
10
|
selectAreResultsOutdated,
|
|
11
11
|
selectProcessedResults,
|
|
12
12
|
selectResultCandidate,
|
|
13
|
+
selectResultCandidateIndex,
|
|
13
14
|
selectSolveError,
|
|
14
15
|
solveSlice,
|
|
15
16
|
useTranslate,
|
|
@@ -36,10 +37,11 @@ const SolverBase: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
36
37
|
const dispatch = useDispatch();
|
|
37
38
|
const translate = useTranslate();
|
|
38
39
|
const isTouchDevice = useIsTouchDevice();
|
|
39
|
-
const showCompactControls =
|
|
40
|
+
const showCompactControls = useIsCompactLayout();
|
|
40
41
|
const error = useTypedSelector(selectSolveError);
|
|
41
42
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
42
43
|
const results = useTypedSelector(selectProcessedResults);
|
|
44
|
+
const highlightedIndex = useTypedSelector(selectResultCandidateIndex);
|
|
43
45
|
const store = useTypedStore();
|
|
44
46
|
const [bestResult] = results || [];
|
|
45
47
|
const touchCallbacks = useMemo(
|
|
@@ -100,7 +102,7 @@ const SolverBase: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
100
102
|
</form>
|
|
101
103
|
|
|
102
104
|
<div className={styles.column}>
|
|
103
|
-
<Results callbacks={callbacks} className={styles.results} />
|
|
105
|
+
<Results callbacks={callbacks} className={styles.results} highlightedIndex={highlightedIndex} />
|
|
104
106
|
|
|
105
107
|
<div data-testid="dictionary" className={styles.dictionaryContainer}>
|
|
106
108
|
<Dictionary className={styles.dictionary} />
|
package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss
CHANGED
|
@@ -96,7 +96,6 @@
|
|
|
96
96
|
flex: 1;
|
|
97
97
|
text-transform: uppercase;
|
|
98
98
|
text-align: center;
|
|
99
|
-
white-space: pre-wrap;
|
|
100
99
|
|
|
101
100
|
@include media('<xs') {
|
|
102
101
|
padding: var(--spacing--m);
|
|
@@ -104,6 +103,11 @@
|
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
|
|
106
|
+
.placeholder {
|
|
107
|
+
color: var(--color--foreground--secondary);
|
|
108
|
+
text-transform: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
.iconContainer {
|
|
108
112
|
flex: 0 0 auto;
|
|
109
113
|
display: flex;
|
|
@@ -35,7 +35,7 @@ export const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onR
|
|
|
35
35
|
const results = useTypedSelector(selectProcessedResults);
|
|
36
36
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
37
37
|
const index = resultCandidate && results ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
38
|
-
const disabled = isOutdated || !
|
|
38
|
+
const disabled = isOutdated || !results || results.length === 0;
|
|
39
39
|
const isPreviousDisabled = !results || index <= 0 || disabled;
|
|
40
40
|
const isNextDisabled = !results || index >= results.length - 1 || disabled;
|
|
41
41
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
@@ -77,6 +77,7 @@ export const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onR
|
|
|
77
77
|
<button
|
|
78
78
|
aria-label={translate('results')}
|
|
79
79
|
className={styles.resultCandidate}
|
|
80
|
+
data-testid="result-candidate-picker"
|
|
80
81
|
disabled={disabled}
|
|
81
82
|
type="button"
|
|
82
83
|
onClick={onResultClick}
|
|
@@ -88,7 +89,9 @@ export const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onR
|
|
|
88
89
|
</>
|
|
89
90
|
)}
|
|
90
91
|
|
|
91
|
-
{!resultCandidate &&
|
|
92
|
+
{!resultCandidate && (
|
|
93
|
+
<div className={classNames(styles.word, styles.placeholder)}>{translate('results.select')}</div>
|
|
94
|
+
)}
|
|
92
95
|
|
|
93
96
|
<div className={styles.iconContainer}>
|
|
94
97
|
{isLoading && <Spinner className={styles.loading} />}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { type FunctionComponent, type ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { selectLocale, useTranslate, useTypedSelector } from '@/state';
|
|
5
|
+
import { type TranslationKey } from '@/types';
|
|
6
|
+
|
|
7
|
+
import { Tooltip } from '../Tooltip';
|
|
8
|
+
|
|
9
|
+
import styles from './Table.module.scss';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
'data-testid'?: string;
|
|
15
|
+
primary?: boolean;
|
|
16
|
+
translationKey?: TranslationKey;
|
|
17
|
+
tooltip?: string | number;
|
|
18
|
+
value: string | number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const Cell: FunctionComponent<Props> = ({
|
|
22
|
+
children,
|
|
23
|
+
className,
|
|
24
|
+
'data-testid': dataTestId,
|
|
25
|
+
primary,
|
|
26
|
+
translationKey,
|
|
27
|
+
tooltip,
|
|
28
|
+
value,
|
|
29
|
+
}) => {
|
|
30
|
+
const translate = useTranslate();
|
|
31
|
+
const locale = useTypedSelector(selectLocale);
|
|
32
|
+
const formattedValue = value.toLocaleString(locale);
|
|
33
|
+
const label = translationKey ? `${translate(translationKey)}: ` : '';
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Tooltip tooltip={`${label}${tooltip || formattedValue}`}>
|
|
37
|
+
<div className={classNames(styles.cell, className, { [styles.primary]: primary })} data-testid={dataTestId}>
|
|
38
|
+
<span className={styles.cellValue}>{children || formattedValue}</span>
|
|
39
|
+
</div>
|
|
40
|
+
</Tooltip>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FunctionComponent, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import styles from './Table.module.scss';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Header: FunctionComponent<Props> = ({ children }) => {
|
|
10
|
+
return <div className={styles.header}>{children}</div>;
|
|
11
|
+
};
|
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { type FunctionComponent, type ReactElement, type SVGAttributes, useCallback } from 'react';
|
|
3
|
-
import { useDispatch } from 'react-redux';
|
|
4
3
|
|
|
5
4
|
import SortDown from '@/icons/SortDown.svg';
|
|
6
5
|
import SortUp from '@/icons/SortUp.svg';
|
|
7
|
-
import {
|
|
8
|
-
import { type
|
|
6
|
+
import { useTranslate } from '@/state';
|
|
7
|
+
import { type Sort, SortDirection, type TranslationKey } from '@/types';
|
|
9
8
|
|
|
10
9
|
import { Tooltip } from '../Tooltip';
|
|
11
10
|
|
|
12
|
-
import styles from './
|
|
11
|
+
import styles from './Table.module.scss';
|
|
13
12
|
|
|
14
|
-
interface Props {
|
|
15
|
-
className
|
|
13
|
+
interface Props<Id extends string> {
|
|
14
|
+
className?: string;
|
|
16
15
|
Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
|
|
17
|
-
id:
|
|
16
|
+
id: Id;
|
|
17
|
+
primary?: boolean;
|
|
18
|
+
sort: Sort<Id>;
|
|
18
19
|
translationKey: TranslationKey;
|
|
20
|
+
onSort: (id: Id) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export const HeaderButton =
|
|
22
|
-
|
|
23
|
+
export const HeaderButton = <Id extends string>({
|
|
24
|
+
className,
|
|
25
|
+
Icon,
|
|
26
|
+
id,
|
|
27
|
+
primary,
|
|
28
|
+
sort,
|
|
29
|
+
translationKey,
|
|
30
|
+
onSort,
|
|
31
|
+
}: Props<Id>): ReactElement => {
|
|
23
32
|
const translate = useTranslate();
|
|
24
|
-
const sort = useTypedSelector(selectResultsSort);
|
|
25
33
|
|
|
26
34
|
const handleClick = useCallback(() => {
|
|
27
|
-
|
|
28
|
-
}, [
|
|
35
|
+
onSort(id);
|
|
36
|
+
}, [id, onSort]);
|
|
29
37
|
|
|
30
38
|
return (
|
|
31
39
|
<Tooltip tooltip={translate(translationKey)}>
|
|
32
40
|
<button
|
|
33
41
|
aria-label={translate(translationKey)}
|
|
34
|
-
className={classNames(styles.headerButton, className)}
|
|
42
|
+
className={classNames(styles.headerButton, className, { [styles.primary]: primary })}
|
|
35
43
|
type="button"
|
|
36
44
|
onClick={handleClick}
|
|
37
45
|
>
|
|
38
|
-
<span className={styles.cell}>
|
|
46
|
+
<span className={classNames(styles.cell, { [styles.primary]: primary })}>
|
|
39
47
|
{Icon && <Icon aria-hidden="true" className={styles.headerButtonIcon} role="img" />}
|
|
40
48
|
|
|
41
49
|
{!Icon && <span className={styles.headerButtonLabel}>{translate(translationKey)}</span>}
|