hds-web 1.15.7 → 1.15.9
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/.env +2 -0
- package/README.md +1 -1
- package/dist/index.css +2 -2
- package/dist/index.es.css +2 -2
- package/dist/index.es.js +5 -13
- package/dist/index.js +5 -13
- package/package.json +5 -1
- package/src/HDS/components/Buttons/button.js +5 -6
- package/src/HDS/components/Cards/index.js +0 -1
- package/src/HDS/components/Carousels/homeCarousel.js +1 -6
- package/src/HDS/components/Headers/v3Header.js +2 -0
- package/src/HDS/components/Snippet/CodeSnippet.js +58 -58
- package/src/HDS/components/Snippet/index.js +1 -1
- package/src/HDS/components/index.js +0 -1
- package/src/HDS/helpers/AlgoliaSearch/constants.js +48 -0
- package/src/HDS/helpers/AlgoliaSearch/index.js +1 -0
- package/src/HDS/helpers/AlgoliaSearch/search.js +38 -0
- package/src/HDS/helpers/AlgoliaSearch/search.scss +38 -0
- package/src/HDS/helpers/AlgoliaSearch/searchbox.js +88 -0
- package/src/HDS/helpers/AlgoliaSearch/searchfooter.js +30 -0
- package/src/HDS/helpers/AlgoliaSearch/searchoverlay.js +36 -0
- package/src/HDS/helpers/AlgoliaSearch/searchresults.js +98 -0
- package/src/HDS/helpers/AlgoliaSearch/searchwrapper.js +126 -0
- package/src/HDS/helpers/index.js +1 -0
- package/src/styles/tailwind.css +124 -4
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "hds-web",
|
3
|
-
"version": "1.15.
|
3
|
+
"version": "1.15.9",
|
4
4
|
"private": false,
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"module": "dist/index.es.js",
|
@@ -11,12 +11,16 @@
|
|
11
11
|
"@testing-library/jest-dom": "^5.16.5",
|
12
12
|
"@testing-library/react": "^13.4.0",
|
13
13
|
"@testing-library/user-event": "^13.5.0",
|
14
|
+
"algoliasearch": "^4.18.0",
|
15
|
+
"dotenv": "^16.3.1",
|
14
16
|
"framer-motion": "^10.12.16",
|
17
|
+
"path-browserify": "^1.0.1",
|
15
18
|
"prismjs": "^1.29.0",
|
16
19
|
"prop-types": "^15.8.1",
|
17
20
|
"react": "^17.0.1",
|
18
21
|
"react-dom": "^17.0.1",
|
19
22
|
"react-github-btn": "^1.4.0",
|
23
|
+
"react-instantsearch-dom": "^6.40.1",
|
20
24
|
"react-markdown": "^8.0.7",
|
21
25
|
"react-scripts": "5.0.1",
|
22
26
|
"uuid": "^9.0.0",
|
@@ -14,7 +14,7 @@ const ANIMATED_ARR_CLASSES = {
|
|
14
14
|
'md': 'h-5 w-5 -mt-5',
|
15
15
|
'lg': 'h-6 w-6 -mt-6'
|
16
16
|
},
|
17
|
-
|
17
|
+
|
18
18
|
}
|
19
19
|
|
20
20
|
const Buttonclasses = {
|
@@ -23,7 +23,7 @@ const Buttonclasses = {
|
|
23
23
|
'base': 'db:w-fit tb:w-fit w-full justify-center justify-center bg-blue-500 text-neutral-0',
|
24
24
|
'hover': 'hover:bg-blue-700 hover:text-neutral-0 hover:shadow-md hover:shadow hover:transition-all hover:ease-out hover:duration-300',
|
25
25
|
'focus': 'focus:bg-blue-600 focus:shadow-[0_0px_0px_4px_#DFE8FF] focus:text-neutral-0 focus:outline-none active:bg-blue-600 active:text-neutral-0 active:outline-none',
|
26
|
-
|
26
|
+
|
27
27
|
},
|
28
28
|
'disabled': 'bg-neutral-200 text-neutral-400',
|
29
29
|
},
|
@@ -38,9 +38,7 @@ const Buttonclasses = {
|
|
38
38
|
'secondary': {
|
39
39
|
'default': {
|
40
40
|
'base': 'db:w-fit tb:w-fit w-full justify-center border-2 border-blue-500 text-blue-500',
|
41
|
-
|
42
41
|
'hover': 'hover:text-neutral-0 hover:bg-blue-700 hover:border-2 hover:border-blue-700 hover:shadow-md hover:shadow hover:transition-all hover:ease-out hover:duration-200 ',
|
43
|
-
|
44
42
|
'focus': 'focus:bg-blue-200 focus:text-blue-500 focus:border-2 border-blue-500 focus:shadow-[0px_0px_0px_4px_#DFE8FF] focus:outline-none focus:text-blue-500',
|
45
43
|
},
|
46
44
|
'disabled': 'bg-neutral-200 text-neutral-400',
|
@@ -174,7 +172,7 @@ export default function Button(props) {
|
|
174
172
|
<Icon variant={leftIconVariant} strokeColor={leftIconColor} />
|
175
173
|
</div>
|
176
174
|
)}
|
177
|
-
|
175
|
+
|
178
176
|
{leftIconVariant && leftIconVariant !== 'none' && type=='iconOnly' && (
|
179
177
|
<div className={``}>
|
180
178
|
<Icon variant={leftIconVariant} height={`${ANIMATED_ARR_CLASSES['icon1'][`${size}`]} group-active:stroke-neutral-0 stroke-[1.5px] group-hover:transition-all group-hover:ease-in-out group-hover:duration-100 group-hover:stroke-neutral-0 group-focus:stroke-neutral-0`} strokeColor={leftIconColor} />
|
@@ -209,7 +207,7 @@ Button.propTypes = {
|
|
209
207
|
leftIconVariant: PropTypes.string,
|
210
208
|
rightIconVariant: PropTypes.string,
|
211
209
|
animatedHoverStroke: PropTypes.string
|
212
|
-
|
210
|
+
|
213
211
|
|
214
212
|
};
|
215
213
|
|
@@ -225,6 +223,7 @@ Button.defaultProps = {
|
|
225
223
|
rightAnimatedArrow: 'true' ,
|
226
224
|
rightAnimatedArrowColor: '#FFFFFF',
|
227
225
|
animatedHoverStroke: 'group-hover:stroke-neutral-0',
|
226
|
+
|
228
227
|
btnTextHoverClass: 'hover:text-neutral-0'
|
229
228
|
|
230
229
|
};
|
@@ -3,7 +3,6 @@ import { useRef, useState, useEffect } from "react";
|
|
3
3
|
import { Icon } from "../common-components/Icon";
|
4
4
|
import { HDSButton } from "../Buttons";
|
5
5
|
import { VideoCard } from '../Cards/VideoCard'
|
6
|
-
import smoothscroll from 'smoothscroll-polyfill';
|
7
6
|
const carouselItems = [
|
8
7
|
[
|
9
8
|
"https://res.cloudinary.com/hasura-cms-uploads/image/upload/v1687016995/optum_Card_17973d6328.png",
|
@@ -25,11 +24,7 @@ const SideCard = (item) => (
|
|
25
24
|
|
26
25
|
export default function HomePageCarousePrimary(props) {
|
27
26
|
|
28
|
-
|
29
|
-
smoothscroll.polyfill();
|
30
|
-
|
31
|
-
}, []);
|
32
|
-
|
27
|
+
|
33
28
|
//center carousel
|
34
29
|
let arr =
|
35
30
|
[
|
@@ -8,6 +8,7 @@ import { V3Dropdown } from '../Cards/Dropdown'
|
|
8
8
|
import { HDSColor } from '../../foundation/ColorPalette';
|
9
9
|
import { motion } from "framer-motion"
|
10
10
|
import { AnimatePresence } from 'framer-motion';
|
11
|
+
import { AlgoliaSearch } from '../../helpers/AlgoliaSearch';
|
11
12
|
|
12
13
|
const solutions = [
|
13
14
|
{
|
@@ -965,6 +966,7 @@ export default function V3Header(props) {
|
|
965
966
|
|
966
967
|
<div className='hidden tb-l:flex flex-row items-center gap-x-4 '>
|
967
968
|
|
969
|
+
<AlgoliaSearch />
|
968
970
|
|
969
971
|
<a href='' className='hidden tb:flex'>
|
970
972
|
<Typography textStyle='body3c-medium' className='text-neutral-800'>
|
@@ -1,70 +1,70 @@
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import Prism from 'prismjs';
|
4
|
-
import 'prismjs/themes/prism.css';
|
5
|
-
import 'prismjs/components/prism-javascript';
|
1
|
+
// import React, { useState, useRef, useEffect } from 'react';
|
2
|
+
// import PropTypes from 'prop-types';
|
3
|
+
// import Prism from 'prismjs';
|
4
|
+
// import 'prismjs/themes/prism.css';
|
5
|
+
// import 'prismjs/components/prism-javascript';
|
6
6
|
|
7
|
-
function CodeSnippetWithCopy({ snippet, filename, backgroundColor }) {
|
7
|
+
// function CodeSnippetWithCopy({ snippet, filename, backgroundColor }) {
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
// if(!snippet){
|
10
|
+
// snippet='as'
|
11
|
+
// }
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
// const [isCopied, setIsCopied] = useState(false);
|
14
|
+
// const preRef = useRef(null);
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
// async function copyToClipboard() {
|
17
|
+
// try {
|
18
|
+
// await navigator.clipboard.writeText(snippet);
|
19
|
+
// setIsCopied(true);
|
20
|
+
// setTimeout(() => setIsCopied(false), 1500);
|
21
|
+
// } catch (error) {
|
22
|
+
// console.error('Failed to copy to clipboard', error);
|
23
|
+
// }
|
24
|
+
// }
|
25
25
|
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
// useEffect(() => {
|
28
|
+
// console.log(snippet)
|
29
|
+
// if (preRef.current ) {
|
30
|
+
// Prism.highlightAll();
|
31
|
+
// }
|
32
|
+
// }, [snippet, preRef.current]);
|
33
33
|
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
35
|
+
// const lines = snippet.split('\n');
|
36
|
+
// const lineCount = lines.length;
|
37
|
+
// if(snippet){}
|
38
|
+
// return (
|
39
|
+
// <div className={`${backgroundColor} border border-neutral-100 rounded relative`}>
|
40
|
+
// {filename ? (
|
41
|
+
// <span className='bg-neutral-100 flex p-2 text-sm-medium text-neutral-900'>{filename}</span>
|
42
|
+
// ) : null}
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
44
|
+
// <div className='my-6 mx-10'>
|
45
|
+
// <pre style={{ whiteSpace: 'pre-wrap' }} ref={preRef} className='line-numbers'>
|
46
|
+
// {lines.map((line, index) => (
|
47
|
+
// <code className='language-javascript' key={index}>
|
48
|
+
// <span className='text-sm text-neutral-500 mr-4'>{index + 1} </span>
|
49
|
+
// {line}
|
50
|
+
// {index < lineCount - 1 ? '\n' : null}
|
51
|
+
// </code>
|
52
|
+
// ))}
|
53
|
+
// </pre>
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
}
|
55
|
+
// <button
|
56
|
+
// className='border border-neutral-400 button-sm absolute bottom-0 right-0 mb-4 mr-4'
|
57
|
+
// onClick={copyToClipboard}
|
58
|
+
// >
|
59
|
+
// {isCopied ? 'Copied!' : 'Copy to Clipboard'}
|
60
|
+
// </button>
|
61
|
+
// </div>
|
62
|
+
// </div>
|
63
|
+
// );
|
64
|
+
// }
|
65
65
|
|
66
|
-
// CodeSnippetWithCopy.propTypes = {
|
67
|
-
// snippet: PropTypes.string.isRequired,
|
68
|
-
// };
|
66
|
+
// // CodeSnippetWithCopy.propTypes = {
|
67
|
+
// // snippet: PropTypes.string.isRequired,
|
68
|
+
// // };
|
69
69
|
|
70
|
-
export default CodeSnippetWithCopy;
|
70
|
+
// export default CodeSnippetWithCopy;
|
@@ -1 +1 @@
|
|
1
|
-
export {default as CodeSnippet} from './CodeSnippet';
|
1
|
+
// export {default as CodeSnippet} from './CodeSnippet';
|
@@ -0,0 +1,48 @@
|
|
1
|
+
export const INDEX_TYPES = Object.freeze({
|
2
|
+
blog: "blog",
|
3
|
+
docs: "docs",
|
4
|
+
learn: "learn",
|
5
|
+
});
|
6
|
+
|
7
|
+
export const SEARCH_INDICES = [
|
8
|
+
{ name: `blog-production`, title: `Hasura Blog`, type: INDEX_TYPES.blog },
|
9
|
+
{ name: `graphql-docs-prod`, title: `Hasura GraphQL Engine Docs`, type: INDEX_TYPES.docs },
|
10
|
+
{ name: `learn-intro-graphql`, title: `Learn Intro GraphQL`, type: INDEX_TYPES.learn },
|
11
|
+
{ name: `learn-intro-graphql-zh`, title: `Learn Intro GraphQL`, type: INDEX_TYPES.learn },
|
12
|
+
{ name: `learn-elm-graphql`, title: `Learn ELM GraphQl`, type: INDEX_TYPES.learn },
|
13
|
+
{ name: `learn-flutter-graphql`, title: `Learn Flutter GraphQL`, type: INDEX_TYPES.learn },
|
14
|
+
{ name: `learn-database-mysql`, title: `Learn Database MySQL`, type: INDEX_TYPES.learn },
|
15
|
+
{
|
16
|
+
name: `learn-database-postgresql`,
|
17
|
+
title: `Learn Database PostgreSQL`,
|
18
|
+
type: INDEX_TYPES.learn,
|
19
|
+
},
|
20
|
+
{ name: `learn-hasura-backend`, title: `Learn Hasura Backend`, type: INDEX_TYPES.learn },
|
21
|
+
{ name: `learn-hasura-backend-ja`, title: `Learn Hasura Backend`, type: INDEX_TYPES.learn },
|
22
|
+
{ name: `learn-hasura-backend-zh`, title: `Learn Hasura Backend`, type: INDEX_TYPES.learn },
|
23
|
+
{ name: `learn-hasura-backend-advanced`, title: `Learn Hasura Backend Advanced`, type: INDEX_TYPES.learn },
|
24
|
+
{ name: `learn-hasura-auth-slack`, title: `Learn Hasura Auth Slack`, type: INDEX_TYPES.learn },
|
25
|
+
{ name: `learn-react-apollo-hooks`, title: `Learn React Apollo Hooks`, type: INDEX_TYPES.learn },
|
26
|
+
{ name: `learn-react-apollo`, title: `Learn React Apollo`, type: INDEX_TYPES.learn },
|
27
|
+
{
|
28
|
+
name: `learn-typescript-react-apollo`,
|
29
|
+
title: `Learn TypeScript React Apollo`,
|
30
|
+
type: INDEX_TYPES.learn,
|
31
|
+
},
|
32
|
+
{ name: `learn-angular-apollo`, title: `Learn Angular Apollo`, type: INDEX_TYPES.learn },
|
33
|
+
{ name: `learn-vue-apollo`, title: `Learn Vue Apollo`, type: INDEX_TYPES.learn },
|
34
|
+
{ name: `learn-ios-apollo`, title: `Learn IOS Apollo`, type: INDEX_TYPES.learn },
|
35
|
+
{ name: `learn-svelte-apollo`, title: `Learn Svelte Apollo`, type: INDEX_TYPES.learn },
|
36
|
+
{ name: `learn-android-apollo`, title: `Learn Android Apollo`, type: INDEX_TYPES.learn },
|
37
|
+
{
|
38
|
+
name: `learn-react-native-apollo`,
|
39
|
+
title: `Learn React Native Apollo`,
|
40
|
+
type: INDEX_TYPES.learn,
|
41
|
+
},
|
42
|
+
{
|
43
|
+
name: `learn-reason-react-apollo`,
|
44
|
+
title: `Learn Reason React Apollo`,
|
45
|
+
type: INDEX_TYPES.learn,
|
46
|
+
},
|
47
|
+
{ name: `learn-database-mssql`, title: `Learn MicrosoftSQL`, type: INDEX_TYPES.learn },
|
48
|
+
];
|
@@ -0,0 +1 @@
|
|
1
|
+
export {default as AlgoliaSearch } from './search';
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { useEffect, useState } from "react";
|
2
|
+
import SearchOverlay from "./searchoverlay";
|
3
|
+
export default function Search() {
|
4
|
+
const [showSearch, setShowSearch] = useState(false);
|
5
|
+
|
6
|
+
const handleSearchWithKeyboard = e => {
|
7
|
+
if (e.key === "/" || e.key === "Escape") {
|
8
|
+
e.preventDefault();
|
9
|
+
if (e.key === "/") return setShowSearch(true);
|
10
|
+
if (e.key === "Escape") return onCloseSearch();
|
11
|
+
}
|
12
|
+
|
13
|
+
return null;
|
14
|
+
};
|
15
|
+
|
16
|
+
useEffect(() => {
|
17
|
+
document.addEventListener("keydown", handleSearchWithKeyboard);
|
18
|
+
|
19
|
+
return () => {
|
20
|
+
document.removeEventListener("keydown", handleSearchWithKeyboard);
|
21
|
+
};
|
22
|
+
}, []);
|
23
|
+
|
24
|
+
const onCloseSearch = () => setShowSearch(false);
|
25
|
+
|
26
|
+
return (
|
27
|
+
<>
|
28
|
+
<div className="pt-10">
|
29
|
+
<div className="max-w-7xl mx-auto">
|
30
|
+
<div className="w-full" onClick={() => {setShowSearch(preShowSearch => !preShowSearch);}}>
|
31
|
+
Search
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
<SearchOverlay showSearch={showSearch} onCloseSearch={onCloseSearch} />
|
36
|
+
</>
|
37
|
+
);
|
38
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
.search-results {
|
2
|
+
width: calc(100% - 150px);
|
3
|
+
.ais-Hits, .Hits {
|
4
|
+
.ais-Hits-list {
|
5
|
+
display: grid;
|
6
|
+
grid-template-columns: repeat(3, 1fr);
|
7
|
+
grid-gap: 40px;
|
8
|
+
.ais-Hits-item {
|
9
|
+
border-bottom: 1px solid transparent;
|
10
|
+
&:hover {
|
11
|
+
border-bottom: 1px solid #E5E7EB;
|
12
|
+
}
|
13
|
+
.hit-slug {
|
14
|
+
word-break: break-word;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
@media (max-width: 799px) {
|
22
|
+
.search-results {
|
23
|
+
.ais-Hits, .Hits {
|
24
|
+
.ais-Hits-list {
|
25
|
+
grid-template-columns: 1fr;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
@media (min-width: 800px) and (max-width: 905px) {
|
31
|
+
.search-results {
|
32
|
+
.ais-Hits, .Hits {
|
33
|
+
.ais-Hits-list {
|
34
|
+
grid-template-columns: 1fr 1fr;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
2
|
+
import { connectSearchBox } from "react-instantsearch-dom";
|
3
|
+
import { Icon } from '../../components/common-components/Icon'
|
4
|
+
import { Typography } from '../../foundation/Typography'
|
5
|
+
const searchSuggestions = [
|
6
|
+
"GraphQL",
|
7
|
+
"Actions",
|
8
|
+
"Authentication",
|
9
|
+
"React",
|
10
|
+
"Remote Joins",
|
11
|
+
"Postgres",
|
12
|
+
];
|
13
|
+
|
14
|
+
const DebouncedSearchBox = ({
|
15
|
+
refine,
|
16
|
+
currentRefinement,
|
17
|
+
className,
|
18
|
+
delay = 500,
|
19
|
+
isSearchStalled,
|
20
|
+
}) => {
|
21
|
+
const [value, setValue] = useState(currentRefinement);
|
22
|
+
const timerId = useRef();
|
23
|
+
const searchInputRef = useRef();
|
24
|
+
useEffect(() => {
|
25
|
+
searchInputRef.current?.focus();
|
26
|
+
}, []);
|
27
|
+
|
28
|
+
const trySuggestion = inputValue => {
|
29
|
+
refine(inputValue);
|
30
|
+
setValue(inputValue);
|
31
|
+
};
|
32
|
+
|
33
|
+
const resetInput = () => {
|
34
|
+
refine("");
|
35
|
+
setValue("");
|
36
|
+
};
|
37
|
+
|
38
|
+
const onChangeDebounced = event => {
|
39
|
+
const value = event.target.value;
|
40
|
+
|
41
|
+
clearTimeout(timerId.current);
|
42
|
+
timerId.current = setTimeout(() => refine(value), delay);
|
43
|
+
|
44
|
+
setValue(value);
|
45
|
+
};
|
46
|
+
return (
|
47
|
+
<div className=" sticky -top-20 tb-m:-top-20 z-10 py-8 pt-16 tb-m:pt-16 tb-m:py-9 bg-neutral-50">
|
48
|
+
<div className="flex relative items-center w-full">
|
49
|
+
<div className="absolute left-2">
|
50
|
+
{isSearchStalled ? (
|
51
|
+
<div className="loader-spin border-[3px] border-neutral-400 border-t-blue-500 w-5 h-5 rounded-full" />
|
52
|
+
) : (
|
53
|
+
<Icon height={'w-5 h-5 stroke-[1.5px]'} variant="searchsm" strokeClass="stroke-neutral-800" />
|
54
|
+
)}
|
55
|
+
</div>
|
56
|
+
<form className={`${className} w-full`} onSubmit={e => e.preventDefault()}>
|
57
|
+
<input
|
58
|
+
id="search-input"
|
59
|
+
ref={searchInputRef}
|
60
|
+
className="w-full indent-5 rounded-xl border-neutral-400 h-12"
|
61
|
+
type="text"
|
62
|
+
placeholder="Search for tutorials, articles or docs"
|
63
|
+
aria-label="Search"
|
64
|
+
onChange={onChangeDebounced}
|
65
|
+
value={value}
|
66
|
+
/>
|
67
|
+
</form>
|
68
|
+
<div onClick={resetInput} className="absolute right-2 cursor-pointer bg-neutral-200 rounded-md">
|
69
|
+
<Icon height={'w-6 h-6 stroke-[1.5px]'} variant="xclose" strokeClass="stroke-neutral-800" />
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
<div className="hidden tb:flex flex-wrap items-center mt-4">
|
73
|
+
<Typography textStyle="body2c-bold" className="uppercase text-neutral-500 mr-4">POPULAR</Typography>
|
74
|
+
{searchSuggestions.map(suggestion => (
|
75
|
+
<button
|
76
|
+
key={suggestion}
|
77
|
+
className={`px-3 py-1 border rounded-md m-1 ${currentRefinement === suggestion ? "border-neutral-300 hover:bg-neutral-300 bg-neutral-300" : "bg-neutral-150 hover:bg-neutral-200 border-neutral-150"}`}
|
78
|
+
onClick={() => trySuggestion(suggestion)}
|
79
|
+
>
|
80
|
+
{suggestion}
|
81
|
+
</button>
|
82
|
+
))}
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
);
|
86
|
+
};
|
87
|
+
|
88
|
+
export default connectSearchBox(DebouncedSearchBox);
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React, { Fragment } from "react";
|
2
|
+
import { PoweredBy } from "react-instantsearch-dom";
|
3
|
+
|
4
|
+
const SearchFooter = () => (
|
5
|
+
<Fragment>
|
6
|
+
<hr className="my-5 border-t-neutral-200" />
|
7
|
+
<div className="mt-12 flex flex-wrap justify-between">
|
8
|
+
<div className="community-links-wrapper">
|
9
|
+
<p>Unable to find what you're looking for?</p>
|
10
|
+
<p>
|
11
|
+
Reach out to our{" "}
|
12
|
+
<a className="text-blue-500 hover:text-blue-700" href="https://discord.com/invite/hasura" target="_blank" rel="noopener noreferrer">
|
13
|
+
Discord Community
|
14
|
+
</a>{" "}
|
15
|
+
or start a{" "}
|
16
|
+
<a className="text-blue-500 hover:text-blue-700"
|
17
|
+
href="https://github.com/hasura/graphql-engine/discussions"
|
18
|
+
target="_blank"
|
19
|
+
rel="noopener noreferrer"
|
20
|
+
>
|
21
|
+
Discussion on GitHub
|
22
|
+
</a>
|
23
|
+
</p>
|
24
|
+
</div>
|
25
|
+
<PoweredBy />
|
26
|
+
</div>
|
27
|
+
</Fragment>
|
28
|
+
);
|
29
|
+
|
30
|
+
export default SearchFooter;
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { useEffect, useState } from "react";
|
2
|
+
import { Icon } from '../../components/common-components/Icon'
|
3
|
+
import SearchWrapper from "./searchwrapper";
|
4
|
+
import { SEARCH_INDICES } from "./constants";
|
5
|
+
|
6
|
+
export default function SearchOverlay({showSearch, onCloseSearch}) {
|
7
|
+
|
8
|
+
useEffect(() => {
|
9
|
+
if (showSearch) {
|
10
|
+
document.body.style.overflow = "hidden";
|
11
|
+
}
|
12
|
+
|
13
|
+
return () => {
|
14
|
+
document.body.style.overflow = "unset";
|
15
|
+
};
|
16
|
+
}, [showSearch]);
|
17
|
+
|
18
|
+
if (!showSearch) return null;
|
19
|
+
|
20
|
+
return (
|
21
|
+
<div className="fixed left-0 top-0 w-full h-full z-[10000] bg-neutral-50">
|
22
|
+
<div className="absolute top-4 right-4 cursor-pointer z-[11]" onClick={onCloseSearch}>
|
23
|
+
<Icon height={'w-8 h-8 stroke-[1.5px]'} variant="xclose" strokeClass="stroke-neutral-800" />
|
24
|
+
</div>
|
25
|
+
<div className="py-20 overflow-y-auto h-full w-full">
|
26
|
+
<div className="px-4">
|
27
|
+
<div className="max-w-7xl mx-auto">
|
28
|
+
<div className="w-full">
|
29
|
+
<SearchWrapper indices={SEARCH_INDICES} />
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
);
|
36
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import React, { Fragment } from "react";
|
2
|
+
import { connectStateResults, Highlight, Hits, Index, Snippet } from "react-instantsearch-dom";
|
3
|
+
import { INDEX_TYPES } from "./constants";
|
4
|
+
import SearchFooter from "./searchfooter";
|
5
|
+
import { Typography } from '../../foundation/Typography'
|
6
|
+
|
7
|
+
// import SearchFooter from "./SearchFooter";
|
8
|
+
|
9
|
+
const baseDomain = 'hasura.io';
|
10
|
+
|
11
|
+
|
12
|
+
const HitsHeader = ({ searchResults, indexTitle, showSeparator }) => {
|
13
|
+
const hitCount = searchResults && searchResults.nbHits;
|
14
|
+
return hitCount > 0 ? (
|
15
|
+
<Fragment>
|
16
|
+
{showSeparator && <hr className="my-5 border-t-neutral-200" />}
|
17
|
+
{/* <div className="HitCount">
|
18
|
+
{hitCount} result{hitCount !== 1 ? `s` : ``}
|
19
|
+
</div> */}
|
20
|
+
<Typography textStyle="body2c-medium" className="pb-4">{indexTitle}</Typography>
|
21
|
+
</Fragment>
|
22
|
+
) : null;
|
23
|
+
};
|
24
|
+
|
25
|
+
const CustomHitsHeader = connectStateResults(HitsHeader);
|
26
|
+
|
27
|
+
const PageHit = ({ hit, indexType }) => (
|
28
|
+
<a href={hit.url} className="grid h-full">
|
29
|
+
<div className="self-start">
|
30
|
+
<Typography textStyle="body3c-medium" className="uppercase text-neutral-500">{indexType}</Typography>
|
31
|
+
{indexType === INDEX_TYPES.docs ? (
|
32
|
+
<Fragment>
|
33
|
+
{!!hit.hierarchy && (
|
34
|
+
<Typography textStyle="body1c-bold" className="font-semibold py-3">{`${
|
35
|
+
Object.values(hit.hierarchy)
|
36
|
+
.filter(h => !!h)
|
37
|
+
.reverse()[0]
|
38
|
+
}`}</Typography>
|
39
|
+
)}
|
40
|
+
<Typography textStyle="body2" className="text-neutral-600 break-words">
|
41
|
+
<Snippet attribute="content" hit={hit} tagName="mark" />
|
42
|
+
</Typography>
|
43
|
+
|
44
|
+
</Fragment>
|
45
|
+
) : (
|
46
|
+
<Fragment>
|
47
|
+
<Typography textStyle="body1c-bold" className="font-semibold py-3">
|
48
|
+
<Highlight attribute="title" hit={hit} tagName="mark" />
|
49
|
+
</Typography>
|
50
|
+
<Typography textStyle="body2" className="text-neutral-600 break-words">
|
51
|
+
<Snippet attribute="excerpt" hit={hit} tagName="mark" />
|
52
|
+
</Typography>
|
53
|
+
</Fragment>
|
54
|
+
)}
|
55
|
+
</div>
|
56
|
+
{hit.url ? (
|
57
|
+
<Typography textStyle="body3" className="self-end my-2 pt-3 text-neutral-600">
|
58
|
+
<span className='hit-slug break-words'>{hit.url.replace(`https://${baseDomain}/`, "/")}</span>
|
59
|
+
</Typography>
|
60
|
+
) : null}
|
61
|
+
</a>
|
62
|
+
);
|
63
|
+
|
64
|
+
const HitsByIndexType = ({ indexType }) => {
|
65
|
+
if (INDEX_TYPES[indexType] === undefined) return null;
|
66
|
+
|
67
|
+
return (
|
68
|
+
<Hits
|
69
|
+
className="Hits"
|
70
|
+
hitComponent={hitProps => <PageHit {...hitProps} indexType={indexType} />}
|
71
|
+
/>
|
72
|
+
);
|
73
|
+
};
|
74
|
+
|
75
|
+
const HitsInIndex = ({ index, show }) => (
|
76
|
+
<Index indexName={index.name}>
|
77
|
+
{show && (
|
78
|
+
<Fragment>
|
79
|
+
<CustomHitsHeader
|
80
|
+
indexTitle={index.title}
|
81
|
+
showSeparator={index.type !== INDEX_TYPES.blog}
|
82
|
+
/>
|
83
|
+
<HitsByIndexType indexType={index.type} />
|
84
|
+
</Fragment>
|
85
|
+
)}
|
86
|
+
</Index>
|
87
|
+
);
|
88
|
+
|
89
|
+
const SearchResult = ({ indices, className, id, wrapperRef, activeIndexTypes }) => (
|
90
|
+
<div id={id} className={`${className} search-results z-[2] clear-both`} ref={wrapperRef}>
|
91
|
+
{indices.map(index => (
|
92
|
+
<HitsInIndex index={index} key={index.name} show={activeIndexTypes[index.type]} />
|
93
|
+
))}
|
94
|
+
<SearchFooter />
|
95
|
+
</div>
|
96
|
+
);
|
97
|
+
|
98
|
+
export default SearchResult;
|