-
-
Notifications
You must be signed in to change notification settings - Fork 967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
♻️ (tooltip) refactoring to react-aria (vol.3) #2631
Changes from all commits
c842b90
8834f02
28d909f
3474c12
e0401b0
e6e08b1
3500041
5a812db
2990c6d
4d41d83
7787d38
12dd06c
6135671
4ddf446
cd0fbfa
82beb0f
f6e2a55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,11 +16,11 @@ import { css } from 'glamor'; | |
|
||
import { SvgRemove } from '../../icons/v2'; | ||
import { useResponsive } from '../../ResponsiveProvider'; | ||
import { theme, type CSSProperties, styles } from '../../style'; | ||
import { theme, styles } from '../../style'; | ||
import { Button } from '../common/Button'; | ||
import { Input } from '../common/Input'; | ||
import { Popover } from '../common/Popover'; | ||
import { View } from '../common/View'; | ||
import { Tooltip } from '../tooltips'; | ||
|
||
type CommonAutocompleteProps<T extends Item> = { | ||
focused?: boolean; | ||
|
@@ -31,8 +31,6 @@ type CommonAutocompleteProps<T extends Item> = { | |
onChange?: (value: string) => void; | ||
}; | ||
suggestions?: T[]; | ||
tooltipStyle?: CSSProperties; | ||
tooltipProps?: ComponentProps<typeof Tooltip>; | ||
renderInput?: (props: ComponentProps<typeof Input>) => ReactNode; | ||
renderItems?: ( | ||
items: T[], | ||
|
@@ -214,8 +212,6 @@ function SingleAutocomplete<T extends Item>({ | |
labelProps = {}, | ||
inputProps = {}, | ||
suggestions, | ||
tooltipStyle, | ||
tooltipProps, | ||
renderInput = defaultRenderInput, | ||
renderItems = defaultRenderItems, | ||
itemToString = defaultItemToString, | ||
|
@@ -253,6 +249,8 @@ function SingleAutocomplete<T extends Item>({ | |
onClose?.(); | ||
}; | ||
|
||
const triggerRef = useRef(null); | ||
|
||
const { isNarrowWidth } = useResponsive(); | ||
const narrowInputStyle = isNarrowWidth | ||
? { | ||
|
@@ -442,115 +440,122 @@ function SingleAutocomplete<T extends Item>({ | |
// can't use a View here, but we can fake it be using the | ||
// className | ||
<div className={`view ${css({ display: 'flex' })}`} {...containerProps}> | ||
{renderInput( | ||
getInputProps({ | ||
focused, | ||
...inputProps, | ||
onFocus: e => { | ||
inputProps.onFocus?.(e); | ||
|
||
if (openOnFocus) { | ||
open(); | ||
} | ||
}, | ||
onBlur: e => { | ||
// Should this be e.nativeEvent | ||
e['preventDownshiftDefault'] = true; | ||
inputProps.onBlur?.(e); | ||
|
||
if (!closeOnBlur) return; | ||
|
||
if (clearOnBlur) { | ||
if (e.target.value === '') { | ||
onSelect?.(null, e.target.value); | ||
setSelectedItem(null); | ||
close(); | ||
return; | ||
<View ref={triggerRef} style={{ flexShrink: 0 }}> | ||
{renderInput( | ||
getInputProps({ | ||
focused, | ||
...inputProps, | ||
onFocus: e => { | ||
inputProps.onFocus?.(e); | ||
|
||
if (openOnFocus) { | ||
open(); | ||
} | ||
}, | ||
onBlur: e => { | ||
// Should this be e.nativeEvent | ||
e['preventDownshiftDefault'] = true; | ||
inputProps.onBlur?.(e); | ||
|
||
if (!closeOnBlur) return; | ||
|
||
if (clearOnBlur) { | ||
if (e.target.value === '') { | ||
onSelect?.(null, e.target.value); | ||
setSelectedItem(null); | ||
close(); | ||
return; | ||
} | ||
|
||
// If not using table behavior, reset the input on blur. Tables | ||
// handle saving the value on blur. | ||
const value = selectedItem ? getItemId(selectedItem) : null; | ||
|
||
resetState(value); | ||
} else { | ||
close(); | ||
} | ||
}, | ||
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => { | ||
const { onKeyDown } = inputProps || {}; | ||
|
||
// If the dropdown is open, an item is highlighted, and the user | ||
// pressed enter, always capture that and handle it ourselves | ||
if (isOpen) { | ||
if (e.key === 'Enter') { | ||
if (highlightedIndex != null) { | ||
if ( | ||
inst.lastChangeType === | ||
Downshift.stateChangeTypes.itemMouseEnter | ||
) { | ||
// If the last thing the user did was hover an item, intentionally | ||
// ignore the default behavior of selecting the item. It's too | ||
// common to accidentally hover an item and then save it | ||
e.preventDefault(); | ||
} else { | ||
// Otherwise, stop propagation so that the table navigator | ||
// doesn't handle it | ||
// If not using table behavior, reset the input on blur. Tables | ||
// handle saving the value on blur. | ||
const value = selectedItem ? getItemId(selectedItem) : null; | ||
|
||
resetState(value); | ||
} else { | ||
close(); | ||
} | ||
}, | ||
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => { | ||
const { onKeyDown } = inputProps || {}; | ||
|
||
// If the dropdown is open, an item is highlighted, and the user | ||
// pressed enter, always capture that and handle it ourselves | ||
if (isOpen) { | ||
if (e.key === 'Enter') { | ||
if (highlightedIndex != null) { | ||
if ( | ||
inst.lastChangeType === | ||
Downshift.stateChangeTypes.itemMouseEnter | ||
) { | ||
// If the last thing the user did was hover an item, intentionally | ||
// ignore the default behavior of selecting the item. It's too | ||
// common to accidentally hover an item and then save it | ||
e.preventDefault(); | ||
} else { | ||
// Otherwise, stop propagation so that the table navigator | ||
// doesn't handle it | ||
e.stopPropagation(); | ||
} | ||
} else if (!strict) { | ||
// Handle it ourselves | ||
e.stopPropagation(); | ||
onSelect(value, (e.target as HTMLInputElement).value); | ||
return onSelectAfter(); | ||
} else { | ||
// No highlighted item, still allow the table to save the item | ||
// as `null`, even though we're allowing the table to move | ||
e.preventDefault(); | ||
onKeyDown?.(e); | ||
} | ||
} else if (!strict) { | ||
// Handle it ourselves | ||
e.stopPropagation(); | ||
onSelect(value, (e.target as HTMLInputElement).value); | ||
return onSelectAfter(); | ||
} else { | ||
// No highlighted item, still allow the table to save the item | ||
// as `null`, even though we're allowing the table to move | ||
} else if (shouldSaveFromKey(e)) { | ||
e.preventDefault(); | ||
onKeyDown?.(e); | ||
} | ||
} else if (shouldSaveFromKey(e)) { | ||
e.preventDefault(); | ||
onKeyDown?.(e); | ||
} | ||
} | ||
|
||
// Handle escape ourselves | ||
if (e.key === 'Escape') { | ||
e.nativeEvent['preventDownshiftDefault'] = true; | ||
// Handle escape ourselves | ||
if (e.key === 'Escape') { | ||
e.nativeEvent['preventDownshiftDefault'] = true; | ||
|
||
if (!embedded) { | ||
e.stopPropagation(); | ||
} | ||
if (!embedded) { | ||
e.stopPropagation(); | ||
} | ||
|
||
fireUpdate( | ||
onUpdate, | ||
strict, | ||
suggestions, | ||
null, | ||
getItemId(originalItem), | ||
); | ||
|
||
setValue(getItemName(originalItem)); | ||
setSelectedItem(findItem(strict, suggestions, originalItem)); | ||
setHighlightedIndex(null); | ||
if (embedded) { | ||
open(); | ||
} else { | ||
close(); | ||
fireUpdate( | ||
onUpdate, | ||
strict, | ||
suggestions, | ||
null, | ||
getItemId(originalItem), | ||
); | ||
|
||
setValue(getItemName(originalItem)); | ||
setSelectedItem( | ||
findItem(strict, suggestions, originalItem), | ||
); | ||
setHighlightedIndex(null); | ||
if (embedded) { | ||
open(); | ||
} else { | ||
close(); | ||
} | ||
} | ||
} | ||
}, | ||
onChange: (e: ChangeEvent<HTMLInputElement>) => { | ||
const { onChange } = inputProps || {}; | ||
onChange?.(e.target.value); | ||
}, | ||
}), | ||
)} | ||
}, | ||
onChange: (e: ChangeEvent<HTMLInputElement>) => { | ||
const { onChange } = inputProps || {}; | ||
onChange?.(e.target.value); | ||
}, | ||
}), | ||
)} | ||
</View> | ||
{isOpen && | ||
filtered.length > 0 && | ||
(embedded ? ( | ||
<View style={{ marginTop: 5 }} data-testid="autocomplete"> | ||
<View | ||
style={{ ...styles.darkScrollbar, marginTop: 5 }} | ||
data-testid="autocomplete" | ||
> | ||
{renderItems( | ||
filtered, | ||
getItemProps, | ||
|
@@ -559,17 +564,20 @@ function SingleAutocomplete<T extends Item>({ | |
)} | ||
</View> | ||
) : ( | ||
<Tooltip | ||
position="bottom-stretch" | ||
<Popover | ||
triggerRef={triggerRef} | ||
placement="bottom start" | ||
offset={2} | ||
isOpen={isOpen} | ||
onOpenChange={close} | ||
isNonModal | ||
style={{ | ||
padding: 0, | ||
...styles.darkScrollbar, | ||
backgroundColor: theme.menuAutoCompleteBackground, | ||
color: theme.menuAutoCompleteText, | ||
minWidth: 200, | ||
...tooltipStyle, | ||
width: triggerRef.current?.clientWidth, | ||
}} | ||
{...tooltipProps} | ||
data-testid="autocomplete" | ||
> | ||
{renderItems( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can try wrapping this in a View and setting the scrollbar style there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you be open to try this out locally? I'm working blindly here without a way to reproduce. |
||
|
@@ -578,7 +586,7 @@ function SingleAutocomplete<T extends Item>({ | |
highlightedIndex, | ||
inputValue, | ||
)} | ||
</Tooltip> | ||
</Popover> | ||
))} | ||
</div> | ||
)} | ||
|
@@ -626,13 +634,8 @@ function MultiAutocomplete<T extends Item>({ | |
...props | ||
}: MultiAutocompleteProps<T>) { | ||
const [focused, setFocused] = useState(false); | ||
const lastSelectedItems = useRef<typeof selectedItems>(); | ||
const selectedItemIds = selectedItems.map(getItemId); | ||
|
||
useEffect(() => { | ||
lastSelectedItems.current = selectedItems; | ||
}); | ||
|
||
function onRemoveItem(id: T['id']) { | ||
const items = selectedItemIds.filter(i => i !== id); | ||
onSelect(items); | ||
|
@@ -669,9 +672,6 @@ function MultiAutocomplete<T extends Item>({ | |
onSelect={onAddItem} | ||
highlightFirst | ||
strict={strict} | ||
tooltipProps={{ | ||
forceLayout: lastSelectedItems.current !== selectedItems, | ||
}} | ||
renderInput={inputProps => ( | ||
<View | ||
style={{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of putting this in a
style
prop, can we try using glamor to pass this toclassName
? Maybe the inlinestyle
prop doesn't support pseudo selectors. Another possible option is to just use thescrollbarWidth
andscrollColor
css props instead of thedarkScrollbar
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using glamor is a good idea! Updated it now, let me know how it looks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still is not working, we need to check that the darkScrollbar is set in the children's container element.