@scrabble-solver/scrabble-solver 2.13.6 → 2.13.8

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 (102) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +11 -11
  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/edge-server-production/0.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  9. package/.next/cache/webpack/server-production/0.pack +0 -0
  10. package/.next/cache/webpack/server-production/index.pack +0 -0
  11. package/.next/prerender-manifest.js +1 -1
  12. package/.next/prerender-manifest.json +1 -1
  13. package/.next/routes-manifest.json +1 -1
  14. package/.next/server/chunks/577.js +1 -1
  15. package/.next/server/chunks/807.js +1 -1
  16. package/.next/server/chunks/977.js +1 -1
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/404.html +1 -1
  19. package/.next/server/pages/500.html +1 -1
  20. package/.next/server/pages/_app.js +1 -1
  21. package/.next/server/pages/_error.js +1 -1
  22. package/.next/server/pages/api/solve.js +1 -1
  23. package/.next/server/pages/index.html +1 -1
  24. package/.next/server/pages/index.js +1 -1
  25. package/.next/server/pages/index.json +1 -1
  26. package/.next/server/pages-manifest.json +1 -1
  27. package/.next/static/7zESQYo9UAqNh9LV0b7Sd/_buildManifest.js +1 -0
  28. package/.next/static/chunks/{main-6e708370ad13b1f9.js → main-b5b360c6afb66b05.js} +1 -1
  29. package/.next/static/chunks/pages/{404-129e0943628b6fab.js → 404-63b972b24be99c62.js} +1 -1
  30. package/.next/static/chunks/pages/_app-a2848b7efa6bb6b0.js +17 -0
  31. package/.next/static/chunks/pages/index-7b73be2915cc7099.js +1 -0
  32. package/.next/static/css/6682db14f926d4c7.css +1 -0
  33. package/.next/static/css/b37850c8d5270d91.css +2 -0
  34. package/.next/trace +44 -44
  35. package/package.json +12 -13
  36. package/src/components/Alert/Alert.module.scss +3 -12
  37. package/src/components/Board/Board.module.scss +1 -4
  38. package/src/components/Board/BoardPure.tsx +1 -1
  39. package/src/components/Board/components/Actions/Actions.module.scss +7 -27
  40. package/src/components/Board/components/Cell/Cell.module.scss +5 -32
  41. package/src/components/Board/components/InputPrompt/InputPrompt.module.scss +4 -18
  42. package/src/components/Board/hooks/useBackgroundImage.tsx +15 -4
  43. package/src/components/Board/hooks/useGrid.ts +13 -5
  44. package/src/components/Board/lib/index.ts +0 -1
  45. package/src/components/Button/Button.module.scss +1 -8
  46. package/src/components/Button/Button.tsx +16 -17
  47. package/src/components/Button/Link.tsx +15 -16
  48. package/src/components/Dictionary/Dictionary.module.scss +1 -8
  49. package/src/components/IconButton/IconButton.tsx +8 -8
  50. package/src/components/IconButton/Link.tsx +8 -8
  51. package/src/components/Loading/Loading.module.scss +1 -4
  52. package/src/components/Modal/Modal.module.scss +2 -16
  53. package/src/components/Rack/Rack.module.scss +4 -18
  54. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +1 -4
  55. package/src/components/Results/Cell.tsx +4 -5
  56. package/src/components/Results/HeaderButton.tsx +25 -22
  57. package/src/components/Results/Result.tsx +15 -3
  58. package/src/components/Results/Results.module.scss +31 -48
  59. package/src/components/Results/Results.tsx +1 -1
  60. package/src/components/Results/getCoordinatesColumn.ts +15 -0
  61. package/src/components/Results/getLocaleColumns.ts +7 -0
  62. package/src/components/Results/types.ts +1 -0
  63. package/src/components/Results/useColumns.ts +10 -7
  64. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +9 -40
  65. package/src/components/Tile/Tile.module.scss +6 -26
  66. package/src/components/Tooltip/Tooltip.tsx +28 -0
  67. package/src/components/Tooltip/TooltipContent.tsx +53 -0
  68. package/src/components/Tooltip/TooltipTrigger.tsx +26 -0
  69. package/src/components/Tooltip/context.ts +17 -0
  70. package/src/components/Tooltip/index.ts +1 -1
  71. package/src/components/Tooltip/useTooltip.ts +54 -0
  72. package/src/components/index.ts +1 -2
  73. package/src/hooks/index.ts +0 -1
  74. package/src/i18n/languages/german.json +1 -1
  75. package/src/icons/index.ts +0 -2
  76. package/src/lib/getCoordinates.ts +18 -0
  77. package/src/lib/groupResults.ts +16 -11
  78. package/src/lib/index.ts +2 -1
  79. package/src/lib/sortResults.ts +1 -0
  80. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +2 -2
  81. package/src/modals/MenuModal/MenuModal.module.scss +2 -15
  82. package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +1 -7
  83. package/src/modals/WordsModal/WordsModal.module.scss +2 -15
  84. package/src/pages/_app.tsx +5 -1
  85. package/src/parameters/index.ts +1 -0
  86. package/src/styles/mixins.scss +2 -5
  87. package/src/types/index.ts +1 -0
  88. package/.next/static/4GWIKe7khKxREyq3ZamDK/_buildManifest.js +0 -1
  89. package/.next/static/chunks/pages/_app-cccda36d00fa2328.js +0 -17
  90. package/.next/static/chunks/pages/index-caaf20b2488cb10e.js +0 -1
  91. package/.next/static/css/11366b7489cf90ac.css +0 -1
  92. package/.next/static/css/f549d7823f599b8d.css +0 -2
  93. package/src/components/Checkbox/Checkbox.module.scss +0 -45
  94. package/src/components/Checkbox/Checkbox.tsx +0 -38
  95. package/src/components/Checkbox/index.ts +0 -1
  96. package/src/components/Tooltip/useTooltip.tsx +0 -134
  97. package/src/hooks/usePortal.tsx +0 -47
  98. package/src/icons/CheckboxChecked.svg +0 -4
  99. package/src/icons/CheckboxEmpty.svg +0 -4
  100. package/src/lib/canUseDom.ts +0 -3
  101. /package/.next/static/{4GWIKe7khKxREyq3ZamDK → 7zESQYo9UAqNh9LV0b7Sd}/_ssgManifest.js +0 -0
  102. /package/src/{components/Board/lib → lib}/getCoordinate.ts +0 -0
