@scrabble-solver/scrabble-solver 2.18.1 → 2.18.2
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/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 +2 -2
- package/.next/server/pages/500.html +2 -2
- 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 +2 -2
- 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 +2 -2
- package/.next/server/pages/not-found.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/100.305bdf9c75b1758f.js +1 -0
- package/.next/static/chunks/256.66ff9fb6d7db9ec7.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-f9ec81278ffa7b1a.js +1 -0
- package/.next/static/chunks/pages/index-b5bf018100015277.js +1 -0
- package/.next/static/chunks/webpack-9f5c9d275348829b.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/static/{5KOWzYqfY1Z2rjrzptr8D → nvGZh1gPuX98L07YmxlYD}/_buildManifest.js +1 -1
- package/.next/trace +21 -21
- package/.next/trace-build +1 -1
- package/AGENTS.md +9 -0
- package/CLAUDE.md +1 -0
- package/package.json +14 -12
- 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 +28 -3
- 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 → nvGZh1gPuX98L07YmxlYD}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import {
|
|
3
|
+
type CSSProperties,
|
|
4
|
+
type FocusEventHandler,
|
|
5
|
+
type FunctionComponent,
|
|
6
|
+
type MouseEventHandler,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
|
|
10
|
+
import styles from './Table.module.scss';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
'aria-label': string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
'data-testid'?: string;
|
|
17
|
+
highlighted: boolean;
|
|
18
|
+
inactive: boolean;
|
|
19
|
+
style?: CSSProperties;
|
|
20
|
+
onBlur?: FocusEventHandler;
|
|
21
|
+
onClick?: MouseEventHandler;
|
|
22
|
+
onFocus?: FocusEventHandler;
|
|
23
|
+
onMouseEnter?: MouseEventHandler;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Row: FunctionComponent<Props> = ({
|
|
27
|
+
'aria-label': ariaLabel,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
'data-testid': dataTestId,
|
|
31
|
+
highlighted,
|
|
32
|
+
inactive,
|
|
33
|
+
style,
|
|
34
|
+
onBlur,
|
|
35
|
+
onClick,
|
|
36
|
+
onFocus,
|
|
37
|
+
onMouseEnter,
|
|
38
|
+
}) => {
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
aria-current={highlighted ? 'true' : undefined}
|
|
42
|
+
aria-hidden={inactive ? 'true' : undefined}
|
|
43
|
+
aria-label={ariaLabel}
|
|
44
|
+
className={classNames(styles.row, className, {
|
|
45
|
+
[styles.clickable]: Boolean(onClick),
|
|
46
|
+
[styles.highlighted]: highlighted,
|
|
47
|
+
})}
|
|
48
|
+
data-testid={dataTestId}
|
|
49
|
+
style={style}
|
|
50
|
+
type="button"
|
|
51
|
+
onBlur={onBlur}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
onFocus={onFocus}
|
|
54
|
+
onMouseEnter={onMouseEnter}
|
|
55
|
+
>
|
|
56
|
+
<span className={styles.rowContent}>{children}</span>
|
|
57
|
+
</button>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { type ChangeEvent, type FunctionComponent, type SubmitEventHandler, useState } from 'react';
|
|
3
|
-
import { useDispatch } from 'react-redux';
|
|
4
3
|
|
|
5
4
|
import { isRegExp } from '@/lib/isRegExp';
|
|
6
|
-
import { resultsSlice, selectResultsQuery, useTranslate, useTypedSelector } from '@/state';
|
|
7
5
|
|
|
8
|
-
import styles from './
|
|
6
|
+
import styles from './Table.module.scss';
|
|
9
7
|
|
|
10
8
|
interface Props {
|
|
11
9
|
className?: string;
|
|
10
|
+
placeholder: string;
|
|
11
|
+
value: string;
|
|
12
|
+
onChange: (value: string) => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const
|
|
15
|
-
const dispatch = useDispatch();
|
|
16
|
-
const translate = useTranslate();
|
|
17
|
-
const value = useTypedSelector(selectResultsQuery);
|
|
15
|
+
export const Search: FunctionComponent<Props> = ({ className, placeholder, value, onChange }) => {
|
|
18
16
|
const [localValue, setLocalValue] = useState(value);
|
|
19
17
|
|
|
20
18
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
@@ -23,7 +21,7 @@ export const ResultsInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
23
21
|
setLocalValue(newValue);
|
|
24
22
|
|
|
25
23
|
if (isRegExp(newValue)) {
|
|
26
|
-
|
|
24
|
+
onChange(newValue);
|
|
27
25
|
}
|
|
28
26
|
};
|
|
29
27
|
|
|
@@ -32,10 +30,10 @@ export const ResultsInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
32
30
|
};
|
|
33
31
|
|
|
34
32
|
return (
|
|
35
|
-
<form className={classNames(styles.
|
|
33
|
+
<form className={classNames(styles.search, className)} onSubmit={handleSubmit}>
|
|
36
34
|
<input
|
|
37
|
-
className={styles.
|
|
38
|
-
placeholder={
|
|
35
|
+
className={styles.searchField}
|
|
36
|
+
placeholder={placeholder}
|
|
39
37
|
type="text"
|
|
40
38
|
value={localValue}
|
|
41
39
|
onChange={handleChange}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@use 'styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
$row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
position: relative;
|
|
7
|
+
z-index: 1;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
border-bottom: var(--border);
|
|
12
|
+
border-top-left-radius: inherit;
|
|
13
|
+
border-top-right-radius: inherit;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.headerButton {
|
|
17
|
+
@include button-reset;
|
|
18
|
+
@include focus-effect;
|
|
19
|
+
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
|
|
22
|
+
text-transform: uppercase;
|
|
23
|
+
transition:
|
|
24
|
+
background-color var(--transition--duration) var(--transition--easing),
|
|
25
|
+
color var(--transition--duration) var(--transition--easing);
|
|
26
|
+
background-color: var(--color--background);
|
|
27
|
+
height: 100%;
|
|
28
|
+
|
|
29
|
+
&:first-child {
|
|
30
|
+
border-start-start-radius: inherit;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:last-child {
|
|
34
|
+
border-start-end-radius: inherit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:focus,
|
|
38
|
+
&:hover {
|
|
39
|
+
background-color: var(--color--primary);
|
|
40
|
+
color: var(--color--primary--opposite);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&.primary {
|
|
44
|
+
flex: 1 1 0;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
text-transform: uppercase;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.headerButtonLabel {
|
|
51
|
+
@include ellipsis;
|
|
52
|
+
|
|
53
|
+
flex: 0 1 auto;
|
|
54
|
+
text-align: start;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.headerButtonIcon {
|
|
58
|
+
flex: 0 0 auto;
|
|
59
|
+
width: var(--results--icon--size);
|
|
60
|
+
height: var(--results--icon--size);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.row {
|
|
64
|
+
@include button-reset;
|
|
65
|
+
|
|
66
|
+
user-select: none;
|
|
67
|
+
font-weight: 400;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* :focus-visible rather than :focus - a row left focused by click/hover on another row
|
|
71
|
+
* must not keep looking highlighted.
|
|
72
|
+
*/
|
|
73
|
+
&:focus-visible,
|
|
74
|
+
&:hover,
|
|
75
|
+
&.highlighted {
|
|
76
|
+
&:not(:disabled) {
|
|
77
|
+
background-color: var(--color--primary);
|
|
78
|
+
color: var(--color--primary--opposite);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&[aria-hidden='true'] {
|
|
83
|
+
color: var(--color--inactive);
|
|
84
|
+
|
|
85
|
+
&:focus-visible,
|
|
86
|
+
&:hover,
|
|
87
|
+
&.highlighted {
|
|
88
|
+
&:not(:disabled) {
|
|
89
|
+
background-color: var(--color--primary--light);
|
|
90
|
+
color: var(--color--primary--opposite);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.clickable {
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.rowContent {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
height: 100%;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.cell {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
height: 100%;
|
|
112
|
+
gap: var(--spacing--s);
|
|
113
|
+
line-height: var(--results--item--height);
|
|
114
|
+
|
|
115
|
+
&.primary {
|
|
116
|
+
flex: 1 1 0;
|
|
117
|
+
min-width: 0;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
justify-content: flex-start;
|
|
120
|
+
padding-inline-start: $row-padding-horizontal;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.cellValue {
|
|
125
|
+
@include ellipsis;
|
|
126
|
+
|
|
127
|
+
min-width: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.search {
|
|
131
|
+
@include focus-effect;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.searchField {
|
|
135
|
+
@include text-input;
|
|
136
|
+
|
|
137
|
+
border: none;
|
|
138
|
+
border-radius: inherit;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.sortIcon {
|
|
142
|
+
$size: 19px;
|
|
143
|
+
|
|
144
|
+
flex: 0 0 auto;
|
|
145
|
+
width: $size;
|
|
146
|
+
height: $size;
|
|
147
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated by scripts/generate-scss-types.ts - do not edit.
|
|
2
|
+
declare const styles: {
|
|
3
|
+
readonly cell: string;
|
|
4
|
+
readonly cellValue: string;
|
|
5
|
+
readonly clickable: string;
|
|
6
|
+
readonly header: string;
|
|
7
|
+
readonly headerButton: string;
|
|
8
|
+
readonly headerButtonIcon: string;
|
|
9
|
+
readonly headerButtonLabel: string;
|
|
10
|
+
readonly highlighted: string;
|
|
11
|
+
readonly primary: string;
|
|
12
|
+
readonly row: string;
|
|
13
|
+
readonly rowContent: string;
|
|
14
|
+
readonly search: string;
|
|
15
|
+
readonly searchField: string;
|
|
16
|
+
readonly sortIcon: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default styles;
|
package/src/hooks/useColumns.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
|
5
5
|
import { selectLocale, useTypedSelector } from '@/state';
|
|
6
6
|
import { ResultColumnId } from '@/types';
|
|
7
7
|
|
|
8
|
+
import { useIsCompactLayout } from './useIsCompactLayout';
|
|
8
9
|
import { useMediaQueries } from './useMediaQueries';
|
|
9
10
|
|
|
10
11
|
const COLUMNS_XS = [ResultColumnId.Coordinates, ResultColumnId.Word, ResultColumnId.Points];
|
|
@@ -13,7 +14,7 @@ const COLUMNS_S = [...COLUMNS_XS, ResultColumnId.BlanksCount, ResultColumnId.Wor
|
|
|
13
14
|
|
|
14
15
|
const COLUMNS_M = [...COLUMNS_XS];
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const COLUMNS_COMPACT = [...COLUMNS_XS];
|
|
17
18
|
|
|
18
19
|
const selectColumns = createSelector([selectLocale], (locale) => {
|
|
19
20
|
const { consonants, vowels } = LOCALE_FEATURES[locale];
|
|
@@ -39,7 +40,8 @@ const selectColumns = createSelector([selectLocale], (locale) => {
|
|
|
39
40
|
|
|
40
41
|
export const useColumns = (): Partial<Record<ResultColumnId, boolean>> => {
|
|
41
42
|
const columns = useTypedSelector(selectColumns);
|
|
42
|
-
const { isLessThanXs, isLessThanS, isLessThanM
|
|
43
|
+
const { isLessThanXs, isLessThanS, isLessThanM } = useMediaQueries();
|
|
44
|
+
const isCompactLayout = useIsCompactLayout();
|
|
43
45
|
|
|
44
46
|
const filteredColumns = useMemo(() => {
|
|
45
47
|
if (isLessThanXs) {
|
|
@@ -54,12 +56,12 @@ export const useColumns = (): Partial<Record<ResultColumnId, boolean>> => {
|
|
|
54
56
|
return columns.filter((columnId) => COLUMNS_M.includes(columnId));
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
if (
|
|
58
|
-
return columns.filter((columnId) =>
|
|
59
|
+
if (isCompactLayout) {
|
|
60
|
+
return columns.filter((columnId) => COLUMNS_COMPACT.includes(columnId));
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
return columns;
|
|
62
|
-
}, [columns, isLessThanXs, isLessThanS, isLessThanM,
|
|
64
|
+
}, [columns, isLessThanXs, isLessThanS, isLessThanM, isCompactLayout]);
|
|
63
65
|
|
|
64
66
|
const columnsMap = useMemo(() => {
|
|
65
67
|
return Object.fromEntries(filteredColumns.map((column) => [column, true]));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BREAKPOINT_HEIGHT_L } from '@/parameters';
|
|
2
|
+
|
|
3
|
+
import { useMedia } from './useMedia';
|
|
4
|
+
import { useMediaQuery } from './useMediaQuery';
|
|
5
|
+
|
|
6
|
+
const SHORT_VIEWPORT = `(max-height: ${BREAKPOINT_HEIGHT_L - 1}px)`;
|
|
7
|
+
|
|
8
|
+
// Keep in sync with the `compact` media expression in styles/mixins.scss
|
|
9
|
+
export function useIsCompactLayout(): boolean {
|
|
10
|
+
const isNarrowViewport = useMediaQuery('<l');
|
|
11
|
+
const isShortViewport = useMedia(SHORT_VIEWPORT);
|
|
12
|
+
|
|
13
|
+
return isNarrowViewport || isShortViewport;
|
|
14
|
+
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Search results... (RegExp)",
|
|
60
60
|
"results.insert": "Insert",
|
|
61
61
|
"results.preview": "Preview",
|
|
62
|
+
"results.select": "Select...",
|
|
62
63
|
"results.solve": "Solve",
|
|
63
64
|
"settings": "Settings",
|
|
64
65
|
"settings.autoGroupTiles": "Group remaining tiles",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternative",
|
|
79
80
|
"settings.showCoordinates.original": "Original",
|
|
80
81
|
"words": "Created words",
|
|
82
|
+
"words.empty-state.no-words": "No created words - the board is empty.",
|
|
83
|
+
"words.input.placeholder": "Search words... (RegExp)",
|
|
81
84
|
"words.invalid": "Invalid",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Preview",
|
|
86
|
+
"words.valid": "Valid",
|
|
87
|
+
"words.validity": "Validity"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Rechercher les résultats... (RegExp)",
|
|
60
60
|
"results.insert": "Inserer",
|
|
61
61
|
"results.preview": "Prévisualisation",
|
|
62
|
+
"results.select": "Sélectionner...",
|
|
62
63
|
"results.solve": "Résoudre",
|
|
63
64
|
"settings": "Options",
|
|
64
65
|
"settings.autoGroupTiles": "Grouper les cases restantes",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternative",
|
|
79
80
|
"settings.showCoordinates.original": "Original",
|
|
80
81
|
"words": "Mots créés",
|
|
82
|
+
"words.empty-state.no-words": "Pas de mots créés - le plateau est vide.",
|
|
83
|
+
"words.input.placeholder": "Rechercher les mots... (RegExp)",
|
|
81
84
|
"words.invalid": "Incorrect",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Prévisualisation",
|
|
86
|
+
"words.valid": "Corriger",
|
|
87
|
+
"words.validity": "Validité"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Suchergebnisse... (RegExp)",
|
|
60
60
|
"results.insert": "Hinzufügen",
|
|
61
61
|
"results.preview": "Vorschau",
|
|
62
|
+
"results.select": "Auswählen...",
|
|
62
63
|
"results.solve": "Lösen",
|
|
63
64
|
"settings": "Einstellungen",
|
|
64
65
|
"settings.autoGroupTiles": "Restliche Steine gruppieren",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternative",
|
|
79
80
|
"settings.showCoordinates.original": "Original",
|
|
80
81
|
"words": "Gebildete Wörter",
|
|
82
|
+
"words.empty-state.no-words": "Keine gebildeten Wörter - das Brett ist leer.",
|
|
83
|
+
"words.input.placeholder": "Wörter suchen... (RegExp)",
|
|
81
84
|
"words.invalid": "Falsch",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Vorschau",
|
|
86
|
+
"words.valid": "Korrekt",
|
|
87
|
+
"words.validity": "Gültigkeit"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "جستجو در نتایج (RegExp)",
|
|
60
60
|
"results.insert": "وارد کردن",
|
|
61
61
|
"results.preview": "پیش نمایش",
|
|
62
|
+
"results.select": "انتخاب کنید...",
|
|
62
63
|
"results.solve": "حل کن",
|
|
63
64
|
"settings": "تنظیمات",
|
|
64
65
|
"settings.autoGroupTiles": "کاشی های باقی مانده ی طاقچه را کنار هم قرار بده",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "بديل",
|
|
79
80
|
"settings.showCoordinates.original": "إبداعي",
|
|
80
81
|
"words": "کلمات ساخته شده",
|
|
82
|
+
"words.empty-state.no-words": "کلمهای ساخته نشده - صفحه خالی است.",
|
|
83
|
+
"words.input.placeholder": "جستجو در کلمات (RegExp)",
|
|
81
84
|
"words.invalid": "نا معتبر",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "پیش نمایش",
|
|
86
|
+
"words.valid": "معتبر",
|
|
87
|
+
"words.validity": "اعتبار"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Szukaj rozwiązania... (RegExp)",
|
|
60
60
|
"results.insert": "Wstaw",
|
|
61
61
|
"results.preview": "Podgląd",
|
|
62
|
+
"results.select": "Wybierz...",
|
|
62
63
|
"results.solve": "Rozwiąż",
|
|
63
64
|
"settings": "Opcje",
|
|
64
65
|
"settings.autoGroupTiles": "Grupuj pozostałe płytki",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternatywne",
|
|
79
80
|
"settings.showCoordinates.original": "Oryginalne",
|
|
80
81
|
"words": "Utworzone słowa",
|
|
82
|
+
"words.empty-state.no-words": "Brak utworzonych słów - plansza jest pusta.",
|
|
83
|
+
"words.input.placeholder": "Szukaj słów... (RegExp)",
|
|
81
84
|
"words.invalid": "Niepoprawne",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Podgląd",
|
|
86
|
+
"words.valid": "Poprawne",
|
|
87
|
+
"words.validity": "Poprawność"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Cautare solutii... (RegExp)",
|
|
60
60
|
"results.insert": "Introducere",
|
|
61
61
|
"results.preview": "Previzualizare",
|
|
62
|
+
"results.select": "Selectati...",
|
|
62
63
|
"results.solve": "Gasire solutii",
|
|
63
64
|
"settings": "Setari",
|
|
64
65
|
"settings.autoGroupTiles": "Litere ramase",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternativă",
|
|
79
80
|
"settings.showCoordinates.original": "Original",
|
|
80
81
|
"words": "Cuvinte create",
|
|
82
|
+
"words.empty-state.no-words": "Niciun cuvânt creat - tabla este goală.",
|
|
83
|
+
"words.input.placeholder": "Cautare cuvinte... (RegExp)",
|
|
81
84
|
"words.invalid": "Invalid",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Previzualizare",
|
|
86
|
+
"words.valid": "Valid",
|
|
87
|
+
"words.validity": "Validitate"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Busque una solución... (RegExp)",
|
|
60
60
|
"results.insert": "Insertar",
|
|
61
61
|
"results.preview": "Vista previa",
|
|
62
|
+
"results.select": "Seleccionar...",
|
|
62
63
|
"results.solve": "Resolver",
|
|
63
64
|
"settings": "Configuración",
|
|
64
65
|
"settings.autoGroupTiles": "Agrupar casillas restantes",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternativa",
|
|
79
80
|
"settings.showCoordinates.original": "Original",
|
|
80
81
|
"words": "Palabras creadas",
|
|
82
|
+
"words.empty-state.no-words": "No hay palabras creadas; el tablero está vacío.",
|
|
83
|
+
"words.input.placeholder": "Busque una palabra... (RegExp)",
|
|
81
84
|
"words.invalid": "Incorrecto",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Vista previa",
|
|
86
|
+
"words.valid": "Correcto",
|
|
87
|
+
"words.validity": "Validez"
|
|
83
88
|
}
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"results.input.placeholder": "Sonuçları Ara... (RegExp)",
|
|
60
60
|
"results.insert": "Sokmak",
|
|
61
61
|
"results.preview": "Önizleme",
|
|
62
|
+
"results.select": "Seç...",
|
|
62
63
|
"results.solve": "Çöz",
|
|
63
64
|
"settings": "Ayarlar",
|
|
64
65
|
"settings.autoGroupTiles": "Kalan taşları grupla",
|
|
@@ -78,6 +79,10 @@
|
|
|
78
79
|
"settings.showCoordinates.alternative": "Alternatif",
|
|
79
80
|
"settings.showCoordinates.original": "Orijinal",
|
|
80
81
|
"words": "Oluşturulan kelimeler",
|
|
82
|
+
"words.empty-state.no-words": "Oluşturulan kelime yok - tahta boş.",
|
|
83
|
+
"words.input.placeholder": "Kelimeleri Ara... (RegExp)",
|
|
81
84
|
"words.invalid": "Geçersiz",
|
|
82
|
-
"words.
|
|
85
|
+
"words.preview": "Önizleme",
|
|
86
|
+
"words.valid": "Geçerli",
|
|
87
|
+
"words.validity": "Geçerlilik"
|
|
83
88
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/question-square/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z" fill="currentColor" />
|
|
4
|
+
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286m1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94" fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ShowCoordinates } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { type Comparator } from '@/types';
|
|
4
|
+
|
|
5
|
+
import { createStringComparator } from './createStringComparator';
|
|
6
|
+
|
|
7
|
+
interface Parameters<Item> {
|
|
8
|
+
getItemCoordinates: (item: Item, showCoordinates: ShowCoordinates) => string;
|
|
9
|
+
locale: string;
|
|
10
|
+
showCoordinates: ShowCoordinates;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createCoordinatesComparator<Item>({
|
|
14
|
+
getItemCoordinates,
|
|
15
|
+
locale,
|
|
16
|
+
showCoordinates,
|
|
17
|
+
}: Parameters<Item>): Comparator<Item> {
|
|
18
|
+
const stringComparator = createStringComparator(locale);
|
|
19
|
+
|
|
20
|
+
return (a, b) => {
|
|
21
|
+
return stringComparator(getItemCoordinates(a, showCoordinates), getItemCoordinates(b, showCoordinates));
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -3,10 +3,7 @@ import { type Comparator } from '@/types';
|
|
|
3
3
|
import { createStringComparator } from './createStringComparator';
|
|
4
4
|
import { numberComparator } from './numberComparator';
|
|
5
5
|
|
|
6
|
-
export const createKeyComparator = <T extends
|
|
7
|
-
key: keyof T,
|
|
8
|
-
locale: string,
|
|
9
|
-
): Comparator<T> => {
|
|
6
|
+
export const createKeyComparator = <T extends object>(key: keyof T, locale: string): Comparator<T> => {
|
|
10
7
|
const stringComparator = createStringComparator(locale);
|
|
11
8
|
|
|
12
9
|
return (a: T, b: T): number => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ShowCoordinates } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { type Comparator, type ComparatorFactory, type Sort, SortDirection } from '@/types';
|
|
4
|
+
|
|
5
|
+
import { reverseComparator } from './reverseComparator';
|
|
6
|
+
|
|
7
|
+
interface Parameters<Item, Id extends string> {
|
|
8
|
+
comparators: Record<Id, ComparatorFactory<Item>>;
|
|
9
|
+
locale: string;
|
|
10
|
+
showCoordinates: ShowCoordinates;
|
|
11
|
+
sort: Sort<Id>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSortComparator<Item, Id extends string>({
|
|
15
|
+
comparators,
|
|
16
|
+
locale,
|
|
17
|
+
showCoordinates,
|
|
18
|
+
sort,
|
|
19
|
+
}: Parameters<Item, Id>): Comparator<Item> {
|
|
20
|
+
const comparator = comparators[sort.column](locale, showCoordinates);
|
|
21
|
+
return sort.direction === SortDirection.Descending ? reverseComparator(comparator) : comparator;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getCoordinates } from './getCoordinates';
|
|
2
|
+
|
|
3
|
+
describe('getCoordinates', () => {
|
|
4
|
+
const point = { x: 3, y: 5 };
|
|
5
|
+
|
|
6
|
+
it('encodes a horizontal word as row number followed by column letter in original mode', () => {
|
|
7
|
+
expect(getCoordinates({ ...point, isHorizontal: true }, 'original')).toBe('6D');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('encodes a vertical word as column letter followed by row number in original mode', () => {
|
|
11
|
+
expect(getCoordinates({ ...point, isHorizontal: false }, 'original')).toBe('D6');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('encodes a horizontal word as row letter followed by column number in alternative mode', () => {
|
|
15
|
+
expect(getCoordinates({ ...point, isHorizontal: true }, 'alternative')).toBe('F4');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('encodes a vertical word as column number followed by row letter in alternative mode', () => {
|
|
19
|
+
expect(getCoordinates({ ...point, isHorizontal: false }, 'alternative')).toBe('4F');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ShowCoordinates } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { getCoordinate } from './getCoordinate';
|
|
4
|
+
|
|
5
|
+
interface Parameters {
|
|
6
|
+
isHorizontal: boolean;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getCoordinates = ({ isHorizontal, x, y }: Parameters, showCoordinates: ShowCoordinates): string => {
|
|
12
|
+
const xCoordinate = getCoordinate(x, showCoordinates === 'original' ? 'letter' : 'number');
|
|
13
|
+
const yCoordinate = getCoordinate(y, showCoordinates === 'original' ? 'number' : 'letter');
|
|
14
|
+
|
|
15
|
+
return isHorizontal ? `${yCoordinate}${xCoordinate}` : `${xCoordinate}${yCoordinate}`;
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Sort, SortDirection } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const getNextSort = <Id extends string>(sort: Sort<Id>, column: Id): Sort<Id> => {
|
|
4
|
+
return {
|
|
5
|
+
column,
|
|
6
|
+
direction: sort.column === column ? toggleDirection(sort.direction) : sort.direction,
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const toggleDirection = (direction: SortDirection): SortDirection => {
|
|
11
|
+
return direction === SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending;
|
|
12
|
+
};
|
|
@@ -2,6 +2,7 @@ import { type FunctionComponent, memo } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Badge } from '@/components/Badge';
|
|
4
4
|
import { Modal } from '@/components/Modal';
|
|
5
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
5
6
|
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
6
7
|
import { selectLocale, useTranslate, useTypedSelector } from '@/state';
|
|
7
8
|
|
|
@@ -17,6 +18,7 @@ interface Props {
|
|
|
17
18
|
|
|
18
19
|
const RemainingTilesModalBase: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
19
20
|
const translate = useTranslate();
|
|
21
|
+
const coversViewport = useMediaQuery('<s');
|
|
20
22
|
const locale = useTypedSelector(selectLocale);
|
|
21
23
|
const groups = useTypedSelector(selectRemainingTilesGroups);
|
|
22
24
|
const { direction } = LOCALE_FEATURES[locale];
|
|
@@ -44,7 +46,7 @@ const RemainingTilesModalBase: FunctionComponent<Props> = ({ className, isOpen,
|
|
|
44
46
|
{tiles.map((tile) => {
|
|
45
47
|
return (
|
|
46
48
|
<div className={styles.character} key={tile.character}>
|
|
47
|
-
<Character tile={tile} />
|
|
49
|
+
<Character canHighlight={!coversViewport} tile={tile} />
|
|
48
50
|
</div>
|
|
49
51
|
);
|
|
50
52
|
})}
|