create-instantsearch-app 5.1.2 → 5.2.0
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 +10 -0
- package/package.json +1 -1
- package/src/api/__tests__/__snapshots__/index.test.js.snap +3 -3
- package/src/templates/React InstantSearch Hooks Native/.expo-shared/assets.json +4 -0
- package/src/templates/React InstantSearch Hooks Native/.gitignore.template +14 -0
- package/src/templates/React InstantSearch Hooks Native/.template.js +34 -0
- package/src/templates/React InstantSearch Hooks Native/App.tsx.hbs +72 -0
- package/src/templates/React InstantSearch Hooks Native/README.md +19 -0
- package/src/templates/React InstantSearch Hooks Native/app.json +30 -0
- package/src/templates/React InstantSearch Hooks Native/assets/adaptive-icon.png +0 -0
- package/src/templates/React InstantSearch Hooks Native/assets/favicon.png +0 -0
- package/src/templates/React InstantSearch Hooks Native/assets/icon.png +0 -0
- package/src/templates/React InstantSearch Hooks Native/assets/splash.png +0 -0
- package/src/templates/React InstantSearch Hooks Native/babel.config.js +6 -0
- package/src/templates/React InstantSearch Hooks Native/package.json +31 -0
- package/src/templates/React InstantSearch Hooks Native/src/Highlight.tsx +81 -0
- package/src/templates/React InstantSearch Hooks Native/src/InfiniteHits.tsx +56 -0
- package/src/templates/React InstantSearch Hooks Native/src/SearchBox.tsx.hbs +71 -0
- package/src/templates/React InstantSearch Hooks Native/tsconfig.json +6 -0
- package/src/templates/React InstantSearch Hooks Native/types/ProductHit.ts +28 -0
- package/src/templates/Vue InstantSearch with Vue 3/.editorconfig +9 -0
- package/src/templates/Vue InstantSearch with Vue 3/.gitignore.template +23 -0
- package/src/templates/Vue InstantSearch with Vue 3/.prettierrc +5 -0
- package/src/templates/Vue InstantSearch with Vue 3/.template.js +18 -0
- package/src/templates/Vue InstantSearch with Vue 3/README.md +21 -0
- package/src/templates/Vue InstantSearch with Vue 3/index.html +19 -0
- package/src/templates/Vue InstantSearch with Vue 3/package.json +19 -0
- package/src/templates/Vue InstantSearch with Vue 3/public/favicon.png +0 -0
- package/src/templates/Vue InstantSearch with Vue 3/src/App.vue +161 -0
- package/src/templates/Vue InstantSearch with Vue 3/src/main.js +7 -0
- package/src/templates/Vue InstantSearch with Vue 3/vite.config.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# [5.2.0](https://github.com/algolia/create-instantsearch-app/compare/5.1.2...5.2.0) (2022-02-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **templates:** introduce React InstantSearch Hooks Native template ([#559](https://github.com/algolia/create-instantsearch-app/issues/559)) ([495f014](https://github.com/algolia/create-instantsearch-app/commit/495f0140301185bba9192ccd779597d4d5028241))
|
|
7
|
+
* **vue:** add vue3 template ([#558](https://github.com/algolia/create-instantsearch-app/issues/558)) ([117067e](https://github.com/algolia/create-instantsearch-app/commit/117067ea0c4da0d08105d2de1e8b611f935b533a))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
1
11
|
## [5.1.2](https://github.com/algolia/create-instantsearch-app/compare/5.1.1...5.1.2) (2022-02-02)
|
|
2
12
|
|
|
3
13
|
|
package/package.json
CHANGED
|
@@ -6,10 +6,10 @@ exports[`Options with invalid name throws 1`] = `
|
|
|
6
6
|
- name can only contain URL-friendly characters"
|
|
7
7
|
`;
|
|
8
8
|
|
|
9
|
-
exports[`Options with unknown template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
|
|
9
|
+
exports[`Options with unknown template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Hooks Native, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2, Vue InstantSearch with Vue 3"`;
|
|
10
10
|
|
|
11
|
-
exports[`Options with wrong template path throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
|
|
11
|
+
exports[`Options with wrong template path throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Hooks Native, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2, Vue InstantSearch with Vue 3"`;
|
|
12
12
|
|
|
13
13
|
exports[`Options without path throws 1`] = `"The option \`path\` is required."`;
|
|
14
14
|
|
|
15
|
-
exports[`Options without template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
|
|
15
|
+
exports[`Options without template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, Autocomplete, Autocomplete.js 0, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Hooks Native, React InstantSearch Native, React InstantSearch widget, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2, Vue InstantSearch with Vue 3"`;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const install = require('../../tasks/node/install');
|
|
3
|
+
const teardown = require('../../tasks/node/teardown');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
category: 'Mobile',
|
|
7
|
+
libraryName: 'react-instantsearch-hooks',
|
|
8
|
+
templateName: 'react-instantsearch-hooks-native',
|
|
9
|
+
appName: 'react-instantsearch-hooks-native-app',
|
|
10
|
+
keywords: [
|
|
11
|
+
'algolia',
|
|
12
|
+
'instantSearch',
|
|
13
|
+
'react',
|
|
14
|
+
'react-native',
|
|
15
|
+
'react-instantsearch-hooks-native',
|
|
16
|
+
],
|
|
17
|
+
tasks: {
|
|
18
|
+
setup(config) {
|
|
19
|
+
if (!config.silent && config.attributesForFaceting) {
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(
|
|
22
|
+
`⚠️ The ${chalk.cyan(
|
|
23
|
+
'attributesForFaceting'
|
|
24
|
+
)} option is not supported in this template.`
|
|
25
|
+
);
|
|
26
|
+
console.log();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
},
|
|
31
|
+
install,
|
|
32
|
+
teardown,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { FlatList, SafeAreaView, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import { StatusBar } from 'expo-status-bar';
|
|
4
|
+
import algoliasearch from 'algoliasearch/lite';
|
|
5
|
+
import { InstantSearch } from 'react-instantsearch-hooks';
|
|
6
|
+
|
|
7
|
+
import { InfiniteHits } from './src/InfiniteHits';
|
|
8
|
+
import { SearchBox } from './src/SearchBox';
|
|
9
|
+
{{#if attributesToDisplay}}
|
|
10
|
+
import { Highlight } from './src/Highlight';
|
|
11
|
+
{{/if}}
|
|
12
|
+
import { ProductHit } from './types/ProductHit';
|
|
13
|
+
|
|
14
|
+
const searchClient = algoliasearch(
|
|
15
|
+
'{{appId}}',
|
|
16
|
+
'{{apiKey}}'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default function App() {
|
|
20
|
+
const listRef = useRef<FlatList>(null);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<SafeAreaView style={styles.safe}>
|
|
24
|
+
<StatusBar style="light" />
|
|
25
|
+
<View style={styles.container}>
|
|
26
|
+
<InstantSearch searchClient={searchClient} indexName="{{indexName}}">
|
|
27
|
+
<SearchBox
|
|
28
|
+
onChange={() =>
|
|
29
|
+
listRef.current?.scrollToOffset({ animated: false, offset: 0 })
|
|
30
|
+
}
|
|
31
|
+
/>
|
|
32
|
+
<InfiniteHits ref={listRef} hitComponent={Hit} />
|
|
33
|
+
</InstantSearch>
|
|
34
|
+
</View>
|
|
35
|
+
</SafeAreaView>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type HitProps = {
|
|
40
|
+
hit: ProductHit;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function Hit({ hit }: HitProps) {
|
|
44
|
+
return (
|
|
45
|
+
{{#if attributesToDisplay}}
|
|
46
|
+
<Text>
|
|
47
|
+
<Highlight hit={hit} attribute="{{attributesToDisplay.[0]}}" />
|
|
48
|
+
</Text>
|
|
49
|
+
{{#each attributesToDisplay}}
|
|
50
|
+
{{#unless @first}}
|
|
51
|
+
<Text>
|
|
52
|
+
<Highlight hit={hit} attribute="{{this}}" />
|
|
53
|
+
</Text>
|
|
54
|
+
{{/unless}}
|
|
55
|
+
{{/each}}
|
|
56
|
+
{{else}}
|
|
57
|
+
<Text>{JSON.stringify(hit).slice(0, 100)}</Text>
|
|
58
|
+
{{/if}}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const styles = StyleSheet.create({
|
|
63
|
+
safe: {
|
|
64
|
+
flex: 1,
|
|
65
|
+
backgroundColor: '#252b33',
|
|
66
|
+
},
|
|
67
|
+
container: {
|
|
68
|
+
flex: 1,
|
|
69
|
+
backgroundColor: '#ffffff',
|
|
70
|
+
flexDirection: 'column',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
|
|
4
|
+
|
|
5
|
+
## Get started
|
|
6
|
+
|
|
7
|
+
To run this project locally, install the dependencies and run the local server:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install
|
|
11
|
+
npm start
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Alternatively, you may use [Yarn](https://http://yarnpkg.com/):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
yarn
|
|
18
|
+
yarn start
|
|
19
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"slug": "{{name}}",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/icon.png",
|
|
8
|
+
"splash": {
|
|
9
|
+
"image": "./assets/splash.png",
|
|
10
|
+
"resizeMode": "contain",
|
|
11
|
+
"backgroundColor": "#ffffff"
|
|
12
|
+
},
|
|
13
|
+
"updates": {
|
|
14
|
+
"fallbackToCacheTimeout": 0
|
|
15
|
+
},
|
|
16
|
+
"assetBundlePatterns": ["**/*"],
|
|
17
|
+
"ios": {
|
|
18
|
+
"supportsTablet": true
|
|
19
|
+
},
|
|
20
|
+
"android": {
|
|
21
|
+
"adaptiveIcon": {
|
|
22
|
+
"foregroundImage": "./assets/adaptive-icon.png",
|
|
23
|
+
"backgroundColor": "#FFFFFF"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"web": {
|
|
27
|
+
"favicon": "./assets/favicon.png"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "node_modules/expo/AppEntry.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "expo start",
|
|
8
|
+
"android": "expo start --android",
|
|
9
|
+
"ios": "expo start --ios",
|
|
10
|
+
"web": "expo start --web",
|
|
11
|
+
"eject": "expo eject"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"algoliasearch": "4.12.1",
|
|
15
|
+
"expo": "~44.0.0",
|
|
16
|
+
"expo-status-bar": "~1.2.0",
|
|
17
|
+
"instantsearch.js": "4.38.1",
|
|
18
|
+
"react": "17.0.1",
|
|
19
|
+
"react-dom": "17.0.1",
|
|
20
|
+
"react-instantsearch-hooks": "{{libraryVersion}}",
|
|
21
|
+
"react-native": "0.64.3",
|
|
22
|
+
"react-native-web": "0.17.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@babel/core": "^7.12.9",
|
|
26
|
+
"@types/react": "~17.0.21",
|
|
27
|
+
"@types/react-native": "~0.64.12",
|
|
28
|
+
"expo-cli": "5.1.1",
|
|
29
|
+
"typescript": "~4.3.5"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React, { Fragment } from 'react';
|
|
2
|
+
import { StyleSheet, Text } from 'react-native';
|
|
3
|
+
import { Hit as AlgoliaHit } from '@algolia/client-search';
|
|
4
|
+
import {
|
|
5
|
+
getHighlightedParts,
|
|
6
|
+
getPropertyByPath,
|
|
7
|
+
} from 'instantsearch.js/es/lib/utils';
|
|
8
|
+
|
|
9
|
+
type HighlightPartProps = {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
isHighlighted: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function HighlightPart({ children, isHighlighted }: HighlightPartProps) {
|
|
15
|
+
return (
|
|
16
|
+
<Text style={isHighlighted ? styles.highlighted : styles.nonHighlighted}>
|
|
17
|
+
{children}
|
|
18
|
+
</Text>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HighlightProps<THit> = {
|
|
23
|
+
hit: THit;
|
|
24
|
+
attribute: keyof THit | string[];
|
|
25
|
+
className?: string;
|
|
26
|
+
separator?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function Highlight<THit extends AlgoliaHit<Record<string, unknown>>>({
|
|
30
|
+
hit,
|
|
31
|
+
attribute,
|
|
32
|
+
separator = ', ',
|
|
33
|
+
}: HighlightProps<THit>) {
|
|
34
|
+
const { value: attributeValue = '' } =
|
|
35
|
+
getPropertyByPath(hit._highlightResult, attribute as string) || {};
|
|
36
|
+
const parts = getHighlightedParts(attributeValue);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{parts.map((part, partIndex) => {
|
|
41
|
+
if (Array.isArray(part)) {
|
|
42
|
+
const isLastPart = partIndex === parts.length - 1;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Fragment key={partIndex}>
|
|
46
|
+
{part.map((subPart, subPartIndex) => (
|
|
47
|
+
<HighlightPart
|
|
48
|
+
key={subPartIndex}
|
|
49
|
+
isHighlighted={subPart.isHighlighted}
|
|
50
|
+
>
|
|
51
|
+
{subPart.value}
|
|
52
|
+
</HighlightPart>
|
|
53
|
+
))}
|
|
54
|
+
|
|
55
|
+
{!isLastPart && separator}
|
|
56
|
+
</Fragment>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<HighlightPart key={partIndex} isHighlighted={part.isHighlighted}>
|
|
62
|
+
{part.value}
|
|
63
|
+
</HighlightPart>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const styles = StyleSheet.create({
|
|
71
|
+
highlighted: {
|
|
72
|
+
fontWeight: 'bold',
|
|
73
|
+
backgroundColor: '#f5df4d',
|
|
74
|
+
color: '#6f6106',
|
|
75
|
+
},
|
|
76
|
+
nonHighlighted: {
|
|
77
|
+
fontWeight: 'normal',
|
|
78
|
+
backgroundColor: 'transparent',
|
|
79
|
+
color: 'black',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { StyleSheet, View, FlatList } from 'react-native';
|
|
3
|
+
import { Hit as AlgoliaHit } from '@algolia/client-search';
|
|
4
|
+
import {
|
|
5
|
+
useInfiniteHits,
|
|
6
|
+
UseInfiniteHitsProps,
|
|
7
|
+
} from 'react-instantsearch-hooks';
|
|
8
|
+
|
|
9
|
+
type InfiniteHitsProps<THit> = UseInfiniteHitsProps & {
|
|
10
|
+
hitComponent: (props: { hit: THit }) => JSX.Element;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const InfiniteHits = forwardRef(
|
|
14
|
+
<THit extends AlgoliaHit<Record<string, unknown>>>(
|
|
15
|
+
{ hitComponent: Hit, ...props }: InfiniteHitsProps<THit>,
|
|
16
|
+
ref: React.ForwardedRef<FlatList<THit>>
|
|
17
|
+
) => {
|
|
18
|
+
const { hits, isLastPage, showMore } = useInfiniteHits(props);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<FlatList
|
|
22
|
+
ref={ref}
|
|
23
|
+
data={(hits as unknown) as THit[]}
|
|
24
|
+
keyExtractor={item => item.objectID}
|
|
25
|
+
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
26
|
+
onEndReached={() => {
|
|
27
|
+
if (!isLastPage) {
|
|
28
|
+
showMore();
|
|
29
|
+
}
|
|
30
|
+
}}
|
|
31
|
+
renderItem={({ item }) => (
|
|
32
|
+
<View style={styles.item}>
|
|
33
|
+
<Hit hit={(item as unknown) as THit} />
|
|
34
|
+
</View>
|
|
35
|
+
)}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const styles = StyleSheet.create({
|
|
42
|
+
separator: {
|
|
43
|
+
borderBottomWidth: 1,
|
|
44
|
+
borderColor: '#ddd',
|
|
45
|
+
},
|
|
46
|
+
item: {
|
|
47
|
+
padding: 18,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
declare module 'react' {
|
|
52
|
+
// eslint-disable-next-line no-shadow
|
|
53
|
+
function forwardRef<TRef, TProps = unknown>(
|
|
54
|
+
render: (props: TProps, ref: React.Ref<TRef>) => React.ReactElement | null
|
|
55
|
+
): (props: TProps & React.RefAttributes<TRef>) => React.ReactElement | null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { StyleSheet, View, TextInput } from 'react-native';
|
|
3
|
+
import { useSearchBox, UseSearchBoxProps } from 'react-instantsearch-hooks';
|
|
4
|
+
|
|
5
|
+
type SearchBoxProps = UseSearchBoxProps & {
|
|
6
|
+
onChange: (newValue: string) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function SearchBox({ onChange, ...props }: SearchBoxProps) {
|
|
10
|
+
const { query, refine } = useSearchBox(props);
|
|
11
|
+
const [value, setValue] = useState(query);
|
|
12
|
+
const inputRef = useRef<TextInput>(null);
|
|
13
|
+
|
|
14
|
+
// Track when the value coming from the React state changes to synchronize
|
|
15
|
+
// it with InstantSearch.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (query !== value) {
|
|
18
|
+
refine(value);
|
|
19
|
+
}
|
|
20
|
+
// We don't want to track when the React state value changes.
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
+
}, [value, refine]);
|
|
23
|
+
|
|
24
|
+
// Track when the InstantSearch query changes to synchronize it with
|
|
25
|
+
// the React state.
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// We bypass the state update if the input is focused to avoid concurrent
|
|
28
|
+
// updates when typing.
|
|
29
|
+
if (!inputRef.current?.isFocused() && query !== value) {
|
|
30
|
+
setValue(query);
|
|
31
|
+
}
|
|
32
|
+
// We don't want to track when the React state value changes.
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [query]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View style={styles.container}>
|
|
38
|
+
<TextInput
|
|
39
|
+
ref={inputRef}
|
|
40
|
+
style={styles.input}
|
|
41
|
+
value={value}
|
|
42
|
+
onChangeText={newValue => {
|
|
43
|
+
setValue(newValue);
|
|
44
|
+
onChange(newValue);
|
|
45
|
+
}}
|
|
46
|
+
clearButtonMode="while-editing"
|
|
47
|
+
autoCapitalize="none"
|
|
48
|
+
autoCorrect={false}
|
|
49
|
+
spellCheck={false}
|
|
50
|
+
autoCompleteType="off"
|
|
51
|
+
placeholder="{{searchPlaceholder}}"
|
|
52
|
+
/>
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const styles = StyleSheet.create({
|
|
58
|
+
container: {
|
|
59
|
+
backgroundColor: '#252b33',
|
|
60
|
+
padding: 18,
|
|
61
|
+
},
|
|
62
|
+
input: {
|
|
63
|
+
height: 48,
|
|
64
|
+
padding: 12,
|
|
65
|
+
fontSize: 16,
|
|
66
|
+
backgroundColor: '#fff',
|
|
67
|
+
borderRadius: 4,
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderColor: '#ddd',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Hit as AlgoliaHit } from '@algolia/client-search';
|
|
2
|
+
|
|
3
|
+
export type ProductHit = AlgoliaHit<{
|
|
4
|
+
brand: string;
|
|
5
|
+
categories: string[];
|
|
6
|
+
comments: number;
|
|
7
|
+
description: string;
|
|
8
|
+
free_shipping: boolean;
|
|
9
|
+
hierarchicalCategories: {
|
|
10
|
+
lvl0: string;
|
|
11
|
+
lvl1?: string;
|
|
12
|
+
lvl2?: string;
|
|
13
|
+
lvl3?: string;
|
|
14
|
+
lvl4?: string;
|
|
15
|
+
lvl5?: string;
|
|
16
|
+
lvl6?: string;
|
|
17
|
+
};
|
|
18
|
+
image: string;
|
|
19
|
+
name: string;
|
|
20
|
+
popularity: number;
|
|
21
|
+
price: number;
|
|
22
|
+
prince_range: string;
|
|
23
|
+
rating: number;
|
|
24
|
+
sale: boolean;
|
|
25
|
+
sale_price: string;
|
|
26
|
+
type: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode
|
|
17
|
+
.idea
|
|
18
|
+
.DS_Store
|
|
19
|
+
*.suo
|
|
20
|
+
*.ntvs*
|
|
21
|
+
*.njsproj
|
|
22
|
+
*.sln
|
|
23
|
+
*.sw?
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const install = require('../../tasks/node/install');
|
|
2
|
+
const teardown = require('../../tasks/node/teardown');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
category: 'Web',
|
|
6
|
+
libraryName: 'vue-instantsearch',
|
|
7
|
+
supportedVersion: '>= 4.3.3 < 5.0.0',
|
|
8
|
+
flags: {
|
|
9
|
+
dynamicWidgets: '>=4.2.0',
|
|
10
|
+
},
|
|
11
|
+
templateName: 'vue-instantsearch-vue3',
|
|
12
|
+
appName: 'vue-instantsearch-app',
|
|
13
|
+
keywords: ['algolia', 'InstantSearch', 'Vue', 'vue-instantsearch'],
|
|
14
|
+
tasks: {
|
|
15
|
+
install,
|
|
16
|
+
teardown,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
|
|
4
|
+
|
|
5
|
+
## Get started
|
|
6
|
+
|
|
7
|
+
To run this project locally, install the dependencies and run the local server:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Alternatively, you may use [Yarn](https://http://yarnpkg.com/):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
yarn
|
|
18
|
+
yarn dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Open http://localhost:3000 to see your app.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<link rel="icon" href="/favicon.png" />
|
|
8
|
+
<!--
|
|
9
|
+
Do not use @7 in production, use a complete version like x.x.x, see website for latest version:
|
|
10
|
+
https://www.algolia.com/doc/guides/building-search-ui/installation/react/#load-the-style
|
|
11
|
+
-->
|
|
12
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7/themes/algolia-min.css">
|
|
13
|
+
<title>{{name}}</title>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="app"></div>
|
|
17
|
+
<script type="module" src="/src/main.js"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"algoliasearch": "4.12.1",
|
|
12
|
+
"vue": "3.2.25",
|
|
13
|
+
"vue-instantsearch": "{{libraryVersion}}"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-vue": "2.2.0",
|
|
17
|
+
"vite": "2.8.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<header class="header">
|
|
4
|
+
<h1 class="header-title">
|
|
5
|
+
<a href="/">{{ name }}</a>
|
|
6
|
+
</h1>
|
|
7
|
+
<p class="header-subtitle">
|
|
8
|
+
using
|
|
9
|
+
<a href="https://github.com/algolia/vue-instantsearch">
|
|
10
|
+
Vue InstantSearch
|
|
11
|
+
</a>
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<div class="container">
|
|
16
|
+
<ais-instant-search
|
|
17
|
+
:search-client="searchClient"
|
|
18
|
+
index-name="{{indexName}}"
|
|
19
|
+
>
|
|
20
|
+
<div class="search-panel">
|
|
21
|
+
<div class="search-panel__filters">
|
|
22
|
+
{{#if flags.dynamicWidgets}}
|
|
23
|
+
<ais-dynamic-widgets>
|
|
24
|
+
{{#each attributesForFaceting}}
|
|
25
|
+
<ais-refinement-list attribute="{{this}}" />
|
|
26
|
+
{{/each}}
|
|
27
|
+
</ais-dynamic-widgets>
|
|
28
|
+
{{else}}
|
|
29
|
+
{{#each attributesForFaceting}}
|
|
30
|
+
<ais-refinement-list attribute="{{this}}" />
|
|
31
|
+
{{/each}}
|
|
32
|
+
{{/if}}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="search-panel__results">
|
|
36
|
+
<div class="searchbox">
|
|
37
|
+
<ais-search-box placeholder="{{searchPlaceholder}}" />
|
|
38
|
+
</div>
|
|
39
|
+
{{#if attributesToDisplay}}
|
|
40
|
+
<ais-hits>
|
|
41
|
+
<template v-slot:item="{ item }">
|
|
42
|
+
<article>
|
|
43
|
+
<h1>
|
|
44
|
+
<ais-highlight
|
|
45
|
+
:hit="item"
|
|
46
|
+
attribute="{{attributesToDisplay.[0]}}"
|
|
47
|
+
/>
|
|
48
|
+
</h1>
|
|
49
|
+
{{#each attributesToDisplay}}
|
|
50
|
+
{{#unless @first}}
|
|
51
|
+
<p>
|
|
52
|
+
<ais-highlight :hit="item" attribute="{{this}}" />
|
|
53
|
+
</p>
|
|
54
|
+
{{/unless}}
|
|
55
|
+
{{/each}}
|
|
56
|
+
</article>
|
|
57
|
+
</template>
|
|
58
|
+
</ais-hits>
|
|
59
|
+
{{else}}
|
|
60
|
+
<ais-hits />
|
|
61
|
+
{{/if}}
|
|
62
|
+
|
|
63
|
+
<div class="pagination">
|
|
64
|
+
<ais-pagination />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</ais-instant-search>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script>
|
|
74
|
+
import algoliasearch from 'algoliasearch/lite';
|
|
75
|
+
|
|
76
|
+
export default {
|
|
77
|
+
data() {
|
|
78
|
+
return {
|
|
79
|
+
searchClient: algoliasearch('{{appId}}', '{{apiKey}}'),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style>
|
|
86
|
+
body,
|
|
87
|
+
h1 {
|
|
88
|
+
margin: 0;
|
|
89
|
+
padding: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
body {
|
|
93
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
|
94
|
+
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
h1 {
|
|
98
|
+
font-size: 1rem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
em {
|
|
102
|
+
background: cyan;
|
|
103
|
+
font-style: normal;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.header {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
min-height: 50px;
|
|
110
|
+
padding: 0.5rem 1rem;
|
|
111
|
+
background-image: linear-gradient(to right, #4dba87, #2f9088);
|
|
112
|
+
color: #fff;
|
|
113
|
+
margin-bottom: 1rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.header a {
|
|
117
|
+
color: #fff;
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.header-title {
|
|
122
|
+
font-size: 1.2rem;
|
|
123
|
+
font-weight: normal;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.header-title::after {
|
|
127
|
+
content: ' ▸ ';
|
|
128
|
+
padding: 0 0.5rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.header-subtitle {
|
|
132
|
+
font-size: 1.2rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.container {
|
|
136
|
+
max-width: 1200px;
|
|
137
|
+
margin: 0 auto;
|
|
138
|
+
padding: 1rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.search-panel {
|
|
142
|
+
display: flex;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.search-panel__filters {
|
|
146
|
+
flex: 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.search-panel__results {
|
|
150
|
+
flex: 3;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.searchbox {
|
|
154
|
+
margin-bottom: 2rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.pagination {
|
|
158
|
+
margin: 2rem auto;
|
|
159
|
+
text-align: center;
|
|
160
|
+
}
|
|
161
|
+
</style>
|