Skip to content

Commit

Permalink
Make the SearchOverlay more extendable
Browse files Browse the repository at this point in the history
  • Loading branch information
paales committed Sep 5, 2024
1 parent 2f6aebd commit 5696acd
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 102 deletions.
121 changes: 79 additions & 42 deletions packages/magento-search/components/SearchOverlay/SearchOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,72 @@
import { ProductListItemRenderer } from '@graphcommerce/magento-product'
import { breakpointVal } from '@graphcommerce/next-ui'
import { breakpointVal, Overlay } from '@graphcommerce/next-ui'
import { LayoutHeaderClose } from '@graphcommerce/next-ui/Layout/components/LayoutHeaderClose'
import { Box, styled } from '@mui/material'
import { Box, styled, useTheme } from '@mui/material'
import { useState } from 'react'
import { SearchOverlayCategories } from './SearchOverlayCategories'
import { SearchInput } from './SearchOverlayInput'
import { SearchOverlayProducts } from './SearchOverlayProducts'
import { useSearchOverlay, SearchOverlayProvider } from './SearchOverlayProvider'
import { SearchOverlaySuggestions } from './SearchOverlaySuggestions'
import { memoDeep } from '@graphcommerce/next-ui'

function SearchOverlayHeader() {
const SearchOverlayHeaderRoot = styled(Box, { name: 'SearchOverlayHeader', slot: 'Root' })(
({ theme }) => ({
position: 'sticky',
display: 'grid',
top: 0,
zIndex: 1,
background: 'transparent',
boxShadow: theme.shadows[4],
height: theme.appShell.headerHeightSm,
gap: theme.page.horizontal,
paddingRight: theme.page.horizontal,
alignItems: 'center',
gridTemplateColumns: '1fr auto auto',
[theme.breakpoints.up('md')]: {
height: theme.appShell.appBarHeightMd,
},
}),
)

type SearchOverlayHeaderProps = {
className?: string
slotProps?: {
root?: React.ComponentProps<typeof SearchOverlayHeaderRoot>
input?: React.ComponentProps<typeof SearchOverlayHeaderInput>
close?: React.ComponentProps<typeof LayoutHeaderClose>
}
}

const SearchOverlayHeader = memoDeep((props: SearchOverlayHeaderProps) => {
const { params, setClosed } = useSearchOverlay()
const { className, slotProps } = props

return (
<Box
sx={(theme) => ({
position: 'sticky',
display: 'grid',
top: 0,
zIndex: 1,
background: 'transparent',
boxShadow: theme.shadows[4],
height: theme.appShell.headerHeightSm,
[theme.breakpoints.up('md')]: {
height: theme.appShell.appBarHeightMd,
},
gap: theme.page.horizontal,
pr: theme.page.horizontal,
alignItems: 'center',
gridTemplateColumns: '1fr auto auto',
})}
>
<SearchOverlayHeaderRoot className={className} {...slotProps?.root}>
<SearchInput
inputSx={{
typography: 'h4',
p: 0,
}}
autoFocus
sx={(theme) => ({
width: '100%',
height: '100%',
typography: 'h4',
pl: theme.page.horizontal,
px: theme.page.horizontal,
...breakpointVal(
'borderRadius',
theme.shape.borderRadius * 3,
theme.shape.borderRadius * 4,
theme.breakpoints.values,
),
})}
inputSx={{ typography: 'h4', p: 0 }}
autoFocus
params={params}
size='medium'
{...slotProps?.input}
/>

<LayoutHeaderClose onClose={setClosed} />
</Box>
<LayoutHeaderClose onClose={setClosed} {...slotProps?.close} />
</SearchOverlayHeaderRoot>
)
}
})

