@scrabble-solver/scrabble-solver 2.13.12 → 2.13.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +14 -14
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/webpack/client-production/0.pack +0 -0
  6. package/.next/cache/webpack/client-production/index.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  11. package/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  14. package/.next/prerender-manifest.js +1 -1
  15. package/.next/prerender-manifest.json +1 -1
  16. package/.next/routes-manifest.json +1 -1
  17. package/.next/server/chunks/807.js +1 -1
  18. package/.next/server/middleware-build-manifest.js +1 -1
  19. package/.next/server/pages/404.html +1 -1
  20. package/.next/server/pages/500.html +1 -1
  21. package/.next/server/pages/api/solve.js +1 -1
  22. package/.next/server/pages/index.html +1 -1
  23. package/.next/server/pages/index.js +1 -1
  24. package/.next/server/pages/index.json +1 -1
  25. package/.next/server/pages-manifest.json +1 -1
  26. package/.next/static/chunks/{main-b5b360c6afb66b05.js → main-8b0b4e610892a916.js} +1 -1
  27. package/.next/static/chunks/pages/{404-0c9f3e0f8b15f487.js → 404-b0c2ccded2455be0.js} +1 -1
  28. package/.next/static/chunks/pages/_app-42ce6b4032e931ff.js +17 -0
  29. package/.next/static/chunks/pages/index-3718075f2ba2220c.js +1 -0
  30. package/.next/static/chunks/webpack-c4acd79e87956a0e.js +1 -0
  31. package/.next/static/css/2adc9736d823979b.css +2 -0
  32. package/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → qwJjm2FeDHHGY92CY5oQQ}/_buildManifest.js +1 -1
  33. package/.next/trace +45 -45
  34. package/package.json +13 -13
  35. package/src/components/Board/Board.module.scss +3 -9
  36. package/src/components/Board/Board.tsx +2 -2
  37. package/src/components/Board/BoardPure.tsx +3 -2
  38. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -0
  39. package/src/components/Board/hooks/useBackgroundImage.tsx +2 -6
  40. package/src/components/Board/hooks/useBoardStyle.ts +10 -4
  41. package/src/components/Dictionary/Dictionary.module.scss +10 -7
  42. package/src/components/Dictionary/Dictionary.tsx +38 -36
  43. package/src/components/Loading/Loading.module.scss +1 -0
  44. package/src/components/Loading/Loading.tsx +1 -1
  45. package/src/components/Modal/components/Section/Section.tsx +3 -2
  46. package/src/components/NavButtons/NavButtons.tsx +1 -0
  47. package/src/components/Rack/Rack.tsx +1 -0
  48. package/src/components/Results/Cell.tsx +7 -3
  49. package/src/components/Results/Header.tsx +99 -0
  50. package/src/components/Results/HeaderButton.tsx +18 -13
  51. package/src/components/Results/Result.tsx +23 -16
  52. package/src/components/Results/Results.module.scss +24 -12
  53. package/src/components/Results/Results.tsx +3 -9
  54. package/src/components/Results/types.ts +0 -8
  55. package/src/components/Solver/Solver.tsx +1 -1
  56. package/src/components/Tile/TilePure.tsx +1 -0
  57. package/src/hooks/index.ts +1 -0
  58. package/src/hooks/useAppLayout.ts +12 -1
  59. package/src/hooks/useColumns.ts +47 -0
  60. package/src/icons/GeoAlt.svg +5 -0
  61. package/src/icons/OneTwoThree.svg +4 -0
  62. package/src/icons/SquareA.svg +6 -0
  63. package/src/icons/SquareB.svg +6 -0
  64. package/src/icons/Squares.svg +34 -0
  65. package/src/icons/Words.svg +22 -0
  66. package/src/icons/index.ts +6 -0
  67. package/src/lib/groupResults.ts +1 -1
  68. package/src/lib/index.ts +0 -1
  69. package/src/lib/sortResults.ts +10 -10
  70. package/src/modals/KeyMapModal/KeyMapModal.tsx +8 -9
  71. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +1 -0
  72. package/src/modals/SettingsModal/SettingsModal.tsx +5 -5
  73. package/src/modals/WordsModal/WordsModal.tsx +2 -0
  74. package/src/parameters/index.ts +12 -0
  75. package/src/state/selectors.ts +26 -1
  76. package/src/state/slices/resultsInitialState.ts +2 -2
  77. package/src/state/slices/resultsSlice.ts +2 -2
  78. package/src/state/useTranslate.ts +5 -1
  79. package/src/styles/variables.scss +1 -0
  80. package/src/types/index.ts +11 -2
  81. package/.next/static/chunks/pages/_app-8246f5b39b6a5e59.js +0 -17
  82. package/.next/static/chunks/pages/index-65bfe83d121535ab.js +0 -1
  83. package/.next/static/chunks/webpack-6ef43a8d4a395f49.js +0 -1
  84. package/.next/static/css/2f727b21d1331ea5.css +0 -2
  85. package/src/components/Results/getCoordinatesColumn.ts +0 -14
  86. package/src/components/Results/getLocaleColumns.ts +0 -58
  87. package/src/components/Results/useColumns.ts +0 -44
  88. package/src/lib/dataUrlToBlob.ts +0 -20
  89. /package/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → qwJjm2FeDHHGY92CY5oQQ}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.13.12",
