Skip to content

Commit 39a7bcf

Browse files
committed
feat(specs): add playground
goal of this is to replace storybook at term, once it's all working as we expect. What's currently done: An example done for Breadcrumb using Sandpack for all flavours, using npm/codesandbox to get the package contents What still should be done - fix ssr styling (somehow dark first) - local package (and built package for site) - some styling: - Maybe tabs for the examples - actually differentiate between "focused widget" and the regular app - "full screen mode" - see if we really need only one example per widget, otherwise design for multiple examples
1 parent fd5dc5b commit 39a7bcf

File tree

7 files changed

+802
-26
lines changed

7 files changed

+802
-26
lines changed

specs/astro.config.mjs

+11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import react from '@astrojs/react';
12
import { defineConfig } from 'astro/config';
23

34
export default defineConfig({
45
site: 'https://instantsearchjs.netlify.app/',
56
base: '/specs',
67
outDir: '../website/specs',
8+
integrations: [react()],
9+
vite: {
10+
ssr: {
11+
noExternal: [
12+
'@codesandbox/sandpack-react',
13+
'@codesandbox/sandpack-themes',
14+
'@codesandbox/sandpack-client',
15+
],
16+
},
17+
},
718
});

specs/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
"devDependencies": {
2121
"@types/node": "18.11.13",
2222
"astro": "4.0.4",
23+
"@astrojs/react": "3.0.8",
2324
"instantsearch.css": "8.1.0",
24-
"sass": "1.56.2"
25+
"sass": "1.56.2",
26+
"@codesandbox/sandpack-react": "2.10.0",
27+
"@codesandbox/sandpack-themes": "2.0.21",
28+
"dedent": "1.5.1"
2529
}
2630
}

specs/src/components/Sandbox.tsx

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/* eslint-disable react/react-in-jsx-scope */
2+
import { Sandpack } from '@codesandbox/sandpack-react';
3+
import { githubLight } from '@codesandbox/sandpack-themes';
4+
import dedent from 'dedent';
5+
6+
const settings = {
7+
js: {
8+
html: /* HTML */ `
9+
<style>
10+
body {
11+
font-family: sans-serif;
12+
}
13+
</style>
14+
<div id="custom"></div>
15+
<div id="searchbox"></div>
16+
<div id="hits"></div>
17+
`,
18+
preamble: /* JS */ `
19+
import 'instantsearch.css/themes/satellite-min.css';
20+
import algoliasearch from 'algoliasearch/lite';
21+
import instantsearch from 'instantsearch.js';
22+
import { history } from 'instantsearch.js/es/lib/routers';
23+
import { searchBox, hits } from 'instantsearch.js/es/widgets';
24+
import { createWidgets } from './widget.ts';
25+
26+
const search = instantsearch({
27+
indexName: 'instant_search',
28+
searchClient: algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76'),
29+
future: {
30+
preserveSharedStateOnUnmount: true,
31+
},
32+
routing: {
33+
router: history({
34+
cleanUrlOnDispose: false,
35+
})
36+
}
37+
});
38+
39+
search.addWidgets([
40+
...createWidgets(document.querySelector('#custom')),
41+
searchBox({
42+
container: '#searchbox',
43+
}),
44+
hits({
45+
container: '#hits',
46+
templates: {
47+
item: (hit, { components }) => components.Highlight({ attribute: 'name', hit }),
48+
},
49+
}),
50+
]);
51+
52+
search.start();
53+
`,
54+
dependencies: {
55+
// TODO: use current version somehow, both locally and in the built website
56+
'instantsearch.js': 'latest',
57+
'instantsearch.css': 'latest',
58+
algoliasearch: 'latest',
59+
},
60+
filename: '/widget.ts',
61+
},
62+
react: {
63+
html: /* HTML */ `
64+
<style>
65+
body {
66+
font-family: sans-serif;
67+
}
68+
</style>
69+
<main id="root"></main>
70+
`,
71+
preamble: /* TSX */ `
72+
import 'instantsearch.css/themes/satellite-min.css';
73+
import React from "react";
74+
import { createRoot } from "react-dom/client";
75+
import algoliasearch from "algoliasearch/lite";
76+
import { history } from "instantsearch.js/es/lib/routers";
77+
import { InstantSearch, SearchBox, Hits, Highlight } from "react-instantsearch";
78+
import { widgets } from "./widget.tsx";
79+
80+
createRoot(document.getElementById('root')).render(
81+
<InstantSearch
82+
indexName="instant_search"
83+
searchClient={algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76')}
84+
future={{
85+
preserveSharedStateOnUnmount: true,
86+
}}
87+
routing={{
88+
router: history({
89+
cleanUrlOnDispose: false,
90+
})
91+
}}
92+
>
93+
{widgets}
94+
<SearchBox />
95+
<Hits hitComponent={Hit}/>
96+
</InstantSearch>
97+
);
98+
99+
function Hit({ hit }) {
100+
return <Highlight hit={hit} attribute="name" />;
101+
}
102+
`,
103+
dependencies: {
104+
react: 'latest',
105+
'react-dom': 'latest',
106+
algoliasearch: 'latest',
107+
'instantsearch.css': 'latest',
108+
'react-instantsearch': 'latest',
109+
},
110+
filename: '/widget.tsx',
111+
},
112+
vue: {
113+
html: /* HTML */ `
114+
<style>
115+
body {
116+
font-family: sans-serif;
117+
}
118+
</style>
119+
<main id="app"></main>
120+
`,
121+
preamble: `
122+
import "instantsearch.css/themes/satellite-min.css";
123+
import Vue from "vue";
124+
import algoliasearch from "algoliasearch/lite";
125+
import { history } from "instantsearch.js/es/lib/routers";
126+
import { AisInstantSearch, AisHits, AisSearchBox } from "vue-instantsearch/vue2/es";
127+
import Widget from "./Widget.vue";
128+
129+
Vue.config.productionTip = false;
130+
131+
new Vue({
132+
render: (h) =>
133+
h(
134+
AisInstantSearch,
135+
{
136+
props: {
137+
searchClient: algoliasearch(
138+
"latency",
139+
"6be0576ff61c053d5f9a3225e2a90f76"
140+
),
141+
indexName: "instant_search",
142+
future: {
143+
preserveSharedStateOnUnmount: true,
144+
},
145+
routing: {
146+
router: history({
147+
cleanUrlOnDispose: false,
148+
})
149+
}
150+
},
151+
},
152+
[h(Widget), h(AisSearchBox), h(AisHits)]
153+
),
154+
}).$mount("#app");
155+
`,
156+
dependencies: {
157+
vue: '2',
158+
algoliasearch: 'latest',
159+
'instantsearch.css': 'latest',
160+
'vue-instantsearch': 'latest',
161+
},
162+
filename: '/Widget.vue',
163+
},
164+
};
165+
166+
export default function Sandbox({
167+
code,
168+
flavor,
169+
}: {
170+
code: string;
171+
flavor: 'react' | 'js' | 'vue';
172+
}) {
173+
const { preamble, html, filename, dependencies } = settings[flavor];
174+
return (
175+
<Sandpack
176+
files={{
177+
'/index.html': {
178+
hidden: true,
179+
code: dedent(html),
180+
},
181+
'/index.js': {
182+
code: dedent(preamble),
183+
},
184+
[filename]: {
185+
code,
186+
},
187+
}}
188+
customSetup={{
189+
dependencies,
190+
entry: '/index.js',
191+
}}
192+
options={{
193+
activeFile: filename,
194+
showNavigator: true,
195+
}}
196+
theme={githubLight}
197+
/>
198+
);
199+
}

