scrabble-solver 2.13.12 → 2.14.0
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/.eslintrc.js +13 -4
- package/.github/workflows/build.yml +6 -14
- package/.github/workflows/cypress.yml +28 -0
- package/.github/workflows/eslint.yml +9 -16
- package/.github/workflows/jest.yml +21 -0
- package/.nx/cache/nx_files.nxt +0 -0
- package/.nx/workspace-data/file-map.json +639 -543
- package/.nx/workspace-data/lockfile.hash +1 -1
- package/.nx/workspace-data/parsed-lock-file.json +6131 -5552
- package/.nx/workspace-data/project-graph.json +4276 -2896
- package/README.md +5 -3
- package/bin/scrabble-solver.js +0 -0
- package/cypress/e2e/app.spec.cy.ts +118 -0
- package/cypress/e2e/bugs/112-character-bonus.ts +40 -0
- package/cypress/e2e/bugs/129-esc-sidebar.ts +23 -0
- package/cypress/e2e/bugs/2-q-tile-in-english.ts +33 -0
- package/cypress/e2e/bugs/3-x-tile-in-polish.ts +33 -0
- package/cypress/e2e/bugs/30-click-inside-radio-icon.ts +26 -0
- package/cypress/e2e/bugs/333-disabled-radio-cursor.ts +24 -0
- package/cypress/e2e/bugs/342-results-sorting.ts +48 -0
- package/cypress/e2e/bugs/348-results-word-padding.ts +74 -0
- package/cypress/e2e/bugs.spec.cy.ts +8 -0
- package/cypress/support/commands.ts +4 -0
- package/cypress/support/e2e.ts +1 -0
- package/cypress/support/index.ts +1 -0
- package/cypress/support/lib/actions.ts +57 -0
- package/cypress/support/lib/index.ts +2 -0
- package/cypress/support/lib/selectors.ts +70 -0
- package/cypress/tsconfig.json +9 -0
- package/cypress.config.ts +9 -0
- package/jest.config.js +2 -3
- package/lerna.json +1 -1
- package/package.json +29 -22
- package/packages/configs/package.json +3 -3
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/package-lock.json +2 -2
- package/packages/dictionaries/package.json +4 -4
- package/packages/logger/package-lock.json +3 -3
- package/packages/logger/package.json +2 -2
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +14 -14
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/prerender-manifest.js +1 -1
- package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/807.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
- package/packages/scrabble-solver/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → TxfjxrH5h31sPQxPhrJzG}/_buildManifest.js +1 -1
- package/packages/scrabble-solver/.next/static/chunks/{main-b5b360c6afb66b05.js → main-8b0b4e610892a916.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/{404-0c9f3e0f8b15f487.js → 404-b0c2ccded2455be0.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-2912876c7b6e698e.js +17 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-86d9ad372c48c8b7.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/webpack-c4acd79e87956a0e.js +1 -0
- package/packages/scrabble-solver/.next/static/css/14625ff7b4d265d0.css +2 -0
- package/packages/scrabble-solver/.next/trace +45 -45
- package/packages/scrabble-solver/package-lock.json +6 -6
- package/packages/scrabble-solver/package.json +13 -13
- package/packages/scrabble-solver/public/service-worker.js +1 -1
- package/packages/scrabble-solver/src/components/Alert/Alert.tsx +1 -1
- package/packages/scrabble-solver/src/components/Board/Board.module.scss +3 -9
- package/packages/scrabble-solver/src/components/Board/Board.tsx +2 -2
- package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +4 -3
- package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +2 -6
- package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +10 -4
- package/packages/scrabble-solver/src/components/Button/Button.tsx +1 -1
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +10 -7
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +38 -36
- package/packages/scrabble-solver/src/components/IconButton/IconButton.tsx +1 -1
- package/packages/scrabble-solver/src/components/IconButton/Link.tsx +1 -1
- package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.tsx +4 -4
- package/packages/scrabble-solver/src/components/Loading/Loading.module.scss +1 -0
- package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
- package/packages/scrabble-solver/src/components/Modal/components/Section/Section.tsx +3 -2
- package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +1 -0
- package/packages/scrabble-solver/src/components/NotFound/NotFound.tsx +1 -1
- package/packages/scrabble-solver/src/components/Rack/Rack.tsx +1 -0
- package/packages/scrabble-solver/src/components/Radio/Radio.tsx +1 -1
- package/packages/scrabble-solver/src/components/Results/Cell.tsx +7 -3
- package/packages/scrabble-solver/src/components/Results/Header.tsx +99 -0
- package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +18 -13
- package/packages/scrabble-solver/src/components/Results/Result.tsx +23 -16
- package/packages/scrabble-solver/src/components/Results/Results.module.scss +27 -18
- package/packages/scrabble-solver/src/components/Results/Results.tsx +3 -9
- package/packages/scrabble-solver/src/components/Results/types.ts +0 -8
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +1 -1
- package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +1 -1
- package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +2 -1
- package/packages/scrabble-solver/src/hooks/index.ts +1 -0
- package/packages/scrabble-solver/src/hooks/useAppLayout.ts +12 -1
- package/packages/scrabble-solver/src/hooks/useColumns.ts +47 -0
- package/packages/scrabble-solver/src/icons/GeoAlt.svg +5 -0
- package/packages/scrabble-solver/src/icons/OneTwoThree.svg +4 -0
- package/packages/scrabble-solver/src/icons/SquareA.svg +6 -0
- package/packages/scrabble-solver/src/icons/SquareB.svg +6 -0
- package/packages/scrabble-solver/src/icons/Squares.svg +34 -0
- package/packages/scrabble-solver/src/icons/Words.svg +22 -0
- package/packages/scrabble-solver/src/icons/index.ts +6 -0
- package/packages/scrabble-solver/src/lib/groupResults.ts +1 -1
- package/packages/scrabble-solver/src/lib/index.ts +0 -1
- package/packages/scrabble-solver/src/lib/sortResults.ts +10 -10
- package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +8 -9
- package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +1 -1
- package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +1 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +5 -5
- package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +1 -1
- package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +4 -2
- package/packages/scrabble-solver/src/parameters/index.ts +12 -0
- package/packages/scrabble-solver/src/state/selectors.ts +26 -1
- package/packages/scrabble-solver/src/state/slices/resultsInitialState.ts +2 -2
- package/packages/scrabble-solver/src/state/slices/resultsSlice.ts +2 -2
- package/packages/scrabble-solver/src/state/useTranslate.ts +5 -1
- package/packages/scrabble-solver/src/styles/variables.scss +1 -0
- package/packages/scrabble-solver/src/types/index.ts +11 -2
- package/packages/solver/package-lock.json +2 -2
- package/packages/solver/package.json +5 -5
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/build/lib/request.d.ts +0 -1
- package/packages/word-definitions/package-lock.json +2 -2
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/package-lock.json +3 -3
- package/packages/word-lists/package.json +3 -3
- package/tsconfig.jest.json +2 -1
- package/.github/workflows/test.yml +0 -28
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-8246f5b39b6a5e59.js +0 -17
- package/packages/scrabble-solver/.next/static/chunks/pages/index-65bfe83d121535ab.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/webpack-6ef43a8d4a395f49.js +0 -1
- package/packages/scrabble-solver/.next/static/css/2f727b21d1331ea5.css +0 -2
- package/packages/scrabble-solver/src/components/Results/getCoordinatesColumn.ts +0 -14
- package/packages/scrabble-solver/src/components/Results/getLocaleColumns.ts +0 -58
- package/packages/scrabble-solver/src/components/Results/useColumns.ts +0 -44
- package/packages/scrabble-solver/src/lib/dataUrlToBlob.ts +0 -20
- /package/packages/scrabble-solver/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → TxfjxrH5h31sPQxPhrJzG}/_ssgManifest.js +0 -0
|
@@ -25,49 +25,51 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
|
25
25
|
[styles.isNotAllowed]: isLastAllowed === false,
|
|
26
26
|
})}
|
|
27
27
|
>
|
|
28
|
-
|
|
28
|
+
<div className={styles.content}>
|
|
29
|
+
{typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
{typeof error === 'undefined' && !isLoading && results.length === 0 && (
|
|
32
|
+
<EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
|
|
33
|
+
)}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
{results.map(({ definitions, exists, isAllowed, word }) => (
|
|
36
|
+
<div
|
|
37
|
+
className={classNames(styles.result, {
|
|
38
|
+
[styles.isAllowed]: isAllowed === true,
|
|
39
|
+
[styles.isNotAllowed]: isAllowed === false,
|
|
40
|
+
})}
|
|
41
|
+
key={word}
|
|
42
|
+
>
|
|
43
|
+
<div className={styles.resultContent}>
|
|
44
|
+
{word && <h2 className={styles.word}>{word}</h2>}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
{isAllowed === true && definitions.length === 0 && (
|
|
49
|
+
<>
|
|
50
|
+
<div>
|
|
51
|
+
{exists
|
|
52
|
+
? translate('dictionary.empty-state.no-definitions')
|
|
53
|
+
: translate('dictionary.empty-state.no-results')}
|
|
54
|
+
</div>
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
{isAllowed === true && definitions.length > 0 && (
|
|
59
|
+
<ul className={styles.definitions}>
|
|
60
|
+
{definitions.map((result, index) => (
|
|
61
|
+
<li key={index} className={styles.definition}>
|
|
62
|
+
{result}
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
)}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
|
|
69
|
+
</div>
|
|
68
70
|
</div>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
71
73
|
|
|
72
74
|
{isLoading && <Loading />}
|
|
73
75
|
</div>
|
|
@@ -19,7 +19,7 @@ const IconButton: FunctionComponent<Props> = ({ className, Icon, tooltip, ...pro
|
|
|
19
19
|
<Tooltip tooltip={tooltip}>
|
|
20
20
|
<button className={classNames(styles.iconButton, className)} type="button" {...props}>
|
|
21
21
|
<span className={styles.content}>
|
|
22
|
-
<Icon className={styles.icon} />
|
|
22
|
+
<Icon aria-hidden="true" className={styles.icon} role="img" />
|
|
23
23
|
</span>
|
|
24
24
|
</button>
|
|
25
25
|
</Tooltip>
|
|
@@ -18,7 +18,7 @@ const Link: FunctionComponent<Props> = ({ className, Icon, tooltip, ...props })
|
|
|
18
18
|
<Tooltip tooltip={tooltip}>
|
|
19
19
|
<a className={classNames(styles.iconButton, className)} {...props}>
|
|
20
20
|
<span className={styles.content}>
|
|
21
|
-
<Icon className={styles.icon} />
|
|
21
|
+
<Icon aria-hidden="true" className={styles.icon} role="img" />
|
|
22
22
|
</span>
|
|
23
23
|
</a>
|
|
24
24
|
</Tooltip>
|
|
@@ -14,16 +14,16 @@ interface Props {
|
|
|
14
14
|
const Arrows: FunctionComponent<Props> = ({ className }) => (
|
|
15
15
|
<div className={classNames(styles.arrows, className)}>
|
|
16
16
|
<Key className={classNames(styles.arrow, styles.left)}>
|
|
17
|
-
<ArrowLeft />
|
|
17
|
+
<ArrowLeft aria-hidden="true" role="img" />
|
|
18
18
|
</Key>
|
|
19
19
|
<Key className={classNames(styles.arrow, styles.up)}>
|
|
20
|
-
<ArrowUp />
|
|
20
|
+
<ArrowUp aria-hidden="true" role="img" />
|
|
21
21
|
</Key>
|
|
22
22
|
<Key className={classNames(styles.arrow, styles.right)}>
|
|
23
|
-
<ArrowRight />
|
|
23
|
+
<ArrowRight aria-hidden="true" role="img" />
|
|
24
24
|
</Key>
|
|
25
25
|
<Key className={classNames(styles.arrow, styles.down)}>
|
|
26
|
-
<ArrowDown />
|
|
26
|
+
<ArrowDown aria-hidden="true" role="img" />
|
|
27
27
|
</Key>
|
|
28
28
|
</div>
|
|
29
29
|
);
|
|
@@ -29,7 +29,7 @@ const Loading: FunctionComponent<Props> = ({ className, wave = true }) => {
|
|
|
29
29
|
const content = useMemo(() => prepareContent(message), [message]);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<div aria-label={translation} className={classNames(styles.loading, className)} role="status">
|
|
32
|
+
<div aria-label={translation} className={classNames(styles.loading, className)} data-testid="loading" role="status">
|
|
33
33
|
<div className={styles.dim} />
|
|
34
34
|
<div className={styles.text}>
|
|
35
35
|
<PlainTiles className={classNames(styles.tiles)} content={content} dropShadow wave={wave} />
|
|
@@ -6,11 +6,12 @@ import styles from './Section.module.scss';
|
|
|
6
6
|
interface Props {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
className?: string;
|
|
9
|
+
label: string;
|
|
9
10
|
title: ReactNode;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const Section: FunctionComponent<Props> = ({ children, className, title }) => (
|
|
13
|
-
<section className={classNames(styles.section, className)}>
|
|
13
|
+
const Section: FunctionComponent<Props> = ({ children, className, label, title }) => (
|
|
14
|
+
<section aria-label={label} className={classNames(styles.section, className)}>
|
|
14
15
|
<h2 className={styles.heading}>{title}</h2>
|
|
15
16
|
<div>{children}</div>
|
|
16
17
|
</section>
|
|
@@ -11,7 +11,7 @@ const CONTENT = [['HTTP', '404']];
|
|
|
11
11
|
const NotFound: FunctionComponent = () => (
|
|
12
12
|
<div className={styles.notFound}>
|
|
13
13
|
<a className={styles.link} href="/">
|
|
14
|
-
<DashCircleFill className={styles.icon} />
|
|
14
|
+
<DashCircleFill aria-hidden="true" className={styles.icon} role="img" />
|
|
15
15
|
<PlainTiles className={styles.tiles} content={CONTENT} />
|
|
16
16
|
</a>
|
|
17
17
|
</div>
|
|
@@ -168,6 +168,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
|
|
|
168
168
|
className={classNames(styles.rack, className, {
|
|
169
169
|
[styles.hidden]: showInputPrompt,
|
|
170
170
|
})}
|
|
171
|
+
data-testid="rack"
|
|
171
172
|
ref={ref}
|
|
172
173
|
style={{ fontSize: tileFontSize }}
|
|
173
174
|
onPaste={handlePaste}
|
|
@@ -30,7 +30,7 @@ const Radio: FunctionComponent<Props> = ({ checked, children, className, disable
|
|
|
30
30
|
onChange={onChange}
|
|
31
31
|
/>
|
|
32
32
|
|
|
33
|
-
<div className={styles.icon} />
|
|
33
|
+
<div aria-hidden="true" className={styles.icon} role="img" />
|
|
34
34
|
|
|
35
35
|
<div className={styles.content}>{children}</div>
|
|
36
36
|
</label>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { FunctionComponent, ReactNode } from 'react';
|
|
2
|
+
import { CSSProperties, FunctionComponent, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { selectLocale, useTranslate, useTypedSelector } from 'state';
|
|
5
5
|
import { TranslationKey } from 'types';
|
|
@@ -11,19 +11,23 @@ import styles from './Results.module.scss';
|
|
|
11
11
|
interface Props {
|
|
12
12
|
children?: ReactNode;
|
|
13
13
|
className?: string;
|
|
14
|
+
dataTestId?: string;
|
|
15
|
+
style?: CSSProperties;
|
|
14
16
|
translationKey: TranslationKey;
|
|
15
17
|
tooltip?: string | number;
|
|
16
18
|
value: string | number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const Cell: FunctionComponent<Props> = ({ children, className, translationKey, tooltip, value }) => {
|
|
21
|
+
const Cell: FunctionComponent<Props> = ({ children, className, dataTestId, style, translationKey, tooltip, value }) => {
|
|
20
22
|
const translate = useTranslate();
|
|
21
23
|
const locale = useTypedSelector(selectLocale);
|
|
22
24
|
const formattedValue = value.toLocaleString(locale);
|
|
23
25
|
|
|
24
26
|
return (
|
|
25
27
|
<Tooltip tooltip={`${translate(translationKey)}: ${tooltip || formattedValue}`}>
|
|
26
|
-
<div className={classNames(styles.cell, className)}
|
|
28
|
+
<div className={classNames(styles.cell, className)} data-testid={dataTestId} style={style}>
|
|
29
|
+
{children || formattedValue}
|
|
30
|
+
</div>
|
|
27
31
|
</Tooltip>
|
|
28
32
|
);
|
|
29
33
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FunctionComponent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useAppLayout, useColumns } from 'hooks';
|
|
4
|
+
import { GeoAlt, OneTwoThree, Square, SquareA, SquareB, Squares, Words } from 'icons';
|
|
5
|
+
import { RESULTS_COLUMN_WIDTH } from 'parameters';
|
|
6
|
+
import { ResultColumnId } from 'types';
|
|
7
|
+
|
|
8
|
+
import HeaderButton from './HeaderButton';
|
|
9
|
+
import styles from './Results.module.scss';
|
|
10
|
+
|
|
11
|
+
const Results: FunctionComponent = () => {
|
|
12
|
+
const { resultWordWidth } = useAppLayout();
|
|
13
|
+
const columns = useColumns();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={styles.header}>
|
|
17
|
+
{columns[ResultColumnId.Coordinates] && (
|
|
18
|
+
<HeaderButton
|
|
19
|
+
className={styles.coordinates}
|
|
20
|
+
Icon={GeoAlt}
|
|
21
|
+
id={ResultColumnId.Coordinates}
|
|
22
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Coordinates] }}
|
|
23
|
+
translationKey="settings.showCoordinates"
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
26
|
+
|
|
27
|
+
{columns[ResultColumnId.Word] && (
|
|
28
|
+
<HeaderButton
|
|
29
|
+
className={styles.word}
|
|
30
|
+
id={ResultColumnId.Word}
|
|
31
|
+
style={{ flexBasis: resultWordWidth }}
|
|
32
|
+
translationKey="common.word"
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
{columns[ResultColumnId.TilesCount] && (
|
|
37
|
+
<HeaderButton
|
|
38
|
+
className={styles.stat}
|
|
39
|
+
Icon={Squares}
|
|
40
|
+
id={ResultColumnId.TilesCount}
|
|
41
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.TilesCount] }}
|
|
42
|
+
translationKey="common.tiles"
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
|
|
46
|
+
{columns[ResultColumnId.VowelsCount] && (
|
|
47
|
+
<HeaderButton
|
|
48
|
+
className={styles.stat}
|
|
49
|
+
Icon={SquareA}
|
|
50
|
+
id={ResultColumnId.VowelsCount}
|
|
51
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.VowelsCount] }}
|
|
52
|
+
translationKey="common.vowels"
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{columns[ResultColumnId.ConsonantsCount] && (
|
|
57
|
+
<HeaderButton
|
|
58
|
+
className={styles.stat}
|
|
59
|
+
Icon={SquareB}
|
|
60
|
+
id={ResultColumnId.ConsonantsCount}
|
|
61
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.ConsonantsCount] }}
|
|
62
|
+
translationKey="common.consonants"
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
|
|
66
|
+
{columns[ResultColumnId.BlanksCount] && (
|
|
67
|
+
<HeaderButton
|
|
68
|
+
className={styles.stat}
|
|
69
|
+
Icon={Square}
|
|
70
|
+
id={ResultColumnId.BlanksCount}
|
|
71
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.BlanksCount] }}
|
|
72
|
+
translationKey="common.blanks"
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{columns[ResultColumnId.WordsCount] && (
|
|
77
|
+
<HeaderButton
|
|
78
|
+
className={styles.stat}
|
|
79
|
+
Icon={Words}
|
|
80
|
+
id={ResultColumnId.WordsCount}
|
|
81
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.WordsCount] }}
|
|
82
|
+
translationKey="common.words"
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{columns[ResultColumnId.Points] && (
|
|
87
|
+
<HeaderButton
|
|
88
|
+
className={styles.points}
|
|
89
|
+
Icon={OneTwoThree}
|
|
90
|
+
id={ResultColumnId.Points}
|
|
91
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Points] }}
|
|
92
|
+
translationKey="common.points"
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default Results;
|
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { ReactElement, useCallback } from 'react';
|
|
2
|
+
import { CSSProperties, FunctionComponent, ReactElement, SVGAttributes, useCallback } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
5
|
import { SortDown, SortUp } from 'icons';
|
|
6
6
|
import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
|
|
7
|
-
import { SortDirection } from 'types';
|
|
7
|
+
import { ResultColumnId, SortDirection, TranslationKey } from 'types';
|
|
8
8
|
|
|
9
9
|
import { Tooltip } from '../Tooltip';
|
|
10
10
|
|
|
11
11
|
import styles from './Results.module.scss';
|
|
12
|
-
import { Column } from './types';
|
|
13
12
|
|
|
14
13
|
interface Props {
|
|
15
|
-
|
|
14
|
+
className: string;
|
|
15
|
+
Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
|
|
16
|
+
id: ResultColumnId;
|
|
17
|
+
translationKey: TranslationKey;
|
|
18
|
+
style?: CSSProperties;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
const HeaderButton = ({
|
|
21
|
+
const HeaderButton = ({ className, Icon, id, translationKey, style }: Props): ReactElement => {
|
|
19
22
|
const dispatch = useDispatch();
|
|
20
23
|
const translate = useTranslate();
|
|
21
24
|
const sort = useTypedSelector(selectResultsSort);
|
|
22
25
|
|
|
23
26
|
const handleClick = useCallback(() => {
|
|
24
|
-
dispatch(resultsSlice.actions.sort(
|
|
25
|
-
}, [
|
|
27
|
+
dispatch(resultsSlice.actions.sort(id));
|
|
28
|
+
}, [dispatch, id]);
|
|
26
29
|
|
|
27
30
|
return (
|
|
28
|
-
<Tooltip tooltip={translate(
|
|
31
|
+
<Tooltip tooltip={translate(translationKey)}>
|
|
29
32
|
<button
|
|
30
|
-
aria-label={translate(
|
|
31
|
-
className={classNames(styles.headerButton,
|
|
32
|
-
|
|
33
|
+
aria-label={translate(translationKey)}
|
|
34
|
+
className={classNames(styles.headerButton, className)}
|
|
35
|
+
style={style}
|
|
33
36
|
type="button"
|
|
34
37
|
onClick={handleClick}
|
|
35
38
|
>
|
|
36
39
|
<span className={styles.cell}>
|
|
37
|
-
<
|
|
40
|
+
{Icon && <Icon aria-hidden="true" className={styles.headerButtonIcon} role="img" />}
|
|
38
41
|
|
|
39
|
-
{
|
|
42
|
+
{!Icon && <span className={styles.headerButtonLabel}>{translate(translationKey)}</span>}
|
|
43
|
+
|
|
44
|
+
{sort.column === id && (
|
|
40
45
|
<>
|
|
41
46
|
{sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
|
|
42
47
|
{sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
|
|
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useMemo, useRef } from 'react';
|
|
3
3
|
import Highlighter from 'react-highlight-words';
|
|
4
4
|
|
|
5
|
+
import { useAppLayout, useColumns } from 'hooks';
|
|
5
6
|
import { LOCALE_FEATURES } from 'i18n';
|
|
6
7
|
import { getCoordinates, noop } from 'lib';
|
|
7
8
|
import {
|
|
@@ -11,12 +12,11 @@ import {
|
|
|
11
12
|
selectShowCoordinates,
|
|
12
13
|
useTypedSelector,
|
|
13
14
|
} from 'state';
|
|
14
|
-
import {
|
|
15
|
+
import { ResultColumnId } from 'types';
|
|
15
16
|
|
|
16
17
|
import Cell from './Cell';
|
|
17
18
|
import styles from './Results.module.scss';
|
|
18
19
|
import { ResultData } from './types';
|
|
19
|
-
import useColumns from './useColumns';
|
|
20
20
|
|
|
21
21
|
interface Props {
|
|
22
22
|
data: ResultData;
|
|
@@ -34,16 +34,16 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
34
34
|
onMouseEnter = noop,
|
|
35
35
|
onMouseLeave = noop,
|
|
36
36
|
} = data;
|
|
37
|
+
const { resultWordWidth } = useAppLayout();
|
|
37
38
|
const ref = useRef<HTMLButtonElement>(null);
|
|
38
39
|
const columns = useColumns();
|
|
39
40
|
const locale = useTypedSelector(selectLocale);
|
|
40
41
|
const showCoordinates = useTypedSelector(selectShowCoordinates);
|
|
41
42
|
const query = useTypedSelector(selectResultsQuery);
|
|
42
|
-
const {
|
|
43
|
+
const { direction, separator } = LOCALE_FEATURES[locale];
|
|
43
44
|
const result = results[index];
|
|
44
45
|
const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
|
|
45
46
|
const words = direction === 'rtl' ? [...result.words].reverse() : result.words;
|
|
46
|
-
const enabledColumns = Object.fromEntries(columns.map((column) => [column.id, true]));
|
|
47
47
|
const coordinates = useMemo(() => getCoordinates(result, showCoordinates), [result, showCoordinates]);
|
|
48
48
|
|
|
49
49
|
const handleClick: MouseEventHandler = (event) => onClick(result, event);
|
|
@@ -54,11 +54,13 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
56
|
<button
|
|
57
|
+
aria-hidden={isMatching ? undefined : 'true'}
|
|
57
58
|
aria-label={result.word}
|
|
58
59
|
className={classNames(styles.result, {
|
|
59
60
|
[styles.highlighted]: index === highlightedIndex,
|
|
60
61
|
[styles.notMatching]: !isMatching,
|
|
61
62
|
})}
|
|
63
|
+
data-testid="result"
|
|
62
64
|
ref={ref}
|
|
63
65
|
style={style}
|
|
64
66
|
type="button"
|
|
@@ -69,33 +71,38 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
69
71
|
onMouseLeave={handleMouseLeave}
|
|
70
72
|
>
|
|
71
73
|
<span className={styles.resultContent}>
|
|
72
|
-
{
|
|
74
|
+
{columns[ResultColumnId.Coordinates] && (
|
|
73
75
|
<Cell className={styles.coordinates} translationKey="settings.showCoordinates" value={coordinates} />
|
|
74
76
|
)}
|
|
75
77
|
|
|
76
|
-
{
|
|
77
|
-
<Cell
|
|
78
|
+
{columns[ResultColumnId.Word] && (
|
|
79
|
+
<Cell
|
|
80
|
+
className={styles.word}
|
|
81
|
+
style={{ flexBasis: resultWordWidth }}
|
|
82
|
+
translationKey="common.word"
|
|
83
|
+
value={result.word}
|
|
84
|
+
>
|
|
78
85
|
<Highlighter highlightClassName={styles.highlight} searchWords={[query]} textToHighlight={result.word} />
|
|
79
86
|
</Cell>
|
|
80
87
|
)}
|
|
81
88
|
|
|
82
|
-
{
|
|
89
|
+
{columns[ResultColumnId.TilesCount] && (
|
|
83
90
|
<Cell className={styles.stat} translationKey="common.tiles" value={result.tilesCount} />
|
|
84
91
|
)}
|
|
85
92
|
|
|
86
|
-
{
|
|
87
|
-
<Cell className={styles.stat} translationKey="common.
|
|
93
|
+
{columns[ResultColumnId.VowelsCount] && (
|
|
94
|
+
<Cell className={styles.stat} translationKey="common.vowels" value={result.vowelsCount} />
|
|
88
95
|
)}
|
|
89
96
|
|
|
90
|
-
{
|
|
91
|
-
<Cell className={styles.stat} translationKey="common.
|
|
97
|
+
{columns[ResultColumnId.ConsonantsCount] && (
|
|
98
|
+
<Cell className={styles.stat} translationKey="common.consonants" value={result.consonantsCount} />
|
|
92
99
|
)}
|
|
93
100
|
|
|
94
|
-
{
|
|
101
|
+
{columns[ResultColumnId.BlanksCount] && (
|
|
95
102
|
<Cell className={styles.stat} translationKey="common.blanks" value={result.blanksCount} />
|
|
96
103
|
)}
|
|
97
104
|
|
|
98
|
-
{
|
|
105
|
+
{columns[ResultColumnId.WordsCount] && (
|
|
99
106
|
<Cell
|
|
100
107
|
className={styles.stat}
|
|
101
108
|
translationKey="common.words"
|
|
@@ -104,8 +111,8 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
104
111
|
/>
|
|
105
112
|
)}
|
|
106
113
|
|
|
107
|
-
{
|
|
108
|
-
<Cell className={styles.points} translationKey="common.points" value={result.points} />
|
|
114
|
+
{columns[ResultColumnId.Points] && (
|
|
115
|
+
<Cell className={styles.points} dataTestId="points" translationKey="common.points" value={result.points} />
|
|
109
116
|
)}
|
|
110
117
|
</span>
|
|
111
118
|
</button>
|
|
@@ -44,7 +44,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
44
44
|
display: flex;
|
|
45
45
|
align-items: center;
|
|
46
46
|
justify-content: space-between;
|
|
47
|
-
font-weight: 700;
|
|
48
47
|
border-bottom: var(--border);
|
|
49
48
|
border-top-left-radius: inherit;
|
|
50
49
|
border-top-right-radius: inherit;
|
|
@@ -53,11 +52,15 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
53
52
|
.headerButton {
|
|
54
53
|
@include button-reset;
|
|
55
54
|
@include focus-effect;
|
|
55
|
+
|
|
56
56
|
cursor: pointer;
|
|
57
57
|
|
|
58
58
|
text-transform: uppercase;
|
|
59
|
-
transition:
|
|
59
|
+
transition:
|
|
60
|
+
background-color var(--transition--duration) var(--transition--easing),
|
|
61
|
+
color var(--transition--duration) var(--transition--easing);
|
|
60
62
|
background-color: var(--color--background);
|
|
63
|
+
height: 100%;
|
|
61
64
|
|
|
62
65
|
&:first-child {
|
|
63
66
|
border-start-start-radius: inherit;
|
|
@@ -72,6 +75,17 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
72
75
|
background-color: var(--color--primary);
|
|
73
76
|
color: var(--color--primary--opposite);
|
|
74
77
|
}
|
|
78
|
+
|
|
79
|
+
&.points {
|
|
80
|
+
@include scrollbars;
|
|
81
|
+
|
|
82
|
+
overflow-y: scroll;
|
|
83
|
+
|
|
84
|
+
&,
|
|
85
|
+
&:hover {
|
|
86
|
+
scrollbar-color: transparent transparent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
.headerButtonLabel {
|
|
@@ -81,6 +95,12 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
81
95
|
text-align: start;
|
|
82
96
|
}
|
|
83
97
|
|
|
98
|
+
.headerButtonIcon {
|
|
99
|
+
flex: 0 0 auto;
|
|
100
|
+
width: var(--results--icon--size);
|
|
101
|
+
height: var(--results--icon--size);
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
.result {
|
|
85
105
|
@include button-reset;
|
|
86
106
|
|
|
@@ -98,7 +118,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
98
118
|
}
|
|
99
119
|
}
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
&[aria-hidden='true'] {
|
|
102
122
|
color: var(--color--inactive);
|
|
103
123
|
|
|
104
124
|
&:focus,
|
|
@@ -120,6 +140,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
120
140
|
|
|
121
141
|
.word {
|
|
122
142
|
@include ellipsis;
|
|
143
|
+
|
|
123
144
|
display: flex;
|
|
124
145
|
}
|
|
125
146
|
}
|
|
@@ -129,33 +150,22 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
129
150
|
align-items: center;
|
|
130
151
|
justify-content: center;
|
|
131
152
|
height: 100%;
|
|
132
|
-
padding: 0 var(--spacing--s);
|
|
133
153
|
gap: var(--spacing--s);
|
|
134
154
|
line-height: var(--results--item--height);
|
|
135
155
|
|
|
136
|
-
&.word,
|
|
156
|
+
.result &.word,
|
|
137
157
|
.headerButton.word & {
|
|
138
158
|
justify-content: flex-start;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.result &:first-child,
|
|
142
|
-
.headerButton:first-child & {
|
|
143
|
-
text-align: start;
|
|
144
159
|
padding-inline-start: $row-padding-horizontal;
|
|
145
160
|
}
|
|
146
161
|
|
|
147
|
-
.result &:last-child,
|
|
148
|
-
.headerButton:last-child & {
|
|
149
|
-
padding-inline-end: $row-padding-horizontal;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
162
|
&:last-child {
|
|
153
163
|
flex: 1;
|
|
154
164
|
}
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
.word {
|
|
158
|
-
flex:
|
|
168
|
+
flex: 0 0;
|
|
159
169
|
text-transform: uppercase;
|
|
160
170
|
}
|
|
161
171
|
|
|
@@ -171,12 +181,11 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
171
181
|
$width: 80px;
|
|
172
182
|
|
|
173
183
|
flex: 1 0 $width;
|
|
174
|
-
max-width: $width;
|
|
175
184
|
font-weight: bold;
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
.coordinates {
|
|
179
|
-
$width:
|
|
188
|
+
$width: 55px;
|
|
180
189
|
|
|
181
190
|
flex: 0 0 $width;
|
|
182
191
|
max-width: $width;
|