3
+ "version": "2.13.13",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -27,21 +27,21 @@
27
27
  "start": "env-cmd next start -p 3333"
28
28
  },
29
29
  "dependencies": {
30
- "@floating-ui/react": "^0.26.17",
30
+ "@floating-ui/react": "^0.26.19",
31
31
  "@kamilmielnik/trie": "^3.0.0",
32
- "@reduxjs/toolkit": "^2.2.5",
33
- "@scrabble-solver/configs": "^2.13.12",
34
- "@scrabble-solver/constants": "^2.13.12",
35
- "@scrabble-solver/dictionaries": "^2.13.12",
36
- "@scrabble-solver/logger": "^2.13.12",
37
- "@scrabble-solver/solver": "^2.13.12",
38
- "@scrabble-solver/types": "^2.13.12",
39
- "@scrabble-solver/word-definitions": "^2.13.12",
32
+ "@reduxjs/toolkit": "^2.2.6",
33
+ "@scrabble-solver/configs": "^2.13.13",
34
+ "@scrabble-solver/constants": "^2.13.13",
35
+ "@scrabble-solver/dictionaries": "^2.13.13",
36
+ "@scrabble-solver/logger": "^2.13.13",
37
+ "@scrabble-solver/solver": "^2.13.13",
38
+ "@scrabble-solver/types": "^2.13.13",
39
+ "@scrabble-solver/word-definitions": "^2.13.13",
40
40
  "classnames": "^2.5.1",
41
41
  "env-cmd": "^10.1.0",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
44
- "next": "^14.2.4",
44
+ "next": "^14.2.5",
45
45
  "normalize.css": "^8.0.1",
46
46
  "react": "^18.3.1",
47
47
  "react-cool-onclickoutside": "^1.7.0",
@@ -71,7 +71,7 @@
71
71
  "@types/react-window": "^1.8.8",
72
72
  "@types/redux": "^3.6.31",
73
73
  "@types/redux-saga": "^0.10.5",
74
- "sass": "^1.77.5"
74
+ "sass": "^1.77.8"
75
75
  },
76
- "gitHead": "092d0fb2988a32cfd29384d6d075e106e0042b11"
76
+ "gitHead": "8cfc206dec35314cd879a3ce8f9b0c60c3fd231a"
77
77
  }
@@ -5,6 +5,7 @@
5
5
  display: grid;
6
6
  gap: var(--border--width);
7
7
  box-shadow: var(--box-shadow);
8
+ background-color: white;
8
9
  border: var(--border);
9
10
  border-radius: var(--border--radius);
10
11
  }
@@ -64,16 +65,9 @@
64
65
  color: var(--color--white);
65
66
  }
66
67
 
