diff --git a/app/components/breadcrumb.tsx b/app/components/breadcrumb.tsx
new file mode 100644
index 00000000..d4e72cda
--- /dev/null
+++ b/app/components/breadcrumb.tsx
@@ -0,0 +1,18 @@
+import { cn } from "~/lib/cn";
+import { Link } from "./link";
+
+export function BreadCrumb({
+ homeLabel = "Home",
+ page,
+ className,
+}: { homeLabel?: string; page: string; className?: string }) {
+ return (
+
+
+ {homeLabel}
+
+ /
+ {page}
+
+ );
+}
diff --git a/app/components/layout/predictive-search/index.tsx b/app/components/layout/predictive-search/index.tsx
index af1e92c8..443f3a91 100644
--- a/app/components/layout/predictive-search/index.tsx
+++ b/app/components/layout/predictive-search/index.tsx
@@ -57,7 +57,6 @@ function PredictiveSearch() {
onClear={fetchResults}
placeholder="Enter a keyword"
ref={inputRef}
- className="rounded"
autoComplete="off"
prefixElement={
diff --git a/app/components/overlay-and-background.tsx b/app/components/overlay-and-background.tsx
index be8e84f0..b0f66398 100644
--- a/app/components/overlay-and-background.tsx
+++ b/app/components/overlay-and-background.tsx
@@ -4,8 +4,8 @@ import type { OverlayProps } from "./overlay";
import { Overlay } from "./overlay";
export interface OverlayAndBackgroundProps
- extends BackgroundImageProps,
- OverlayProps {}
+ extends Partial,
+ Partial {}
export function OverlayAndBackground(props: OverlayAndBackgroundProps) {
let {
diff --git a/app/components/section.tsx b/app/components/section.tsx
index 3ce78d23..250375c8 100644
--- a/app/components/section.tsx
+++ b/app/components/section.tsx
@@ -22,14 +22,14 @@ export type BackgroundProps = BackgroundImageProps & {
export interface SectionProps
extends Omit, "padding">,
- Omit, "children">,
+ Partial, "children">>,
Omit, "children">,
Partial,
- OverlayProps {
- as: React.ElementType;
- borderRadius: number;
- containerClassName: string;
- children: React.ReactNode;
+ Partial {
+ as?: React.ElementType;
+ borderRadius?: number;
+ containerClassName?: string;
+ children?: React.ReactNode;
}
let variants = cva("relative", {
diff --git a/app/routes/($locale).search.tsx b/app/routes/($locale).search.tsx
index 5cc1379b..8090f3a2 100644
--- a/app/routes/($locale).search.tsx
+++ b/app/routes/($locale).search.tsx
@@ -1,3 +1,4 @@
+import { MagnifyingGlass, X } from "@phosphor-icons/react";
import { Await, Form, useLoaderData } from "@remix-run/react";
import {
Analytics,
@@ -7,15 +8,17 @@ import {
} from "@shopify/hydrogen";
import type { LoaderFunctionArgs, MetaArgs } from "@shopify/remix-oxygen";
import { defer } from "@shopify/remix-oxygen";
-import { Suspense } from "react";
+import { clsx } from "clsx";
+import { Fragment, Suspense, useRef } from "react";
+import type { PaginatedProductsSearchQuery } from "storefrontapi.generated";
+import { BreadCrumb } from "~/components/breadcrumb";
+import Link from "~/components/link";
+import { ProductCard } from "~/components/product/product-card";
+import { Section } from "~/components/section";
+import { Swimlane } from "~/components/swimlane";
import { PRODUCT_CARD_FRAGMENT } from "~/data/fragments";
import { PAGINATION_SIZE, getImageLoadingPriority } from "~/lib/const";
import { seoPayload } from "~/lib/seo.server";
-import { Heading, PageHeader, Section, Text } from "~/modules/text";
-import { Grid } from "~/modules/grid";
-import { Input } from "~/modules/input";
-import { ProductCard } from "~/components/product/product-card";
-import { ProductSwimlane } from "~/modules/product-swimlane";
import {
type FeaturedData,
getFeaturedData,
@@ -25,46 +28,56 @@ export async function loader({
request,
context: { storefront },
}: LoaderFunctionArgs) {
- let searchParams = new URL(request.url).searchParams;
+ let { searchParams } = new URL(request.url);
let searchTerm = searchParams.get("q");
- let variables = getPaginationVariables(request, {
- pageBy: PAGINATION_SIZE,
- });
+ let products: PaginatedProductsSearchQuery["products"] = null;
- let { products } = await storefront.query(SEARCH_QUERY, {
- variables: {
- searchTerm,
- ...variables,
- country: storefront.i18n.country,
- language: storefront.i18n.language,
- },
- });
-
- let shouldGetRecommendations = !searchTerm || products?.nodes?.length === 0;
+ if (searchTerm) {
+ let variables = getPaginationVariables(request, {
+ pageBy: PAGINATION_SIZE,
+ });
- let seo = seoPayload.collection({
- url: request.url,
- collection: {
- id: "search",
- title: "Search",
- handle: "search",
- descriptionHtml: "Search results",
- description: "Search results",
- seo: {
- title: "Search",
- description: `Showing ${products.nodes.length} search results for "${searchTerm}"`,
+ let data = await storefront.query(
+ SEARCH_QUERY,
+ {
+ variables: {
+ searchTerm,
+ ...variables,
+ country: storefront.i18n.country,
+ language: storefront.i18n.language,
+ },
},
- metafields: [],
- products,
- updatedAt: new Date().toISOString(),
- },
- });
+ );
+ products = data.products;
+ }
+
+ let noResults = products?.nodes?.length === 0;
return defer({
- seo,
+ seo: seoPayload.collection({
+ url: request.url,
+ collection: {
+ id: "search",
+ title: "Search",
+ handle: "search",
+ descriptionHtml: "Search results",
+ description: "Search results",
+ seo: {
+ title: "Search",
+ description: noResults
+ ? searchTerm
+ ? `No results found for "${searchTerm}"`
+ : "Search our store"
+ : `Showing ${products.nodes.length} search results for "${searchTerm}"`,
+ },
+ metafields: [],
+ products,
+ updatedAt: new Date().toISOString(),
+ },
+ }),
searchTerm,
products,
- noResultRecommendations: shouldGetRecommendations
+ noResultRecommendations: noResults
? getNoResultRecommendations(storefront)
: Promise.resolve(null),
});
@@ -74,88 +87,129 @@ export let meta = ({ matches }: MetaArgs) => {
return getSeoMeta(...matches.map((match) => (match.data as any).seo));
};
+const POPULAR_SEARCHES = ["French Linen", "Shirt", "Cotton"];
+
export default function Search() {
let { searchTerm, products, noResultRecommendations } =
useLoaderData();
+ let inputRef = useRef(null);
let noResults = products?.nodes?.length === 0;
return (
- <>
-
-
- Search
-
-
-
- {!searchTerm || noResults ? (
+
+
+ Search
+
+ Popular Searches:
+ {POPULAR_SEARCHES.map((search, ind) => (
+
+
+ {search}
+
+ {ind < POPULAR_SEARCHES.length - 1 && (
+ ,
+ )}
+
+ ))}
+
+
+ {noResults ? (
) : (
-
-
- {({ nodes, isLoading, NextLink, PreviousLink }) => {
- let itemsMarkup = nodes.map((product, i) => (
-
- ));
-
- return (
- <>
-
-
- {isLoading ? "Loading..." : "Previous"}
-
-
- {itemsMarkup}
-
-
- {isLoading ? "Loading..." : "Next"}
-
-
- >
- );
- }}
-
-
+
+ {({
+ nodes,
+ isLoading,
+ nextPageUrl,
+ hasNextPage,
+ previousPageUrl,
+ hasPreviousPage,
+ }) => {
+ return (
+
+ {hasPreviousPage && (
+
+ {isLoading ? "Loading..." : "Previous"}
+
+ )}
+
+ {nodes.map((product, idx) => (
+
+ ))}
+
+ {hasNextPage && (
+
+ {isLoading ? "Loading..." : "Next"}
+
+ )}
+
+ );
+ }}
+
)}
- >
+
);
}
function NoResults({
- noResults,
+ searchTerm,
recommendations,
}: {
- noResults: boolean;
+ searchTerm: string;
recommendations: Promise;
}) {
return (
<>
- {noResults && (
-
-
- No results, try a different search.
-
-
+ {searchTerm && (
+
+ No results for "{searchTerm}", try a different search.
+
)}
{(result) => {
- if (!result) return null;
+ if (!result) {
+ return null;
+ }
let { featuredProducts } = result;
-
return (
- <>
-
- >
+
+
Trending Products
+
+ {featuredProducts.nodes.map((product) => (
+
+ ))}
+
+
);
}}
@@ -182,7 +243,7 @@ function NoResults({
}
export function getNoResultRecommendations(
- storefront: LoaderFunctionArgs["context"]["storefront"]
+ storefront: LoaderFunctionArgs["context"]["storefront"],
) {
return getFeaturedData(storefront, { pageBy: PAGINATION_SIZE });
}
@@ -218,4 +279,4 @@ let SEARCH_QUERY = `#graphql
}
${PRODUCT_CARD_FRAGMENT}
-` as let;
+` as const;
diff --git a/app/sections/all-products.tsx b/app/sections/all-products.tsx
index 4b60b238..270b5323 100644
--- a/app/sections/all-products.tsx
+++ b/app/sections/all-products.tsx
@@ -4,6 +4,7 @@ import type { HydrogenComponentSchema } from "@weaverse/hydrogen";
import clsx from "clsx";
import { forwardRef } from "react";
import type { AllProductsQuery } from "storefrontapi.generated";
+import { BreadCrumb } from "~/components/breadcrumb";
import Link from "~/components/link";
import { ProductCard } from "~/components/product/product-card";
import { Section, type SectionProps, layoutInputs } from "~/components/section";
@@ -21,6 +22,7 @@ let AllProducts = forwardRef((props, ref) => {
return (
+
{heading}
{({
diff --git a/app/sections/collection-filters/index.tsx b/app/sections/collection-filters/index.tsx
index 376f836a..0ff062f0 100644
--- a/app/sections/collection-filters/index.tsx
+++ b/app/sections/collection-filters/index.tsx
@@ -1,13 +1,13 @@
import { useLoaderData } from "@remix-run/react";
+import { Image } from "@shopify/hydrogen";
import type { HydrogenComponentSchema } from "@weaverse/hydrogen";
import { forwardRef, useEffect, useState } from "react";
import type { CollectionDetailsQuery } from "storefrontapi.generated";
-import Link from "~/components/link";
+import { BreadCrumb } from "~/components/breadcrumb";
import { Section, type SectionProps, layoutInputs } from "~/components/section";
import { Filters } from "./filters";
import { ProductsPagination } from "./products-pagination";
import { ToolsBar } from "./tools-bar";
-import { Image } from "@shopify/hydrogen";
export interface CollectionFiltersData {
showBreadcrumb: boolean;
@@ -79,13 +79,7 @@ let CollectionFilters = forwardRef(
{showBreadcrumb && (
-
-
- Home
-
- /
- {collection.title}
-
+
)}
{collection.title}
{showDescription && collection.description && (