feat: LinkPreview component#10243
Conversation
60493e5 to
4b2768d
Compare
| * A LinkPreview wraps a Link and a Popover to display a non-modal preview of the link's content | ||
| * on hover, focus, or long press. Unlike a tooltip, the popover may contain interactive content. | ||
| */ | ||
| export function LinkPreview(props: LinkPreviewProps): JSX.Element { |
There was a problem hiding this comment.
Name? Should it be LinkPreviewTrigger to match other trigger components? Is this too specific to this one use case? Are there other use cases that this would cover?
There was a problem hiding this comment.
IMO it doesn't seem specific to links, could see this being used for cards/other pieces of content that may want to display meta data + actions/interactive content on hover or focus. That being said, I can't find an actual example of that so far haha
This is like HoverCard in Radix, so maybe something similar? PreviewTrigger?
| * used by components such as LinkPreview to skip animations when quickly swapping between | ||
| * overlays. | ||
| */ | ||
| isInstant?: boolean; |
There was a problem hiding this comment.
not sure about this name. shouldSkipAnimation? any other ideas?
There was a problem hiding this comment.
I think I would prefer shouldSkipAnimation over isInstant, longer but usage is clear
| <Overlay | ||
| {...props} | ||
| shouldContainFocus={isDialog} | ||
| shouldContainFocus={isDialog && props.trigger !== 'LinkPreview'} |
There was a problem hiding this comment.
All these exceptions are pretty gross. We should discuss a general cleanup to popovers.
4b2768d to
f9a6408
Compare
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:Popover Popover {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowBoundaryOffset?: number = 0
arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
className?: ClassNameOrFunction<PopoverRenderProps> = 'react-aria-Popover'
containerPadding?: number = 12
crossOffset?: number = 0
defaultOpen?: boolean
getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
isEntering?: boolean
isExiting?: boolean
+ isInstant?: boolean
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
+ onBlurWithin?: (FocusEvent) => void
+ onFocusWithin?: (FocusEvent) => void
+ onFocusWithinChange?: (boolean) => void
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, PopoverRenderProps>
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
slot?: string | null
style?: StyleOrFunction<PopoverRenderProps>
trigger?: string
triggerRef?: RefObject<Element | null>
}/react-aria-components:PopoverProps PopoverProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowBoundaryOffset?: number = 0
arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
className?: ClassNameOrFunction<PopoverRenderProps> = 'react-aria-Popover'
containerPadding?: number = 12
crossOffset?: number = 0
defaultOpen?: boolean
getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
isEntering?: boolean
isExiting?: boolean
+ isInstant?: boolean
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
+ onBlurWithin?: (FocusEvent) => void
+ onFocusWithin?: (FocusEvent) => void
+ onFocusWithinChange?: (boolean) => void
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, PopoverRenderProps>
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
slot?: string | null
style?: StyleOrFunction<PopoverRenderProps>
trigger?: string
triggerRef?: RefObject<Element | null>
}/react-aria-components:TooltipTriggerState TooltipTriggerState {
close: (boolean) => void
+ isInstant: boolean
isOpen: boolean
open: (boolean) => void
}/react-aria-components:LinkPreview+LinkPreview {
+ children: ReactNode
+ closeDelay?: number = 500
+ defaultOpen?: boolean
+ delay?: number = 1500
+ isDisabled?: boolean
+ isOpen?: boolean
+ onOpenChange?: (boolean) => void
+}/react-aria-components:LinkPreviewProps+LinkPreviewProps {
+ children: ReactNode
+ closeDelay?: number = 500
+ defaultOpen?: boolean
+ delay?: number = 1500
+ isDisabled?: boolean
+ isOpen?: boolean
+ onOpenChange?: (boolean) => void
+}@react-aria/interactions/@react-aria/interactions:LongPressProps LongPressProps {
accessibilityDescription?: string
isDisabled?: boolean
onLongPress?: (LongPressEvent) => void
onLongPressEnd?: (LongPressEvent) => void
onLongPressStart?: (LongPressEvent) => void
+ pointerType?: 'mouse' | 'touch'
threshold?: number = 500ms
}@react-aria/overlays/@react-aria/overlays:AriaPopoverProps AriaPopoverProps {
arrowBoundaryOffset?: number = 0
arrowRef?: RefObject<Element | null>
arrowSize?: number = 0
boundaryElement?: Element = document.body
containerPadding?: number = 12
crossOffset?: number = 0
getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
groupRef?: RefObject<Element | null>
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
maxHeight?: number
offset?: number = 0
+ onBlurWithin?: (FocusEvent) => void
+ onFocusWithin?: (FocusEvent) => void
+ onFocusWithinChange?: (boolean) => void
placement?: Placement = 'bottom'
popoverRef: RefObject<Element | null>
scrollRef?: RefObject<Element | null> = overlayRef
shouldCloseOnInteractOutside?: (Element) => boolean
shouldUpdatePosition?: boolean = true
triggerRef: RefObject<Element | null>
}@react-spectrum/ai/@react-spectrum/ai:PromptTokenFieldPopoverProps PromptTokenFieldPopoverProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
containerPadding?: number = 12
crossOffset?: number = 0
defaultOpen?: boolean
filterAnchor?: Position | null
getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
hideArrow?: boolean = false
isEntering?: boolean
isExiting?: boolean
isFocused?: boolean
+ isInstant?: boolean
isNonModal?: boolean
isOpen?: boolean
items?: Array<React.ReactNode> | null | Promise<Array<React.ReactNode> | null>
maxHeight?: number
offset?: number = 8
+ onBlurWithin?: (FocusEvent) => void
+ onFocusWithin?: (FocusEvent) => void
+ onFocusWithinChange?: (boolean) => void
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
slot?: string | null
styles?: StyleString
trigger?: string
triggerRef?: RefObject<Element | null>
}@react-spectrum/s2/@react-spectrum/s2:PopoverProps PopoverProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
containerPadding?: number = 12
crossOffset?: number = 0
defaultOpen?: boolean
getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
hideArrow?: boolean = false
isEntering?: boolean
isExiting?: boolean
+ isInstant?: boolean
isNonModal?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
+ onBlurWithin?: (FocusEvent) => void
+ onFocusWithin?: (FocusEvent) => void
+ onFocusWithinChange?: (boolean) => void
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
slot?: string | null
styles?: StyleString
trigger?: string
triggerRef?: RefObject<Element | null>
}@react-stately/tooltip/@react-stately/tooltip:TooltipTriggerState TooltipTriggerState {
close: (boolean) => void
+ isInstant: boolean
isOpen: boolean
open: (boolean) => void
} |
Agent Skills ChangesModified (4)
InstallReact Spectrum S2: React Aria: |
LFDanLu
left a comment
There was a problem hiding this comment.
The behavior feels good to me so far mouse and keyboard wise. I'm not quite sure how I feel about the long press behavior on mobile though since it prevents me from opening the native long press menu (aka if I wanted to open in group/incog/copy link text) but I don't know if there is a good alternative though.
| * used by components such as LinkPreview to skip animations when quickly swapping between | ||
| * overlays. | ||
| */ | ||
| isInstant?: boolean; |
There was a problem hiding this comment.
I think I would prefer shouldSkipAnimation over isInstant, longer but usage is clear
| * A LinkPreview wraps a Link and a Popover to display a non-modal preview of the link's content | ||
| * on hover, focus, or long press. Unlike a tooltip, the popover may contain interactive content. | ||
| */ | ||
| export function LinkPreview(props: LinkPreviewProps): JSX.Element { |
There was a problem hiding this comment.
IMO it doesn't seem specific to links, could see this being used for cards/other pieces of content that may want to display meta data + actions/interactive content on hover or focus. That being said, I can't find an actual example of that so far haha
This is like HoverCard in Radix, so maybe something similar? PreviewTrigger?
Closes #8905, closes #9257
WIP: opening for API and behavior discussion.
This introduces a new
LinkPreviewcomponent, which composes aLinkwith a non-modalPopoverthat opens on hover, keyboard focus, and long press on touch devices. It's intended to display a preview of the link's content, similar to GitHub's UI when hovering over the issue links above. Unlike a tooltip, the popover may contain interactive/focusable content. Once open, users can tab from the trigger link into the popover, and from the popover to the following content.Behavior details:
isInstantstate (name tbd) indicating that it was opened when the global warmup period was active. This causes tooltip/link previews to skip their opening animations.