Skip to content

Commit

Permalink
feat(core): add Search component
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemoya committed Aug 7, 2024
1 parent 7d9e865 commit b74880a
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 116 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-pens-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

Add `Search` component.
34 changes: 0 additions & 34 deletions core/client/fragments/prices.ts

This file was deleted.

8 changes: 2 additions & 6 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { getSessionCustomerId } from '~/auth';
import { FragmentOf, graphql } from '~/client/graphql';

import { Link } from '../link';
import { QuickSearch } from '../quick-search';
import { StoreLogo, StoreLogoFragment } from '../store-logo';
import { Button } from '../ui/button';
import { Dropdown } from '../ui/dropdown';
Expand All @@ -16,6 +15,7 @@ import { Header as ComponentsHeader } from '../ui/header';
import { logout } from './_actions/logout';
import { CartLink } from './cart';
import { LocaleSwitcher } from './locale-switcher';
import { QuickSearch } from './quick-search';

export const HeaderFragment = graphql(
`
Expand Down Expand Up @@ -63,11 +63,7 @@ export const Header = async ({ cart, data }: Props) => {

return (
<ComponentsHeader links={categoryTree} logo={logo}>
<QuickSearch>
<Link className="overflow-hidden text-ellipsis py-3" href="/">
{logo}
</Link>
</QuickSearch>
<QuickSearch logo={logo} />

{customerId ? (
<Dropdown
Expand Down
138 changes: 138 additions & 0 deletions core/components/header/quick-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
'use client';

import { useFormatter } from 'next-intl';
import { ReactNode } from 'react';

import { getQuickSearchResults } from '~/client/queries/get-quick-search-results';
import { ExistingResultType } from '~/client/util';

import { Price, Search, type SearchResults } from '../ui/search';

import { getSearchResults } from './_actions/get-search-results';

interface SearchProps {
logo: ReactNode;
}

type QuickSearchResults = ExistingResultType<typeof getQuickSearchResults>;

const isSearchQuery = (data: unknown): data is QuickSearchResults => {
if (typeof data === 'object' && data !== null && 'products' in data) {
return true;
}

return false;
};

export const QuickSearch = ({ logo }: SearchProps) => {
const format = useFormatter();

const fetchSearchResults = async (
term: string,
setSearchResults: React.Dispatch<React.SetStateAction<SearchResults | null>>,
) => {
const { data: searchResults } = await getSearchResults(term);

const formatPrice = (prices: QuickSearchResults['products'][0]['prices']): Price | null => {
if (!prices) {
return null;
}

const isPriceRange = prices.priceRange.min.value !== prices.priceRange.max.value;
const isSalePrice = prices.salePrice?.value !== prices.basePrice?.value;

if (isPriceRange) {
return {
type: 'range',
min: format.number(prices.priceRange.min.value, {
style: 'currency',
currency: prices.price.currencyCode,
}),
max: format.number(prices.priceRange.max.value, {
style: 'currency',
currency: prices.price.currencyCode,
}),
};
}

if (isSalePrice && prices.salePrice && prices.basePrice) {
return {
type: 'sale',
originalAmount: format.number(prices.basePrice.value, {
style: 'currency',
currency: prices.price.currencyCode,
}),
amount: format.number(prices.salePrice.value, {
style: 'currency',
currency: prices.price.currencyCode,
}),
msrp:
prices.retailPrice && prices.retailPrice.value !== prices.basePrice.value
? format.number(prices.retailPrice.value, {
style: 'currency',
currency: prices.price.currencyCode,
})
: undefined,
};
}

return {
type: 'fixed',
amount: format.number(prices.price.value, {
style: 'currency',
currency: prices.price.currencyCode,
}),
msrp:
prices.retailPrice && prices.retailPrice.value !== prices.price.value
? format.number(prices.retailPrice.value, {
style: 'currency',
currency: prices.price.currencyCode,
})
: undefined,
};
};

if (isSearchQuery(searchResults)) {
setSearchResults({
products: searchResults.products.map((product) => {
return {
name: product.name,
path: product.path,
image: product.defaultImage ?? undefined,
price: formatPrice(product.prices) ?? undefined,
};
}),
categories:
searchResults.products.length > 0
? Object.entries(
searchResults.products.reduce<Record<string, string>>((categories, product) => {
product.categories.edges?.forEach((category) => {
categories[category.node.name] = category.node.path;
});

return categories;
}, {}),
).map(([name, path]) => {
return { name, path };
})
: [],
brands:
searchResults.products.length > 0
? Object.entries(
searchResults.products.reduce<Record<string, string>>((brands, product) => {
if (product.brand) {
brands[product.brand.name] = product.brand.path;
}

return brands;
}, {}),
).map(([name, path]) => {
return { name, path };
})
: [],
});
}
};

return <Search logo={logo} onSearch={fetchSearchResults} />;
};
3 changes: 3 additions & 0 deletions core/components/ui/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use client';

export * from './search';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Search, Loader2 as Spinner, X } from 'lucide-react';
import { ComponentPropsWithRef, ElementRef, forwardRef } from 'react';

import { Button } from '../ui/button';
import { Button } from '../button';

interface Props extends ComponentPropsWithRef<'input'> {
pending?: boolean;
Expand All @@ -10,7 +10,7 @@ interface Props extends ComponentPropsWithRef<'input'> {
showClear?: boolean;
}

export const SearchInput = forwardRef<ElementRef<'input'>, Props>(
export const Input = forwardRef<ElementRef<'input'>, Props>(
({ className, pending, showClear, onClickClear, ...props }, ref) => (
<div className="relative">
<input
Expand Down
Loading

0 comments on commit b74880a

Please sign in to comment.