From 85e6ed9666c4c75699a1bd8bfb435d2136035465 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 15:35:41 +0600 Subject: [PATCH 01/21] Extract and create a separate portal component - Update story. --- src/components/search/search.stories.tsx | 57 +++++++++++++----------- src/components/search/search.tsx | 36 +++++++++++++-- 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/components/search/search.stories.tsx b/src/components/search/search.stories.tsx index a23061bf..daafcb83 100644 --- a/src/components/search/search.stories.tsx +++ b/src/components/search/search.stories.tsx @@ -10,6 +10,7 @@ const meta: Meta = { 'SearchBox.Input': SearchBox.Input, 'SearchBox.Loading': SearchBox.Loading, 'SearchBox.Separator': SearchBox.Separator, + 'SearchBox.Portal': SearchBox.Portal, 'SearchBox.Content': SearchBox.Content, 'SearchBox.List': SearchBox.List, 'SearchBox.Empty': SearchBox.Empty, @@ -51,33 +52,35 @@ const Template: StoryFn = ( args ) => { onOpenChange={ handleOpenChange } > - - - - }> - Calendar - - }> - Document - - }> - Attendance - - - - - }> - Calendar Folder - - }> - Document Folder - - }> - Attendance Folder - - - - + + + + + }> + Calendar + + }> + Document + + }> + Attendance + + + + + }> + Calendar Folder + + }> + Document Folder + + }> + Attendance Folder + + + + + ); }; diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 906a733f..78661582 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -79,6 +79,20 @@ export interface BaseSearchBoxProps { children?: ReactNode; } +type SearchBoxPortalProps = { + /** Additional class names for styling. */ + className?: string; + + /** Child components to be rendered. */ + children: ReactNode; + + /** Unique identifier for the portal, which determines where the dropdown will be rendered in the DOM. */ + id?: string; + + /** The HTML element that serves as the root for the portal, defining the location in the DOM where the dropdown will be displayed. This can be null if no specific root is provided. */ + root?: HTMLElement | null; +}; + // Extend the type to allow assigning subcomponents to SearchBox type SearchBoxComponent = React.ForwardRefExoticComponent< BaseSearchBoxProps & React.RefAttributes @@ -91,6 +105,7 @@ type SearchBoxComponent = React.ForwardRefExoticComponent< Empty: typeof SearchBoxEmpty; Group: typeof SearchBoxGroup; Item: typeof SearchBoxItem; + Portal: typeof SearchBoxPortal; }; export const SearchBox = forwardRef( @@ -330,8 +345,8 @@ export interface SearchBoxContentProps { export const SearchBoxContent = ( { className, - dropdownPortalRoot = null, // Root element where the dropdown will be rendered. - dropdownPortalId = '', // Id of the dropdown portal where the dropdown will be rendered. + dropdownPortalRoot, // Root element where the dropdown will be rendered. + dropdownPortalId, // Id of the dropdown portal where the dropdown will be rendered. children, ...props }: SearchBoxContentProps ) => { @@ -364,6 +379,19 @@ export const SearchBoxContent = ( { }; SearchBoxContent.displayName = 'SearchBox.Content'; +export const SearchBoxPortal = ( { + children, + id, + root, +}: SearchBoxPortalProps ) => { + return ( + + { children } + + ); +}; +SearchBoxPortal.displayName = 'SearchBox.Portal'; + // Define props for SearchBoxList export interface SearchBoxListProps { /** Whether to filter children based on the search term. */ @@ -470,7 +498,7 @@ export const SearchBoxGroup = ( { heading, children }: SearchBoxGroupProps ) =>
{ heading } @@ -589,5 +617,5 @@ SearchBox.List = SearchBoxList; SearchBox.Empty = SearchBoxEmpty; SearchBox.Group = SearchBoxGroup; SearchBox.Item = SearchBoxItem; - +SearchBox.Portal = SearchBoxPortal; export default SearchBox; From 2986bcf7358428b686f5ea31c457805a8a2d8719 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:26:56 +0600 Subject: [PATCH 02/21] style: Enhance search input focus and disabled states - Add focus-within styles for primary and secondary variants - Update disabled state to use outline instead of border - Improve visual feedback for different input states --- src/components/search/styles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/search/styles.ts b/src/components/search/styles.ts index 02b48208..103568f6 100644 --- a/src/components/search/styles.ts +++ b/src/components/search/styles.ts @@ -43,9 +43,9 @@ export const sizeClassNames = { }; export const variantClassNames = { primary: - 'bg-field-primary-background outline outline-1 outline-field-border hover:outline-border-strong', + 'bg-field-primary-background outline outline-1 outline-field-border hover:outline-border-strong focus-within:outline-focus-border focus-within:hover:outline-focus-border', secondary: - 'bg-field-secondary-background outline outline-1 outline-field-border hover:outline-border-strong', + 'bg-field-secondary-background outline outline-1 outline-field-border hover:outline-border-strong focus-within:outline-focus-border focus-within:hover:outline-focus-border', ghost: 'bg-field-secondary-background outline outline-1 outline-transparent', }; @@ -55,7 +55,7 @@ export const iconClasses = export const disabledClassNames = { ghost: 'cursor-not-allowed text-text-disabled placeholder:text-text-disabled', primary: - 'border-border-disabled hover:border-border-disabled bg-field-background-disabled cursor-not-allowed text-text-disabled placeholder:text-text-disabled', + 'outline-border-disabled hover:outline-border-disabled bg-field-background-disabled cursor-not-allowed text-text-disabled placeholder:text-text-disabled', secondary: - 'border-border-disabled hover:border-border-disabled cursor-not-allowed text-text-disabled placeholder:text-text-disabled', + 'outline-border-disabled hover:outline-border-disabled cursor-not-allowed text-text-disabled placeholder:text-text-disabled', }; From 97d40a6f626536aafd47adc752de4969eae28f93 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:31:51 +0600 Subject: [PATCH 03/21] feat: Improve SearchBox component with new features and interactions - Add `clearSearchOnClick` prop to optionally clear search term - Implement focus handling to open dropdown when input is not empty - Enhance SearchBoxItem with click and accessibility improvements - Add tabindex and role for better keyboard navigation - Utilize `callAll` utility for combining multiple click handlers --- src/components/search/search.tsx | 59 +++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 78661582..e8df1e69 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -7,9 +7,10 @@ import React, { useContext, Children, cloneElement, + type MouseEventHandler, } from 'react'; import { omit } from 'lodash'; // or define your own omit function -import { cn, getOperatingSystem } from '@/utilities/functions'; +import { callAll, cn, getOperatingSystem } from '@/utilities/functions'; import { Search } from 'lucide-react'; import Loader from '../loader'; import Badge from '../badge'; @@ -46,6 +47,7 @@ type TSearchContentValue = Partial<{ open: boolean; context: UseFloatingReturn['context']; setIsLoading: ( loading: boolean ) => void; + clearSearchOnClick: boolean; }>; // Define a context for the SearchBox @@ -77,12 +79,12 @@ export interface BaseSearchBoxProps { /** Child components to be rendered. */ children?: ReactNode; + + /** Clear search on clicking result item. */ + clearSearchOnClick?: boolean; } type SearchBoxPortalProps = { - /** Additional class names for styling. */ - className?: string; - /** Child components to be rendered. */ children: ReactNode; @@ -116,6 +118,7 @@ export const SearchBox = forwardRef( open = false, onOpenChange = () => {}, loading = false, + clearSearchOnClick = false, ...props }, ref @@ -195,6 +198,7 @@ export const SearchBox = forwardRef( setSearchTerm, isLoading, setIsLoading, + clearSearchOnClick, } } >
( setSearchTerm, } = useSearchContext(); const badgeSize = size === 'lg' ? 'sm' : 'xs'; + const handleChange = ( event: React.ChangeEvent ) => { const newValue = event.target.value; setSearchTerm!( newValue ); @@ -268,18 +273,28 @@ export const SearchBoxInput = forwardRef( } }; + const handleFocus = () => { + if ( disabled || typeof onOpenChange !== 'function' ) { + return; + } + if ( searchTerm?.trim() ) { + onOpenChange( true ); // Open the dropdown on focus if input is not empty + } + }; + return (
( className={ cn( textSizeClassNames[ size! ], 'flex-grow font-medium bg-transparent border-none outline-none border-transparent focus:ring-0 py-0', - disabled - ? disabledClassNames[ variant ] - : [ - 'text-field-placeholder focus-within:text-field-input group-hover:text-field-input', - 'placeholder:text-field-placeholder', - ], - className + disabled && + 'text-field-placeholder focus-within:text-field-input group-hover:text-field-input placeholder:text-field-placeholder', ) } disabled={ disabled } value={ searchTerm } onChange={ handleChange } + onFocus={ handleFocus } // Set open state on focus placeholder={ placeholder } // Omit custom props that are not valid for input { ...omit( props, [ @@ -520,11 +531,20 @@ export interface SearchBoxItemProps { /** Child components to be rendered. */ children: ReactNode; + + /** On click handler. */ + onClick?: () => void; } export const SearchBoxItem = forwardRef( - ( { className, icon, children, ...props }, ref ) => { - const { size } = useSearchContext(); + ( { className, icon, children, onClick, ...props }, ref ) => { + const { size, setSearchTerm, clearSearchOnClick } = useSearchContext(); + + const handleClick = () => { + if ( typeof onClick === 'function' ) { + onClick(); + } + }; return (
( 'flex items-center justify-start gap-1 p-1 hover:bg-background-secondary focus:bg-background-secondary cursor-pointer', sizeClassNames.item[ size! ] ) } + onClick={ callAll( handleClick, () => { + if ( clearSearchOnClick ) { + setSearchTerm!( '' ); + } + } ) as MouseEventHandler } { ...props } + tabIndex={ 0 } + role="button" > { icon && ( Date: Mon, 10 Mar 2025 16:38:35 +0600 Subject: [PATCH 04/21] Update changelog.txt --- changelog.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.txt b/changelog.txt index 6f5311e3..b67f6f49 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +Version 1.4.3 - xth xxxx, 2025 +- Improvement - Enhanced the UI and functionality of the Searchbox component for better flexibility and user experience. + + Version 1.4.2 - 6th March, 2025 - New - Added new size 'xs' to the Switch component. - Improvement - Adjusted the ring width and padding of the Radio Button component. From cd1d4a5b6caf62518876b4a35a2cdfb2ce99b779 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 17:01:43 +0600 Subject: [PATCH 05/21] style: Refine SearchBox input styling with minimal padding and height --- src/components/search/search.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index e8df1e69..76c8f487 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -310,7 +310,7 @@ export const SearchBoxInput = forwardRef( ref={ ref } className={ cn( textSizeClassNames[ size! ], - 'flex-grow font-medium bg-transparent border-none outline-none border-transparent focus:ring-0 py-0', + 'flex-grow font-medium bg-transparent border-none outline-none border-transparent focus:ring-0 p-0 min-h-fit', disabled && 'text-field-placeholder focus-within:text-field-input group-hover:text-field-input placeholder:text-field-placeholder', ) } From 6a4196cb3b96f3cef2718cc4134982450c4444d7 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 17:54:35 +0600 Subject: [PATCH 06/21] feat: Enhance SearchBox with advanced keyboard navigation and accessibility - Implement list navigation using Floating UI's useListNavigation - Add keyboard interactions for dropdown opening and item selection - Convert SearchBoxItem to a button for better accessibility - Integrate FloatingFocusManager for improved focus management - Add active state highlighting for list items - Remove unused imports and simplify component logic --- src/components/search/search.tsx | 147 +++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 38 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 76c8f487..4194d1d1 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -7,10 +7,9 @@ import React, { useContext, Children, cloneElement, - type MouseEventHandler, } from 'react'; import { omit } from 'lodash'; // or define your own omit function -import { callAll, cn, getOperatingSystem } from '@/utilities/functions'; +import { cn, getOperatingSystem } from '@/utilities/functions'; import { Search } from 'lucide-react'; import Loader from '../loader'; import Badge from '../badge'; @@ -32,6 +31,10 @@ import { useInteractions, type UseFloatingReturn, type UseInteractionsReturn, + useListNavigation, + FloatingFocusManager, + FloatingList, + useListItem, } from '@floating-ui/react'; type TSearchContentValue = Partial<{ @@ -43,6 +46,10 @@ type TSearchContentValue = Partial<{ floatingStyles: UseFloatingReturn['floatingStyles']; getReferenceProps: UseInteractionsReturn['getReferenceProps']; getFloatingProps: UseInteractionsReturn['getFloatingProps']; + getItemProps: ( userProps?: React.HTMLProps ) => Record; + activeIndex: number | null; + setActiveIndex: React.Dispatch>; + listRef: React.MutableRefObject<( HTMLElement | null )[]>; setSearchTerm: React.Dispatch>; open: boolean; context: UseFloatingReturn['context']; @@ -125,6 +132,8 @@ export const SearchBox = forwardRef( ) => { const [ searchTerm, setSearchTerm ] = useState( '' ); const [ isLoading, setIsLoading ] = useState( loading ?? false ); + const [ activeIndex, setActiveIndex ] = useState( null ); + const listRef = React.useRef<( HTMLElement | null )[]>( [] ); const { refs, floatingStyles, context } = useFloating( { open, @@ -146,10 +155,19 @@ export const SearchBox = forwardRef( } ), ], } ); + + const listNavigation = useListNavigation( context, { + listRef, + activeIndex, + onNavigate: setActiveIndex, + loop: true, + } ); + const dismiss = useDismiss( context ); - const { getReferenceProps, getFloatingProps } = useInteractions( [ + const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions( [ dismiss, + listNavigation, ] ); useEffect( () => { @@ -183,6 +201,13 @@ export const SearchBox = forwardRef( }; }, [ refs.reference ] ); + // Reset active index when closing dropdown + useEffect( () => { + if ( ! open ) { + setActiveIndex( null ); + } + }, [ open ] ); + return ( ( context, getReferenceProps, getFloatingProps, + getItemProps, + activeIndex, + setActiveIndex, + listRef, searchTerm, setSearchTerm, isLoading, @@ -256,6 +285,8 @@ export const SearchBoxInput = forwardRef( getReferenceProps, searchTerm, setSearchTerm, + setActiveIndex, + open, } = useSearchContext(); const badgeSize = size === 'lg' ? 'sm' : 'xs'; @@ -282,6 +313,22 @@ export const SearchBoxInput = forwardRef( } }; + const handleKeyDown = ( event: React.KeyboardEvent ) => { + if ( disabled ) { + return; + } + + if ( event.key === 'ArrowDown' ) { + event.preventDefault(); + if ( ! open && searchTerm?.trim() ) { + onOpenChange!( true ); + } + setActiveIndex!( 0 ); + } else if ( event.key === 'Escape' ) { + onOpenChange!( false ); + } + }; + return (
( disabled={ disabled } value={ searchTerm } onChange={ handleChange } - onFocus={ handleFocus } // Set open state on focus + onFocus={ handleFocus } + onKeyDown={ handleKeyDown } placeholder={ placeholder } // Omit custom props that are not valid for input { ...omit( props, [ @@ -356,12 +404,10 @@ export interface SearchBoxContentProps { export const SearchBoxContent = ( { className, - dropdownPortalRoot, // Root element where the dropdown will be rendered. - dropdownPortalId, // Id of the dropdown portal where the dropdown will be rendered. children, ...props }: SearchBoxContentProps ) => { - const { size, open, refs, floatingStyles, getFloatingProps } = + const { size, open, refs, floatingStyles, getFloatingProps, context } = useSearchContext(); if ( ! open ) { @@ -369,14 +415,14 @@ export const SearchBoxContent = ( { } return ( - +
{ children }
-
+ ); }; SearchBoxContent.displayName = 'SearchBox.Content'; @@ -416,10 +462,14 @@ export const SearchBoxList = ( { filter = true, children, }: SearchBoxListProps ) => { - const { searchTerm, isLoading } = useSearchContext(); + const { searchTerm, isLoading, listRef } = useSearchContext(); if ( ! filter ) { - return
{ children }
; + return ( + +
{ children }
+
+ ); } const filteredChildren = Children.toArray( children ) .map( ( child ) => { @@ -448,17 +498,19 @@ export const SearchBoxList = ( { return ; } return ( -
- { filteredChildren.some( - ( child ) => - React.isValidElement( child ) && - child.type !== SearchBoxSeparator - ) ? ( - filteredChildren - ) : ( - - ) } -
+ +
+ { filteredChildren.some( + ( child ) => + React.isValidElement( child ) && + child.type !== SearchBoxSeparator + ) ? ( + filteredChildren + ) : ( + + ) } +
+
); }; SearchBoxList.displayName = 'SearchBox.List'; @@ -536,30 +588,49 @@ export interface SearchBoxItemProps { onClick?: () => void; } -export const SearchBoxItem = forwardRef( +export const SearchBoxItem = forwardRef( ( { className, icon, children, onClick, ...props }, ref ) => { - const { size, setSearchTerm, clearSearchOnClick } = useSearchContext(); + const { size, setSearchTerm, clearSearchOnClick, getItemProps, activeIndex } = useSearchContext(); + const { ref: itemRef, index } = useListItem(); + + // Combine the refs + const combinedRef = ( node: HTMLButtonElement | null ) => { + if ( typeof ref === 'function' ) { + ref( node ); + } else if ( ref ) { + ref.current = node; + } + itemRef( node ); + }; + + const isActive = activeIndex === index; const handleClick = () => { if ( typeof onClick === 'function' ) { onClick(); } + + if ( clearSearchOnClick ) { + setSearchTerm!( '' ); + } }; + return ( -
{ - if ( clearSearchOnClick ) { - setSearchTerm!( '' ); - } - } ) as MouseEventHandler } - { ...props } - tabIndex={ 0 } - role="button" + { ...getItemProps?.( { + role: 'option', + 'aria-selected': isActive, + onClick: handleClick, + ...props, + } ) } > { icon && ( ( ) } { children } -
+ ); } ); From 0ac45560cffc70b3ea92976fc32de59d4c86deb3 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 18:02:13 +0600 Subject: [PATCH 07/21] feat: Add `closeOnClick` option to SearchBox for improved interaction --- src/components/search/search.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 4194d1d1..2b384d1f 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -55,6 +55,7 @@ type TSearchContentValue = Partial<{ context: UseFloatingReturn['context']; setIsLoading: ( loading: boolean ) => void; clearSearchOnClick: boolean; + closeOnClick: boolean; }>; // Define a context for the SearchBox @@ -89,6 +90,9 @@ export interface BaseSearchBoxProps { /** Clear search on clicking result item. */ clearSearchOnClick?: boolean; + + /** Close on clicking result item. */ + closeOnClick?: boolean; } type SearchBoxPortalProps = { @@ -126,6 +130,7 @@ export const SearchBox = forwardRef( onOpenChange = () => {}, loading = false, clearSearchOnClick = false, + closeOnClick = false, ...props }, ref @@ -228,6 +233,7 @@ export const SearchBox = forwardRef( isLoading, setIsLoading, clearSearchOnClick, + closeOnClick, } } >
( ( { className, icon, children, onClick, ...props }, ref ) => { - const { size, setSearchTerm, clearSearchOnClick, getItemProps, activeIndex } = useSearchContext(); + const { + size, + setSearchTerm, + clearSearchOnClick, + getItemProps, + activeIndex, + onOpenChange, + closeOnClick, + } = useSearchContext(); const { ref: itemRef, index } = useListItem(); // Combine the refs @@ -613,6 +627,10 @@ export const SearchBoxItem = forwardRef( if ( clearSearchOnClick ) { setSearchTerm!( '' ); } + + if ( closeOnClick ) { + onOpenChange!( false ); + } }; return ( From 42b444db03e0706e4b81ad78d3b06621e1845035 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 10 Mar 2025 19:15:31 +0600 Subject: [PATCH 08/21] Prevent opening the dropdown - After pressing arrow down - After pressing arrow up --- src/components/search/search.tsx | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 2b384d1f..df737d2d 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -166,6 +166,8 @@ export const SearchBox = forwardRef( activeIndex, onNavigate: setActiveIndex, loop: true, + // Prevent opening the dropdown with arrow keys + openOnArrowKeyDown: false, } ); const dismiss = useDismiss( context ); @@ -291,8 +293,9 @@ export const SearchBoxInput = forwardRef( getReferenceProps, searchTerm, setSearchTerm, - setActiveIndex, open, + setActiveIndex, + listRef, } = useSearchContext(); const badgeSize = size === 'lg' ? 'sm' : 'xs'; @@ -324,13 +327,28 @@ export const SearchBoxInput = forwardRef( return; } - if ( event.key === 'ArrowDown' ) { - event.preventDefault(); - if ( ! open && searchTerm?.trim() ) { - onOpenChange!( true ); + // Do not open dropdown on arrow keys + if ( event.key === 'ArrowDown' || event.key === 'ArrowUp' ) { + // Only navigate if dropdown is already open + if ( open ) { + event.preventDefault(); + if ( event.key === 'ArrowDown' ) { + // Navigate to first item if none selected, otherwise listNavigation will handle it + setActiveIndex!( ( prev ) => ( prev === null ? 0 : prev ) ); + } else if ( event.key === 'ArrowUp' ) { + // Navigate to last item if none selected, otherwise listNavigation will handle it + setActiveIndex!( ( prev ) => { + // Get the length of the list to select the last item + const listLength = listRef?.current?.length || 0; + return prev === null && listLength > 0 ? listLength - 1 : prev; + } ); + } } - setActiveIndex!( 0 ); - } else if ( event.key === 'Escape' ) { + // Do not open the dropdown + return; + } + + if ( event.key === 'Escape' ) { onOpenChange!( false ); } }; From d3b90ec3f7e29a88e7d8e9dc13e26ec9060d81ce Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:30:44 +0600 Subject: [PATCH 09/21] Removed props that are not required --- src/components/search/search.tsx | 52 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index df737d2d..feeff6e7 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -46,7 +46,9 @@ type TSearchContentValue = Partial<{ floatingStyles: UseFloatingReturn['floatingStyles']; getReferenceProps: UseInteractionsReturn['getReferenceProps']; getFloatingProps: UseInteractionsReturn['getFloatingProps']; - getItemProps: ( userProps?: React.HTMLProps ) => Record; + getItemProps: ( + userProps?: React.HTMLProps + ) => Record; activeIndex: number | null; setActiveIndex: React.Dispatch>; listRef: React.MutableRefObject<( HTMLElement | null )[]>; @@ -65,6 +67,11 @@ const useSearchContext = () => { return useContext( SearchContext ); }; +export interface CommonSearchBoxProps { + /** Additional class names for styling. */ + className?: string; +} + // Define the Size type type Size = 'sm' | 'md' | 'lg'; @@ -76,6 +83,9 @@ export interface BaseSearchBoxProps { /** Size of the SearchBox. */ size?: 'sm' | 'md' | 'lg'; + /** Style variant of the input. */ + variant?: 'primary' | 'secondary' | 'ghost'; + /** Whether the dropdown is open. */ open?: boolean; @@ -131,6 +141,7 @@ export const SearchBox = forwardRef( loading = false, clearSearchOnClick = false, closeOnClick = false, + variant = 'primary', ...props }, ref @@ -172,10 +183,8 @@ export const SearchBox = forwardRef( const dismiss = useDismiss( context ); - const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions( [ - dismiss, - listNavigation, - ] ); + const { getReferenceProps, getFloatingProps, getItemProps } = + useInteractions( [ dismiss, listNavigation ] ); useEffect( () => { const operatingSystem = getOperatingSystem(); @@ -236,6 +245,7 @@ export const SearchBox = forwardRef( setIsLoading, clearSearchOnClick, closeOnClick, + variant, } } >
( SearchBox.displayName = 'SearchBox'; // Define props for SearchBoxInput -export interface SearchBoxInputProps extends BaseSearchBoxProps { +export interface SearchBoxInputProps extends CommonSearchBoxProps { /** Type of the input (e.g., text, search). */ type?: string; /** Placeholder text for the input. */ placeholder?: string; - /** Style variant of the input. */ - variant?: 'primary' | 'secondary' | 'ghost'; - /** Whether the input is disabled. */ disabled?: boolean; @@ -279,7 +286,6 @@ export const SearchBoxInput = forwardRef( className, type = 'text', placeholder = 'Search...', - variant = 'primary', disabled = false, onChange = () => {}, ...props @@ -288,7 +294,6 @@ export const SearchBoxInput = forwardRef( ) => { const { size, - onOpenChange, refs, getReferenceProps, searchTerm, @@ -296,6 +301,8 @@ export const SearchBoxInput = forwardRef( open, setActiveIndex, listRef, + onOpenChange, + variant, } = useSearchContext(); const badgeSize = size === 'lg' ? 'sm' : 'xs'; @@ -318,7 +325,7 @@ export const SearchBoxInput = forwardRef( return; } if ( searchTerm?.trim() ) { - onOpenChange( true ); // Open the dropdown on focus if input is not empty + onOpenChange!( true ); // Open the dropdown on focus if input is not empty } }; @@ -340,7 +347,9 @@ export const SearchBoxInput = forwardRef( setActiveIndex!( ( prev ) => { // Get the length of the list to select the last item const listLength = listRef?.current?.length || 0; - return prev === null && listLength > 0 ? listLength - 1 : prev; + return prev === null && listLength > 0 + ? listLength - 1 + : prev; } ); } } @@ -383,7 +392,7 @@ export const SearchBoxInput = forwardRef( textSizeClassNames[ size! ], 'flex-grow font-medium bg-transparent border-none outline-none border-transparent focus:ring-0 p-0 min-h-fit', disabled && - 'text-field-placeholder focus-within:text-field-input group-hover:text-field-input placeholder:text-field-placeholder', + 'text-field-placeholder focus-within:text-field-input group-hover:text-field-input placeholder:text-field-placeholder' ) } disabled={ disabled } value={ searchTerm } @@ -416,12 +425,6 @@ export interface SearchBoxContentProps { /** Additional class names for styling. */ className?: string; - /** Root element where the dropdown will be rendered. */ - dropdownPortalRoot?: HTMLElement | null; - - /** Id of the dropdown portal where the dropdown will be rendered. */ - dropdownPortalId?: string; - /** Child components to be rendered inside the dropdown. */ children: ReactNode; } @@ -439,7 +442,11 @@ export const SearchBoxContent = ( { } return ( - +
( className={ cn( 'flex w-full items-center justify-start gap-1 p-1 cursor-pointer border-none bg-transparent text-left focus:outline-none', isActive && 'bg-background-secondary', - ! isActive && 'hover:bg-background-secondary focus:bg-background-secondary', + ! isActive && + 'hover:bg-background-secondary focus:bg-background-secondary', sizeClassNames.item[ size! ] ) } { ...getItemProps?.( { From e83a3719f04037bdfe4a763a1a352f316974b573 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:36:06 +0600 Subject: [PATCH 10/21] fix: TS errors --- src/components/search/search.tsx | 101 ++++++++++++++++--------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index feeff6e7..ab6dd984 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -37,36 +37,6 @@ import { useListItem, } from '@floating-ui/react'; -type TSearchContentValue = Partial<{ - size: 'sm' | 'md' | 'lg'; - searchTerm: string; - isLoading: boolean; - onOpenChange: ( open: boolean ) => void; - refs: UseFloatingReturn['refs']; - floatingStyles: UseFloatingReturn['floatingStyles']; - getReferenceProps: UseInteractionsReturn['getReferenceProps']; - getFloatingProps: UseInteractionsReturn['getFloatingProps']; - getItemProps: ( - userProps?: React.HTMLProps - ) => Record; - activeIndex: number | null; - setActiveIndex: React.Dispatch>; - listRef: React.MutableRefObject<( HTMLElement | null )[]>; - setSearchTerm: React.Dispatch>; - open: boolean; - context: UseFloatingReturn['context']; - setIsLoading: ( loading: boolean ) => void; - clearSearchOnClick: boolean; - closeOnClick: boolean; -}>; - -// Define a context for the SearchBox -const SearchContext = createContext( {} ); - -const useSearchContext = () => { - return useContext( SearchContext ); -}; - export interface CommonSearchBoxProps { /** Additional class names for styling. */ className?: string; @@ -116,6 +86,24 @@ type SearchBoxPortalProps = { root?: HTMLElement | null; }; +// Define props for SearchBoxInput +export interface SearchBoxInputProps extends CommonSearchBoxProps { + /** Type of the input (e.g., text, search). */ + type?: string; + + /** Placeholder text for the input. */ + placeholder?: string; + + /** Whether the input is disabled. */ + disabled?: boolean; + + /** Callback for input changes. */ + onChange?: ( value: string ) => void; + + /** Child components to be rendered. */ + children?: ReactNode; +} + // Extend the type to allow assigning subcomponents to SearchBox type SearchBoxComponent = React.ForwardRefExoticComponent< BaseSearchBoxProps & React.RefAttributes @@ -131,6 +119,37 @@ type SearchBoxComponent = React.ForwardRefExoticComponent< Portal: typeof SearchBoxPortal; }; +type TSearchContentValue = Partial<{ + size: 'sm' | 'md' | 'lg'; + searchTerm: string; + isLoading: boolean; + onOpenChange: ( open: boolean ) => void; + refs: UseFloatingReturn['refs']; + floatingStyles: UseFloatingReturn['floatingStyles']; + getReferenceProps: UseInteractionsReturn['getReferenceProps']; + getFloatingProps: UseInteractionsReturn['getFloatingProps']; + getItemProps: ( + userProps?: React.HTMLProps + ) => Record; + activeIndex: number | null; + setActiveIndex: React.Dispatch>; + listRef: React.MutableRefObject<( HTMLElement | null )[]>; + setSearchTerm: React.Dispatch>; + open: boolean; + context: UseFloatingReturn['context']; + setIsLoading: ( loading: boolean ) => void; + clearSearchOnClick: boolean; + closeOnClick: boolean; + variant: BaseSearchBoxProps['variant']; +}>; + +// Define a context for the SearchBox +const SearchContext = createContext( {} ); + +const useSearchContext = () => { + return useContext( SearchContext ); +}; + export const SearchBox = forwardRef( ( { @@ -262,24 +281,6 @@ export const SearchBox = forwardRef( ) as SearchBoxComponent; SearchBox.displayName = 'SearchBox'; -// Define props for SearchBoxInput -export interface SearchBoxInputProps extends CommonSearchBoxProps { - /** Type of the input (e.g., text, search). */ - type?: string; - - /** Placeholder text for the input. */ - placeholder?: string; - - /** Whether the input is disabled. */ - disabled?: boolean; - - /** Callback for input changes. */ - onChange?: ( value: string ) => void; - - /** Child components to be rendered. */ - children?: ReactNode; -} - export const SearchBoxInput = forwardRef( ( { @@ -367,10 +368,10 @@ export const SearchBoxInput = forwardRef( ref={ refs!.setReference } className={ cn( 'w-full group relative flex justify-center items-center gap-1.5 focus-within:z-10 transition-all ease-in-out duration-200', - variantClassNames[ variant ], + variantClassNames[ variant! ], sizeClassNames.input[ size! ], disabled - ? disabledClassNames[ variant ] + ? disabledClassNames[ variant! ] : 'focus-within:ring-2 focus-within:ring-focus focus-within:ring-offset-2 focus-within:border-focus-border focus-within:hover:border-focus-border', className ) } From b65063d6500f464dad634a0ad9436af8ea212594 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:39:42 +0600 Subject: [PATCH 11/21] Update search.stories.tsx --- src/components/search/search.stories.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/search/search.stories.tsx b/src/components/search/search.stories.tsx index daafcb83..d8d19074 100644 --- a/src/components/search/search.stories.tsx +++ b/src/components/search/search.stories.tsx @@ -94,8 +94,8 @@ export const SecondarySearchBox = Template.bind( {} ); SecondarySearchBox.args = {}; SecondarySearchBox.decorators = [ () => ( - - + + ), ]; @@ -104,8 +104,8 @@ export const GhostSearchBox = Template.bind( {} ); GhostSearchBox.args = {}; GhostSearchBox.decorators = [ () => ( - - + + ), ]; From c4f8675c4122fdecd4df89c1b0ac4558802813fb Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:06:12 +0600 Subject: [PATCH 12/21] Moved filter prop to the root component --- src/components/search/search.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index ab6dd984..fe71afe5 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -62,6 +62,9 @@ export interface BaseSearchBoxProps { /** Callback when dropdown state changes. */ onOpenChange?: ( open: boolean ) => void; + /** Whether to filter children based on the search term. Turn off when you want to filter children manually. */ + filter?: boolean; + /** Whether to show loading state. */ loading?: boolean; @@ -141,6 +144,7 @@ type TSearchContentValue = Partial<{ clearSearchOnClick: boolean; closeOnClick: boolean; variant: BaseSearchBoxProps['variant']; + filter: boolean; }>; // Define a context for the SearchBox @@ -161,6 +165,7 @@ export const SearchBox = forwardRef( clearSearchOnClick = false, closeOnClick = false, variant = 'primary', + filter = true, ...props }, ref @@ -265,6 +270,7 @@ export const SearchBox = forwardRef( clearSearchOnClick, closeOnClick, variant, + filter, } } >
{ - const { searchTerm, isLoading, listRef } = useSearchContext(); + const { searchTerm, isLoading, listRef, filter = true } = useSearchContext(); if ( ! filter ) { return ( @@ -531,7 +534,7 @@ export const SearchBoxList = ( { } return ( -
+
{ filteredChildren.some( ( child ) => React.isValidElement( child ) && From df1cf628712680a809388b56e30030dcb149ed4c Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:56:04 +0600 Subject: [PATCH 13/21] Apply className to the parent element --- src/components/search/search.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index fe71afe5..654836a7 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -671,7 +671,8 @@ export const SearchBoxItem = forwardRef( isActive && 'bg-background-secondary', ! isActive && 'hover:bg-background-secondary focus:bg-background-secondary', - sizeClassNames.item[ size! ] + sizeClassNames.item[ size! ], + className ) } { ...getItemProps?.( { role: 'option', @@ -694,7 +695,6 @@ export const SearchBoxItem = forwardRef( className={ cn( 'flex-grow p-1 font-normal', sizeClassNames.item[ size! ], - className ) } > { children } From 9152210d67e789e33e3693025f35a58ac253eb00 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:37:28 +0600 Subject: [PATCH 14/21] Update prop name --- src/components/search/search.stories.tsx | 2 +- src/components/search/search.tsx | 63 +++++++++++++++++------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/components/search/search.stories.tsx b/src/components/search/search.stories.tsx index d8d19074..fd16c7f2 100644 --- a/src/components/search/search.stories.tsx +++ b/src/components/search/search.stories.tsx @@ -49,7 +49,7 @@ const Template: StoryFn = ( args ) => { diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 654836a7..065b31f9 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -7,6 +7,7 @@ import React, { useContext, Children, cloneElement, + useMemo, } from 'react'; import { omit } from 'lodash'; // or define your own omit function import { cn, getOperatingSystem } from '@/utilities/functions'; @@ -59,7 +60,16 @@ export interface BaseSearchBoxProps { /** Whether the dropdown is open. */ open?: boolean; - /** Callback when dropdown state changes. */ + /** + * Call back function to handle the open state of the dropdown. + */ + setOpen?: ( open: boolean ) => void; + + /** + * Callback when dropdown state changes. + * + * @deprecated Use `setOpen` instead. + */ onOpenChange?: ( open: boolean ) => void; /** Whether to filter children based on the search term. Turn off when you want to filter children manually. */ @@ -71,11 +81,19 @@ export interface BaseSearchBoxProps { /** Child components to be rendered. */ children?: ReactNode; - /** Clear search on clicking result item. */ - clearSearchOnClick?: boolean; - - /** Close on clicking result item. */ - closeOnClick?: boolean; + /** + * Clear search term after selecting a result. + * + * @default true + */ + clearAfterSelect?: boolean; + + /** + * Close dropdown after selecting a result. + * + * @default true + */ + closeAfterSelect?: boolean; } type SearchBoxPortalProps = { @@ -141,8 +159,8 @@ type TSearchContentValue = Partial<{ open: boolean; context: UseFloatingReturn['context']; setIsLoading: ( loading: boolean ) => void; - clearSearchOnClick: boolean; - closeOnClick: boolean; + clearAfterSelect: boolean; + closeAfterSelect: boolean; variant: BaseSearchBoxProps['variant']; filter: boolean; }>; @@ -160,10 +178,11 @@ export const SearchBox = forwardRef( className, size = 'sm' as Size, open = false, - onOpenChange = () => {}, + setOpen = () => {}, + onOpenChange: _onOpenChange = () => {}, loading = false, - clearSearchOnClick = false, - closeOnClick = false, + clearAfterSelect = true, + closeAfterSelect = true, variant = 'primary', filter = true, ...props @@ -175,6 +194,16 @@ export const SearchBox = forwardRef( const [ activeIndex, setActiveIndex ] = useState( null ); const listRef = React.useRef<( HTMLElement | null )[]>( [] ); + /** + * Memoized function to handle the open state of the dropdown. + */ + const onOpenChange = useMemo( () => { + if ( typeof setOpen === 'function' ) { + return setOpen; + } + return _onOpenChange; + }, [ setOpen, _onOpenChange ] ); + const { refs, floatingStyles, context } = useFloating( { open, onOpenChange, @@ -267,8 +296,8 @@ export const SearchBox = forwardRef( setSearchTerm, isLoading, setIsLoading, - clearSearchOnClick, - closeOnClick, + clearAfterSelect, + closeAfterSelect, variant, filter, } } @@ -628,11 +657,11 @@ export const SearchBoxItem = forwardRef( const { size, setSearchTerm, - clearSearchOnClick, + clearAfterSelect, getItemProps, activeIndex, onOpenChange, - closeOnClick, + closeAfterSelect, } = useSearchContext(); const { ref: itemRef, index } = useListItem(); @@ -653,11 +682,11 @@ export const SearchBoxItem = forwardRef( onClick(); } - if ( clearSearchOnClick ) { + if ( clearAfterSelect ) { setSearchTerm!( '' ); } - if ( closeOnClick ) { + if ( closeAfterSelect ) { onOpenChange!( false ); } }; From 36fc5f718ba1f272aff36c8742fa0cb73727082d Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:07:39 +0600 Subject: [PATCH 15/21] Set default value to undefined --- src/components/search/search.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 065b31f9..22aa201d 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -178,8 +178,8 @@ export const SearchBox = forwardRef( className, size = 'sm' as Size, open = false, - setOpen = () => {}, - onOpenChange: _onOpenChange = () => {}, + setOpen, + onOpenChange: _onOpenChange, loading = false, clearAfterSelect = true, closeAfterSelect = true, From ecff26354c1780eb99adf424f34490c6ddaa4bc9 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:16:32 +0600 Subject: [PATCH 16/21] Add className to the wrapper - Add className prop to the Empty component --- src/components/search/search.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 22aa201d..1d02c2cf 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -531,7 +531,7 @@ export const SearchBoxList = ( { if ( ! filter ) { return ( -
{ children }
+
{ children }
); } @@ -583,10 +583,14 @@ SearchBoxList.displayName = 'SearchBox.List'; export interface SearchBoxEmptyProps { /** Content to display when there are no results. */ children?: ReactNode; + + /** Additional class names for styling. */ + className?: string; } export const SearchBoxEmpty = ( { children = 'No results found.', + className, }: SearchBoxEmptyProps ) => { const { size } = useSearchContext(); return ( @@ -594,7 +598,8 @@ export const SearchBoxEmpty = ( { className={ cn( 'flex justify-center items-center', sizeClassNames.item[ size! ], - 'text-text-tertiary p-4' + 'text-text-tertiary p-4', + className ) } > { children } From b9e1115715977ab807dc340260b116eca7bc8a2c Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Fri, 14 Mar 2025 15:12:43 +0600 Subject: [PATCH 17/21] Show inner outline on focus for ghost variant --- src/components/search/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/search/styles.ts b/src/components/search/styles.ts index 103568f6..191a0b59 100644 --- a/src/components/search/styles.ts +++ b/src/components/search/styles.ts @@ -46,7 +46,7 @@ export const variantClassNames = { 'bg-field-primary-background outline outline-1 outline-field-border hover:outline-border-strong focus-within:outline-focus-border focus-within:hover:outline-focus-border', secondary: 'bg-field-secondary-background outline outline-1 outline-field-border hover:outline-border-strong focus-within:outline-focus-border focus-within:hover:outline-focus-border', - ghost: 'bg-field-secondary-background outline outline-1 outline-transparent', + ghost: 'bg-field-secondary-background outline outline-1 outline-transparent focus-within:outline-focus-border', }; export const iconClasses = From ee98d5e7c48dd2c90f7a58e48f0ed53be1c34859 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Fri, 14 Mar 2025 15:15:54 +0600 Subject: [PATCH 18/21] Update search result item padding --- src/components/search/search.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 1d02c2cf..467cb541 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -727,7 +727,7 @@ export const SearchBoxItem = forwardRef( ) } From f05731b90755a00c8c5c4ef58b90a412e620cc12 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Fri, 14 Mar 2025 15:23:23 +0600 Subject: [PATCH 19/21] Update dropdown offset value --- src/components/search/search.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 467cb541..6e00b555 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -204,13 +204,27 @@ export const SearchBox = forwardRef( return _onOpenChange; }, [ setOpen, _onOpenChange ] ); + // Offset value for the dropdown based on the size. + const offsetValue = useMemo( () => { + switch ( size ) { + case 'sm': + return 4; + case 'md': + return 6; + case 'lg': + return 8; + default: + return 6; + } + }, [ size ] ); + const { refs, floatingStyles, context } = useFloating( { open, onOpenChange, placement: 'bottom-start', whileElementsMounted: autoUpdate, middleware: [ - offset( size === 'sm' ? 4 : 6 ), + offset( offsetValue ), flip( { padding: 10 } ), floatingSize( { apply( { rects, elements, availableHeight } ) { From 6fa2bafdad13f40f232eeffc82ded6e7bb88c75c Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Fri, 14 Mar 2025 15:41:48 +0600 Subject: [PATCH 20/21] Update offset to match the Figma mockup --- src/components/search/search.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 6e00b555..696f9d98 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -204,27 +204,13 @@ export const SearchBox = forwardRef( return _onOpenChange; }, [ setOpen, _onOpenChange ] ); - // Offset value for the dropdown based on the size. - const offsetValue = useMemo( () => { - switch ( size ) { - case 'sm': - return 4; - case 'md': - return 6; - case 'lg': - return 8; - default: - return 6; - } - }, [ size ] ); - const { refs, floatingStyles, context } = useFloating( { open, onOpenChange, placement: 'bottom-start', whileElementsMounted: autoUpdate, middleware: [ - offset( offsetValue ), + offset( 2 ), flip( { padding: 10 } ), floatingSize( { apply( { rects, elements, availableHeight } ) { From 6e8447266ec6567d83f0467ab923190029819745 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:02:34 +0600 Subject: [PATCH 21/21] Show keyboard shortcut based on OS --- src/components/search/search.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/search/search.tsx b/src/components/search/search.tsx index 696f9d98..3ec676a6 100644 --- a/src/components/search/search.tsx +++ b/src/components/search/search.tsx @@ -445,10 +445,11 @@ export const SearchBoxInput = forwardRef( ] ) } />
);