Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This is a monorepo containing multiple standalone projects. Each project lives i
code-samples/
├── typesense-astro-search/ # Astro + Typesense search implementation
├── typesense-next-search-bar/ # Next.js + Typesense search implementation
├── typesense-solid-js-search/ # SolidJS + Typesense search implementation
├── typesense-vanilla-js-search/ # Vanilla JS + Typesense search implementation
├── ... # More projects coming soon
└── README.md # You are here
Expand All @@ -21,6 +22,7 @@ code-samples/
| -------------------------------------------------------------- | ---------- | ---------------------------------------------------- |
| [typesense-astro-search](./typesense-astro-search) | Astro | A modern search bar with instant search capabilities |
| [typesense-next-search-bar](./typesense-next-search-bar) | Next.js | A modern search bar with instant search capabilities |
| [typesense-solid-js-search](./typesense-solid-js-search) | SolidJS | A modern search bar with instant search capabilities |
| [typesense-vanilla-js-search](./typesense-vanilla-js-search) | Vanilla JS | A modern search bar with instant search capabilities |

## Getting Started
Expand Down
4 changes: 4 additions & 0 deletions typesense-solid-js-search/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VITE_TYPESENSE_API_KEY=xyz
VITE_TYPESENSE_HOST=localhost
VITE_TYPESENSE_PORT=8108
VITE_TYPESENSE_PROTOCOL=http
81 changes: 81 additions & 0 deletions typesense-solid-js-search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# SolidJS Search Bar with Typesense

A modern search bar application built with SolidJS and Typesense, featuring instant search capabilities.

## Tech Stack

- SolidJS
- Typesense
- typesense-instantsearch-adapter & instantsearch.js

## Prerequisites

- Node.js 18+ and npm 9+.
- Docker (for running Typesense locally). Alternatively, you can use a Typesense Cloud cluster.
- Basic knowledge of SolidJS.

## Quick Start

### 1. Clone the repository

```bash
git clone https://github.com/typesense/code-samples.git
cd typesense-solid-js-search
```

### 2. Install dependencies

```bash
npm install
```

### 3. Set up environment variables

Create a `.env` file in the project root with the following content:

```env
VITE_TYPESENSE_API_KEY=xyz
VITE_TYPESENSE_HOST=localhost
VITE_TYPESENSE_PORT=8108
VITE_TYPESENSE_PROTOCOL=http
```

### 4. Project Structure

```text
├── src
│ ├── components
│ │ ├── BookCard.tsx
│ │ ├── BookList.tsx
│ │ ├── BookSearch.tsx
│ │ ├── Heading.tsx
│ │ └── icons.tsx
│ ├── types
│ │ └── Book.ts
│ ├── utils
│ │ └── typesense.ts
│ ├── App.tsx
│ ├── index.css
│ └── index.tsx
├── index.html
└── package.json
```

### 5. Start the development server

```bash
npm run dev
```

Open [http://localhost:5173](http://localhost:5173) in your browser.

### 6. Deployment

Set env variables to point the app to the Typesense Cluster:

```env
VITE_TYPESENSE_API_KEY=xxx
VITE_TYPESENSE_HOST=xxx.typesense.net
VITE_TYPESENSE_PORT=443
VITE_TYPESENSE_PROTOCOL=https
```
13 changes: 13 additions & 0 deletions typesense-solid-js-search/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Typesense Solid.js Search</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions typesense-solid-js-search/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "typesense-solid-js-search",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"instantsearch.js": "^4.86.1",
"solid-js": "^1.9.10",
"typesense": "^2.1.0",
"typesense-instantsearch-adapter": "^2.9.0"
},
"devDependencies": {
"@types/node": "^24.10.1",
"typescript": "~5.9.3",
"vite": "^7.2.4",
"vite-plugin-solid": "^2.11.10"
}
}
Binary file added typesense-solid-js-search/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions typesense-solid-js-search/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-weight: 400;
}
14 changes: 14 additions & 0 deletions typesense-solid-js-search/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "./App.css";
import Heading from "./components/Heading";
import { BookSearch } from "./components/BookSearch";

