hds-web 1.15.8 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hds-web",
3
- "version": "1.15.8",
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",
@@ -5,7 +5,6 @@ export * from './Dropdown';
5
5
  export * from './Feedback';
6
6
  export * from './Link';
7
7
  export * from './TalkDetailCard';
8
- export * from './ImageSlider';
9
8
  export * from './CommunityCard';
10
9
  export * from './Announcement';
11
10
  export * from './VideoCard';
@@ -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
- if(!snippet){
10
- snippet='as'
11
- }
9
+ // if(!snippet){
10
+ // snippet='as'
11
+ // }
12
12
 
13
- const [isCopied, setIsCopied] = useState(false);
14
- const preRef = useRef(null);
13
+ // const [isCopied, setIsCopied] = useState(false);
14
+ // const preRef = useRef(null);
15
15
 
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
- }
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
- useEffect(() => {
28
- console.log(snippet)
29
- if (preRef.current ) {
30
- Prism.highlightAll();
31
- }
32
- }, [snippet, preRef.current]);
27
+ // useEffect(() => {
28
+ // console.log(snippet)
29
+ // if (preRef.current ) {
30
+ // Prism.highlightAll();
31
+ // }
32
+ // }, [snippet, preRef.current]);
33
33
 
34
34
 
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}
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
- <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>
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
- <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
- }
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';
@@ -8,7 +8,6 @@ export * from './Checkbox';
8
8
  export * from './Headers';
9
9
  export * from './common-components';
10
10
  export * from './InputFields';
11
- export * from './Snippet';
12
11
  export * from './Tabs';
13
12
  export * from './Tooltip';
14
13
  export * from './Hero';
@@ -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;