88} from '@react-aria/utils' ;
99import { useComboBoxState } from '@react-stately/combobox' ;
1010import { AriaButtonProps } from '@react-types/button' ;
11+ import { LoadingState } from '@react-types/shared' ;
1112import React , {
1213 ForwardedRef ,
1314 InputHTMLAttributes ,
@@ -21,7 +22,7 @@ import React, {
2122} from 'react' ;
2223
2324import { FieldButton } from '@keystar/ui/button' ;
24- import { useProvider , useProviderProps } from '@keystar/ui/core' ;
25+ import { useProviderProps } from '@keystar/ui/core' ;
2526import { FieldPrimitive } from '@keystar/ui/field' ;
2627import { Icon } from '@keystar/ui/icon' ;
2728import { chevronDownIcon } from '@keystar/ui/icon/icons/chevronDownIcon' ;
@@ -74,12 +75,11 @@ const ComboboxBase = React.forwardRef(function ComboboxBase<T extends object>(
7475 shouldFlip = true ,
7576 direction = 'bottom' ,
7677 loadingState,
77- menuWidth : menuWidthProp ,
78+ menuWidth,
7879 onLoadMore,
7980 } = props ;
8081
8182 let isAsync = loadingState != null ;
82- let stringFormatter = useLocalizedStringFormatter ( localizedMessages ) ;
8383 let buttonRef = useRef < HTMLButtonElement > ( null ) ;
8484 let inputRef = useRef < HTMLInputElement > ( null ) ;
8585 let listBoxRef = useRef < HTMLDivElement > ( null ) ;
@@ -114,31 +114,13 @@ const ComboboxBase = React.forwardRef(function ComboboxBase<T extends object>(
114114 state
115115 ) ;
116116
117- // Measure the width of the input and the button to inform the width of the menu (below).
118- let [ menuWidth , setMenuWidth ] = useState < number > ( ) ;
119- let { scale } = useProvider ( ) ;
120-
121- let onResize = useCallback ( ( ) => {
122- if ( buttonRef . current && inputRef . current ) {
123- let buttonWidth = buttonRef . current . offsetWidth ;
124- let inputWidth = inputRef . current . offsetWidth ;
125-
126- setMenuWidth ( inputWidth + buttonWidth ) ;
127- }
128- } , [ buttonRef , inputRef , setMenuWidth ] ) ;
129-
130- useResizeObserver ( {
131- ref : fieldRef ,
132- onResize : onResize ,
117+ let popoverStyle = usePopoverStyles ( {
118+ menuWidth,
119+ buttonRef,
120+ inputRef,
121+ fieldRef,
133122 } ) ;
134123
135- useLayoutEffect ( onResize , [ scale , onResize ] ) ;
136-
137- let style = {
138- width : menuWidth ,
139- minWidth : menuWidthProp ?? menuWidth ,
140- } ;
141-
142124 return (
143125 < >
144126 < FieldPrimitive
@@ -162,7 +144,7 @@ const ComboboxBase = React.forwardRef(function ComboboxBase<T extends object>(
162144 </ FieldPrimitive >
163145 < Popover
164146 state = { state }
165- UNSAFE_style = { style }
147+ UNSAFE_style = { popoverStyle }
166148 ref = { popoverRef }
167149 triggerRef = { align === 'end' ? buttonRef : inputRef }
168150 scrollRef = { listBoxRef }
@@ -185,30 +167,64 @@ const ComboboxBase = React.forwardRef(function ComboboxBase<T extends object>(
185167 onLoadMore = { onLoadMore }
186168 UNSAFE_className = { listStyles }
187169 renderEmptyState = { ( ) =>
188- isAsync && (
189- < Flex
190- height = "element.regular"
191- alignItems = "center"
192- paddingX = "medium"
193- >
194- < Text color = "neutralSecondary" >
195- { loadingState === 'loading'
196- ? stringFormatter . format ( 'loading' )
197- : stringFormatter . format ( 'noResults' ) }
198- </ Text >
199- </ Flex >
200- )
170+ isAsync && < ComboboxEmptyState loadingState = { loadingState } />
201171 }
202172 />
203173 </ Popover >
204174 </ >
205175 ) ;
206176} ) ;
207177
178+ export function ComboboxEmptyState ( props : { loadingState ?: LoadingState } ) {
179+ let stringFormatter = useLocalizedStringFormatter ( localizedMessages ) ;
180+ return (
181+ < Flex height = "element.regular" alignItems = "center" paddingX = "medium" >
182+ < Text color = "neutralSecondary" >
183+ { props . loadingState === 'loading'
184+ ? stringFormatter . format ( 'loading' )
185+ : stringFormatter . format ( 'noResults' ) }
186+ </ Text >
187+ </ Flex >
188+ ) ;
189+ }
190+
191+ export function usePopoverStyles ( props : {
192+ menuWidth ?: number ;
193+ buttonRef : RefObject < HTMLButtonElement > ;
194+ inputRef : RefObject < HTMLInputElement > ;
195+ fieldRef : RefObject < HTMLDivElement > ;
196+ } ) {
197+ const { buttonRef, inputRef, fieldRef, menuWidth : menuWidthProp } = props ;
198+
199+ // Measure the width of the input and the button to inform the width of the menu (below).
200+ let [ menuWidth , setMenuWidth ] = useState < number > ( ) ;
201+
202+ let onResize = useCallback ( ( ) => {
203+ if ( buttonRef . current && inputRef . current ) {
204+ let buttonWidth = buttonRef . current . offsetWidth ;
205+ let inputWidth = inputRef . current . offsetWidth ;
206+
207+ setMenuWidth ( inputWidth + buttonWidth ) ;
208+ }
209+ } , [ buttonRef , inputRef , setMenuWidth ] ) ;
210+
211+ useResizeObserver ( {
212+ ref : fieldRef ,
213+ onResize : onResize ,
214+ } ) ;
215+
216+ useLayoutEffect ( onResize , [ onResize ] ) ;
217+
218+ return {
219+ width : menuWidth ,
220+ minWidth : menuWidthProp ?? menuWidth ,
221+ } ;
222+ }
223+
208224// FIXME: this is a hack to work around a requirement of react-aria. object refs
209225// never have the value early enough, so we need to use a stateful ref to force
210226// a re-render.
211- function useStatefulRef < T extends HTMLElement > ( ) {
227+ export function useStatefulRef < T extends HTMLElement > ( ) {
212228 let [ current , statefulRef ] = useState < T | null > ( null ) ;
213229 return useMemo ( ( ) => {
214230 return [ { current } , statefulRef ] as const ;
@@ -224,7 +240,8 @@ interface ComboboxInputProps extends ComboboxProps<unknown> {
224240 isOpen ?: boolean ;
225241}
226242
227- const ComboboxInput = React . forwardRef ( function ComboboxInput (
243+ /** @private Used by multi variant. */
244+ export const ComboboxInput = React . forwardRef ( function ComboboxInput (
228245 props : ComboboxInputProps ,
229246 forwardedRef : ForwardedRef < HTMLDivElement >
230247) {
0 commit comments