function App() {
return (
<>
<Heading />
<BookSearch />
</>
);
}

export default App;
73 changes: 73 additions & 0 deletions typesense-solid-js-search/src/components/BookCard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.bookCard {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}

.bookCard:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.bookImageContainer {
width: 100%;
height: 250px;
overflow: hidden;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}

.bookImage {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
}

.bookInfo {
padding: 1rem;
}

.bookTitle {
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
margin: 0 0 0.5rem;
line-height: 1.3;
}

.bookAuthor {
color: #7f8c8d;
font-size: 0.9rem;
margin: 0 0 0.5rem;
}

.ratingContainer {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}

.starRating {
color: #f39c12;
font-size: 0.9rem;
}

.ratingText {
color: #95a5a6;
font-size: 0.8rem;
}

.bookYear {
color: #95a5a6;
font-size: 0.85rem;
margin: 0;
}
42 changes: 42 additions & 0 deletions typesense-solid-js-search/src/components/BookCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styles from "./BookCard.module.css";
import type { Book } from "../types/Book";

interface BookCardProps {
book: Book;
}

export function BookCard(props: BookCardProps) {
const stars = "★".repeat(Math.round(props.book.average_rating || 0));

return (
<div class={styles.bookCard}>
{props.book.image_url && (
<div class={styles.bookImageContainer}>
<img
src={props.book.image_url}
alt={`Cover of ${props.book.title}`}
class={styles.bookImage}
/>
</div>
)}
<div class={styles.bookInfo}>
<h3 class={styles.bookTitle}>{props.book.title}</h3>
<p class={styles.bookAuthor}>
{props.book.authors?.join(", ") || "Unknown Author"}
</p>
<div class={styles.ratingContainer}>
<span class={styles.starRating}>{stars}</span>
<span class={styles.ratingText}>
{props.book.average_rating?.toFixed(1) || "0"} (
{props.book.ratings_count?.toLocaleString() || 0} ratings)
</span>
</div>
{props.book.publication_year && (
<p class={styles.bookYear}>
Published: {props.book.publication_year}
</p>
)}
</div>
</div>
);
}
79 changes: 79 additions & 0 deletions typesense-solid-js-search/src/components/BookList.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.bookList {
padding-bottom: 2rem;
}

.loadingContainer {
text-align: center;
padding: 3rem 1rem;
}

.spinner {
display: inline-block;
width: 2rem;
height: 2rem;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

.noResults {
text-align: center;
padding: 3rem 1rem;
background: #f9f9f9;
border-radius: 8px;
margin: 2rem 0;
}

.noResults h3 {
color: #333;
margin-bottom: 0.5rem;
}

.noResults p {
color: #666;
margin: 0;
}

.resultsCount {
color: #666;
margin-bottom: 1.5rem;
font-size: 0.95rem;
}

.bookGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
list-style: none;
padding: 0;
margin: 0;
}

@media (min-width: 768px) {
.bookGrid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (min-width: 1024px) {
.bookGrid {
grid-template-columns: repeat(3, 1fr);
}
}

@media (min-width: 1280px) {
.bookGrid {
grid-template-columns: repeat(4, 1fr);
}
}
35 changes: 35 additions & 0 deletions typesense-solid-js-search/src/components/BookList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { For, Show } from "solid-js";
import { BookCard } from "./BookCard";
import styles from "./BookList.module.css";
import type { Book } from "../types/Book";

interface BookListProps {
books: Book[];
loading: boolean;
}

export function BookList(props: BookListProps) {
return (
<div class={styles.bookList}>
<Show when={props.loading}>
<div class={styles.loadingContainer}>
<div class={styles.spinner}></div>
<p>Searching...</p>
</div>
</Show>

<Show when={!props.loading && props.books.length === 0}>
<div class={styles.noResults}>
<h3>No books found</h3>
<p>Try adjusting your search or try different keywords.</p>
</div>
</Show>

<Show when={!props.loading && props.books.length > 0}>
<div class={styles.bookGrid}>
<For each={props.books}>{(book) => <BookCard book={book} />}</For>
</div>
</Show>
</div>
);
}
Loading