specs/src/components/WidgetContent.astro

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import { Code } from 'astro/components';
33
import type { WidgetFrontmatter } from '../types';
44
import ThemeSelector from './ThemeSelector.astro';
5+
import Sandbox from './Sandbox.jsx';
6+
import { getSandpackCssText } from "@codesandbox/sandpack-react";
7+
58
69
type Props = {
710
frontmatter: WidgetFrontmatter;
@@ -23,7 +26,8 @@ const title = frontmatter.title;
2326
{
2427
frontmatter.info && (
2528
<div class="info">
26-
<p>{frontmatter.info}</p>
29+
{' '}
30+
<p>{frontmatter.info}</p>{' '}
2731
</div>
2832
)
2933
}
@@ -110,6 +114,22 @@ const title = frontmatter.title;
110114
)
111115
}
112116

117+
{frontmatter.examples ? (
118+
<>
119+
<h3 id="example">Usage</h3>
120+
<!-- TODO: theme is implied dark? -->
121+
<style is:inline set:html={getSandpackCssText()}></style>
122+
{frontmatter.examples.map(({ code, flavor, library }) => (
123+
<div class="example">
124+
<h4>{library}</h4>
125+
<div class="code-output">
126+
<Sandbox code={code} flavor={flavor} client:load />
127+
</div>
128+
</div>
129+
))}
130+
</>
131+
) : null}
132+
113133
<h3 id="css-classes">CSS classes</h3>
114134
{
115135
frontmatter.classes ? (

specs/src/env.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
/// <reference path="../.astro/types.d.ts" />
12
// / <reference types="astro/client" />

specs/src/pages/widgets/breadcrumb.md

+53
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,57 @@ translations:
5050
- name: separatorText
5151
default: '" > "'
5252
description: The text for the breadcrumb’s separator.
53+
examples:
54+
- flavor: js
55+
library: instantsearch.js
56+
code: |
57+
import { breadcrumb } from 'instantsearch.js/es/widgets';
58+
59+
export const createWidgets = (container) => [
60+
breadcrumb({
61+
container,
62+
attributes: [
63+
'hierarchicalCategories.lvl0',
64+
'hierarchicalCategories.lvl1',
65+
'hierarchicalCategories.lvl2',
66+
],
67+
}),
68+
];
69+
- flavor: react
70+
library: react-instantsearch
71+
code: |
72+
import React from 'react';
73+
import { Breadcrumb } from 'react-instantsearch';
74+
75+
export const widgets = (
76+
<Breadcrumb
77+
attributes={[
78+
'hierarchicalCategories.lvl0',
79+
'hierarchicalCategories.lvl1',
80+
'hierarchicalCategories.lvl2',
81+
]}
82+
/>
83+
);
84+
- flavor: vue
85+
library: vue-instantsearch
86+
code: |
87+
<template>
88+
<ais-breadcrumb
89+
:attributes="[
90+
'hierarchicalCategories.lvl0',
91+
'hierarchicalCategories.lvl1',
92+
'hierarchicalCategories.lvl2',
93+
]"
94+
/>
95+
</template>
96+
97+
<script>
98+
import { AisBreadcrumb } from 'vue-instantsearch';
99+
100+
export default {
101+
components: {
102+
AisBreadcrumb,
103+
},
104+
};
105+
</script>
53106
---

0 commit comments

Comments
 (0)