gatsby-core-theme 44.0.16 → 44.0.18
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/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/src/components/atoms/search/autocomplete/article/article.module.scss +27 -0
- package/src/components/atoms/search/autocomplete/article/index.js +25 -0
- package/src/components/atoms/search/autocomplete/default/default.module.scss +27 -0
- package/src/components/atoms/search/autocomplete/default/index.js +25 -0
- package/src/components/atoms/search/autocomplete/game/game.module.scss +24 -0
- package/src/components/atoms/search/autocomplete/game/index.js +42 -0
- package/src/components/atoms/search/autocomplete/loading.js +34 -0
- package/src/components/atoms/search/autocomplete/loading.module.scss +47 -0
- package/src/components/atoms/search/autocomplete/no-results.js +9 -0
- package/src/components/atoms/search/autocomplete/no-results.module.scss +3 -0
- package/src/components/atoms/search/autocomplete/operator/index.js +41 -0
- package/src/components/atoms/search/autocomplete/operator/operator.module.scss +23 -0
- package/src/components/organisms/cards/cards.test.js +1 -0
- package/src/components/organisms/navigation/navigation.test.js +8 -7
- package/src/components/organisms/search/index.js +67 -2
- package/src/components/organisms/search/search.module.scss +14 -0
- package/src/constants/site-settings/main.js +1 -0
- package/src/helpers/autocomplete.js +16 -0
- package/src/helpers/autocomplete.test.js +75 -0
- package/src/helpers/getters.mjs +22 -18
- package/src/helpers/processor/index.mjs +15 -7
- package/src/helpers/processor/modules.mjs +13 -0
- package/static/images/default-article.jpg +0 -0
- package/static/images/default-slot.jpg +0 -0
- package/storybook/deploy.php +1 -1
- package/src/components/atoms/search/autocomplete/article.js +0 -20
- package/src/components/atoms/search/autocomplete/article.module.scss +0 -9
- package/src/components/atoms/search/autocomplete/game.js +0 -18
- package/src/components/atoms/search/autocomplete/game.module.scss +0 -9
- package/src/components/atoms/search/autocomplete/operator.js +0 -38
- package/src/components/atoms/search/autocomplete/operator.module.scss +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [44.0.18](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.0.17...v44.0.18) (2025-04-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* added autocomplete implementation ([b32c30f](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/b32c30f99a56f26e5a1558e5afd34207c12edfb9))
|
|
7
|
+
* added settings ([3e41337](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/3e41337b18ca26a6bac09cd218b64382f37e6563))
|
|
8
|
+
* conflicts ([9cfd59c](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/9cfd59c5b2cdfd582fc6a3569d0183d662aae8e7))
|
|
9
|
+
* tests ([f7141d4](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/f7141d475210efec322f785da9211291ae4f9466))
|
|
10
|
+
* update server ip ([90d2608](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/90d260864d808cb4c06732b1d1c25fad16b33d86))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
* Merge branch 'tm-4548-autocomplete' into 'master' ([779e0ae](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/779e0ae6ca6d46d22dcefe1cc2ee50d429e615c6))
|
|
14
|
+
|
|
15
|
+
## [44.0.17](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.0.16...v44.0.17) (2025-04-14)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* add alphabetical sorting ([a923c31](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/a923c3132448a4502a24eea457825fdeb955cb2e))
|
|
21
|
+
|
|
1
22
|
## [44.0.16](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.0.15...v44.0.16) (2025-04-14)
|
|
2
23
|
|
|
3
24
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.row {
|
|
2
|
+
@include flex-align(center, start);
|
|
3
|
+
|
|
4
|
+
gap: .8rem;
|
|
5
|
+
width: 100%;
|
|
6
|
+
background-color: var(--color-7);
|
|
7
|
+
border-radius: .8rem;
|
|
8
|
+
padding: 1.2rem;
|
|
9
|
+
|
|
10
|
+
> a{
|
|
11
|
+
margin-left: auto;
|
|
12
|
+
|
|
13
|
+
> svg{
|
|
14
|
+
margin: 0 !important;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.operatorImage{
|
|
20
|
+
border-radius: .8rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.operatorTitle{
|
|
24
|
+
margin: 0 !important;
|
|
25
|
+
font-size: 1.6rem;
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Button from 'gatsby-core-theme/src/components/atoms/button/button';
|
|
4
|
+
import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight';
|
|
5
|
+
import styles from './article.module.scss';
|
|
6
|
+
|
|
7
|
+
const Article = ({ item = {} }) => (
|
|
8
|
+
<li className={styles.row || ''}>
|
|
9
|
+
<h3 className={styles.operatorTitle}>{item.title}</h3>
|
|
10
|
+
<Button
|
|
11
|
+
btnText=''
|
|
12
|
+
to={item?.path}
|
|
13
|
+
gtmClass="autocomplete-article-item-gtm btn-cta"
|
|
14
|
+
icon={<FaArrowRight title="Right-pointing Arrow Icon" />}
|
|
15
|
+
/>
|
|
16
|
+
</li>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Article.propTypes = {
|
|
20
|
+
item: PropTypes.shape({
|
|
21
|
+
title: PropTypes.string,
|
|
22
|
+
}).isRequired,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default Article;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.row {
|
|
2
|
+
@include flex-align(center, start);
|
|
3
|
+
|
|
4
|
+
gap: .8rem;
|
|
5
|
+
width: 100%;
|
|
6
|
+
background-color: var(--color-7);
|
|
7
|
+
border-radius: .8rem;
|
|
8
|
+
padding: 1.2rem;
|
|
9
|
+
|
|
10
|
+
> a{
|
|
11
|
+
margin-left: auto;
|
|
12
|
+
|
|
13
|
+
> svg{
|
|
14
|
+
margin: 0 !important;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.operatorImage{
|
|
20
|
+
border-radius: .8rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.operatorTitle{
|
|
24
|
+
margin: 0 !important;
|
|
25
|
+
font-size: 1.6rem;
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Button from 'gatsby-core-theme/src/components/atoms/button/button';
|
|
4
|
+
import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight';
|
|
5
|
+
import styles from './default.module.scss';
|
|
6
|
+
|
|
7
|
+
const Default = ({ item = {} }) => (
|
|
8
|
+
<li className={styles.row || ''}>
|
|
9
|
+
<h3 className={styles.operatorTitle}>{item.title}</h3>
|
|
10
|
+
<Button
|
|
11
|
+
btnText=''
|
|
12
|
+
to={item?.path}
|
|
13
|
+
gtmClass="autocomplete-article-item-gtm btn-cta"
|
|
14
|
+
icon={<FaArrowRight title="Right-pointing Arrow Icon" />}
|
|
15
|
+
/>
|
|
16
|
+
</li>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Default.propTypes = {
|
|
20
|
+
item: PropTypes.shape({
|
|
21
|
+
title: PropTypes.string,
|
|
22
|
+
}).isRequired,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default Default;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.row {
|
|
2
|
+
@include flex-align(center, start);
|
|
3
|
+
|
|
4
|
+
gap: .8rem;
|
|
5
|
+
width: 100%;
|
|
6
|
+
background-color: var(--color-7);
|
|
7
|
+
border-radius: .8rem;
|
|
8
|
+
padding: 1.2rem;
|
|
9
|
+
|
|
10
|
+
> a{
|
|
11
|
+
margin-left: auto;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.gameImage{
|
|
16
|
+
border-radius: .8rem;
|
|
17
|
+
height: revert-layer !important;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.gameTitle{
|
|
21
|
+
margin: 0 !important;
|
|
22
|
+
font-size: 1.6rem;
|
|
23
|
+
font-weight: 400;
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight';
|
|
4
|
+
import Button from 'gatsby-core-theme/src/components/atoms/button/button';
|
|
5
|
+
import LazyImage from '~hooks/lazy-image';
|
|
6
|
+
import { imagePrettyUrl } from '~helpers/getters';
|
|
7
|
+
import styles from './game.module.scss';
|
|
8
|
+
|
|
9
|
+
const Game = ({ item = {}, width = 56, height = 56 }) => {
|
|
10
|
+
const { logo } = item;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<li className={styles.row || ''}>
|
|
14
|
+
<LazyImage
|
|
15
|
+
className={styles.gameImage}
|
|
16
|
+
width={width}
|
|
17
|
+
height={height}
|
|
18
|
+
src={logo ? imagePrettyUrl(logo, width, height) : '/images/default-slot.jpg'}
|
|
19
|
+
alt={item.title}
|
|
20
|
+
/>
|
|
21
|
+
<h3 className={styles.gameTitle}>{item.title}</h3>
|
|
22
|
+
<Button
|
|
23
|
+
to={item?.path}
|
|
24
|
+
gtmClass="autocomplete-game-item-gtm btn-cta"
|
|
25
|
+
icon={<FaArrowRight title="Right-pointing Arrow Icon" />}
|
|
26
|
+
/>
|
|
27
|
+
</li>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Game.propTypes = {
|
|
32
|
+
item: PropTypes.shape({
|
|
33
|
+
title: PropTypes.string,
|
|
34
|
+
relation: PropTypes.shape({
|
|
35
|
+
logo: PropTypes.shape({}),
|
|
36
|
+
}),
|
|
37
|
+
}).isRequired,
|
|
38
|
+
width: PropTypes.number,
|
|
39
|
+
height: PropTypes.number,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default Game;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styles from './loading.module.scss';
|
|
4
|
+
|
|
5
|
+
const Loading = ({ single = false }) => single ?
|
|
6
|
+
(
|
|
7
|
+
<div className={styles.post}>
|
|
8
|
+
<div className={styles.avatar} />
|
|
9
|
+
<div className={styles.line} />
|
|
10
|
+
<div className={styles.line} />
|
|
11
|
+
</div>
|
|
12
|
+
) : (
|
|
13
|
+
<div className={styles.container}>
|
|
14
|
+
<div className={styles.post}>
|
|
15
|
+
<div className={styles.avatar} />
|
|
16
|
+
<div className={styles.line} />
|
|
17
|
+
<div className={styles.line} />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div className={styles.post}>
|
|
21
|
+
<div className={styles.avatar} />
|
|
22
|
+
<div className={styles.line} />
|
|
23
|
+
<div className={styles.line} />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className={styles.post}>
|
|
27
|
+
<div className={styles.avatar} />
|
|
28
|
+
<div className={styles.line} />
|
|
29
|
+
<div className={styles.line} />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export default Loading;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.post {
|
|
2
|
+
width: 220px;
|
|
3
|
+
height: 80px;
|
|
4
|
+
|
|
5
|
+
.avatar {
|
|
6
|
+
float: left;
|
|
7
|
+
width: 52px;
|
|
8
|
+
height: 52px;
|
|
9
|
+
background-color: #ccc;
|
|
10
|
+
border-radius: 25%;
|
|
11
|
+
margin: 8px;
|
|
12
|
+
animation: loading 1.5s infinite ease-in-out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.line {
|
|
16
|
+
float: left;
|
|
17
|
+
width: 140px;
|
|
18
|
+
height: 16px;
|
|
19
|
+
margin-top: 12px;
|
|
20
|
+
background-color: #ccc;
|
|
21
|
+
border-radius: 7px;
|
|
22
|
+
animation: loading 1.5s infinite ease-in-out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.avatar + .line {
|
|
26
|
+
margin-top: 11px;
|
|
27
|
+
width: 100px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.line ~ .line {
|
|
31
|
+
background-color: #ddd;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes loading {
|
|
35
|
+
0% {
|
|
36
|
+
background-color: #ccc;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
50% {
|
|
40
|
+
background-color: #e0e0e0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
100% {
|
|
44
|
+
background-color: #ccc;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight';
|
|
4
|
+
import Button from 'gatsby-core-theme/src/components/atoms/button/button';
|
|
5
|
+
import LazyImage from '~hooks/lazy-image';
|
|
6
|
+
import { imagePrettyUrl } from '~helpers/getters';
|
|
7
|
+
import styles from './operator.module.scss';
|
|
8
|
+
|
|
9
|
+
const Operator = ({ item = {}, width = 56, height = 56 }) => {
|
|
10
|
+
const { logo } = item;
|
|
11
|
+
return (
|
|
12
|
+
<li className={styles.row || ''}>
|
|
13
|
+
<LazyImage
|
|
14
|
+
className={styles.operatorImage}
|
|
15
|
+
width={width}
|
|
16
|
+
height={height}
|
|
17
|
+
src={imagePrettyUrl(logo, width, height)}
|
|
18
|
+
alt={item.title}
|
|
19
|
+
/>
|
|
20
|
+
<h3 className={styles.operatorTitle}>{item.title}</h3>
|
|
21
|
+
<Button
|
|
22
|
+
to={item?.path}
|
|
23
|
+
gtmClass="autocomplete-operator-item-gtm btn-cta"
|
|
24
|
+
icon={<FaArrowRight title="Right-pointing Arrow Icon" />}
|
|
25
|
+
/>
|
|
26
|
+
</li>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Operator.propTypes = {
|
|
31
|
+
item: PropTypes.shape({
|
|
32
|
+
title: PropTypes.string,
|
|
33
|
+
relation: PropTypes.shape({
|
|
34
|
+
logo: PropTypes.shape({}),
|
|
35
|
+
}),
|
|
36
|
+
}).isRequired,
|
|
37
|
+
width: PropTypes.number,
|
|
38
|
+
height: PropTypes.number,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default Operator;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.row {
|
|
2
|
+
@include flex-align(center, start);
|
|
3
|
+
|
|
4
|
+
gap: .8rem;
|
|
5
|
+
width: 100%;
|
|
6
|
+
background-color: var(--color-7);
|
|
7
|
+
border-radius: .8rem;
|
|
8
|
+
padding: 1.2rem;
|
|
9
|
+
|
|
10
|
+
> a{
|
|
11
|
+
margin-left: auto;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.operatorImage{
|
|
16
|
+
border-radius: .8rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.operatorTitle{
|
|
20
|
+
margin: 0 !important;
|
|
21
|
+
font-size: 1.6rem;
|
|
22
|
+
font-weight: 400;
|
|
23
|
+
}
|
|
@@ -3,36 +3,37 @@ import { render, cleanup, waitFor } from '@testing-library/react';
|
|
|
3
3
|
import '@testing-library/jest-dom/extend-expect';
|
|
4
4
|
|
|
5
5
|
import { getNavigationMenus } from '~tests/factories/sections/navigation.factory';
|
|
6
|
+
import getPageData from '../../../../tests/factories/pages/default.factory';
|
|
6
7
|
import Navigation from '.';
|
|
7
8
|
|
|
8
9
|
const menuSection = getNavigationMenus(1, 2, true);
|
|
9
|
-
|
|
10
|
+
const pageContext = {page: getPageData()}
|
|
10
11
|
describe('Navigation Component', () => {
|
|
11
12
|
test('render navigation', async () => {
|
|
12
|
-
const { container } = render(<Navigation section={menuSection} />);
|
|
13
|
+
const { container } = render(<Navigation pageContext={pageContext} section={menuSection} />);
|
|
13
14
|
|
|
14
15
|
await waitFor(() => {
|
|
15
|
-
expect(container.querySelectorAll('.nav')).
|
|
16
|
+
expect(container.querySelectorAll('.nav')).toHaveLength(1);
|
|
16
17
|
expect(container.querySelectorAll('.logo')).toHaveLength(1);
|
|
17
18
|
expect(container.querySelectorAll('.menuContainer')).toHaveLength(1);
|
|
18
19
|
expect(container.querySelectorAll('.search')).toHaveLength(1);
|
|
19
20
|
});
|
|
20
21
|
});
|
|
21
22
|
test('render navigation when template is ppc', async () => {
|
|
22
|
-
const { container } = render(<Navigation section={menuSection} template="ppc" />);
|
|
23
|
+
const { container } = render(<Navigation pageContext={pageContext} section={menuSection} template="ppc" />);
|
|
23
24
|
|
|
24
25
|
await waitFor(() => {
|
|
25
|
-
expect(container.querySelectorAll('.nav')).
|
|
26
|
+
expect(container.querySelectorAll('.nav')).toHaveLength(1);
|
|
26
27
|
expect(container.querySelectorAll('.logo')).toHaveLength(1);
|
|
27
28
|
expect(container.querySelectorAll('.menuContainer')).toHaveLength(0);
|
|
28
29
|
expect(container.querySelectorAll('.search')).toHaveLength(0);
|
|
29
30
|
});
|
|
30
31
|
});
|
|
31
32
|
test('render navigation without search', async () => {
|
|
32
|
-
const { container } = render(<Navigation section={menuSection} hasSearch={false} />);
|
|
33
|
+
const { container } = render(<Navigation pageContext={pageContext} section={menuSection} hasSearch={false} />);
|
|
33
34
|
|
|
34
35
|
await waitFor(() => {
|
|
35
|
-
expect(container.querySelectorAll('.nav')).
|
|
36
|
+
expect(container.querySelectorAll('.nav')).toHaveLength(1);
|
|
36
37
|
expect(container.querySelectorAll('.menuContainer')).toHaveLength(1);
|
|
37
38
|
expect(container.querySelectorAll('.search')).toHaveLength(0);
|
|
38
39
|
});
|
|
@@ -1,21 +1,39 @@
|
|
|
1
|
+
/* eslint-disable no-unused-expressions */
|
|
2
|
+
/* eslint-disable no-console */
|
|
1
3
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
-
import React, { useRef, useState, useEffect } from 'react';
|
|
4
|
+
import React, { useRef, useState, useEffect, Suspense } from 'react';
|
|
3
5
|
import PropTypes from 'prop-types';
|
|
4
6
|
import { BiSearchAlt } from '@react-icons/all-files/bi/BiSearchAlt';
|
|
7
|
+
import loadSource from '../../../helpers/search-source';
|
|
5
8
|
import useTranslate from '~hooks/useTranslate/useTranslate';
|
|
6
9
|
import { toggleScroll } from '~helpers/scroll';
|
|
10
|
+
import keygen from '~helpers/keygen';
|
|
11
|
+
import { getComponent } from '../../../helpers/autocomplete';
|
|
12
|
+
import Loading from '../../atoms/search/autocomplete/loading';
|
|
13
|
+
import NoResults from '../../atoms/search/autocomplete/no-results';
|
|
14
|
+
import { mainSettings } from '../../../constants/site-settings/main';
|
|
7
15
|
|
|
8
16
|
import styles from './search.module.scss';
|
|
9
17
|
|
|
10
18
|
const SearchForm = ({
|
|
19
|
+
pageContext,
|
|
11
20
|
className,
|
|
12
21
|
searchIcon,
|
|
13
22
|
iconWidth = 24,
|
|
14
23
|
iconHeight = 24,
|
|
15
24
|
marketPrefix = '/',
|
|
25
|
+
startSearchAt = 3,
|
|
16
26
|
isDisabled,
|
|
17
27
|
}) => {
|
|
28
|
+
const { market, template } = pageContext.page;
|
|
18
29
|
const [localSearch, setLocalSearch] = useState(false);
|
|
30
|
+
|
|
31
|
+
const [autoCompleteLoading, setAutoCompleteLoading] = useState(false);
|
|
32
|
+
const autoCompleteData = useRef(null);
|
|
33
|
+
const [filteredData, setFilteredData] = useState(null);
|
|
34
|
+
const [showAutoComplete, setShowAutoComplete] = useState(false);
|
|
35
|
+
const autocompleteEnabled = mainSettings[template]?.autocomplete || mainSettings?.default?.autocomplete;
|
|
36
|
+
|
|
19
37
|
const [searchBoxQuery,] = useState();
|
|
20
38
|
const searchInputRef = useRef();
|
|
21
39
|
const searchFormRef = useRef();
|
|
@@ -45,6 +63,28 @@ const SearchForm = ({
|
|
|
45
63
|
}
|
|
46
64
|
};
|
|
47
65
|
|
|
66
|
+
const autoCompleteHandler = async (event) => {
|
|
67
|
+
const query = event.target.value.trim();
|
|
68
|
+
setShowAutoComplete(prevState => (query.length >= startSearchAt) !== prevState ? query.length >= startSearchAt : prevState);
|
|
69
|
+
|
|
70
|
+
setAutoCompleteLoading(true);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
autoCompleteData.current = autoCompleteData.current || await loadSource(market)
|
|
74
|
+
|
|
75
|
+
setFilteredData(autoCompleteData.current.filter(item =>
|
|
76
|
+
item.title.toLowerCase().includes(query.toLowerCase())
|
|
77
|
+
));
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Error loading autocomplete data:", error);
|
|
81
|
+
} finally {
|
|
82
|
+
setAutoCompleteLoading(false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
48
88
|
useEffect(() => {
|
|
49
89
|
const handleClickOutside = (event) => {
|
|
50
90
|
if (localSearch && searchFormRef.current && !searchFormRef.current.contains(event.target)) {
|
|
@@ -100,11 +140,30 @@ const SearchForm = ({
|
|
|
100
140
|
maxLength="60"
|
|
101
141
|
ref={searchInputRef}
|
|
102
142
|
onKeyUp={(event) => {
|
|
143
|
+
autoCompleteHandler(event);
|
|
103
144
|
if (event.key === 'Enter') {
|
|
104
145
|
formSubmitBoxHandler(event);
|
|
105
146
|
}
|
|
106
147
|
}}
|
|
107
148
|
/>
|
|
149
|
+
{showAutoComplete && autocompleteEnabled && (
|
|
150
|
+
<div className={styles.autoCompleteContainer}>
|
|
151
|
+
{autoCompleteLoading ? (
|
|
152
|
+
<div className={styles.autoCompleteInner}>
|
|
153
|
+
<Loading />
|
|
154
|
+
</div>
|
|
155
|
+
) : (
|
|
156
|
+
<div className={styles.autoCompleteInner}>
|
|
157
|
+
{filteredData.length ? <ul>
|
|
158
|
+
{filteredData.map((value) => {
|
|
159
|
+
const PageTypeCard = getComponent(value.pageType);
|
|
160
|
+
return <Suspense fallback={<Loading single />} key={keygen()}><PageTypeCard item={value} /></Suspense>
|
|
161
|
+
})}
|
|
162
|
+
</ul> : <NoResults text={useTranslate('autocomplete_no_results', 'No Results Found')} />}
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
108
167
|
</div>
|
|
109
168
|
</div>
|
|
110
169
|
)}
|
|
@@ -117,9 +176,15 @@ SearchForm.propTypes = {
|
|
|
117
176
|
searchIcon: PropTypes.string,
|
|
118
177
|
iconWidth: PropTypes.number,
|
|
119
178
|
iconHeight: PropTypes.number,
|
|
120
|
-
pageContext: PropTypes.shape({
|
|
179
|
+
pageContext: PropTypes.shape({
|
|
180
|
+
page: PropTypes.shape({
|
|
181
|
+
market: PropTypes.string,
|
|
182
|
+
template: PropTypes.string
|
|
183
|
+
})
|
|
184
|
+
}),
|
|
121
185
|
marketPrefix: PropTypes.string,
|
|
122
186
|
isDisabled: PropTypes.bool,
|
|
187
|
+
startSearchAt: PropTypes.number
|
|
123
188
|
};
|
|
124
189
|
|
|
125
190
|
export default SearchForm;
|
|
@@ -67,3 +67,17 @@
|
|
|
67
67
|
cursor: not-allowed;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
.autoCompleteContainer{
|
|
72
|
+
background-color: #fff;
|
|
73
|
+
margin-top: .8rem;
|
|
74
|
+
border-radius: .8rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.autoCompleteInner > ul{
|
|
78
|
+
@include flex-direction(column);
|
|
79
|
+
|
|
80
|
+
gap: .8rem;
|
|
81
|
+
padding: 1.2rem;
|
|
82
|
+
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
+
import { lazy } from 'react';
|
|
4
|
+
|
|
5
|
+
export function getComponent(type) {
|
|
6
|
+
switch (type) {
|
|
7
|
+
case 'operator':
|
|
8
|
+
return lazy(() => import(`../components/atoms/search/autocomplete/operator`));
|
|
9
|
+
case 'article':
|
|
10
|
+
return lazy(() => import(`../components/atoms/search/autocomplete/article`));
|
|
11
|
+
case 'game':
|
|
12
|
+
return lazy(() => import(`../components/atoms/search/autocomplete/game`));
|
|
13
|
+
default:
|
|
14
|
+
return lazy(() => import(`../components/atoms/search/autocomplete/default`));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* eslint-disable no-unused-expressions */
|
|
2
|
+
import React, { Suspense } from "react";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import "@testing-library/jest-dom/extend-expect";
|
|
5
|
+
import { getComponent } from "./autocomplete";
|
|
6
|
+
|
|
7
|
+
describe("Autocomplete Get Component Helper", () => {
|
|
8
|
+
test("Autocomplete Get Component Operator", async () => {
|
|
9
|
+
const Component = getComponent('operator');
|
|
10
|
+
render(
|
|
11
|
+
<Suspense fallback={<></>}>
|
|
12
|
+
<Component item={{
|
|
13
|
+
title: 'Test Operator',
|
|
14
|
+
path: '/test'
|
|
15
|
+
}} />
|
|
16
|
+
</Suspense>
|
|
17
|
+
);
|
|
18
|
+
const link = await screen.findByRole('link'); // gets <a>
|
|
19
|
+
expect(link).toHaveAttribute('href', '/test');
|
|
20
|
+
|
|
21
|
+
const title = await screen.findByText('Test Operator');
|
|
22
|
+
expect(title).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("Autocomplete Get Component Article", async () => {
|
|
26
|
+
const Component = getComponent('article');
|
|
27
|
+
render(
|
|
28
|
+
<Suspense fallback={<></>}>
|
|
29
|
+
<Component item={{
|
|
30
|
+
title: 'Test Article',
|
|
31
|
+
path: '/test'
|
|
32
|
+
}} />
|
|
33
|
+
</Suspense>
|
|
34
|
+
);
|
|
35
|
+
const link = await screen.findByRole('link'); // gets <a>
|
|
36
|
+
expect(link).toHaveAttribute('href', '/test');
|
|
37
|
+
|
|
38
|
+
const title = await screen.findByText('Test Article');
|
|
39
|
+
expect(title).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("Autocomplete Get Component Game", async () => {
|
|
43
|
+
const Component = getComponent('game');
|
|
44
|
+
render(
|
|
45
|
+
<Suspense fallback={<></>}>
|
|
46
|
+
<Component item={{
|
|
47
|
+
title: 'Test Game',
|
|
48
|
+
path: '/test'
|
|
49
|
+
}} />
|
|
50
|
+
</Suspense>
|
|
51
|
+
);
|
|
52
|
+
const link = await screen.findByRole('link'); // gets <a>
|
|
53
|
+
expect(link).toHaveAttribute('href', '/test');
|
|
54
|
+
|
|
55
|
+
const title = await screen.findByText('Test Game');
|
|
56
|
+
expect(title).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("Autocomplete Get Component Default", async () => {
|
|
60
|
+
const Component = getComponent('payment_methods');
|
|
61
|
+
render(
|
|
62
|
+
<Suspense fallback={<></>}>
|
|
63
|
+
<Component item={{
|
|
64
|
+
title: 'Test Default',
|
|
65
|
+
path: '/test'
|
|
66
|
+
}} />
|
|
67
|
+
</Suspense>
|
|
68
|
+
);
|
|
69
|
+
const link = await screen.findByRole('link'); // gets <a>
|
|
70
|
+
expect(link).toHaveAttribute('href', '/test');
|
|
71
|
+
|
|
72
|
+
const title = await screen.findByText('Test Default');
|
|
73
|
+
expect(title).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
package/src/helpers/getters.mjs
CHANGED
|
@@ -142,28 +142,32 @@ export function isBrowserWebpCompatable(feature, callback) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
export function imagePrettyUrl(
|
|
145
|
-
filename
|
|
145
|
+
filename,
|
|
146
146
|
width = null,
|
|
147
147
|
height = null
|
|
148
148
|
) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
149
|
+
if(filename){
|
|
150
|
+
const extension = getImageExtension(filename);
|
|
151
|
+
|
|
152
|
+
const hasFilters = extension !== "svg" && extension !== "gif";
|
|
153
|
+
const cdnURL =
|
|
154
|
+
(hasFilters
|
|
155
|
+
? process.env.IMAGE_CDN_URL
|
|
156
|
+
: process.env.IMAGE_CDN_URL
|
|
157
|
+
? process.env.IMAGE_CDN_URL.replace("/filters:format(webp)", "")
|
|
158
|
+
: "") || process.env.STORYBOOK_IMAGE_CDN_URL;
|
|
159
|
+
|
|
160
|
+
if (width && height && hasFilters) {
|
|
161
|
+
const urlPath = "/fit-in";
|
|
162
|
+
const urlSize = `/${width}x${height}`;
|
|
163
|
+
|
|
164
|
+
return `${cdnURL}${urlPath}${urlSize}/${filename}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return `${cdnURL}/${filename}`;
|
|
164
168
|
}
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
return '/images/placeholder-image.jpg';
|
|
170
|
+
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
export function getPageImage(page) {
|
|
@@ -49,6 +49,7 @@ let relations = null;
|
|
|
49
49
|
let menus = null;
|
|
50
50
|
let prefilledMarketModules = null;
|
|
51
51
|
let prefilledMarketModulesRaw = null;
|
|
52
|
+
|
|
52
53
|
const searchData = {};
|
|
53
54
|
|
|
54
55
|
export function processSitemapPages(pages, markets) {
|
|
@@ -82,8 +83,7 @@ export function processSitemapPages(pages, markets) {
|
|
|
82
83
|
|
|
83
84
|
const groupByMarketAndType = (data) =>
|
|
84
85
|
Object.values(data).reduce((acc, item) => {
|
|
85
|
-
const market = item
|
|
86
|
-
const type = item.type;
|
|
86
|
+
const {market, type} = item;
|
|
87
87
|
|
|
88
88
|
// Initialize language and type groupings if not already present
|
|
89
89
|
if (!acc[market]) acc[market] = {};
|
|
@@ -364,7 +364,6 @@ export default {
|
|
|
364
364
|
prefilledMarketModules = data.prefilled_market_modules || {};
|
|
365
365
|
prefilledMarketModulesRaw = cloneDeep(data.prefilled_market_modules);
|
|
366
366
|
searchData[market] = [];
|
|
367
|
-
|
|
368
367
|
Object.keys(transformedPages[market]).forEach((pageType) => {
|
|
369
368
|
transformedPages[market][pageType].forEach((page, index) => {
|
|
370
369
|
const translations = translationsData
|
|
@@ -476,10 +475,19 @@ export default {
|
|
|
476
475
|
|
|
477
476
|
// add search data
|
|
478
477
|
if (searchTemplatesAcitve.includes(page.template) && searchEnabled) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
478
|
+
const { title, path, status, relation } = transformedPages[market][pageType][index];
|
|
479
|
+
|
|
480
|
+
const minimalPage = {
|
|
481
|
+
title,
|
|
482
|
+
pageType,
|
|
483
|
+
path,
|
|
484
|
+
...(relation?.short_name && { short_name: relation.short_name }),
|
|
485
|
+
...(relation?.logo && { logo: relation.logo.filename }),
|
|
486
|
+
...(relation?.game_id && { game_id: relation.game_id })
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
status === 'active' && searchData[page.market].push(
|
|
490
|
+
cloneDeep(minimalPage)
|
|
483
491
|
);
|
|
484
492
|
}
|
|
485
493
|
|
|
@@ -256,6 +256,19 @@ export function processCardsV2(
|
|
|
256
256
|
cardItems[b64].sort((a, b) => (a.created_at > b.created_at ? -1 : 1));
|
|
257
257
|
module.items = cardItems[b64].slice(0, itemLimit);
|
|
258
258
|
}
|
|
259
|
+
} else if (sortType === "alphabetical") {
|
|
260
|
+
if (cardItems[b64].length > 0) {
|
|
261
|
+
cardItems[b64].sort((a, b) => {
|
|
262
|
+
if (a.title < b.title) {
|
|
263
|
+
return -1;
|
|
264
|
+
}
|
|
265
|
+
if (a.title > b.title) {
|
|
266
|
+
return 1;
|
|
267
|
+
}
|
|
268
|
+
return 0;
|
|
269
|
+
});
|
|
270
|
+
module.items = module.cards_selector_filters_limit ? cardItems[b64].slice(0, module.cards_selector_filters_limit) : cardItems[b64];
|
|
271
|
+
}
|
|
259
272
|
}
|
|
260
273
|
}
|
|
261
274
|
}
|
|
Binary file
|
|
Binary file
|
package/storybook/deploy.php
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import { IoMdArrowRoundForward } from '@react-icons/all-files/io/IoMdArrowRoundForward';
|
|
4
|
-
|
|
5
|
-
import styles from './article.module.scss';
|
|
6
|
-
|
|
7
|
-
const Article = ({ item = {} }) => (
|
|
8
|
-
<div className={styles.row || ''}>
|
|
9
|
-
<h3>{item.title}</h3>
|
|
10
|
-
<IoMdArrowRoundForward title="Right-pointing Arrow Icon" />
|
|
11
|
-
</div>
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
Article.propTypes = {
|
|
15
|
-
item: PropTypes.shape({
|
|
16
|
-
title: PropTypes.string,
|
|
17
|
-
}).isRequired,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default Article;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
|
|
4
|
-
import styles from './game.module.scss';
|
|
5
|
-
|
|
6
|
-
const Game = ({ item = {} }) => (
|
|
7
|
-
<div className={styles.row || ''}>
|
|
8
|
-
<h3>{item.title}</h3>
|
|
9
|
-
</div>
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
Game.propTypes = {
|
|
13
|
-
item: PropTypes.shape({
|
|
14
|
-
title: PropTypes.string,
|
|
15
|
-
}).isRequired,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default Game;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
|
|
4
|
-
import LazyImage from '~hooks/lazy-image';
|
|
5
|
-
import { imagePrettyUrl } from '~helpers/getters';
|
|
6
|
-
import OperatorCta from '~atoms/button/operator-cta';
|
|
7
|
-
import styles from './operator.module.scss';
|
|
8
|
-
|
|
9
|
-
const Operator = ({ item = {}, width = 56, height = 56 }) => {
|
|
10
|
-
const { relation } = item;
|
|
11
|
-
const img = relation?.logo?.filename;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<div className={styles.row || ''}>
|
|
15
|
-
<LazyImage
|
|
16
|
-
width={width}
|
|
17
|
-
height={height}
|
|
18
|
-
src={imagePrettyUrl(img, width, height)}
|
|
19
|
-
alt={item.title}
|
|
20
|
-
/>
|
|
21
|
-
<h3>{item.title}</h3>
|
|
22
|
-
<OperatorCta moduleName="search_autocomplete" operator={relation} gtmClass="toplist-operator-cta-gtm" />
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
Operator.propTypes = {
|
|
28
|
-
item: PropTypes.shape({
|
|
29
|
-
title: PropTypes.string,
|
|
30
|
-
relation: PropTypes.shape({
|
|
31
|
-
logo: PropTypes.shape({}),
|
|
32
|
-
}),
|
|
33
|
-
}).isRequired,
|
|
34
|
-
width: PropTypes.number,
|
|
35
|
-
height: PropTypes.number,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export default Operator;
|