Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions specs/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import react from '@astrojs/react';
import { defineConfig } from 'astro/config';

export default defineConfig({
site: 'https://instantsearchjs.netlify.app/',
base: '/specs',
outDir: '../website/specs',
integrations: [react()],
vite: {
ssr: {
noExternal: [
'@codesandbox/sandpack-react',
'@codesandbox/sandpack-themes',
'@codesandbox/sandpack-client',
],
},
},
});
9 changes: 8 additions & 1 deletion specs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@
"prepare": "rm -rf public && mkdir public && cp -r ../node_modules/instantsearch.css/themes public/themes"
},
"devDependencies": {
"@types/node": "18.11.13",
"@types/node": "20.10.0",
"@codesandbox/sandpack-react": "2.19.1",
"@codesandbox/sandpack-themes": "2.0.21",
"dedent": "1.5.1",
"astro": "1.6.14",
"@astrojs/react": "1.2.2",
"instantsearch.css": "8.5.0",
"instantsearch.js": "4.74.0",
"react-instantsearch": "7.13.0",
"vue-instantsearch": "4.19.3",
"sass": "1.56.2"
}
}
207 changes: 207 additions & 0 deletions specs/src/components/Sandbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/* eslint-disable react/react-in-jsx-scope */
import { Sandpack } from '@codesandbox/sandpack-react';
import { githubLight } from '@codesandbox/sandpack-themes';
import dedent from 'dedent';

const settings = {
js: {
html: /* HTML */ `
<style>
body {
font-family: sans-serif;
}
</style>
<div id="custom"></div>
<hr />
<div id="searchbox"></div>
<div id="hits"></div>
`,
preamble: /* JS */ `
import 'instantsearch.css/themes/satellite-min.css';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import instantsearch from 'instantsearch.js';
import { history } from 'instantsearch.js/es/lib/routers';
import { searchBox, hits } from 'instantsearch.js/es/widgets';
import { createWidgets } from './widget.ts';

const search = instantsearch({
indexName: 'instant_search',
searchClient: algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76'),
future: {
preserveSharedStateOnUnmount: true,
},
routing: {
router: history({
cleanUrlOnDispose: false,
}),
},
});

search.addWidgets([
...createWidgets(() =>
document.querySelector('#custom').appendChild(document.createElement('div'))
),
searchBox({
container: '#searchbox',
}),
hits({
container: '#hits',
templates: {
item: (hit, { components }) =>
components.Highlight({ attribute: 'name', hit }),
},
}),
]);

search.start();
`,
dependencies: {
algoliasearch: '5.1.1',
},
filename: '/widget.ts',
},
react: {
html: /* HTML */ `
<style>
body {
font-family: sans-serif;
}
</style>
<main id="root"></main>
`,
preamble: /* TSX */ `
import 'instantsearch.css/themes/satellite-min.css';
import React from "react";
import { createRoot } from "react-dom/client";
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { history } from "instantsearch.js/es/lib/routers";
import { InstantSearch, SearchBox, Hits, Highlight } from "react-instantsearch";
import { widgets } from "./widget.tsx";

createRoot(document.getElementById('root')).render(
<InstantSearch
indexName="instant_search"
searchClient={algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76')}
future={{
preserveSharedStateOnUnmount: true,
}}
routing={{
router: history({
cleanUrlOnDispose: false,
})
}}
>
{widgets}
<hr />
<SearchBox />
<Hits hitComponent={Hit}/>
</InstantSearch>
);

function Hit({ hit }) {
return <Highlight hit={hit} attribute="name" />;
}
`,
dependencies: {
react: '18.2.0',
'react-dom': '18.2.0',
algoliasearch: '5.1.1',
},
filename: '/widget.tsx',
},
vue: {
html: /* HTML */ `
<style>
body {
font-family: sans-serif;
}
</style>
<main id="app"></main>
`,
preamble: `
import "instantsearch.css/themes/satellite-min.css";
import Vue from "vue";
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { history } from "instantsearch.js/es/lib/routers";
import { AisInstantSearch, AisHits, AisSearchBox } from "vue-instantsearch/vue2/es";
import Widget from "./Widget.vue";

Vue.config.productionTip = false;

new Vue({
render: (h) =>
h(
AisInstantSearch,
{
props: {
searchClient: algoliasearch(
"latency",
"6be0576ff61c053d5f9a3225e2a90f76"
),
indexName: "instant_search",
future: {
preserveSharedStateOnUnmount: true,
},
routing: {
router: history({
cleanUrlOnDispose: false,
})
}
},
},
[h(Widget), h('hr'), h(AisSearchBox), h(AisHits)]
),
}).$mount("#app");
`,
dependencies: {
vue: '2.7.14',
algoliasearch: '5.1.1',
},
filename: '/Widget.vue',
},
};

