From a0f312fbc758f6b4b6f87bc9b5ea44c84f0cd8ae Mon Sep 17 00:00:00 2001 From: Joeri de Gooijer Date: Mon, 18 Nov 2024 14:00:12 -0800 Subject: [PATCH] fix: Split searchbox into several compound components --- .../__snapshots__/pagination.spec.tsx.snap | 312 ------------------ .../components/attribute-suggestions.tsx | 65 ++++ .../search-box/components/first-query.tsx | 40 +++ .../components/query-suggestions.tsx | 65 ++++ .../search-box/components/search-box-form.tsx | 103 ++++++ .../components/search-box-reset.tsx | 26 ++ .../search-box/components/search-box-root.tsx | 165 +-------- .../components/search-box-submit.tsx | 29 ++ .../components/search-box-suggestions.tsx | 264 --------------- .../components/search-suggestions.tsx | 78 +++++ .../search-box/components/suggestions.tsx | 141 ++++++++ src/components/search-box/index.ts | 2 +- src/components/search-box/search-box.spec.tsx | 35 +- .../search-box/search-box.stories.tsx | 21 +- src/components/search-box/search-box.types.ts | 66 +++- src/contexts/limitless-ui.provider.tsx | 5 +- 16 files changed, 651 insertions(+), 766 deletions(-) delete mode 100644 src/components/pagination/__snapshots__/pagination.spec.tsx.snap create mode 100644 src/components/search-box/components/attribute-suggestions.tsx create mode 100644 src/components/search-box/components/first-query.tsx create mode 100644 src/components/search-box/components/query-suggestions.tsx create mode 100644 src/components/search-box/components/search-box-form.tsx create mode 100644 src/components/search-box/components/search-box-reset.tsx create mode 100644 src/components/search-box/components/search-box-submit.tsx delete mode 100644 src/components/search-box/components/search-box-suggestions.tsx create mode 100644 src/components/search-box/components/search-suggestions.tsx create mode 100644 src/components/search-box/components/suggestions.tsx diff --git a/src/components/pagination/__snapshots__/pagination.spec.tsx.snap b/src/components/pagination/__snapshots__/pagination.spec.tsx.snap deleted file mode 100644 index e577c15..0000000 --- a/src/components/pagination/__snapshots__/pagination.spec.tsx.snap +++ /dev/null @@ -1,312 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Pagination > should render successfully and match snapshot 1`] = ` -
-
- -
- Showing - 11 - - to - 20 - - of - 100 -
-
-
- -
-
-`; diff --git a/src/components/search-box/components/attribute-suggestions.tsx b/src/components/search-box/components/attribute-suggestions.tsx new file mode 100644 index 0000000..99fd904 --- /dev/null +++ b/src/components/search-box/components/attribute-suggestions.tsx @@ -0,0 +1,65 @@ +import clsx from 'clsx'; +import Highlighter from 'react-highlight-words'; +import { AttributeSuggestionsProps } from '../search-box.types'; +import { SuggestResponseAttributeSuggestions } from '@bloomreach/discovery-web-sdk'; +import { FloatingUIContext } from '../../../contexts'; +import { useContext } from 'react'; + +export const AttributeSuggestions = (props: AttributeSuggestionsProps) => { + const { inputValue, group, classNames, labels, onAttributeSelect, offset } = props; + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const attributeItemProps = ( + index: number, + offset: number, + query: SuggestResponseAttributeSuggestions, + ) => { + const { getItemProps, listRef, handleAttributeSelect } = floatingUIContext; + + return getItemProps({ + ref(node) { + listRef.current[index + offset] = node; + }, + onClick(event) { + handleAttributeSelect(); + onAttributeSelect?.(query, event); + }, + }); + }; + + return ( +
+

{labels?.attributeSuggestions ?? 'Brand Suggestions'}

+ +
+ ); +}; diff --git a/src/components/search-box/components/first-query.tsx b/src/components/search-box/components/first-query.tsx new file mode 100644 index 0000000..c2d9199 --- /dev/null +++ b/src/components/search-box/components/first-query.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx'; +import { useContext } from 'react'; +import { FloatingUIContext } from '../../../contexts'; +import { ArrowRight } from '../../../icons/arrow-right'; +import { FirstQueryProps } from '../search-box.types'; + +export const FirstQuery = (props: FirstQueryProps) => { + const { inputValue, onQuerySelect } = props; + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const { getItemProps, listRef, handleQuerySelect } = floatingUIContext; + + return inputValue ? ( + + + Search for: {inputValue} + + + + + ) : ( + <> + ); +}; diff --git a/src/components/search-box/components/query-suggestions.tsx b/src/components/search-box/components/query-suggestions.tsx new file mode 100644 index 0000000..0cf3211 --- /dev/null +++ b/src/components/search-box/components/query-suggestions.tsx @@ -0,0 +1,65 @@ +import clsx from 'clsx'; +import Highlighter from 'react-highlight-words'; +import { ArrowLeft } from '../../../icons/arrow-left'; +import { QuerySuggestionsProps } from '../search-box.types'; +import { useContext } from 'react'; +import { FloatingUIContext } from '../../../contexts'; +import { SuggestResponseQuerySuggestions } from '@bloomreach/discovery-web-sdk'; + +export const QuerySuggestions = (props: QuerySuggestionsProps) => { + const { inputValue, setInputValue, group, classNames, labels, onQuerySelect } = props; + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const queryItemProps = (index: number, query: SuggestResponseQuerySuggestions) => { + const { getItemProps, listRef, handleQuerySelect } = floatingUIContext; + + return getItemProps({ + ref(node) { + listRef.current[index] = node; + }, + onClick(event) { + handleQuerySelect(); + onQuerySelect?.(query, event); + }, + }); + }; + + return ( +
+

{labels?.querySuggestions ?? 'Keyword Suggestions'}

+ +
+ ); +}; diff --git a/src/components/search-box/components/search-box-form.tsx b/src/components/search-box/components/search-box-form.tsx new file mode 100644 index 0000000..b7062a5 --- /dev/null +++ b/src/components/search-box/components/search-box-form.tsx @@ -0,0 +1,103 @@ +import { useMergeRefs } from '@floating-ui/react'; +import * as Form from '@radix-ui/react-form'; +import { clsx } from 'clsx'; +import type { ChangeEvent, KeyboardEventHandler, ReactElement } from 'react'; +import { forwardRef, useContext, useRef } from 'react'; + +import { useSearchBox } from '../../../hooks/search-box.hook'; +import type { SearchBoxProps } from '../search-box.types'; + +import { FloatingUIContext } from '../../../contexts/floating-ui.context'; +import { SearchBoxReset } from './search-box-reset'; +import { SearchBoxSubmit } from './search-box-submit'; + +/** + * A search box component to interface with the Bloomreach Discovery search + * functionality + */ +export const SearchBoxForm = forwardRef( + (props, forwardedRef): ReactElement => { + const { + configuration, + searchOptions, + debounceDelay, + searchType, + classNames, + labels, + autoQuery, + onSubmit, + onChange, + onReset, + submitIcon, + resetIcon, + children, + ...elementProps + } = props; + const { changeHandler, inputValue, submitHandler, resetHandler } = useSearchBox(props); + const fieldName = elementProps.name || 'lui-search-box-input'; + const submitRef = useRef(null); + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const { refs, getReferenceProps, setOpen, open, handleInputChange } = floatingUIContext; + + const handleKeyDown: KeyboardEventHandler = (event) => { + if (event.key === 'Enter') { + submitRef.current?.click(); + } + }; + + const inputChange = (event: ChangeEvent) => { + handleInputChange(event); + changeHandler(event); + }; + + const mergedRefs = useMergeRefs([forwardedRef, refs.setReference]); + + return ( + <> + + + + + + { + setOpen(!!inputValue); + }} + value={inputValue} + placeholder={labels?.placeholder} + onKeyDown={handleKeyDown} + className={clsx('lui-search-box-input', classNames?.input)} + /> + + + + + + + {children} + + ); + }, +); diff --git a/src/components/search-box/components/search-box-reset.tsx b/src/components/search-box/components/search-box-reset.tsx new file mode 100644 index 0000000..490d5b0 --- /dev/null +++ b/src/components/search-box/components/search-box-reset.tsx @@ -0,0 +1,26 @@ +import clsx from 'clsx'; +import { SearchBoxResetProps } from '../search-box.types'; +import { forwardRef } from 'react'; +import { CloseIcon } from '../../../icons/clear-icon'; + +export const SearchBoxReset = forwardRef( + (props, forwardedRef) => { + const { children, classNames, labels } = props; + return ( + + ); + }, +); diff --git a/src/components/search-box/components/search-box-root.tsx b/src/components/search-box/components/search-box-root.tsx index bf81be3..651e28f 100644 --- a/src/components/search-box/components/search-box-root.tsx +++ b/src/components/search-box/components/search-box-root.tsx @@ -1,17 +1,12 @@ -import { FloatingFocusManager, FloatingPortal, useMergeRefs } from '@floating-ui/react'; -import * as Form from '@radix-ui/react-form'; -import { clsx } from 'clsx'; -import type { ChangeEvent, KeyboardEventHandler, ReactElement } from 'react'; -import { forwardRef, useContext, useRef } from 'react'; +import type { ReactElement } from 'react'; +import { forwardRef } from 'react'; import '../search-box.scss'; -import { useSearchBox } from '../../../hooks/search-box.hook'; import type { SearchBoxProps } from '../search-box.types'; -import { FloatingUIContext } from '../../../contexts/floating-ui.context'; -import { CloseIcon } from '../../../icons/clear-icon'; -import { SearchIcon } from '../../../icons/search-icon'; +import { FloatingUIContextProvider } from '../../../contexts/floating-ui.context'; +import { SearchBoxForm } from './search-box-form'; /** * A search box component to interface with the Bloomreach Discovery search @@ -19,154 +14,14 @@ import { SearchIcon } from '../../../icons/search-icon'; */ export const SearchBoxRoot = forwardRef( (props, forwardedRef): ReactElement => { - const { - configuration, - searchOptions, - debounceDelay, - searchType, - classNames, - labels, - autoQuery, - onSubmit, - onChange, - onReset, - submitIcon, - resetIcon, - children, - ...elementProps - } = props; - const { changeHandler, inputValue, submitHandler, resetHandler } = useSearchBox(props); - const fieldName = elementProps.name || 'lui-search-box-input'; - const submitRef = useRef(null); - const searchIconRef = useRef(null); - const closeIconRef = useRef(null); - - const floatingUIContext = useContext(FloatingUIContext); - - if (!floatingUIContext) { - throw new Error('Floating UI Provider is not provided'); - } - - const { - refs, - getReferenceProps, - setOpen, - open, - floatingStyles, - getFloatingProps, - handleInputChange, - context, - } = floatingUIContext; - - const handleKeyDown: KeyboardEventHandler = (event) => { - if (event.key === 'Enter') { - submitRef.current?.click(); - } - }; - - const inputChange = (event: ChangeEvent) => { - handleInputChange(event); - changeHandler(event); - }; - - const mergedRefs = useMergeRefs([forwardedRef, refs.setReference]); + const { children } = props; return ( - <> - - - - - - - <> - {labels?.label && ( - - {labels.label} - - )} - - - { - setOpen(!!inputValue); - }} - value={inputValue} - placeholder={labels?.placeholder} - onKeyDown={handleKeyDown} - className={clsx('lui-search-box-input', classNames?.input)} - /> - - - - - - - - {children && ( - - {open && ( - -
- {children} -
-
- )} -
- )} - + + + {children} + + ); }, ); diff --git a/src/components/search-box/components/search-box-submit.tsx b/src/components/search-box/components/search-box-submit.tsx new file mode 100644 index 0000000..357e3a2 --- /dev/null +++ b/src/components/search-box/components/search-box-submit.tsx @@ -0,0 +1,29 @@ +import * as Form from '@radix-ui/react-form'; +import { clsx } from 'clsx'; +import { forwardRef, ReactElement } from 'react'; +import { SearchIcon } from '../../../icons/search-icon'; +import { SearchBoxSubmitProps } from '../search-box.types'; + +export const SearchBoxSubmit = forwardRef( + (props, forwardedRef): ReactElement => { + const { labels, classNames, children } = props; + return ( + + + + ); + }, +); diff --git a/src/components/search-box/components/search-box-suggestions.tsx b/src/components/search-box/components/search-box-suggestions.tsx deleted file mode 100644 index 9b468b3..0000000 --- a/src/components/search-box/components/search-box-suggestions.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import type { - SuggestResponseAttributeSuggestions, - SuggestResponseQuerySuggestions, - SuggestResponseSearchSuggestions, -} from '@bloomreach/discovery-web-sdk'; -import * as Tabs from '@radix-ui/react-tabs'; -import { clsx } from 'clsx'; -import type { ReactElement } from 'react'; -import { useContext } from 'react'; -import Highlighter from 'react-highlight-words'; - -import type { SuggestionsProps } from '../search-box.types'; - -import { SearchContext } from '../../../contexts'; -import { useSuggestions } from '../../../hooks/suggestions.hook'; -import { ArrowLeft } from '../../../icons/arrow-left'; -import { ArrowRight } from '../../../icons/arrow-right'; -import { ProductCard } from '../../product-card'; - -import { FloatingUIContext } from '../../../contexts/floating-ui.context'; - -export const Suggestions = (props: SuggestionsProps): ReactElement => { - const { - configuration, - suggestOptions, - debounceDelay, - labels, - classNames, - onQuerySelect, - onSearchSelect, - onAttributeSelect, - ...rest - } = props; - const searchContext = useContext(SearchContext); - - if (!searchContext) { - throw new Error('SearchContext not provided'); - } - - const { inputValue, setInputValue } = searchContext; - - const { response } = useSuggestions(props, inputValue); - - const floatingUIContext = useContext(FloatingUIContext); - - const queryItemProps = (index: number, query: SuggestResponseQuerySuggestions) => { - if (!floatingUIContext) { - return {}; - } - - const { getItemProps, listRef, handleQuerySelect } = floatingUIContext; - - return getItemProps({ - ref(node) { - listRef.current[index] = node; - }, - onClick(event) { - handleQuerySelect(); - onQuerySelect?.(query, event); - }, - }); - }; - - const searchItemProps = ( - index: number, - offset: number, - query: SuggestResponseSearchSuggestions, - ) => { - if (!floatingUIContext) { - return {}; - } - - const { getItemProps, listRef, handleProductSelect } = floatingUIContext; - - return getItemProps({ - ref(node) { - listRef.current[index + offset] = node; - }, - onClick(event) { - handleProductSelect(); - onSearchSelect?.(query, event); - }, - }); - }; - - const attributeItemProps = ( - index: number, - offset: number, - query: SuggestResponseAttributeSuggestions, - ) => { - if (!floatingUIContext) { - return {}; - } - - const { getItemProps, listRef, handleAttributeSelect } = floatingUIContext; - - return getItemProps({ - ref(node) { - listRef.current[index + offset] = node; - }, - onClick(event) { - handleAttributeSelect(); - onAttributeSelect?.(query, event); - }, - }); - }; - return response ? ( - - - {response.suggestionGroups?.map((group, index, groups) => ( - - {group.catalogName} - {group.view} - - ))} - - - {response.suggestionGroups?.map((group, index) => ( - - {inputValue && ( - - - Search for: {inputValue} - - - - - )} -
- {group.querySuggestions && group.querySuggestions.length > 0 && ( -
-

{labels?.querySuggestions ?? 'Keyword Suggestions'}

-
    - {group.querySuggestions.map((query, index) => ( -
  • - - - - setInputValue(query.displayText)} /> -
  • - ))} -
-
- )} - {group.attributeSuggestions && group.attributeSuggestions.length > 0 && ( -
-

{labels?.attributeSuggestions ?? 'Brand Suggestions'}

-
    - {group.attributeSuggestions.map((attribute) => ( -
  • - -
  • - ))} -
-
- )} - {group.searchSuggestions && group.searchSuggestions.length > 0 && ( -
-

{labels?.searchSuggestions ?? 'Product Suggestions'}

-
    - {group.searchSuggestions.map((product, index) => ( -
  • - - - - - - - - - - - -
  • - ))} -
-
- )} -
-
- ))} -
- ) : ( - <> - ); -}; - -Suggestions.displayName = 'Suggestions'; diff --git a/src/components/search-box/components/search-suggestions.tsx b/src/components/search-box/components/search-suggestions.tsx new file mode 100644 index 0000000..19198d5 --- /dev/null +++ b/src/components/search-box/components/search-suggestions.tsx @@ -0,0 +1,78 @@ +import clsx from 'clsx'; +import Highlighter from 'react-highlight-words'; +import { SearchSuggestionsProps } from '../search-box.types'; +import { ProductCard } from '../../product-card'; +import { useContext } from 'react'; +import { FloatingUIContext } from '../../../contexts'; +import { SuggestResponseSearchSuggestions } from '@bloomreach/discovery-web-sdk'; + +export const SearchSuggestions = (props: SearchSuggestionsProps) => { + const { inputValue, group, classNames, labels, onSearchSelect, offset } = props; + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const searchItemProps = ( + index: number, + offset: number, + query: SuggestResponseSearchSuggestions, + ) => { + const { getItemProps, listRef, handleProductSelect } = floatingUIContext; + + return getItemProps({ + ref(node) { + listRef.current[index + offset] = node; + }, + onClick(event) { + handleProductSelect(); + onSearchSelect?.(query, event); + }, + }); + }; + return ( +
+

{labels?.searchSuggestions ?? 'Product Suggestions'}

+
    + {group.map((product, index) => ( +
  • + + + + + + + + + + + +
  • + ))} +
+
+ ); +}; diff --git a/src/components/search-box/components/suggestions.tsx b/src/components/search-box/components/suggestions.tsx new file mode 100644 index 0000000..1892f5f --- /dev/null +++ b/src/components/search-box/components/suggestions.tsx @@ -0,0 +1,141 @@ +import * as Tabs from '@radix-ui/react-tabs'; +import { clsx } from 'clsx'; +import type { ReactElement } from 'react'; +import { useContext } from 'react'; + +import type { SuggestionsProps } from '../search-box.types'; + +import { SearchContext } from '../../../contexts'; +import { useSuggestions } from '../../../hooks/suggestions.hook'; + +import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; +import { FloatingUIContext } from '../../../contexts/floating-ui.context'; +import { AttributeSuggestions } from './attribute-suggestions'; +import { FirstQuery } from './first-query'; +import { QuerySuggestions } from './query-suggestions'; +import { SearchSuggestions } from './search-suggestions'; + +export const Suggestions = (props: SuggestionsProps): ReactElement => { + const { + configuration, + suggestOptions, + debounceDelay, + labels, + onQuerySelect, + onAttributeSelect, + onSearchSelect, + classNames, + ...rest + } = props; + const searchContext = useContext(SearchContext); + + if (!searchContext) { + throw new Error('SearchContext not provided'); + } + + const floatingUIContext = useContext(FloatingUIContext); + + if (!floatingUIContext) { + throw new Error('Floating UI Provider is not provided'); + } + + const { inputValue, setInputValue } = searchContext; + + const { response } = useSuggestions(props, inputValue); + + const { refs, open, floatingStyles, getFloatingProps, context } = floatingUIContext; + + return response ? ( + + {open && ( + +
+ + + {response.suggestionGroups?.map((group, index, groups) => ( + + {group.catalogName} - {group.view} + + ))} + + + {response.suggestionGroups?.map((group, index) => ( + + + +
+ {group.querySuggestions && group.querySuggestions.length > 0 && ( + + )} + {group.attributeSuggestions && group.attributeSuggestions.length > 0 && ( + + )} + {group.searchSuggestions && group.searchSuggestions.length > 0 && ( + + )} +
+
+ ))} +
+
+
+ )} +
+ ) : ( + <> + ); +}; + +Suggestions.displayName = 'Suggestions'; diff --git a/src/components/search-box/index.ts b/src/components/search-box/index.ts index a862428..c9e5ac3 100644 --- a/src/components/search-box/index.ts +++ b/src/components/search-box/index.ts @@ -1,5 +1,5 @@ import { SearchBoxRoot } from './components/search-box-root'; -import { Suggestions } from './components/search-box-suggestions'; +import { Suggestions } from './components/suggestions'; export const SearchBox = { Root: SearchBoxRoot, diff --git a/src/components/search-box/search-box.spec.tsx b/src/components/search-box/search-box.spec.tsx index 1147910..9d86427 100644 --- a/src/components/search-box/search-box.spec.tsx +++ b/src/components/search-box/search-box.spec.tsx @@ -7,7 +7,7 @@ import { LimitlessUIProvider } from '../../contexts/limitless-ui.provider'; import { createSetupConfigMock } from '../../mocks/configuration.mock'; import { createProductSearchOptionsMock } from '../../mocks/product-search-options.mock'; import { setupMockServer } from '../../mocks/server.mock'; -import { SearchBoxRoot } from './components/search-box-root'; +import { SearchBox } from '.'; import { SearchBoxProps, SearchType } from './search-box.types'; describe('SearchBox', () => { @@ -50,7 +50,7 @@ describe('SearchBox', () => { it('renders a search input and submit button', () => { const { getByRole, getByLabelText } = render( - + , ); @@ -62,7 +62,7 @@ describe('SearchBox', () => { it('displays custom label when provided', () => { const { getByLabelText } = render( - + , ); expect(getByLabelText(props.labels!.label!)).toBeInTheDocument(); @@ -71,7 +71,7 @@ describe('SearchBox', () => { it('displays custom placeholder when provided', () => { const { getByPlaceholderText } = render( - + , ); expect(getByPlaceholderText(props.labels!.placeholder!)).toBeInTheDocument(); @@ -80,7 +80,7 @@ describe('SearchBox', () => { it('displays custom submit text when provided', () => { const { getByLabelText } = render( - + , ); expect(getByLabelText(props.labels!.submit!)).toBeInTheDocument(); @@ -89,7 +89,7 @@ describe('SearchBox', () => { it('displays custom reset text when provided', () => { const { getByLabelText } = render( - + , ); expect(getByLabelText(props.labels!.reset!)).toBeInTheDocument(); @@ -97,14 +97,13 @@ describe('SearchBox', () => { }); it('applies custom class names when provided', () => { - const { getByRole, getByText, getAllByRole } = render( + const { getByRole, getAllByRole } = render( - + , ); expect(getByRole('search')).toHaveClass(props.classNames!.form!); expect(getByRole('textbox')).toHaveClass(props.classNames!.input!); - expect(getByText(props.labels!.label!)).toHaveClass(props.classNames!.label!); expect(getAllByRole('button').find((e) => e.getAttribute('type') === 'submit')).toHaveClass( props.classNames!.submit!, ); @@ -118,7 +117,7 @@ describe('SearchBox', () => { it('submits the search when the user clicks the submit button', () => { const { getByRole, getAllByRole } = render( - + , ); @@ -134,7 +133,7 @@ describe('SearchBox', () => { it('submits the search when the user presses Enter in the input field', () => { const { getByRole } = render( - + , ); @@ -149,7 +148,7 @@ describe('SearchBox', () => { it('calls onSubmit prop when form is submitted', () => { const { getByRole } = render( - + , ); @@ -165,7 +164,7 @@ describe('SearchBox', () => { it('prevents default form submission behavior', () => { const { getByRole } = render( - + , ); @@ -188,7 +187,7 @@ describe('SearchBox', () => { const query = faker.commerce.product(); const { getByRole, getAllByRole } = render( - + , ); @@ -212,7 +211,7 @@ describe('SearchBox', () => { const autoQueryProps = { ...props, autoQuery: true, debounceDelay: customDebounceDelay }; const { getByRole } = render( - + , ); const apiBackend = vi.fn(); @@ -246,7 +245,7 @@ describe('SearchBox', () => { it('calls onChange prop when input value changes', () => { const { getByRole } = render( - + , ); @@ -266,7 +265,7 @@ describe('SearchBox', () => { const { getAllByRole } = render( - + , ); @@ -284,7 +283,7 @@ describe('SearchBox', () => { const { getAllByRole } = render( - + , ); diff --git a/src/components/search-box/search-box.stories.tsx b/src/components/search-box/search-box.stories.tsx index 7c759dc..54c7272 100644 --- a/src/components/search-box/search-box.stories.tsx +++ b/src/components/search-box/search-box.stories.tsx @@ -9,13 +9,12 @@ import { useContext } from 'react'; import { LimitlessUIProvider } from '../../contexts/limitless-ui.provider'; import { SearchContext } from '../../contexts/search.context'; import { Theme } from '../theme'; -import { SearchBoxRoot } from './components/search-box-root'; +import { SearchBox } from './index'; import { SearchBoxProps } from './search-box.types'; -import { Suggestions } from './components/search-box-suggestions'; -const meta: Meta = { +const meta: Meta = { title: 'COMPONENTS/SearchBox', - component: SearchBoxRoot, + component: SearchBox.Root, tags: ['autodocs'], parameters: { layout: 'padded', @@ -30,8 +29,8 @@ const meta: Meta = { export default meta; -export type Story = StoryObj; -export type SuggestionsStory = StoryObj; +export type Story = StoryObj; +export type SuggestionsStory = StoryObj; const configuration: Configuration = { account_id: 7634, @@ -85,7 +84,7 @@ export const Basic: Story = { return ( - + @@ -109,9 +108,9 @@ export const Autosuggest: SuggestionsStory = { return ( - - - + + + ); @@ -127,7 +126,7 @@ export const AutoQuery: Story = { return ( - + diff --git a/src/components/search-box/search-box.types.ts b/src/components/search-box/search-box.types.ts index e9c5e08..7b05945 100644 --- a/src/components/search-box/search-box.types.ts +++ b/src/components/search-box/search-box.types.ts @@ -8,8 +8,16 @@ import { SuggestResponseAttributeSuggestions, SuggestResponseQuerySuggestions, SuggestResponseSearchSuggestions, + SuggestResponseSuggestionGroups, } from '@bloomreach/discovery-web-sdk'; -import { ComponentPropsWithRef, MouseEvent, PropsWithChildren, ReactElement } from 'react'; +import { + ComponentPropsWithRef, + Dispatch, + MouseEvent, + PropsWithChildren, + ReactElement, + SetStateAction, +} from 'react'; export type SearchBoxProps = PropsWithChildren & Pick, 'onSubmit' | 'onReset'> & @@ -86,6 +94,30 @@ export type SearchBoxClassElement = */ export type SearchBoxLabelElement = 'placeholder' | 'label' | 'submit' | 'reset'; +export type SearchBoxSubmitProps = PropsWithChildren & { + /** + * Classnames to be added to their respective elements, e.g. input or submit button + */ + classNames?: Partial>; + + /** + * Text to be added to their respective elements, e.g. input label or submit button + */ + labels?: Partial>; +}; + +export type SearchBoxResetProps = PropsWithChildren & { + /** + * Classnames to be added to their respective elements, e.g. input or reset button + */ + classNames?: Partial>; + + /** + * Text to be added to their respective elements, e.g. input label or reset button + */ + labels?: Partial>; +}; + /** * Props for the suggestion component */ @@ -159,3 +191,35 @@ export type SuggestionsLabelElement = | 'querySuggestions' | 'searchSuggestions' | 'attributeSuggestions'; + +export type QuerySuggestionsProps = { + inputValue: string; + setInputValue: Dispatch>; + classNames?: Partial>; + labels?: Partial>; + group: SuggestResponseQuerySuggestions[]; + onQuerySelect?: SuggestionsProps['onQuerySelect']; +}; + +export type AttributeSuggestionsProps = { + inputValue: string; + classNames?: Partial>; + labels?: Partial>; + group: SuggestResponseAttributeSuggestions[]; + offset: number; + onAttributeSelect?: SuggestionsProps['onAttributeSelect']; +}; + +export type SearchSuggestionsProps = { + inputValue: string; + classNames?: Partial>; + labels?: Partial>; + group: SuggestResponseSearchSuggestions[]; + offset: number; + onSearchSelect?: SuggestionsProps['onSearchSelect']; +}; + +export type FirstQueryProps = { + inputValue: string; + onQuerySelect?: SuggestionsProps['onQuerySelect']; +}; diff --git a/src/contexts/limitless-ui.provider.tsx b/src/contexts/limitless-ui.provider.tsx index a1f71ba..a8c8e13 100644 --- a/src/contexts/limitless-ui.provider.tsx +++ b/src/contexts/limitless-ui.provider.tsx @@ -1,14 +1,11 @@ import { PropsWithChildren, ReactElement } from 'react'; import { SearchContextProvider } from './search.context'; import { AutoSuggestContextProvider } from './autosuggest.context'; -import { FloatingUIContextProvider } from './floating-ui.context'; export const LimitlessUIProvider = ({ children }: PropsWithChildren): ReactElement => { return ( - - {children} - + {children} ); };