diff --git a/e2e/__snapshots__/templates.test.js.snap b/e2e/__snapshots__/templates.test.js.snap
index 300f7a0b6..8d9f17dfd 100644
--- a/e2e/__snapshots__/templates.test.js.snap
+++ b/e2e/__snapshots__/templates.test.js.snap
@@ -5536,6 +5536,450 @@ Array [
]
`;
+exports[`Templates React InstantSearch widget File content: .eslintignore 1`] = `
+"/dist
+node_modules/"
+`;
+
+exports[`Templates React InstantSearch widget File content: .eslintrc.cjs 1`] = `
+"// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ extends: ['algolia', 'algolia/jest', 'algolia/react', 'algolia/typescript'],
+ rules: {
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ 'jest/expect-expect': 'off',
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+};"
+`;
+
+exports[`Templates React InstantSearch widget File content: .gitignore 1`] = `
+"node_modules
+.DS_Store
+dist
+dist-ssr
+*.local"
+`;
+
+exports[`Templates React InstantSearch widget File content: .prettierrc 1`] = `
+"{
+ \\"singleQuote\\": true,
+ \\"proseWrap\\": \\"never\\",
+ \\"trailingComma\\": \\"es5\\"
+}"
+`;
+
+exports[`Templates React InstantSearch widget File content: README.md 1`] = `
+"# react-instantsearch-app
+
+_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
+
+
+
+## Install
+
+\`\`\`bash
+npm install @algolia/react-instantsearch-app
+# or
+yarn add @algolia/react-instantsearch-app
+\`\`\`
+
+## Widget
+
+### Usage
+
+\`\`\`jsx
+import instantsearch from 'instantsearch.js';
+import algoliasearch from 'algoliasearch/lite';
+import { ReactInstantsearchApp } from '@algolia/react-instantsearch-app';
+
+const searchClient = algoliasearch('appId', 'apiKey');
+
+const App = () => (
+
+
+
+);
+\`\`\`
+
+## Connector
+
+### Usage
+
+\`\`\`jsx
+import { connectReactInstantsearchApp } from '@algolia/react-instantsearch-app';
+
+// 1. Create a render function
+const RenderReactInstantsearchApp = (renderOptions, isFirstRender) => {
+ // Rendering logic
+};
+
+// 2. Create the custom widget
+const CustomReactInstantsearchApp = connectReactInstantsearchApp(
+ RenderReactInstantsearchApp
+);
+
+// 3. Instantiate
+const App = () => (
+
+
+
+);
+\`\`\`
+
+## Test
+
+\`\`\`bash
+npm test
+# or
+yarn test
+\`\`\`
+
+## Build
+
+\`\`\`bash
+npm run build
+# or
+yarn build
+\`\`\`
+
+## Release
+
+\`\`\`bash
+npm run release
+# or
+yarn release
+\`\`\`
+
+### First Release
+
+\`\`\`bash
+npm run release -- --first-release
+# or
+yarn release --first-release
+\`\`\`
+
+This will tag a release without bumping the version.
+
+When you are ready, push the git tag and run \`npm publish\`.
+
+If you want to publish it as a public scoped package, run \`npm publish --access public\` the first time.
+
+[To know more about \`standard-version\`, read this →](https://github.com/conventional-changelog/standard-version#cli-usage)"
+`;
+
+exports[`Templates React InstantSearch widget File content: babel.config.cjs 1`] = `
+"// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ presets: [
+ ['@babel/preset-env', { targets: { node: 'current' } }],
+ '@babel/preset-react',
+ '@babel/preset-typescript',
+ ],
+};"
+`;
+
+exports[`Templates React InstantSearch widget File content: example/index.html 1`] = `
+"
+
+
+
+
+ @algolia/react-instantsearch-app | example
+
+
+
+
+
+"
+`;
+
+exports[`Templates React InstantSearch widget File content: example/index.tsx 1`] = `
+"import algoliasearch from 'algoliasearch/lite';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-dom';
+
+import { ReactInstantsearchApp } from '../src';
+
+const searchClient = algoliasearch(
+ 'latency',
+ '6be0576ff61c053d5f9a3225e2a90f76'
+);
+
+ReactDOM.render(
+
+
+
+
+
+
+ ,
+ document.getElementById('root')
+);"
+`;
+
+exports[`Templates React InstantSearch widget File content: package.json 1`] = `
+"{
+ \\"name\\": \\"@algolia/react-instantsearch-app\\",
+ \\"version\\": \\"1.0.0\\",
+ \\"description\\": \\"\\",
+ \\"keywords\\": [
+ \\"instantsearch-widget\\",
+ \\"instantsearch\\",
+ \\"react-instantsearch\\",
+ \\"react-instantsearch-widget-react-instantsearch-app\\",
+ \\"widget\\",
+ \\"connector\\",
+ \\"algolia\\"
+ ],
+ \\"license\\": \\"MIT\\",
+ \\"main\\": \\"./dist/index.cjs.js\\",
+ \\"module\\": \\"./dist/index.es.js\\",
+ \\"exports\\": {
+ \\"import\\": \\"./dist/index.es.js\\",
+ \\"require\\": \\"./dist/index.cjs.js\\"
+ },
+ \\"jsdelivr\\": \\"./dist/index.umd.js\\",
+ \\"unpkg\\": \\"./dist/index.umd.js\\",
+ \\"types\\": \\"./dist/index.d.ts\\",
+ \\"type\\": \\"module\\",
+ \\"files\\": [
+ \\"dist\\"
+ ],
+ \\"scripts\\": {
+ \\"start\\": \\"vite example\\",
+ \\"prebuild\\": \\"rm -rf dist\\",
+ \\"build\\": \\"vite build && npm run build:types\\",
+ \\"build:types\\": \\"tsc -p tsconfig.declaration.json\\",
+ \\"lint\\": \\"eslint --ext .js,.ts,.tsx .\\",
+ \\"lint:fix\\": \\"npm run lint --fix\\",
+ \\"test\\": \\"NODE_OPTIONS=--experimental-vm-modules jest\\",
+ \\"test:watch\\": \\"npm test -- --watch\\",
+ \\"test:types\\": \\"tsc\\",
+ \\"prerelease\\": \\"npm run build\\",
+ \\"release\\": \\"standard-version\\"
+ },
+ \\"partialDependencies\\": {}
+}"
+`;
+
+exports[`Templates React InstantSearch widget File content: src/index.tsx 1`] = `
+"export { connectReactInstantsearchApp } from './lib/connector';
+export { ReactInstantsearchAppComponent } from './lib/component';
+export { ReactInstantsearchApp } from './lib/widget';"
+`;
+
+exports[`Templates React InstantSearch widget File content: src/lib/__tests__/dummy-test.tsx 1`] = `
+"import { render } from '@testing-library/react';
+import React from 'react';
+import { InstantSearch, Hits } from 'react-instantsearch-dom';
+
+import { ReactInstantsearchApp } from '../widget';
+
+const runAllMicroTasks = (): Promise => new Promise(setImmediate);
+
+describe('nothing', () => {
+ it('tests nothing', async () => {
+ const searchClient = {
+ search(_requests: any[]) {
+ return Promise.resolve({
+ results: [
+ {
+ hits: [
+ {
+ objectID: 'a',
+ name: 'test',
+ },
+ ],
+ },
+ ],
+ });
+ },
+ };
+
+ const { debug } = render(
+
+
+ hit.name} />
+
+ );
+
+ await runAllMicroTasks();
+ debug();
+ });
+});"
+`;
+
+exports[`Templates React InstantSearch widget File content: src/lib/component.tsx 1`] = `
+"import React from 'react';
+
+import type { ProvidedProps } from './connector'
+
+type Props = ProvidedProps & {
+ refine: () => {}
+};
+
+export const ReactInstantsearchAppComponent = ({}: Props) => {
+ return (
+
+ {/* TODO: render something */}
+
+ );
+};"
+`;
+
+exports[`Templates React InstantSearch widget File content: src/lib/connector.ts 1`] = `
+"import { createConnector } from 'react-instantsearch-dom';
+
+export type ProvidedProps = {
+ // TODO: fill props that are returned by \`getProvidedProps\`
+};
+
+export const connectReactInstantsearchApp = createConnector({
+ displayName: 'ReactInstantsearchApp',
+
+ getProvidedProps(props, searchState, searchResults): ProvidedProps {
+ return {
+ // TODO: return a props for the component
+ };
+ },
+
+ refine(props, searchState, nextRefinement) {
+ return {
+ // TODO: return a next searchState
+ };
+ },
+
+ cleanUp(props, searchState, context) {
+ return {
+ // TODO: return a searchState where this widget is removed from the widget tree
+ };
+ },
+
+ getSearchParameters(searchParameters, props, searchState) {
+ // TODO: update and return the searchParameters
+ return searchParameters;
+ },
+});"
+`;
+
+exports[`Templates React InstantSearch widget File content: src/lib/widget.tsx 1`] = `
+"import { ReactInstantsearchAppComponent } from './component';
+import { connectReactInstantsearchApp } from './connector';
+
+import type { ElementType } from 'react';
+
+type WidgetParams = {
+ /**
+ * Placeholder text for input element.
+ */
+ placeholder?: string;
+};
+
+export const ReactInstantsearchApp: ElementType =
+ connectReactInstantsearchApp(ReactInstantsearchAppComponent);"
+`;
+
+exports[`Templates React InstantSearch widget File content: tsconfig.declaration.json 1`] = `
+"{
+ \\"extends\\": \\"./tsconfig\\",
+ \\"compilerOptions\\": {
+ \\"outDir\\": \\"dist\\",
+ \\"noEmit\\": false,
+ \\"declaration\\": true,
+ \\"emitDeclarationOnly\\": true
+ },
+ \\"exclude\\": [\\"example/index.tsx\\", \\"**/__tests__\\"]
+}"
+`;
+
+exports[`Templates React InstantSearch widget File content: tsconfig.json 1`] = `
+"{
+ \\"compilerOptions\\": {
+ \\"target\\": \\"ESNext\\",
+ \\"lib\\": [\\"DOM\\", \\"DOM.Iterable\\", \\"ESNext\\"],
+ \\"types\\": [\\"vite/client\\", \\"node\\", \\"jest\\"],
+ \\"allowJs\\": false,
+ \\"skipLibCheck\\": true,
+ \\"esModuleInterop\\": false,
+ \\"allowSyntheticDefaultImports\\": true,
+ \\"strict\\": true,
+ \\"forceConsistentCasingInFileNames\\": true,
+ \\"module\\": \\"ESNext\\",
+ \\"moduleResolution\\": \\"Node\\",
+ \\"resolveJsonModule\\": true,
+ \\"isolatedModules\\": true,
+ \\"noEmit\\": true,
+ \\"jsx\\": \\"react\\"
+ },
+ \\"include\\": [\\"./src\\", \\"example/index.tsx\\"]
+}"
+`;
+
+exports[`Templates React InstantSearch widget File content: vite.config.ts 1`] = `
+"import path from 'path';
+
+import reactRefresh from '@vitejs/plugin-react-refresh';
+import vite from 'vite';
+
+// import { defineConfig } from 'vite' -> fails when type:module in package.json
+// it seems related to https://github.com/vitejs/vite/issues/1560
+// so far this is the workaround.
+const { defineConfig } = vite;
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [reactRefresh()],
+ build: {
+ lib: {
+ entry: path.resolve(__dirname, 'src/index.tsx'),
+ name: 'SampleCustom',
+ fileName: 'index',
+ formats: ['es', 'cjs', 'umd'],
+ },
+ rollupOptions: {
+ // make sure to externalize deps that shouldn't be bundled
+ // into your library
+ external: ['react', 'react-dom', 'react-instantsearch-dom'],
+ output: {
+ globals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-instantsearch-dom': 'ReactInstantSearchDOM',
+ },
+ },
+ },
+ },
+});"
+`;
+
+exports[`Templates React InstantSearch widget Folder structure: contains the right files 1`] = `
+Array [
+ ".eslintignore",
+ ".eslintrc.cjs",
+ ".gitignore",
+ ".prettierrc",
+ "README.md",
+ "babel.config.cjs",
+ "example/index.html",
+ "example/index.tsx",
+ "package.json",
+ "src/index.tsx",
+ "src/lib/__tests__/dummy-test.tsx",
+ "src/lib/component.tsx",
+ "src/lib/connector.ts",
+ "src/lib/widget.tsx",
+ "tsconfig.declaration.json",
+ "tsconfig.json",
+ "vite.config.ts",
+]
+`;
+
exports[`Templates Vue InstantSearch 1 File content: .editorconfig 1`] = `
"root = true
diff --git a/src/api/__tests__/__snapshots__/index.test.js.snap b/src/api/__tests__/__snapshots__/index.test.js.snap
index 254a0b711..f45710d93 100644
--- a/src/api/__tests__/__snapshots__/index.test.js.snap
+++ b/src/api/__tests__/__snapshots__/index.test.js.snap
@@ -6,10 +6,10 @@ exports[`Options with invalid name throws 1`] = `
- name can only contain URL-friendly characters"
`;
-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.js, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
+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.js, 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"`;
-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.js, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
+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.js, 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"`;
exports[`Options without path throws 1`] = `"The option \`path\` is required."`;
-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.js, InstantSearch Android, InstantSearch iOS, InstantSearch.js, InstantSearch.js 2, InstantSearch.js widget, JavaScript Client, JavaScript Helper, JavaScript Helper 2, React InstantSearch, React InstantSearch Native, Vue InstantSearch, Vue InstantSearch 1, Vue InstantSearch 2"`;
+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.js, 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"`;
diff --git a/src/templates/React InstantSearch widget/.eslintignore b/src/templates/React InstantSearch widget/.eslintignore
new file mode 100644
index 000000000..442f04bcc
--- /dev/null
+++ b/src/templates/React InstantSearch widget/.eslintignore
@@ -0,0 +1,2 @@
+/dist
+node_modules/
\ No newline at end of file
diff --git a/src/templates/React InstantSearch widget/.eslintrc.cjs b/src/templates/React InstantSearch widget/.eslintrc.cjs
new file mode 100644
index 000000000..90eff96ad
--- /dev/null
+++ b/src/templates/React InstantSearch widget/.eslintrc.cjs
@@ -0,0 +1,13 @@
+// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ extends: ['algolia', 'algolia/jest', 'algolia/react', 'algolia/typescript'],
+ rules: {
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ 'jest/expect-expect': 'off',
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+};
diff --git a/src/templates/React InstantSearch widget/.gitignore b/src/templates/React InstantSearch widget/.gitignore
new file mode 100644
index 000000000..d451ff16c
--- /dev/null
+++ b/src/templates/React InstantSearch widget/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/src/templates/React InstantSearch widget/.prettierrc b/src/templates/React InstantSearch widget/.prettierrc
new file mode 100644
index 000000000..833f03b62
--- /dev/null
+++ b/src/templates/React InstantSearch widget/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": true,
+ "proseWrap": "never",
+ "trailingComma": "es5"
+}
diff --git a/src/templates/React InstantSearch widget/.template.js b/src/templates/React InstantSearch widget/.template.js
new file mode 100644
index 000000000..baf8019a1
--- /dev/null
+++ b/src/templates/React InstantSearch widget/.template.js
@@ -0,0 +1,21 @@
+const install = require('../../tasks/node/install');
+const teardown = require('../../tasks/node/teardown');
+
+module.exports = {
+ category: 'Widget',
+ libraryName: 'react-instantsearch',
+ supportedVersion: '>= 6.0.0 < 7.0.0',
+ templateName: 'react-instantsearch-widget',
+ appName: 'react-instantsearch-app',
+ keywords: [
+ 'algolia',
+ 'InstantSearch',
+ 'React',
+ 'react-instantsearch',
+ 'widget',
+ ],
+ tasks: {
+ install,
+ teardown,
+ },
+};
diff --git a/src/templates/React InstantSearch widget/README.md b/src/templates/React InstantSearch widget/README.md
new file mode 100644
index 000000000..9530927a6
--- /dev/null
+++ b/src/templates/React InstantSearch widget/README.md
@@ -0,0 +1,96 @@
+# {{name}}
+
+_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
+
+{{ description }}
+
+## Install
+
+```bash
+npm install {{ packageName }}
+# or
+yarn add {{ packageName }}
+```
+
+## Widget
+
+### Usage
+
+```jsx
+import instantsearch from 'instantsearch.js';
+import algoliasearch from 'algoliasearch/lite';
+import { {{ pascalCaseName }} } from '{{ packageName }}';
+
+const searchClient = algoliasearch('appId', 'apiKey');
+
+const App = () => (
+
+ <{{ pascalCaseName }} />
+
+);
+```
+
+## Connector
+
+### Usage
+
+```jsx
+import { connect{{ pascalCaseName }} } from '{{ packageName }}';
+
+// 1. Create a render function
+const Render{{ pascalCaseName }} = (renderOptions, isFirstRender) => {
+ // Rendering logic
+};
+
+// 2. Create the custom widget
+const Custom{{ pascalCaseName }} = connect{{ pascalCaseName }}(
+ Render{{ pascalCaseName }}
+);
+
+// 3. Instantiate
+const App = () => (
+
+
+
+);
+```
+
+## Test
+
+```bash
+npm test
+# or
+yarn test
+```
+
+## Build
+
+```bash
+npm run build
+# or
+yarn build
+```
+
+## Release
+
+```bash
+npm run release
+# or
+yarn release
+```
+
+### First Release
+
+```bash
+npm run release -- --first-release
+# or
+yarn release --first-release
+```
+
+This will tag a release without bumping the version.
+
+When you are ready, push the git tag and run `npm publish`.
+
+If you want to publish it as a public scoped package, run `npm publish --access public` the first time.
+
+[To know more about `standard-version`, read this →](https://github.com/conventional-changelog/standard-version#cli-usage)
diff --git a/src/templates/React InstantSearch widget/babel.config.cjs b/src/templates/React InstantSearch widget/babel.config.cjs
new file mode 100644
index 000000000..bd1afc1f0
--- /dev/null
+++ b/src/templates/React InstantSearch widget/babel.config.cjs
@@ -0,0 +1,8 @@
+// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ presets: [
+ ['@babel/preset-env', { targets: { node: 'current' } }],
+ '@babel/preset-react',
+ '@babel/preset-typescript',
+ ],
+};
diff --git a/src/templates/React InstantSearch widget/example/index.html.hbs b/src/templates/React InstantSearch widget/example/index.html.hbs
new file mode 100644
index 000000000..1d43b3dd1
--- /dev/null
+++ b/src/templates/React InstantSearch widget/example/index.html.hbs
@@ -0,0 +1,12 @@
+
+
+
+
+
+ {{ packageName }} | example
+
+
+
+
+
+
diff --git a/src/templates/React InstantSearch widget/example/index.tsx.hbs b/src/templates/React InstantSearch widget/example/index.tsx.hbs
new file mode 100644
index 000000000..801ad9a1f
--- /dev/null
+++ b/src/templates/React InstantSearch widget/example/index.tsx.hbs
@@ -0,0 +1,22 @@
+import algoliasearch from 'algoliasearch/lite';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-dom';
+
+import { {{ pascalCaseName }} } from '../src';
+
+const searchClient = algoliasearch(
+ 'latency',
+ '6be0576ff61c053d5f9a3225e2a90f76'
+);
+
+ReactDOM.render(
+
+
+ <{{ pascalCaseName }} />
+
+
+
+ ,
+ document.getElementById('root')
+);
diff --git a/src/templates/React InstantSearch widget/package.json.hbs b/src/templates/React InstantSearch widget/package.json.hbs
new file mode 100644
index 000000000..47c653bf6
--- /dev/null
+++ b/src/templates/React InstantSearch widget/package.json.hbs
@@ -0,0 +1,80 @@
+{
+ "name": "{{ packageName }}",
+ "version": "1.0.0",
+ "description": "{{ description }}",
+ "keywords": [
+ "instantsearch-widget",
+ "instantsearch",
+ "react-instantsearch",
+ "react-instantsearch-widget-{{ name }}",
+ "widget",
+ "connector",
+ "algolia"
+ ],
+ "license": "MIT",
+ "main": "./dist/index.cjs.js",
+ "module": "./dist/index.es.js",
+ "exports": {
+ "import": "./dist/index.es.js",
+ "require": "./dist/index.cjs.js"
+ },
+ "jsdelivr": "./dist/index.umd.js",
+ "unpkg": "./dist/index.umd.js",
+ "types": "./dist/index.d.ts",
+ "type": "module",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "start": "vite example",
+ "prebuild": "rm -rf dist",
+ "build": "vite build && npm run build:types",
+ "build:types": "tsc -p tsconfig.declaration.json",
+ "lint": "eslint --ext .js,.ts,.tsx .",
+ "lint:fix": "npm run lint --fix",
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
+ "test:watch": "npm test -- --watch",
+ "test:types": "tsc",
+ "prerelease": "npm run build",
+ "release": "standard-version"
+ },
+ "dependencies": {
+ "algoliasearch": "^4.9.1",
+ "react": "^16.8.0",
+ "react-dom": "^16.8.0",
+ "react-instantsearch-dom": "^6.11.0"
+ },
+ "devDependencies": {
+ "@babel/core": "7.14.3",
+ "@babel/preset-env": "7.14.2",
+ "@babel/preset-react": "7.13.13",
+ "@babel/preset-typescript": "7.13.0",
+ "@testing-library/react": "11.2.7",
+ "@types/jest": "26.0.23",
+ "@types/node": "15.3.0",
+ "@types/react": "17.0.0",
+ "@types/react-dom": "17.0.0",
+ "@types/react-instantsearch-dom": "6.10.0",
+ "@typescript-eslint/eslint-plugin": "4.24.0",
+ "@typescript-eslint/parser": "4.24.0",
+ "@vitejs/plugin-react-refresh": "1.3.1",
+ "babel-eslint": "10.1.0",
+ "babel-jest": "26.6.3",
+ "eslint": "7.26.0",
+ "eslint-config-algolia": "18.0.0",
+ "eslint-config-prettier": "8.3.0",
+ "eslint-plugin-eslint-comments": "3.2.0",
+ "eslint-plugin-import": "2.23.2",
+ "eslint-plugin-jest": "24.3.6",
+ "eslint-plugin-jsdoc": "34.8.1",
+ "eslint-plugin-jsx-a11y": "6.4.1",
+ "eslint-plugin-prettier": "3.4.0",
+ "eslint-plugin-react": "7.23.2",
+ "eslint-plugin-react-hooks": "4.2.0",
+ "jest": "26.6.3",
+ "prettier": "2.3.0",
+ "standard-version": "9.3.0",
+ "typescript": "4.2.4",
+ "vite": "2.3.3"
+ }
+}
diff --git a/src/templates/React InstantSearch widget/src/index.tsx.hbs b/src/templates/React InstantSearch widget/src/index.tsx.hbs
new file mode 100644
index 000000000..71bcdfd1c
--- /dev/null
+++ b/src/templates/React InstantSearch widget/src/index.tsx.hbs
@@ -0,0 +1,3 @@
+export { connect{{ pascalCaseName }} } from './lib/connector';
+export { {{ pascalCaseName }}Component } from './lib/component';
+export { {{ pascalCaseName }} } from './lib/widget';
diff --git a/src/templates/React InstantSearch widget/src/lib/__tests__/dummy-test.tsx.hbs b/src/templates/React InstantSearch widget/src/lib/__tests__/dummy-test.tsx.hbs
new file mode 100644
index 000000000..822fc68d4
--- /dev/null
+++ b/src/templates/React InstantSearch widget/src/lib/__tests__/dummy-test.tsx.hbs
@@ -0,0 +1,39 @@
+import { render } from '@testing-library/react';
+import React from 'react';
+import { InstantSearch, Hits } from 'react-instantsearch-dom';
+
+import { {{ pascalCaseName }} } from '../widget';
+
+const runAllMicroTasks = (): Promise => new Promise(setImmediate);
+
+describe('nothing', () => {
+ it('tests nothing', async () => {
+ const searchClient = {
+ search(_requests: any[]) {
+ return Promise.resolve({
+ results: [
+ {
+ hits:
+ [
+ {
+ objectID: 'a',
+ name: 'test',
+ },
+ ],
+ },
+ ],
+ });
+ },
+ };
+
+ const { debug } = render(
+
+ <{{ pascalCaseName }} />
+ hit.name} />
+
+ );
+
+ await runAllMicroTasks();
+ debug();
+ });
+});
diff --git a/src/templates/React InstantSearch widget/src/lib/component.tsx.hbs b/src/templates/React InstantSearch widget/src/lib/component.tsx.hbs
new file mode 100644
index 000000000..c8a3dcf57
--- /dev/null
+++ b/src/templates/React InstantSearch widget/src/lib/component.tsx.hbs
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import type { ProvidedProps } from './connector'
+
+type Props = ProvidedProps & {
+ refine: () => {}
+};
+
+export const {{ pascalCaseName }}Component = ({}: Props) => {
+ return (
+
+ {/* TODO: render something */}
+
+ );
+};
diff --git a/src/templates/React InstantSearch widget/src/lib/connector.ts.hbs b/src/templates/React InstantSearch widget/src/lib/connector.ts.hbs
new file mode 100644
index 000000000..964227b08
--- /dev/null
+++ b/src/templates/React InstantSearch widget/src/lib/connector.ts.hbs
@@ -0,0 +1,32 @@
+import { createConnector } from 'react-instantsearch-dom';
+
+export type ProvidedProps = {
+ // TODO: fill props that are returned by `getProvidedProps`
+}
+
+export const connect{{ pascalCaseName }} = createConnector({
+ displayName: '{{ pascalCaseName }}',
+
+ getProvidedProps(props, searchState, searchResults): ProvidedProps {
+ return {
+ // TODO: return a props for the component
+ };
+ },
+
+ refine(props, searchState, nextRefinement) {
+ return {
+ // TODO: return a next searchState
+ };
+ },
+
+ cleanUp(props, searchState, context) {
+ return {
+ // TODO: return a searchState where this widget is removed from the widget tree
+ };
+ },
+
+ getSearchParameters(searchParameters, props, searchState) {
+ // TODO: update and return the searchParameters
+ return searchParameters;
+ },
+});
diff --git a/src/templates/React InstantSearch widget/src/lib/widget.tsx.hbs b/src/templates/React InstantSearch widget/src/lib/widget.tsx.hbs
new file mode 100644
index 000000000..f8251fb1b
--- /dev/null
+++ b/src/templates/React InstantSearch widget/src/lib/widget.tsx.hbs
@@ -0,0 +1,14 @@
+import { {{ pascalCaseName }}Component } from './component';
+import { connect{{ pascalCaseName }} } from './connector';
+
+import type { ElementType } from 'react';
+
+type WidgetParams = {
+ /**
+ * Placeholder text for input element.
+ */
+ placeholder?: string;
+};
+
+export const {{ pascalCaseName }}: ElementType =
+ connect{{ pascalCaseName }}({{ pascalCaseName }}Component);
diff --git a/src/templates/React InstantSearch widget/tsconfig.declaration.json b/src/templates/React InstantSearch widget/tsconfig.declaration.json
new file mode 100644
index 000000000..4469e2657
--- /dev/null
+++ b/src/templates/React InstantSearch widget/tsconfig.declaration.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "outDir": "dist",
+ "noEmit": false,
+ "declaration": true,
+ "emitDeclarationOnly": true
+ },
+ "exclude": ["example/index.tsx", "**/__tests__"]
+}
diff --git a/src/templates/React InstantSearch widget/tsconfig.json b/src/templates/React InstantSearch widget/tsconfig.json
new file mode 100644
index 000000000..116a34574
--- /dev/null
+++ b/src/templates/React InstantSearch widget/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "types": ["vite/client", "node", "jest"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ },
+ "include": ["./src", "example/index.tsx"]
+}
diff --git a/src/templates/React InstantSearch widget/vite.config.ts b/src/templates/React InstantSearch widget/vite.config.ts
new file mode 100644
index 000000000..a29da738d
--- /dev/null
+++ b/src/templates/React InstantSearch widget/vite.config.ts
@@ -0,0 +1,34 @@
+import path from 'path';
+
+import reactRefresh from '@vitejs/plugin-react-refresh';
+import vite from 'vite';
+
+// import { defineConfig } from 'vite' -> fails when type:module in package.json
+// it seems related to https://github.com/vitejs/vite/issues/1560
+// so far this is the workaround.
+const { defineConfig } = vite;
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [reactRefresh()],
+ build: {
+ lib: {
+ entry: path.resolve(__dirname, 'src/index.tsx'),
+ name: 'SampleCustom',
+ fileName: 'index',
+ formats: ['es', 'cjs', 'umd'],
+ },
+ rollupOptions: {
+ // make sure to externalize deps that shouldn't be bundled
+ // into your library
+ external: ['react', 'react-dom', 'react-instantsearch-dom'],
+ output: {
+ globals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-instantsearch-dom': 'ReactInstantSearchDOM',
+ },
+ },
+ },
+ },
+});