67
- .coordinateColumn {
68
+ .coordinate {
69
+ position: relative;
68
70
  display: flex;
69
71
  align-items: center;
70
72
  justify-content: center;
71
- font-size: var(--font--size--h2);
72
- }
73
-
74
- .coordinateRow {
75
- display: flex;
76
- align-items: center;
77
- justify-content: center;
78
- font-size: var(--font--size--h2);
79
73
  }
@@ -176,8 +176,8 @@ const Board: FunctionComponent<Props> = ({ className }) => {
176
176
  ref={floatingFocus.refs.setFloating}
177
177
  style={{
178
178
  position: floatingFocus.strategy,
179
- top: (floatingFocus.y ? floatingFocus.y + cellSize : 0) - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
180
- left: (floatingFocus.x ?? 0) - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
179
+ top: floatingFocus.y + cellSize - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
180
+ left: floatingFocus.x - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
181
181
  width: cellSize,
182
182
  height: cellSize,
183
183
  opacity: hasFocus ? 1 : 0,
@@ -61,6 +61,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
61
61
  ) => (
62
62
  <div
63
63
  className={classNames(styles.board, className)}
64
+ data-testid="board"
64
65
  ref={ref}
65
66
  style={style}
66
67
  onBlur={onBlur}
@@ -73,7 +74,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
73
74
 
74
75
  {rows[0].map((_column, index) => (
75
76
  <div
76
- className={styles.coordinateColumn}
77
+ className={styles.coordinate}
77
78
  key={index}
78
79
  style={{
79
80
  width: cellSize,
@@ -116,7 +117,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
116
117
  <Fragment key={y}>
117
118
  {showCoordinates !== 'hidden' && (
118
119
  <div
119
- className={styles.coordinateRow}
120
+ className={styles.coordinate}
120
121
  style={{
121
122
  width: coordinatesSize,
122
123
  height: cellSize,
@@ -23,6 +23,8 @@ const ToggleDirectionButton: FunctionComponent<Props> = ({ className, direction,
23
23
  <Button
24
24
  aria-label={translate('cell.toggle-direction')}
25
25
  className={classNames(styles.button, className)}
26
+ data-direction={direction}
27
+ data-testid="toggle-direction-button"
26
28
  Icon={ArrowDown}
27
29
  iconClassName={classNames(styles.icon, {
28
30
  [styles.right]: direction === 'horizontal',
@@ -8,7 +8,7 @@ import { Provider } from 'react-redux';
8
8
  import { useAppLayout, useMediaQueries } from 'hooks';
9
9
  import { LOCALE_FEATURES } from 'i18n';
10
10
  import { Star } from 'icons';
11
- import { dataUrlToBlob, getTileSizes } from 'lib';
11
+ import { getTileSizes } from 'lib';
12
12
  import { BORDER_COLOR_LIGHT, BORDER_RADIUS, BORDER_WIDTH, COLOR_BACKGROUND, COLOR_BONUS_START } from 'parameters';
13
13
  import { selectConfig, selectLocale, selectShowCoordinates, store, useTypedSelector } from 'state';
14
14
  import { Point } from 'types';
@@ -211,11 +211,7 @@ const useBackgroundImage = () => {
211
211
 
212
212
  const encodedSvg = useMemo(() => globalThis.btoa(backgroundSvg), [backgroundSvg]);
213
213
  const dataUrl = `data:image/svg+xml;base64,${encodedSvg}`;
214
- const blob = useMemo(() => dataUrlToBlob(dataUrl), [dataUrl]);
215
- const blobUrl = useMemo(() => URL.createObjectURL(blob), [blob]);
216
- const url = `url(${blobUrl})`;
217
-
218
- return url;
214
+ return dataUrl;
219
215
  };
220
216
 
221
217
  export default useBackgroundImage;
@@ -2,6 +2,7 @@ import { CSSProperties, useMemo } from 'react';
2
2
 
3
3
  import { useAppLayout } from 'hooks';
4
4
  import { getTileSizes } from 'lib';
5
+ import { BORDER_WIDTH } from 'parameters';
5
6
  import { selectConfig, selectShowCoordinates, useTypedSelector } from 'state';
6
7
 
7
8
  import useBackgroundImage from './useBackgroundImage';
@@ -10,16 +11,21 @@ const useBoardStyle = () => {
10
11
  const config = useTypedSelector(selectConfig);
11
12
  const { cellSize } = useAppLayout();
12
13
  const { tileFontSize } = getTileSizes(cellSize);
13
- const backgroundImage = useBackgroundImage();
14
14
  const showCoordinates = useTypedSelector(selectShowCoordinates);
15
+ const backgroundImage = useBackgroundImage();
16
+ const coordinatesSize = 0.5 * cellSize - BORDER_WIDTH;
15
17
  const boardStyle = useMemo<CSSProperties>(
16
18
  () => ({
17
- backgroundImage,
19
+ backgroundImage: `url(${backgroundImage})`,
18
20
  fontSize: tileFontSize,
19
21
  gridTemplateColumns:
20
- showCoordinates === 'hidden' ? `repeat(${config.boardSize}, 1fr)` : `0.5fr repeat(${config.boardSize}, 1fr)`,
22
+ showCoordinates === 'hidden'
23
+ ? `repeat(${config.boardSize}, 1fr)`
24
+ : `${coordinatesSize}px repeat(${config.boardSize}, 1fr)`,
21
25
  gridTemplateRows:
22
- showCoordinates === 'hidden' ? `repeat(${config.boardSize}, 1fr)` : `0.5fr repeat(${config.boardSize}, 1fr)`,
26
+ showCoordinates === 'hidden'
27
+ ? `repeat(${config.boardSize}, 1fr)`
28
+ : `${coordinatesSize}px repeat(${config.boardSize}, 1fr)`,
23
29
  }),
24
30
  [backgroundImage, config.boardSize, tileFontSize],
25
31
  );
@@ -1,14 +1,10 @@
1
1
  @import 'styles/mixins';
2
2
 
3
3
  .dictionary {
4
- @include scrollbars;
5
-
6
4
  position: relative;
7
- max-height: var(--dictionary--height);
8
- height: var(--dictionary--height);
9
- overflow-y: auto;
10
- word-break: break-word;
11
5
  transition: var(--transition);
6
+ word-break: break-word;
7
+ overflow: hidden;
12
8
 
13
9
  &.isAllowed {
14
10
  background-color: var(--color--green--light);
@@ -19,6 +15,13 @@
19
15
  }
20
16
  }
21
17
 
18
+ .content {
19
+ @include scrollbars;
20
+
21
+ height: 100%;
22
+ overflow-y: auto;
23
+ }
24
+
22
25
  .result {
23
26
  transition: var(--transition);
24
27
 
@@ -43,7 +46,7 @@
43
46
  }
44
47
  }
45
48
 
46
- .content {
49
+ .resultContent {
47
50
  padding: var(--spacing--l);
48
51
  }
49
52
 
@@ -25,49 +25,51 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
25
25
  [styles.isNotAllowed]: isLastAllowed === false,
26
26
  })}
27
27
  >
28
- {typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
28
+ <div className={styles.content}>
29
+ {typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
29
30
 
30
- {typeof error === 'undefined' && !isLoading && results.length === 0 && (
31
- <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
32
- )}
31
+ {typeof error === 'undefined' && !isLoading && results.length === 0 && (
32
+ <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
33
+ )}
33
34
 
34
- {results.map(({ definitions, exists, isAllowed, word }) => (
35
- <div
36
- className={classNames(styles.result, {
37
- [styles.isAllowed]: isAllowed === true,
38
- [styles.isNotAllowed]: isAllowed === false,
39
- })}
40
- key={word}
41
- >
42
- <div className={styles.content}>
43
- {word && <h2 className={styles.word}>{word}</h2>}
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
- {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
46
+ {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
46
47
 
47
- {isAllowed === true && definitions.length === 0 && (
48
- <>
49
- <div>
50
- {exists
51
- ? translate('dictionary.empty-state.no-definitions')
52
- : translate('dictionary.empty-state.no-results')}
53
- </div>
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
- {isAllowed === true && definitions.length > 0 && (
58
- <ul className={styles.definitions}>
59
- {definitions.map((result, index) => (
60
- <li key={index} className={styles.definition}>
61
- {result}
62
- </li>
63
- ))}
64
- </ul>
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
- {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
68
+ {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
69
+ </div>
68
70
  </div>
69
- </div>
70
- ))}
71
+ ))}
72
+ </div>
71
73
 
72
74
  {isLoading && <Loading />}
73
75
  </div>
@@ -2,6 +2,7 @@
2
2
  .dim {
3
3
  position: absolute;
4
4
  inset: 0;
5
+ border-radius: inherit;
5
6
  }
6
7
 
7
8
  .loading {
@@ -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>
@@ -125,6 +125,7 @@ const NavButtons: FunctionComponent<Props> = ({
125
125
 
126
126
  <IconButton
127
127
  aria-label={translate('settings')}
128
+ data-testid="settings-button"
128
129
  className={styles.button}
129
130
  Icon={Cog}
130
131
  tooltip={translate('settings')}
@@ -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}
@@ -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)}>{children || formattedValue}</div>
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
- column: Column;
14
+ className: string;
15
+ Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
16
+ id: ResultColumnId;
17
+ translationKey: TranslationKey;
18
+ style?: CSSProperties;
16
19
  }
17
20
 
18
- const HeaderButton = ({ column }: Props): ReactElement => {
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(column.id));
25
- }, [column.id, dispatch]);
27
+ dispatch(resultsSlice.actions.sort(id));
28
+ }, [dispatch, id]);
26
29
 
27
30
  return (
28
- <Tooltip tooltip={translate(column.translationKey)}>
31
+ <Tooltip tooltip={translate(translationKey)}>
29
32
  <button
30
- aria-label={translate(column.translationKey)}
31
- className={classNames(styles.headerButton, column.className)}
32
- key={column.id}
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
- <span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
40
+ {Icon && <Icon className={styles.headerButtonIcon} />}
38
41
 
39
- {sort.column === column.id && (
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} />}