export default function Sandbox({
code,
flavor,
modules,
}: {
code: string;
flavor: 'react' | 'js' | 'vue';
modules: {
files: Record<string, string>;
dependencies: Record<string, string>;
};
}) {
const { preamble, html, filename, dependencies } = settings[flavor];
return (
<Sandpack
files={{
'/index.html': {
hidden: true,
code: dedent(html),
},
'/index.js': {
code: dedent(preamble),
},
[filename]: {
code,
},
...modules.files,
}}
customSetup={{
dependencies: {
...dependencies,
...modules.dependencies,
},
entry: '/index.js',
}}
options={{
editorHeight: 500,
activeFile: filename,
showNavigator: true,
}}
theme={githubLight}
/>
);
}
32 changes: 31 additions & 1 deletion specs/src/components/WidgetContent.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import { Code } from 'astro/components';
import type { WidgetFrontmatter } from '../types';
import ThemeSelector from './ThemeSelector.astro';
import Sandbox from './Sandbox.jsx';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
import getFiles from '../getDependencyContent';

const modules = await getFiles();

type Props = {
frontmatter: WidgetFrontmatter;
Expand All @@ -23,7 +28,8 @@ const title = frontmatter.title;
{
frontmatter.info && (
<div class="info">
<p>{frontmatter.info}</p>
{' '}
<p>{frontmatter.info}</p>{' '}
</div>
)
}
Expand Down Expand Up @@ -110,6 +116,30 @@ const title = frontmatter.title;
)
}

{
frontmatter.examples ? (
<>
<h3 id="example">Usage</h3>
<style id="sandpack" is:inline set:html={getSandpackCssText()} />
{frontmatter.examples.map(({ code, flavor, library }) => (
<details class="example">
<summary style="cursor: pointer">
<h4 style="display: inline-block; margin: 0;">{library}</h4>
</summary>
<div class="code-output">
<Sandbox
code={code}
flavor={flavor}
modules={modules[flavor]}
client:load
/>
</div>
</details>
))}
</>
) : null
}

<h3 id="css-classes">CSS classes</h3>
{
frontmatter.classes ? (
Expand Down
1 change: 1 addition & 0 deletions specs/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
// / <reference types="astro/client" />
92 changes: 92 additions & 0 deletions specs/src/getDependencyContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';

const cache = new Map();
async function getFilesFromDir(dir: string) {
if (cache.has(dir)) {
return cache.get(dir);
}
const output = (
await readdir(dir, {
withFileTypes: true,
recursive: true,
})
)
.filter((file) => file.isFile())
.map((file) => join(file.path.replace('../packages/', ''), file.name));
cache.set(dir, output);
return output;
}

async function getFiles(flavor: 'react' | 'js' | 'vue') {
const localDependencies = [
'instantsearch.css',
'instantsearch-ui-components',
'instantsearch.js',
flavor === 'react' && 'react-instantsearch',
flavor === 'react' && 'react-instantsearch-core',
flavor === 'vue' && 'vue-instantsearch',
].filter(Boolean);

return {
files: Object.fromEntries(
await Promise.all(
[
'instantsearch.css/package.json',
...(await getFilesFromDir('../packages/instantsearch.css/themes')),
'instantsearch-ui-components/package.json',
...(await getFilesFromDir(
'../packages/instantsearch-ui-components/dist'
)),
'instantsearch.js/package.json',
...(await getFilesFromDir('../packages/instantsearch.js/es')),
...(flavor === 'react'
? await getFilesFromDir('../packages/instantsearch.js/cjs')
: []),
flavor === 'react' && 'react-instantsearch/package.json',
...(flavor === 'react'
? await getFilesFromDir('../packages/react-instantsearch/dist')
: []),
flavor === 'react' && 'react-instantsearch-core/package.json',
...(flavor === 'react'
? await getFilesFromDir('../packages/react-instantsearch-core/dist')
: []),
flavor === 'vue' && 'vue-instantsearch/package.json',
...(flavor === 'vue'
? await getFilesFromDir('../packages/vue-instantsearch/vue2/es')
: []),
]
.filter(Boolean)
.map(async (file) => [
`/node_modules/${file}`,
{
code: await readFile(`../packages/${file}`, 'utf-8'),
hidden: true,
},
])
)
),
dependencies: Object.assign(
{},
...(await Promise.all(
localDependencies.map(async (pkg) =>
Object.fromEntries(
Object.entries(
JSON.parse(
await readFile(`../packages/${pkg}/package.json`, 'utf-8')
).dependencies || {}
).filter(([key]) => !localDependencies.includes(key))
)
)
))
),
};
}

export default async function getDependencyContent() {
return {
react: await getFiles('react'),
js: await getFiles('js'),
vue: await getFiles('vue'),
};
}
Loading