const SearchOverlayBodyBase = styled('div', { name: 'SearchOverlayBodyBase' })(({ theme }) => ({
padding: `0 ${theme.page.horizontal} ${theme.page.vertical}`,
Expand All @@ -65,20 +75,47 @@ const SearchOverlayBodyBase = styled('div', { name: 'SearchOverlayBodyBase' })((

type SearchOverlayProps = {
productListRenderer: ProductListItemRenderer
slotProps?: {
overlay?: Partial<React.ComponentProps<typeof Overlay>>
header?: React.ComponentProps<typeof SearchOverlayHeader>
body?: Partial<React.ComponentProps<typeof SearchOverlayBodyBase>>
suggestions?: Partial<React.ComponentProps<typeof SearchOverlaySuggestions>>
categories?: Partial<React.ComponentProps<typeof SearchOverlayCategories>>
products?: Partial<React.ComponentProps<typeof SearchOverlayProducts>>
}
}

export function SearchOverlay(props: SearchOverlayProps) {
const { productListRenderer } = props
export const SearchOverlay = memoDeep((props: SearchOverlayProps) => {
const { productListRenderer, slotProps } = props
const [open, setOpen] = useState(true)
const theme = useTheme()

return (
<SearchOverlayProvider open={open} setOpen={setOpen}>
<SearchOverlayHeader />
<SearchOverlayBodyBase>
<SearchOverlaySuggestions />
<SearchOverlayCategories />
<SearchOverlayProducts productListRenderer={productListRenderer} />
</SearchOverlayBodyBase>
</SearchOverlayProvider>
<Overlay
active={open}
onClosed={() => setOpen(false)}
variantMd='top'
variantSm='bottom'
sizeMd='floating'
sizeSm='full'
justifyMd='center'
disableAnimation
disableDrag
widthMd={`min(${theme.breakpoints.values.lg}px, 100vw - ${theme.page.horizontal} * 2)`}
bgColor='paper'
{...slotProps?.overlay}
>
<SearchOverlayProvider open={open} setOpen={setOpen}>
<SearchOverlayHeader {...slotProps?.header} />
<SearchOverlayBodyBase {...slotProps?.body}>
<SearchOverlaySuggestions {...slotProps?.suggestions} />
<SearchOverlayCategories {...slotProps?.categories} />
<SearchOverlayProducts
productListRenderer={productListRenderer}
{...slotProps?.products}
/>
</SearchOverlayBodyBase>
</SearchOverlayProvider>
</Overlay>
)
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { ProductListParams, useProductFiltersPro } from '@graphcommerce/magento-
import { FormAutoSubmit } from '@graphcommerce/react-hook-form'
import { t } from '@lingui/macro'
import { BoxProps, SxProps, Theme, Box, InputBaseProps } from '@mui/material'
import React from 'react'
import React, { forwardRef, memo } from 'react'
import { useSearchResultRemaining } from '../ProductFiltersPro/ProductFiltersProSearchHeader'
import { useSearchInput, useSearchItem } from './SearchOverlayProvider'
import { useSearchInput } from './SearchOverlayProvider'

function SearchInputShadow(
props: BoxProps<'div'> & { params: ProductListParams; inputSx?: SxProps<Theme> },
Expand All @@ -28,12 +28,7 @@ function SearchInputShadow(
<>
<Box
component='span'
sx={[
{
color: 'transparent',
},
...(Array.isArray(inputSx) ? inputSx : [inputSx]),
]}
sx={[{ color: 'transparent' }, ...(Array.isArray(inputSx) ? inputSx : [inputSx])]}
>
{resultSearch}
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { alpha, ListItemButton, ListItemButtonProps } from '@mui/material'
import { forwardRef, ElementType, memo } from 'react'
import { useSearchItem } from './SearchOverlayProvider'

type SearchOverlayItemProps<C extends ElementType = 'li'> = ListItemButtonProps<
export type SearchOverlayItemProps<C extends ElementType = 'li'> = ListItemButtonProps<
C,
{ component?: C }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import React, {
useEffect,
} from 'react'
import { useQuicksearch } from './useQuicksearch'
import { OverlayProps } from '@graphcommerce/next-ui/Overlay/components/OverlaySsr'

type SearchOverlayContextType = {
params: ProductListParams
Expand Down Expand Up @@ -52,7 +53,8 @@ type SearchOverlayProviderProps = {
setOpen: (open: boolean) => void
}

export function SearchOverlayProvider({ children, open, setOpen }: SearchOverlayProviderProps) {
export function SearchOverlayProvider(props: SearchOverlayProviderProps) {
const { children, open, setOpen, ...overlayProps } = props
const [params, setParams] = useState<ProductListParams>({
filters: {},
sort: {},
Expand Down Expand Up @@ -132,44 +134,22 @@ export function SearchOverlayProvider({ children, open, setOpen }: SearchOverlay
[selectedIndex],
)

const theme2 = useTheme()

return (
<SearchOverlayContext.Provider value={searchOverlayContext}>
<SearchOverlaySelectionContext.Provider value={searchOverlaySelectionContext}>
<Overlay
active={open}
onClosed={setClosed}
variantMd='top'
variantSm='bottom'
sizeMd='floating'
sizeSm='full'
justifyMd='center'
disableAnimation
disableDrag
widthMd={`min(${theme2.breakpoints.values.lg}px, 100vw - ${theme2.page.horizontal} * 2)`}
sx={(theme) => ({
'& .LayoutOverlayBase-background': {
backdropFilter: 'blur(16px)',
backgroundColor: alpha(theme.palette.background.paper, 0.8),
},
})}
bgColor='paper'
<ProductFiltersPro
params={params}
filterTypes={{}}
autoSubmitMd
handleSubmit={(formValues) =>
// eslint-disable-next-line @typescript-eslint/require-await
handleSubmit(formValues, async () => {
setParams(toProductListParams(formValues))
})
}
>
<ProductFiltersPro
params={params}
filterTypes={{}}
autoSubmitMd
handleSubmit={(formValues) =>
// eslint-disable-next-line @typescript-eslint/require-await
handleSubmit(formValues, async () => {
setParams(toProductListParams(formValues))
})
}
>
{children}
</ProductFiltersPro>
</Overlay>
{children}
</ProductFiltersPro>
</SearchOverlaySelectionContext.Provider>
</SearchOverlayContext.Provider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,59 @@ import {
ProductListSearchSuggestionFragment,
useProductFiltersPro,
} from '@graphcommerce/magento-product'
import { filterNonNullableKeys, SectionContainer } from '@graphcommerce/next-ui'
import {
filterNonNullableKeys,
SectionContainer,
memoDeep,
SectionContainerProps,
} from '@graphcommerce/next-ui'
import { Trans } from '@lingui/macro'
import { forwardRef } from 'react'
import { SearchOverlayItem } from './SearchOverlayItem'
import { useSearchOverlay } from './SearchOverlayProvider'

export const SearchOverlaySuggestion = forwardRef<
HTMLLIElement,
ProductListSearchSuggestionFragment
>((props, ref) => {
const { search } = props
const { form } = useProductFiltersPro()
type SearchOverlaySuggestionProps = ProductListSearchSuggestionFragment &
React.ComponentPropsWithoutRef<typeof SearchOverlayItem>

return (
<SearchOverlayItem ref={ref} onClick={() => form.setValue('search', search)}>
{search}
</SearchOverlayItem>
)
})
export const SearchOverlaySuggestion = memoDeep(
forwardRef<HTMLLIElement, SearchOverlaySuggestionProps>((props, ref) => {
const { search, ...rest } = props
const { form } = useProductFiltersPro()

return (
<SearchOverlayItem ref={ref} onClick={() => form.setValue('search', search)} {...rest}>
{search}
</SearchOverlayItem>
)
}),
)

SearchOverlaySuggestion.displayName = 'SearchOverlaySuggestion'

export function SearchOverlaySuggestions() {
type SearchOverlaySuggestionsProps = {
slotProps?: {
section?: SectionContainerProps
suggestion?: Omit<
React.ComponentProps<typeof SearchOverlaySuggestion>,
keyof ProductListSearchSuggestionFragment
>
}
}

export function SearchOverlaySuggestions(props: SearchOverlaySuggestionsProps) {
const { products } = useSearchOverlay()
const { slotProps } = props

if (!products?.suggestions || products.suggestions.length === 0) return null

return (
<SectionContainer labelLeft={<Trans>Did you mean?</Trans>}>
<SectionContainer labelLeft={<Trans>Did you mean?</Trans>} {...slotProps?.section}>
{filterNonNullableKeys(products.suggestions).map((suggestion) => (
<SearchOverlaySuggestion key={suggestion.search} {...suggestion} />
<SearchOverlaySuggestion
key={suggestion.search}
{...suggestion}
{...slotProps?.suggestion}
/>
))}
</SectionContainer>
)
Expand Down

0 comments on commit 5696acd

Please sign in to comment.