mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
Both old names are owned by a different npm account (artemis-prime), so publishing them returns 403 — which is how this surfaced: the 8.0.0 run shipped @hanzogui/logo and @hanzogui/demos depending on [email protected], a version that cannot be published and therefore does not exist. Those two were uninstallable. @hanzo/gui and @hanzogui/* are ours, so the framework now carries the name it is imported by everywhere downstream, and the loader moves under the scope we control. The rewrite is limited to package references. `DEBUG?.startsWith('hanzogui')` is a log-namespace filter and join(dir, 'hanzogui') is a path — rewriting either would change behaviour, so both are untouched. Co-authored-by: Hanzo Dev <[email protected]>
433 lines
13 KiB
Plaintext
433 lines
13 KiB
Plaintext
---
|
|
title: Popover
|
|
description: Show content with a trigger in a floating pane
|
|
name: popover
|
|
component: Popover
|
|
package: popover
|
|
demoName: Popover
|
|
---
|
|
|
|
<HeroContainer showAnimationDriverControl>
|
|
<PopoverDemo />
|
|
</HeroContainer>
|
|
|
|
```tsx hero template=Popover
|
|
|
|
```
|
|
|
|
<Highlights
|
|
features={[
|
|
`Optional arrow to point to content.`,
|
|
`Keeps within bounds of page.`,
|
|
`Can be placed into 12 anchor positions.`,
|
|
]}
|
|
/>
|
|
|
|
Popovers are a great way to show content that's only visible when trigger is
|
|
pressed, floating above the current content. They [automatically stack](/ui/z-index) above other content. Be sure to open the code example above for a copy-paste implementation.
|
|
|
|
<Notice>
|
|
Note: Popovers are not a recommended pattern for mobile apps. Instead you can use Adapt
|
|
and render them as a Sheet, or just conditionally render them to some native UI.
|
|
</Notice>
|
|
|
|
## Installation
|
|
|
|
Popover is already installed in `hanzogui`, or you can install it independently:
|
|
|
|
```bash
|
|
npm install @hanzogui/popover
|
|
```
|
|
|
|
<Notice theme="blue">
|
|
For native apps, we recommend [setting up native
|
|
portals](/docs/components/portal#native-portal-setup-recommended) to preserve React
|
|
context inside Popover content.
|
|
</Notice>
|
|
|
|
## Anatomy
|
|
|
|
```tsx
|
|
import { Popover, Adapt, Sheet } from '@hanzo/gui' // or '@hanzogui/popover'
|
|
|
|
export default () => (
|
|
<Popover>
|
|
<Popover.Trigger />
|
|
|
|
{/* Optional: Control focus behavior */}
|
|
<Popover.FocusScope loop trapped focusOnIdle={true}>
|
|
<Popover.Content>
|
|
<Popover.Arrow />
|
|
<Popover.Close />
|
|
{/* ScrollView is optional, can just put any contents inside if not scrollable */}
|
|
<Popover.ScrollView>{/* ... */}</Popover.ScrollView>
|
|
{/* ... */}
|
|
</Popover.Content>
|
|
</Popover.FocusScope>
|
|
|
|
{/* optionally change to sheet when small screen */}
|
|
{/* you can also use <Popover.Adapt /> */}
|
|
<Adapt when="max-md">
|
|
<Sheet>
|
|
<Sheet.Overlay />
|
|
<Sheet.Frame>
|
|
<Sheet.ScrollView>
|
|
<Adapt.Contents />
|
|
</Sheet.ScrollView>
|
|
</Sheet.Frame>
|
|
</Sheet>
|
|
</Adapt>
|
|
</Popover>
|
|
)
|
|
```
|
|
|
|
## Scoping
|
|
|
|
Popover supports scoping which lets you mount one or more Popover instances at
|
|
the root of your app, while having a deeply nested child Trigger or Content
|
|
attach to the proper parent Popover instance.
|
|
|
|
In performance sensitive areas you may want to take advantage of this as it allows
|
|
you to only render the Popover.Trigger inside the sensitive area.
|
|
Popover isn't the cheapest component - it has a lot of functionality
|
|
inside of it like scroll management, focus management, and tracking position.
|
|
|
|
Here's the basic anatomy of using `scope` and placing your Popover higher up
|
|
for performance:
|
|
|
|
```tsx fileName=_layout.tsx
|
|
import { Popover } from '@hanzo/gui'
|
|
|
|
// in your root layout:
|
|
export default ({ children }) => (
|
|
<Popover scope="user-avatar">
|
|
<Popover.Content>
|
|
<Popover.Arrow />
|
|
<Popover.Close />
|
|
<Popover.ScrollView>{/* ... */}</Popover.ScrollView>
|
|
</Popover.Content>
|
|
|
|
{/* the rest of your app, note that it's inside of Popover */}
|
|
{children}
|
|
</Popover>
|
|
)
|
|
```
|
|
|
|
```tsx fileName=UserAvatar.tsx
|
|
export default () => (
|
|
<Popover.Trigger scope="user-avatar">
|
|
<Avatar />
|
|
</Popover.Trigger>
|
|
)
|
|
```
|
|
|
|
Note that the `Trigger` scope ties to the `Popover` scope.
|
|
|
|
## API Reference
|
|
|
|
### Popover
|
|
|
|
Contains every component for the popover.
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'children',
|
|
type: 'React.ReactNode',
|
|
required: true,
|
|
description: `Must contain Popover.Content`,
|
|
},
|
|
{
|
|
name: 'size',
|
|
type: 'SizeTokens',
|
|
required: false,
|
|
description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
|
|
},
|
|
{
|
|
name: 'placement',
|
|
type: 'Placement',
|
|
required: false,
|
|
description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
|
|
},
|
|
{
|
|
name: 'open',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `Controlled open state of the popover.`,
|
|
},
|
|
{
|
|
name: 'defaultOpen',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `Initial open state when uncontrolled.`,
|
|
},
|
|
{
|
|
name: 'onOpenChange',
|
|
type: `(open: boolean, via?: 'hover' | 'press') => void`,
|
|
required: false,
|
|
description: `Called when the popover opens or closes. The optional second argument indicates how it was triggered - 'hover' for hover events (when using the hoverable prop) or 'press' for click/press events.`,
|
|
},
|
|
{
|
|
name: 'keepChildrenMounted',
|
|
type: 'boolean | "lazy"',
|
|
required: false,
|
|
description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.`,
|
|
},
|
|
{
|
|
name: 'disableDismissable',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `Disables the dismissable layer (escape key, outside click handling). Useful when using keepChildrenMounted for popovers that stay mounted but are visually hidden - set this to true when the popover is hidden to prevent it from capturing escape key presses.`,
|
|
},
|
|
{
|
|
name: 'stayInFrame',
|
|
type: 'ShiftProps | boolean',
|
|
required: false,
|
|
default: '{ padding: 10 }',
|
|
description: `Shifts the popover horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).`,
|
|
},
|
|
{
|
|
name: 'allowFlip',
|
|
type: 'FlipProps | boolean',
|
|
required: false,
|
|
description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
|
|
},
|
|
{
|
|
name: 'offset',
|
|
type: 'OffsetOptions',
|
|
required: false,
|
|
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
|
|
},
|
|
{
|
|
name: 'hoverable',
|
|
type: 'boolean | UseFloatingProps',
|
|
required: false,
|
|
description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
|
|
},
|
|
{
|
|
name: 'resize',
|
|
type: 'SizeProps | boolean',
|
|
required: false,
|
|
description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
|
|
},
|
|
{
|
|
name: 'zIndex',
|
|
type: 'number',
|
|
required: false,
|
|
description: `Override the automatic z-index stacking. By default, Gui automatically stacks overlays so later-opened content appears above earlier content. Only set this if you need to override the automatic behavior.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
For most of these properties, you'll want to reference the
|
|
[floating-ui docs](https://floating-ui.com/docs/getting-started).
|
|
|
|
<Notice>
|
|
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
|
|
installation](#portalprovider) for more information.
|
|
</Notice>
|
|
|
|
### Popover.Arrow
|
|
|
|
Popover.Arrow can be used to show an arrow that points at the Trigger element.
|
|
In order for the Arrow to show you must have a Trigger element within your
|
|
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'animatePosition',
|
|
type: 'boolean',
|
|
description: 'Enable smooth animation when the arrow position changes.',
|
|
},
|
|
{
|
|
name: 'size',
|
|
type: 'SizeTokens',
|
|
description: 'Size of the arrow.',
|
|
},
|
|
{
|
|
name: 'offset',
|
|
type: 'number',
|
|
description: 'Offset from the content edge.',
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Popover.Trigger
|
|
|
|
Used to trigger opening of the popover when uncontrolled, just renders a YStack,
|
|
see [Stacks](/docs/components/stacks).
|
|
|
|
### Popover.Content
|
|
|
|
Extends PopperContent which extends SizableStack which extends a YStack (see
|
|
[Stacks](/docs/components/stacks)). Used to display the content of the popover.
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'animatePosition',
|
|
type: "boolean | 'even-when-repositioning'",
|
|
description:
|
|
'Enable smooth animation when the content position changes (e.g., when flipping sides).',
|
|
},
|
|
{
|
|
name: 'transformOrigin',
|
|
type: 'boolean',
|
|
default: 'true',
|
|
description:
|
|
'Automatically sets CSS transform-origin based on placement and arrow position. Updates when the popover flips. Enables natural scale animations that grow from the arrow point.',
|
|
},
|
|
{
|
|
name: 'size',
|
|
type: 'SizeTokens',
|
|
required: false,
|
|
description: 'Controls default padding/borderRadius when unstyled is false.',
|
|
},
|
|
{
|
|
name: 'unstyled',
|
|
required: false,
|
|
type: `boolean`,
|
|
default: false,
|
|
description: `Removes all default Gui styles.`,
|
|
},
|
|
{
|
|
name: 'trapFocus',
|
|
type: 'boolean',
|
|
default: false,
|
|
description: 'Whether focus should be trapped within the `Popover`',
|
|
},
|
|
{
|
|
name: 'disableFocusScope',
|
|
type: 'boolean',
|
|
default: false,
|
|
description: 'Whether popover should not focus contents on open',
|
|
},
|
|
{
|
|
name: 'onOpenAutoFocus',
|
|
type: `FocusScopeProps['onMountAutoFocus']`,
|
|
default: false,
|
|
description: 'Event handler called when auto-focusing on open. Can be prevented.',
|
|
},
|
|
{
|
|
name: 'onCloseAutoFocus',
|
|
type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
|
|
default: false,
|
|
description: 'Event handler called when auto-focusing on close. Can be prevented.',
|
|
},
|
|
{
|
|
name: 'lazyMount',
|
|
required: false,
|
|
type: `boolean`,
|
|
description: `Delays mounting content until first open.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Popover.Anchor
|
|
|
|
Renders as YStack, see [Stacks](/docs/components/stacks).
|
|
|
|
When you want the Trigger to be in another location from where the Popover
|
|
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
|
|
Trigger will open it.
|
|
|
|
### Sheet (with Adapt)
|
|
|
|
When used with `Adapt`, you can render a Sheet when that breakpoint is
|
|
active. Import `Sheet` directly from `hanzogui` or `@hanzogui/sheet`.
|
|
|
|
See [Sheet](/docs/components/sheet) for more props.
|
|
|
|
Must use `Adapt.Contents` inside the `Sheet.Frame` to insert the
|
|
contents given to `Popover.Content`
|
|
|
|
### Popover.FocusScope
|
|
|
|
Provides access to the underlying FocusScope component used by Popover for focus
|
|
management. Can be used to control focus behavior from a parent component.
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'enabled',
|
|
type: 'boolean',
|
|
default: 'true',
|
|
description: `Whether focus management is enabled`,
|
|
},
|
|
{
|
|
name: 'loop',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
|
|
},
|
|
{
|
|
name: 'trapped',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
|
|
},
|
|
{
|
|
name: 'focusOnIdle',
|
|
type: 'boolean | number',
|
|
default: 'false',
|
|
description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
|
|
},
|
|
{
|
|
name: 'onMountAutoFocus',
|
|
type: '(event: Event) => void',
|
|
description: `Event handler called when auto-focusing on mount. Can be prevented`,
|
|
},
|
|
{
|
|
name: 'onUnmountAutoFocus',
|
|
type: '(event: Event) => void',
|
|
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Popover.ScrollView
|
|
|
|
Must be nested inside Content. Renders as a plain React Native ScrollView. If
|
|
used alongside `<Adapt />` and Sheet, Gui will automatically know to remove
|
|
this ScrollView when swapping into the Sheet, as the Sheet must use its own
|
|
ScrollView that handles special logic for interactions with dragging.
|
|
|
|
## Utility Functions
|
|
|
|
These functions allow you to programmatically manage open popovers.
|
|
|
|
```tsx
|
|
import {
|
|
closeOpenPopovers,
|
|
closeLastOpenedPopover,
|
|
hasOpenPopovers,
|
|
} from '@hanzogui/popover'
|
|
```
|
|
|
|
### closeOpenPopovers
|
|
|
|
Closes all currently open popovers. Returns `true` if any popovers were closed, `false` if none were open.
|
|
|
|
```tsx
|
|
const didClose = closeOpenPopovers()
|
|
```
|
|
|
|
### closeLastOpenedPopover
|
|
|
|
Closes only the most recently opened popover. Returns `true` if a popover was closed, `false` if none were open.
|
|
|
|
```tsx
|
|
const didClose = closeLastOpenedPopover()
|
|
```
|
|
|
|
### hasOpenPopovers
|
|
|
|
Returns `true` if there are any open popovers, `false` otherwise.
|
|
|
|
```tsx
|
|
if (hasOpenPopovers()) {
|
|
// handle open popovers
|
|
}
|
|
```
|