@@ -6,45 +6,48 @@ import { SortDown, SortUp } from 'icons';
6
6
  import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
7
7
  import { SortDirection } from 'types';
8
8
 
9
- import { useTooltip } from '../Tooltip';
9
+ import { Tooltip } from '../Tooltip';
10
10
 
11
11
  import styles from './Results.module.scss';
12
12
  import { Column } from './types';
13
13
 
14
14
  interface Props {
15
15
  column: Column;
16
+ sortable?: boolean;
16
17
  }
17
18
 
18
- const HeaderButton = ({ column }: Props): ReactElement => {
19
+ const HeaderButton = ({ column, sortable }: Props): ReactElement => {
19
20
  const dispatch = useDispatch();
20
21
  const translate = useTranslate();
21
22
  const sort = useTypedSelector(selectResultsSort);
22
- const triggerProps = useTooltip(translate(column.translationKey));
23
23
 
24
24
  const handleClick = useCallback(() => {
25
25
  dispatch(resultsSlice.actions.sort(column.id));
26
26
  }, [column.id, dispatch]);
27
27
 
28
28
  return (
29
- <button
30
- aria-label={translate(column.translationKey)}
31
- className={classNames(styles.headerButton, column.className)}
32
- key={column.id}
33
- type="button"
34
- onClick={handleClick}
35
- {...triggerProps}
36
- >
37
- <span className={styles.cell}>
38
- <span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
39
-
40
- {sort.column === column.id && (
41
- <>
42
- {sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
43
- {sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
44
- </>
45
- )}
46
- </span>
47
- </button>
29
+ <Tooltip tooltip={translate(column.translationKey)}>
30
+ <button
31
+ aria-label={translate(column.translationKey)}
32
+ className={classNames(styles.headerButton, column.className, {
33
+ [styles.sortable]: sortable,
34
+ })}
35
+ key={column.id}
36
+ type="button"
37
+ onClick={sortable ? handleClick : undefined}
38
+ >
39
+ <span className={styles.cell}>
40
+ <span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
41
+
42
+ {sort.column === column.id && (
43
+ <>
44
+ {sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
45
+ {sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
46
+ </>
47
+ )}
48
+ </span>
49
+ </button>
50
+ </Tooltip>
48
51
  );
49
52
  };
50
53
 
@@ -1,10 +1,16 @@
1
1
  import classNames from 'classnames';
2
- import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useRef } from 'react';
2
+ import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useMemo, useRef } from 'react';
3
3
  import Highlighter from 'react-highlight-words';
4
4
 
5
5
  import { LOCALE_FEATURES } from 'i18n';
6
- import { noop } from 'lib';
7
- import { selectIsResultMatching, selectLocale, selectResultsQuery, useTypedSelector } from 'state';
6
+ import { getCoordinates, noop } from 'lib';
7
+ import {
8
+ selectIsResultMatching,
9
+ selectLocale,
10
+ selectResultsQuery,
11
+ selectShowCoordinates,
12
+ useTypedSelector,
13
+ } from 'state';
8
14
  import { ResultColumn } from 'types';
9
15
 
10
16
  import Cell from './Cell';
@@ -31,12 +37,14 @@ const Result = ({ data, index, style }: Props): ReactElement => {
31
37
  const ref = useRef<HTMLButtonElement>(null);
32
38
  const columns = useColumns();
33
39
  const locale = useTypedSelector(selectLocale);
40
+ const showCoordinates = useTypedSelector(selectShowCoordinates);
34
41
  const query = useTypedSelector(selectResultsQuery);
35
42
  const { consonants, direction, separator, vowels } = LOCALE_FEATURES[locale];
36
43
  const result = results[index];
37
44
  const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
38
45
  const words = direction === 'rtl' ? [...result.words].reverse() : result.words;
39
46
  const enabledColumns = Object.fromEntries(columns.map((column) => [column.id, true]));
47
+ const coordinates = useMemo(() => getCoordinates(result, showCoordinates), [result, showCoordinates]);
40
48
 
41
49
  const handleClick: MouseEventHandler = (event) => onClick(result, event);
42
50
  const handleMouseEnter: MouseEventHandler = (event) => onMouseEnter(result, event);
@@ -61,6 +69,10 @@ const Result = ({ data, index, style }: Props): ReactElement => {
61
69
  onMouseLeave={handleMouseLeave}
62
70
  >
63
71
  <span className={styles.resultContent}>
72
+ {enabledColumns[ResultColumn.Coordinates] && (
73
+ <Cell className={styles.coordinates} translationKey="settings.showCoordinates" value={coordinates} />
74
+ )}
75
+
64
76
  {enabledColumns[ResultColumn.Word] && (
65
77
  <Cell className={styles.word} translationKey="common.word" value={result.word}>
66
78
  <Highlighter highlightClassName={styles.highlight} searchWords={[query]} textToHighlight={result.word} />
@@ -24,10 +24,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
24
24
 
25
25
  .listContainer {
26
26
  position: absolute;
27
- top: 0;
28
- left: 0;
29
- right: 0;
30
- bottom: 0;
27
+ inset: 0;
31
28
  }
32
29
 
33
30
  .list {
@@ -55,37 +52,29 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
55
52
 
56
53
  .headerButton {
57
54
  @include button-reset;
58
- @include focus-effect;
59
55
 
60
- cursor: pointer;
61
56
  text-transform: uppercase;
62
57
  transition: var(--transition);
63
58
  background-color: var(--color--background);
64
59
 
65
- &:focus,
66
- &:hover {
67
- background-color: var(--color--primary);
68
- color: var(--color--primary--opposite);
69
- }
70
-
71
60
  &:first-child {
72
- [dir='ltr'] & {
73
- border-top-left-radius: inherit;
74
- }
75
-
76
- [dir='rtl'] & {
77
- border-top-right-radius: inherit;
78
- }
61
+ border-start-start-radius: inherit;
79
62
  }
80
63
 
81
64
  &:last-child {
82
- [dir='ltr'] & {
83
- border-top-right-radius: inherit;
84
- }
65
+ border-start-end-radius: inherit;
66
+ }
67
+ }
85
68
 
86
- [dir='rtl'] & {
87
- border-top-left-radius: inherit;
88
- }
69
+ .sortable {
70
+ @include focus-effect;
71
+
72
+ cursor: pointer;
73
+
74
+ &:focus,
75
+ &:hover {
76
+ background-color: var(--color--primary);
77
+ color: var(--color--primary--opposite);
89
78
  }
90
79
  }
91
80
 
@@ -93,11 +82,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
93
82
  @include ellipsis;
94
83
 
95
84
  flex: 0 1 auto;
96
- text-align: left;
97
-
98
- [dir='rtl'] & {
99
- text-align: right;
100
- }
85
+ text-align: start;
101
86
  }
102
87
 
103
88
  .result {
@@ -139,6 +124,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
139
124
 
140
125
  .word {
141
126
  @include ellipsis;
127
+ display: flex;
142
128
  }
143
129
  }
144
130
 
@@ -151,30 +137,20 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
151
137
  gap: var(--spacing--s);
152
138
  line-height: var(--results--item--height);
153
139
 
154
- .result &:first-child,
155
- .headerButton:first-child & {
140
+ &.word,
141
+ .headerButton.word & {
156
142
  justify-content: flex-start;
143
+ }
157
144
 
158
- [dir='ltr'] & {
159
- padding-left: $row-padding-horizontal;
160
- text-align: left;
161
- }
162
-
163
- [dir='rtl'] & {
164
- padding-right: $row-padding-horizontal;
165
- text-align: right;
166
- }
145
+ .result &:first-child,
146
+ .headerButton:first-child & {
147
+ text-align: start;
148
+ padding-inline-start: $row-padding-horizontal;
167
149
  }
168
150
 
169
151
  .result &:last-child,
170
152
  .headerButton:last-child & {
171
- [dir='ltr'] & {
172
- padding-right: $row-padding-horizontal;
173
- }
174
-
175
- [dir='rtl'] & {
176
- padding-left: $row-padding-horizontal;
177
- }
153
+ padding-inline-end: $row-padding-horizontal;
178
154
  }
179
155
 
180
156
  &:last-child {
@@ -203,6 +179,13 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
203
179
  font-weight: bold;
204
180
  }
205
181
 
182
+ .coordinates {
183
+ $width: 50px;
184
+
185
+ flex: 0 0 $width;
186
+ max-width: $width;
187
+ }
188
+
206
189
  .solveButton {
207
190
  display: block;
208
191
  margin: var(--spacing--xl) auto 0;
@@ -69,7 +69,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
69
69
  <div className={classNames(styles.results, className)}>
70
70
  <div className={styles.header}>
71
71
  {columns.map((column) => (
72
- <HeaderButton column={column} key={column.id} />
72
+ <HeaderButton column={column} key={column.id} sortable={column.sortable} />
73
73
  ))}
74
74
  </div>
75
75
 
@@ -0,0 +1,15 @@
1
+ import { ResultColumn } from 'types';
2
+
3
+ import styles from './Results.module.scss';
4
+ import { Column } from './types';
5
+
6
+ const getCoordinatesColumn = (): Column => {
7
+ return {
8
+ className: styles.coordinates,
9
+ id: ResultColumn.Coordinates,
10
+ translationKey: 'settings.showCoordinates',
11
+ sortable: false,
12
+ };
13
+ };
14
+
15
+ export default getCoordinatesColumn;
@@ -10,11 +10,13 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
10
10
  className: styles.word,
11
11
  id: ResultColumn.Word,
12
12
  translationKey: 'common.word',
13
+ sortable: true,
13
14
  },
14
15
  {
15
16
  className: styles.stat,
16
17
  id: ResultColumn.TilesCount,
17
18
  translationKey: 'common.tiles',
19
+ sortable: true,
18
20
  },
19
21
  ];
20
22
 
@@ -23,6 +25,7 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
23
25
  className: styles.stat,
24
26
  id: ResultColumn.ConsonantsCount,
25
27
  translationKey: 'common.consonants',
28
+ sortable: true,
26
29
  });
27
30
  }
28
31
 
@@ -31,6 +34,7 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
31
34
  className: styles.stat,
32
35
  id: ResultColumn.VowelsCount,
33
36
  translationKey: 'common.vowels',
37
+ sortable: true,
34
38
  });
35
39
  }
36
40
 
@@ -39,16 +43,19 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
39
43
  className: styles.stat,
40
44
  id: ResultColumn.BlanksCount,
41
45
  translationKey: 'common.blanks',
46
+ sortable: true,
42
47
  },
