Skip to content

Commit 448f9ce

Browse files
authored
Merge pull request #360 from Weaverse/dev
v2026.3.31 - Add 3D model viewer, enhance search page, React 19 ref cleanup
2 parents 966cedc + 19acba4 commit 448f9ce

101 files changed

Lines changed: 1619 additions & 544 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Feature: Enhanced Search Page with Tabs and Multi-Type Results
2+
3+
| Field | Value |
4+
| ---------------- | -------------------------------------------------------- |
5+
| **Status** | completed |
6+
| **Owner** | @hta218 |
7+
| **Created** | 2026-03-25 |
8+
| **Last Updated** | 2026-03-25 |
9+
10+
## Original Prompt
11+
12+
> Guess we need a plan to update this search page, here are what I want:
13+
> 1. use search query instead of products query
14+
> 2. add more things to query just like predictive search
15+
> 3. update layout to use tab to switch between result (product should be the default active tab)
16+
> 4. apply infinite scroll for this page (use @app/components/product-grid/products-loaded-on-scroll.tsx component)
17+
> 5. Clarified: Tabs show result counts (e.g., "Products (24)" / "Articles (5)" / "Pages (3)")
18+
> 6. Clarified: No "All" tab - separate tabs for each type
19+
> 7. Clarified: Include Products, Articles, Pages, Collections
20+
> 8. Clarified: Infinite scroll applies to products tab only
21+
> 9. Clarified: Show "No results" message for empty tabs
22+
> 10. Clarified: Active tab in URL (?type=products|articles|pages|collections)
23+
24+
## Summary
25+
26+
Enhance the search page to support multi-type search results (products, articles, pages, collections) with a tabbed interface. Products tab uses infinite scroll while other tabs show static results. Each tab displays result counts and the active tab is reflected in the URL for shareability.
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# Implementation Plan: Enhanced Search Page
2+
3+
## Files to Modify
4+
5+
| File | Changes |
6+
|------|---------|
7+
| `app/routes/search/index.tsx` | Complete rewrite - new search query, tab interface, infinite scroll for products |
8+
| `app/routes/search/types.ts` | Create new types for search results |
9+
| `app/routes/search/search-tabs.tsx` | Create new tab component with result counts |
10+
11+
## Implementation Steps
12+
13+
### 1. Create Search Types (`app/routes/search/types.ts`)
14+
15+
```typescript
16+
import type { ProductCardFragment } from "storefront-api.generated";
17+
18+
export type SearchType = "products" | "articles" | "pages" | "collections";
19+
20+
export interface ArticleSearchResult {
21+
id: string;
22+
title: string;
23+
handle: string;
24+
image?: {
25+
url: string;
26+
altText?: string;
27+
};
28+
publishedAt: string;
29+
excerpt?: string;
30+
blog: {
31+
handle: string;
32+
title: string;
33+
};
34+
}
35+
36+
export interface PageSearchResult {
37+
id: string;
38+
title: string;
39+
handle: string;
40+
}
41+
42+
export interface CollectionSearchResult {
43+
id: string;
44+
title: string;
45+
handle: string;
46+
image?: {
47+
url: string;
48+
altText?: string;
49+
};
50+
}
51+
52+
export interface SearchResults {
53+
products: {
54+
nodes: ProductCardFragment[];
55+
pageInfo: {
56+
hasNextPage: boolean;
57+
endCursor: string;
58+
};
59+
};
60+
articles: ArticleSearchResult[];
61+
pages: PageSearchResult[];
62+
collections: CollectionSearchResult[];
63+
}
64+
```
65+
66+
### 2. Update Search Query
67+
68+
Replace products-only query with multi-type search:
69+
70+
```graphql
71+
query searchPage(
72+
$country: CountryCode
73+
$language: LanguageCode
74+
$searchTerm: String!
75+
$first: Int
76+
$after: String
77+
) @inContext(country: $country, language: $language) {
78+
# Products with pagination
79+
products(
80+
first: $first
81+
after: $after
82+
query: $searchTerm
83+
sortKey: RELEVANCE
84+
) {
85+
nodes {
86+
...ProductCard
87+
}
88+
pageInfo {
89+
hasNextPage
90+
endCursor
91+
}
92+
}
93+
94+
# Articles (no pagination - usually fewer results)
95+
articles(
96+
first: 20
97+
query: $searchTerm
98+
) {
99+
nodes {
100+
id
101+
title
102+
handle
103+
image {
104+
url
105+
altText
106+
}
107+
publishedAt
108+
excerpt
109+
blog {
110+
handle
111+
title
112+
}
113+
}
114+
}
115+
116+
# Pages
117+
pages(
118+
first: 20
119+
query: $searchTerm
120+
) {
121+
nodes {
122+
id
123+
title
124+
handle
125+
}
126+
}
127+
128+
# Collections
129+
collections(
130+
first: 20
131+
query: $searchTerm
132+
) {
133+
nodes {
134+
id
135+
title
136+
handle
137+
image {
138+
url
139+
altText
140+
}
141+
}
142+
}
143+
}
144+
${PRODUCT_CARD_FRAGMENT}
145+
```
146+
147+
### 3. Create Search Tabs Component (`app/routes/search/search-tabs.tsx`)
148+
149+
```typescript
150+
import { Link } from "react-router";
151+
import { cn } from "~/utils/cn";
152+
import type { SearchType } from "./types";
153+
154+
interface SearchTabsProps {
155+
activeTab: SearchType;
156+
counts: {
157+
products: number;
158+
articles: number;
159+
pages: number;
160+
collections: number;
161+
};
162+
searchTerm: string;
163+
}
164+
165+
const tabs: { type: SearchType; label: string }[] = [
166+
{ type: "products", label: "Products" },
167+
{ type: "articles", label: "Articles" },
168+
{ type: "pages", label: "Pages" },
169+
{ type: "collections", label: "Collections" },
170+
];
171+
172+
export function SearchTabs({ activeTab, counts, searchTerm }: SearchTabsProps) {
173+
return (
174+
<div className="border-b border-line">
175+
<div className="flex gap-6">
176+
{tabs.map(({ type, label }) => (
177+
<Link
178+
key={type}
179+
to={`/search?q=${encodeURIComponent(searchTerm)}&type=${type}`}
180+
className={cn(
181+
"relative py-3 text-sm font-medium transition-colors",
182+
activeTab === type
183+
? "text-foreground"
184+
: "text-body-subtle hover:text-foreground"
185+
)}
186+
>
187+
{label} ({counts[type]})
188+
{activeTab === type && (
189+
<span className="absolute bottom-0 left-0 right-0 h-0.5 bg-foreground" />
190+
)}
191+
</Link>
192+
))}
193+
</div>
194+
</div>
195+
);
196+
}
197+
```
198+
199+
### 4. Update Search Route (`app/routes/search/index.tsx`)
200+
201+
**Loader Changes:**
202+
203+
```typescript
204+
export async function loader({ request, context }: LoaderFunctionArgs) {
205+
const { searchParams } = new URL(request.url);
206+
const searchTerm = searchParams.get("q") || "";
207+
const activeTab = (searchParams.get("type") as SearchType) || "products";
208+
209+
// Build query based on active tab to optimize data fetching
210+
// Products need pagination, others don't
211+
const variables: Record<string, any> = {
212+
searchTerm,
213+
country: context.storefront.i18n.country,
214+
language: context.storefront.i18n.language,
215+
};
216+
217+
// Add pagination only for products
218+
if (activeTab === "products") {
219+
Object.assign(variables, getPaginationVariables(request, { pageBy: 16 }));
220+
}
221+
222+
const data = await context.storefront.query<SearchPageQuery>(SEARCH_PAGE_QUERY, {
223+
variables,
224+
});
225+
226+
return {
227+
searchTerm,
228+
activeTab,
229+
products: data.products,
230+
articles: data.articles?.nodes || [],
231+
pages: data.pages?.nodes || [],
232+
collections: data.collections?.nodes || [],
233+
};
234+
}
235+
```
236+
237+
**Component Changes:**
238+
239+
- Read `activeTab` from loader data
240+
- Render `SearchTabs` with result counts
241+
- For products tab: Use `ProductsLoadedOnScroll` component
242+
- For other tabs: Render simple grid/list
243+
- Show "No results" message when tab count is 0
244+
245+
**Products Tab with Infinite Scroll:**
246+
247+
```typescript
248+
// In Search component
249+
const { searchTerm, activeTab, products, articles, pages, collections } = useLoaderData<typeof loader>();
250+
251+
const counts = {
252+
products: products?.nodes?.length || 0,
253+
articles: articles?.length || 0,
254+
pages: pages?.length || 0,
255+
collections: collections?.length || 0,
256+
};
257+
258+
// ... render tabs
259+
260+
{activeTab === "products" && (
261+
<Pagination connection={products}>
262+
{({ nodes, ...paginationProps }) => (
263+
<ProductsLoadedOnScroll
264+
nodes={nodes}
265+
minCardWidth={280}
266+
gapX={16}
267+
gapY={24}
268+
{...paginationProps}
269+
/>
270+
)}
271+
</Pagination>
272+
)}
273+
274+
{activeTab === "articles" && (
275+
articles.length > 0 ? (
276+
<ArticlesGrid articles={articles} />
277+
) : (
278+
<NoResults type="articles" searchTerm={searchTerm} />
279+
)
280+
)}
281+
282+
// Similar for pages and collections
283+
```
284+
285+
### 5. Create Result Components
286+
287+
**Articles Grid:**
288+
- Show article image, title, excerpt, publish date
289+
- Link to `/blogs/{blog.handle}/{article.handle}`
290+
291+
**Pages List:**
292+
- Simple list of page titles
293+
- Link to `/pages/{handle}`
294+
295+
**Collections Grid:**
296+
- Show collection image and title
297+
- Link to `/collections/{handle}`
298+
299+
**NoResults Component:**
300+
- Show friendly message like "No {type} found for \"{searchTerm}\""
301+
- Suggest checking spelling or trying different keywords
302+
303+
## Data Flow
304+
305+
```
306+
User Search → URL (?q=term&type=products)
307+
308+
Loader reads type param (default: products)
309+
310+
GraphQL query with search term
311+
312+
Return results for all types
313+
314+
Component renders tabs with counts
315+
316+
Active tab determines what to show:
317+
- products: Infinite scroll grid
318+
- others: Static list/grid
319+
```
320+
321+
## URL Examples
322+
323+
- `/search?q=dress`Defaults to Products tab
324+
- `/search?q=dress&type=products`Products with infinite scroll
325+
- `/search?q=dress&type=articles`Articles tab
326+
- `/search?q=dress&type=collections`Collections tab
327+
328+
## Edge Cases
329+
330+
1. **No search term**: Show empty state with popular searches
331+
2. **No results for any type**: Show "No results found" with suggestions
332+
3. **Invalid type param**: Default to products tab
333+
4. **Direct URL access with type**: Works correctly, tab pre-selected
334+
335+
## Testing Checklist
336+
337+
- [ ] Search returns products matching title
338+
- [ ] Search returns articles matching content
339+
- [ ] Search returns pages matching content
340+
- [ ] Search returns collections matching title
341+
- [ ] Tab counts update correctly
342+
- [ ] Active tab reflected in URL
343+
- [ ] Infinite scroll works on products tab
344+
- [ ] No results message displays correctly
345+
- [ ] Tab switching preserves search term
346+
- [ ] Browser back/forward works with tab state

0 commit comments

Comments
 (0)