43
48
  {
44
49
  className: styles.stat,
45
50
  id: ResultColumn.WordsCount,
46
51
  translationKey: 'common.words',
52
+ sortable: true,
47
53
  },
48
54
  {
49
55
  className: styles.points,
50
56
  id: ResultColumn.Points,
51
57
  translationKey: 'common.points',
58
+ sortable: true,
52
59
  },
53
60
  );
54
61
 
@@ -7,6 +7,7 @@ export interface Column {
7
7
  className: string;
8
8
  id: ResultColumn;
9
9
  translationKey: TranslationKey;
10
+ sortable?: boolean;
10
11
  }
11
12
 
12
13
  export interface ResultCallbacks {
@@ -1,12 +1,13 @@
1
1
  import { useMediaQueries } from 'hooks';
2
2
  import { LOCALE_FEATURES } from 'i18n';
3
- import { selectLocale, useTypedSelector } from 'state';
3
+ import { selectLocale, selectShowCoordinates, useTypedSelector } from 'state';
4
4
  import { ResultColumn } from 'types';
5
5
 
6
+ import getCoordinatesColumn from './getCoordinatesColumn';
6
7
  import getLocaleColumns from './getLocaleColumns';
7
8
  import { Column } from './types';
8
9
 
9
- const COLUMNS_XS = [ResultColumn.Word, ResultColumn.Points];
10
+ const COLUMNS_XS = [ResultColumn.Coordinates, ResultColumn.Word, ResultColumn.Points];
10
11
 
11
12
  const COLUMNS_S = [...COLUMNS_XS, ResultColumn.BlanksCount, ResultColumn.WordsCount];
12
13
 
@@ -17,25 +18,27 @@ const COLUMNS_L = [...COLUMNS_XS];
17
18
  const useColumns = (): Column[] => {
18
19
  const locale = useTypedSelector(selectLocale);
19
20
  const localeColumns = getLocaleColumns(LOCALE_FEATURES[locale]);
21
+ const showCoordinates = useTypedSelector(selectShowCoordinates);
22
+ const columns = showCoordinates === 'hidden' ? localeColumns : [getCoordinatesColumn(), ...localeColumns];
20
23
  const { isLessThanXs, isLessThanS, isLessThanM, isLessThanL } = useMediaQueries();
21
24
 
22
25
  if (isLessThanXs) {
23
- return localeColumns.filter((column) => COLUMNS_XS.includes(column.id));
26
+ return columns.filter((column) => COLUMNS_XS.includes(column.id));
24
27
  }
25
28
 
26
29
  if (isLessThanS) {
27
- return localeColumns.filter((column) => COLUMNS_S.includes(column.id));
30
+ return columns.filter((column) => COLUMNS_S.includes(column.id));
28
31
  }
29
32
 
30
33
  if (isLessThanM) {
31
- return localeColumns.filter((column) => COLUMNS_M.includes(column.id));
34
+ return columns.filter((column) => COLUMNS_M.includes(column.id));
32
35
  }
33
36
 
34
37
  if (isLessThanL) {
35
- return localeColumns.filter((column) => COLUMNS_L.includes(column.id));
38
+ return columns.filter((column) => COLUMNS_L.includes(column.id));
36
39
  }
37
40
 
38
- return localeColumns;
41
+ return columns;
39
42
  };
40
43
 
41
44
  export default useColumns;
@@ -82,23 +82,11 @@
82
82
  .points {
83
83
  flex: 0 0 auto;
84
84
  font-weight: bold;
85
+ border-inline-end: var(--border);
85
86
 
86
- [dir='ltr'] & {
87
- border-right: var(--border);
88
-
89
- @include media('<xs') {
90
- border-right: none;
91
- padding-right: var(--spacing--xs);
92
- }
93
- }
94
-
95
- [dir='rtl'] & {
96
- border-left: var(--border);
97
-
98
- @include media('<xs') {
99
- border-left: none;
100
- padding-left: var(--spacing--xs);
101
- }
87
+ @include media('<xs') {
88
+ border-inline-end: none;
89
+ padding-inline-end: var(--spacing--xs);
102
90
  }
103
91
  }
104
92
 
@@ -110,40 +98,21 @@
110
98
  text-align: center;
111
99
  white-space: pre-wrap;
112
100
 
113
- [dir='ltr'] & {
114
- @include media('<xs') {
115
- padding: var(--spacing--m);
116
- text-align: left;
117
- }
118
- }
119
-
120
- [dir='rtl'] & {
121
- @include media('<xs') {
122
- padding: var(--spacing--m);
123
- text-align: right;
124
- }
101
+ @include media('<xs') {
102
+ padding: var(--spacing--m);
103
+ text-align: start;
125
104
  }
126
105
  }
127
106
 
128
107
  .iconContainer {
129
108
  flex: 0 0 auto;
130
109
  display: flex;
131
-
132
- [dir='ltr'] & {
133
- padding-right: var(--spacing--l);
134
- }
135
-
136
- [dir='rtl'] & {
137
- padding-left: var(--spacing--l);
138
- }
110
+ padding-inline-end: var(--spacing--l);
139
111
  }
140
112
 
141
113
  .spinnerContainer {
142
114
  position: absolute;
143
- top: 0;
144
- right: 0;
145
- bottom: 0;
146
- left: 0;
115
+ inset: 0;
147
116
  display: flex;
148
117
  align-items: center;
149
118
  justify-content: center;
@@ -76,10 +76,7 @@
76
76
 
77
77
  .input {
78
78
  position: absolute;
79
- top: 0;
80
- right: 0;
81
- bottom: 0;
82
- left: 0;
79
+ inset: 0;
83
80
  width: 100%;
84
81
  height: 100%;
85
82
  border: none;
@@ -98,6 +95,8 @@
98
95
  @include text-stroke(var(--background-color), 1px);
99
96
 
100
97
  position: absolute;
98
+ inset-block-end: 1%;
99
+ inset-inline-end: 9%;
101
100
  font-weight: bold;
102
101
  user-select: none;
103
102
  pointer-events: none;
@@ -106,16 +105,6 @@
106
105
  @include media('<xs') {
107
106
  display: none;
108
107
  }
109
-
110
- [dir='ltr'] & {
111
- bottom: 1%;
112
- right: 9%;
113
- }
114
-
115
- [dir='rtl'] & {
116
- top: 1%;
117
- left: 9%;
118
- }
119
108
  }
120
109
 
121
110
  .alert {
@@ -124,6 +113,9 @@
124
113
  position: absolute;
125
114
  width: var(--size);
126
115
  height: var(--size);
116
+ inset-block-start: 0;
117
+ inset-inline-end: 0;
118
+ border-start-end-radius: inherit;
127
119
  background: radial-gradient(
128
120
  var(--color--error--opposite),
129
121
  var(--color--error--opposite) 85%,
@@ -132,16 +124,4 @@
132
124
  );
133
125
  color: var(--color--error);
134
126
  pointer-events: none;
135
-
136
- [dir='ltr'] & {
137
- top: 0;
138
- right: 0;
139
- border-top-right-radius: inherit;
140
- }
141
-
142
- [dir='rtl'] & {
143
- top: 0;
144
- left: 0;
145
- border-top-left-radius: inherit;
146
- }
147
127
  }
@@ -0,0 +1,28 @@
1
+ import type { Placement } from '@floating-ui/react';
2
+ import { FunctionComponent, ReactNode } from 'react';
3
+
4
+ import { TooltipContext } from './context';
5
+ import { TooltipContent } from './TooltipContent';
6
+ import { TooltipTrigger } from './TooltipTrigger';
7
+ import { useTooltip } from './useTooltip';
8
+
9
+ interface Props {
10
+ children: ReactNode;
11
+ placement?: Placement;
12
+ tooltip?: ReactNode;
13
+ }
14
+
15
+ export const Tooltip: FunctionComponent<Props> = ({ children, placement, tooltip }) => {
16
+ const state = useTooltip({ placement });
17
+
18
+ if (!tooltip) {
19
+ return children;
20
+ }
21
+
22
+ return (
23
+ <TooltipContext.Provider value={state}>
24
+ <TooltipTrigger>{children}</TooltipTrigger>
25
+ <TooltipContent>{tooltip}</TooltipContent>
26
+ </TooltipContext.Provider>
27
+ );
28
+ };
@@ -0,0 +1,53 @@
1
+ import { FloatingArrow, FloatingPortal, useDelayGroup, useMergeRefs, useTransitionStyles } from '@floating-ui/react';
2
+ import { forwardRef, HTMLProps } from 'react';
3
+
4
+ import { TOOLTIP_DURATION } from 'parameters';
5
+
6
+ import { useTooltipContext } from './context';
7
+ import styles from './Tooltip.module.scss';
8
+
9
+ type Props = HTMLProps<HTMLDivElement>;
10
+
11
+ const INSTANT_DURATION = 0;
12
+
13
+ export const TooltipContent = forwardRef<HTMLDivElement, Props>((props, ref) => {
14
+ const state = useTooltipContext();
15
+ const { context } = state;
16
+ const { isInstantPhase, currentId } = useDelayGroup(context, { id: context.floatingId });
17
+ const finalRef = useMergeRefs([state.refs.setFloating, ref]);
18
+
19
+ const instantPhaseDuration = {
20
+ open: INSTANT_DURATION,
21
+ close: currentId === context.floatingId ? TOOLTIP_DURATION : INSTANT_DURATION,
22
+ };
23
+
24
+ const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
25
+ duration: isInstantPhase ? instantPhaseDuration : TOOLTIP_DURATION,
26
+ initial: {
27
+ opacity: 0,
28
+ },
29
+ });
30
+
31
+ if (!isMounted) {
32
+ return null;
33
+ }
34
+
35
+ return (
36
+ <FloatingPortal>
37
+ <div
38
+ className={styles.tooltip}
39
+ ref={finalRef}
40
+ style={{
41
+ ...state.floatingStyles,
42
+ ...props.style,
43
+ ...transitionStyles,
44
+ }}
45
+ {...state.getFloatingProps(props)}
46
+ >
47
+ <div>{props.children}</div>
48
+
49
+ <FloatingArrow className={styles.arrow} context={context} ref={state.arrowRef} />
50
+ </div>
51
+ </FloatingPortal>
52
+ );
53
+ });
@@ -0,0 +1,26 @@
1
+ import { useMergeRefs } from '@floating-ui/react';
2
+ import { HTMLProps, cloneElement, forwardRef, isValidElement } from 'react';
3
+
4
+ import { useTooltipContext } from './context';
5
+
6
+ type Props = HTMLProps<HTMLElement>;
7
+
8
+ export const TooltipTrigger = forwardRef<HTMLElement, Props>(({ children, ...props }, ref) => {
9
+ const state = useTooltipContext();
10
+ const childrenElement = children as any; // eslint-disable-line @typescript-eslint/no-explicit-any
11
+ const finalRef = useMergeRefs([state.refs.setReference, ref, childrenElement.ref]);
12
+
13
+ if (!isValidElement(children)) {
14
+ throw new Error("TooltipTrigger's children are not a valid element");
15
+ }
16
+
17
+ return cloneElement(
18
+ children,
19
+ state.getReferenceProps({
20
+ ref: finalRef,
21
+ ...props,
22
+ ...children.props,
23
+ 'data-state': state.open ? 'open' : 'closed',
24
+ }),
25
+ );
26
+ });
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ import { useTooltip } from './useTooltip';
4
+
5
+ type TooltipContextType = ReturnType<typeof useTooltip> | null;
6
+
7
+ export const TooltipContext = createContext<TooltipContextType>(null);
8
+
9
+ export const useTooltipContext = () => {
10
+ const context = useContext(TooltipContext);
11
+
12
+ if (context === null) {
13
+ throw new Error('Tooltip components must be wrapped in <Tooltip />');
14
+ }
15
+
16
+ return context;
17
+ };
@@ -1 +1 @@
1
- export { default as useTooltip } from './useTooltip';
1
+ export { Tooltip } from './Tooltip';