mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(popover): component created, a11y passing, tests passing
This commit is contained in:
parent
f51d19b09b
commit
fdaaad4210
@ -1,11 +1,21 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import {render, fireEvent, act} from "@testing-library/react";
|
||||
import {Button} from "@nextui-org/button";
|
||||
|
||||
import {Popover} from "../src";
|
||||
import {Popover, PopoverContent, PopoverTrigger} from "../src";
|
||||
|
||||
describe("Popover", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Popover />);
|
||||
const wrapper = render(
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<button>Open popover</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
@ -13,7 +23,105 @@ describe("Popover", () => {
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(<Popover ref={ref} />);
|
||||
render(
|
||||
<Popover ref={ref}>
|
||||
<PopoverTrigger>
|
||||
<button>Open popover</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should hide the popover when pressing the escape key", () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
const wrapper = render(
|
||||
<Popover isOpen onOpenChange={(isOpen) => (!isOpen ? onClose() : undefined)}>
|
||||
<PopoverTrigger>
|
||||
<button>Open popover</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent data-testid="content-test">
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
const content = wrapper.getByTestId("content-test");
|
||||
|
||||
fireEvent.keyDown(content, {key: "Escape"});
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should still hide the popover when pressing the escape key ", () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
const wrapper = render(
|
||||
<Popover isOpen onOpenChange={(isOpen) => (!isOpen ? onClose() : undefined)}>
|
||||
<PopoverTrigger>
|
||||
<button>Open popover</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent data-testid="content-test">
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
const content = wrapper.getByTestId("content-test");
|
||||
|
||||
fireEvent.keyDown(content, {key: "Escape"});
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should hide the popover on blur ", () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
const wrapper = render(
|
||||
<Popover isOpen onOpenChange={(isOpen) => (!isOpen ? onClose() : undefined)}>
|
||||
<PopoverTrigger>
|
||||
<button>Open popover</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent data-testid="content-test">
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
const content = wrapper.getByTestId("content-test");
|
||||
|
||||
fireEvent.blur(content);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should work with NextUI button", () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
const wrapper = render(
|
||||
<Popover onOpenChange={(isOpen) => (!isOpen ? onClose() : undefined)}>
|
||||
<PopoverTrigger>
|
||||
<Button data-testid="trigger-test">Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<p>This is the content of the popover.</p>
|
||||
</PopoverContent>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
const trigger = wrapper.getByTestId("trigger-test");
|
||||
|
||||
// open popover
|
||||
act(() => {
|
||||
trigger.click();
|
||||
});
|
||||
expect(onClose).toHaveBeenCalledTimes(0);
|
||||
|
||||
// close popover
|
||||
act(() => {
|
||||
trigger.click();
|
||||
});
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -10,5 +10,8 @@ export type {PopoverContentProps} from "./popover-content";
|
||||
// export hooks
|
||||
export {usePopover} from "./use-popover";
|
||||
|
||||
// export context
|
||||
export * from "./popover-context";
|
||||
|
||||
// export components
|
||||
export {Popover, PopoverTrigger, PopoverContent};
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import {ReactNode, useMemo, useCallback} from "react";
|
||||
import type {AriaDialogProps} from "@react-aria/dialog";
|
||||
|
||||
import {ReactNode, useMemo, useRef} from "react";
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {DismissButton} from "@react-aria/overlays";
|
||||
import {TRANSITION_VARIANTS} from "@nextui-org/framer-transitions";
|
||||
import {FocusScope} from "@react-aria/focus";
|
||||
import {motion} from "framer-motion";
|
||||
import {getTransformOrigins} from "@nextui-org/aria-utils";
|
||||
import {useDialog} from "@react-aria/dialog";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
|
||||
import {usePopoverContext} from "./popover-context";
|
||||
|
||||
export interface PopoverContentProps {
|
||||
export interface PopoverContentProps extends AriaDialogProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
@ -17,19 +20,25 @@ const PopoverContent = forwardRef<PopoverContentProps, "section">((props, _) =>
|
||||
|
||||
const {
|
||||
Component: OverlayComponent,
|
||||
isOpen,
|
||||
placement,
|
||||
showArrow,
|
||||
motionProps,
|
||||
disableAnimation,
|
||||
getPopoverProps,
|
||||
getArrowProps,
|
||||
getDialogProps,
|
||||
onClose,
|
||||
} = usePopoverContext();
|
||||
|
||||
const Component = as || OverlayComponent || "div";
|
||||
|
||||
const {style, className, ...otherPopoverProps} = getPopoverProps(otherProps);
|
||||
const dialogRef = useRef(null);
|
||||
const {dialogProps} = useDialog(
|
||||
{
|
||||
role: "dialog",
|
||||
},
|
||||
dialogRef,
|
||||
);
|
||||
|
||||
const arrowContent = useMemo(() => {
|
||||
if (!showArrow) return null;
|
||||
@ -37,42 +46,24 @@ const PopoverContent = forwardRef<PopoverContentProps, "section">((props, _) =>
|
||||
return <span {...getArrowProps()} />;
|
||||
}, [showArrow, getArrowProps]);
|
||||
|
||||
const ContentWrapper = useCallback(
|
||||
({children}: {children: ReactNode}) => {
|
||||
return (
|
||||
<FocusScope restoreFocus>
|
||||
<Component className={className}>
|
||||
<DismissButton onDismiss={onClose} />
|
||||
{children}
|
||||
{arrowContent}
|
||||
<DismissButton onDismiss={onClose} />
|
||||
</Component>
|
||||
</FocusScope>
|
||||
);
|
||||
},
|
||||
[Component, className, onClose, arrowContent],
|
||||
const content = (
|
||||
<>
|
||||
<DismissButton onDismiss={onClose} />
|
||||
<Component {...getDialogProps(mergeProps(dialogProps, otherProps))} ref={dialogRef}>
|
||||
{children}
|
||||
{arrowContent}
|
||||
</Component>
|
||||
<DismissButton onDismiss={onClose} />
|
||||
</>
|
||||
);
|
||||
|
||||
const visibility = useMemo(() => {
|
||||
if (disableAnimation) return isOpen ? "visible" : "hidden";
|
||||
|
||||
return "visible";
|
||||
}, [disableAnimation, isOpen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...otherPopoverProps}
|
||||
style={{
|
||||
...style,
|
||||
visibility,
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
<div {...getPopoverProps()}>
|
||||
{disableAnimation ? (
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
content
|
||||
) : (
|
||||
<motion.div
|
||||
animate={isOpen ? "enter" : "exit"}
|
||||
animate="enter"
|
||||
exit="exit"
|
||||
initial="exit"
|
||||
style={{
|
||||
@ -81,7 +72,7 @@ const PopoverContent = forwardRef<PopoverContentProps, "section">((props, _) =>
|
||||
variants={TRANSITION_VARIANTS.scaleSpring}
|
||||
{...motionProps}
|
||||
>
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
{content}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {OverlayContainer} from "@react-aria/overlays";
|
||||
import {Children, ReactNode} from "react";
|
||||
import {AnimatePresence} from "framer-motion";
|
||||
import {Overlay} from "@react-aria/overlays";
|
||||
|
||||
import {UsePopoverProps, usePopover} from "./use-popover";
|
||||
import {PopoverProvider} from "./popover-context";
|
||||
@ -19,12 +20,16 @@ const Popover = forwardRef<PopoverProps, "div">((props, ref) => {
|
||||
|
||||
const [trigger, content] = Children.toArray(children);
|
||||
|
||||
const mountOverlay = context.isOpen;
|
||||
|
||||
return (
|
||||
<PopoverProvider value={context}>
|
||||
{trigger}
|
||||
{mountOverlay && <OverlayContainer>{content}</OverlayContainer>}
|
||||
{context.disableAnimation && context.isOpen ? (
|
||||
<Overlay>{content}</Overlay>
|
||||
) : (
|
||||
<AnimatePresence initial={false}>
|
||||
{context.isOpen ? <Overlay>{content}</Overlay> : null}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</PopoverProvider>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,35 +1,32 @@
|
||||
import type {PopoverVariantProps, SlotsToClasses, PopoverSlots} from "@nextui-org/theme";
|
||||
import type {HTMLMotionProps} from "framer-motion";
|
||||
import type {OverlayPlacement, OverlayOptions} from "@nextui-org/aria-utils";
|
||||
import type {OverlayPlacement} from "@nextui-org/aria-utils";
|
||||
import type {RefObject, Ref} from "react";
|
||||
|
||||
import {useOverlayTriggerState} from "@react-stately/overlays";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {
|
||||
AriaOverlayProps,
|
||||
AriaPopoverProps,
|
||||
useOverlayTrigger,
|
||||
useOverlayPosition,
|
||||
useOverlay,
|
||||
useModal,
|
||||
usePopover as useReactAriaPopover,
|
||||
} from "@react-aria/overlays";
|
||||
import {useDialog} from "@react-aria/dialog";
|
||||
import {OverlayTriggerProps} from "@react-types/overlays";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {toReactAriaPlacement, getArrowPlacement} from "@nextui-org/aria-utils";
|
||||
import {popover} from "@nextui-org/theme";
|
||||
import {chain, mergeProps, mergeRefs} from "@react-aria/utils";
|
||||
import {mergeProps, mergeRefs} from "@react-aria/utils";
|
||||
import {createDOMRef} from "@nextui-org/dom-utils";
|
||||
import {ReactRef, clsx} from "@nextui-org/shared-utils";
|
||||
import {useId, useMemo, useCallback, useImperativeHandle, useRef} from "react";
|
||||
|
||||
export interface Props extends HTMLNextUIProps<"div", PopoverVariantProps> {
|
||||
export interface Props extends HTMLNextUIProps<"div"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/**
|
||||
* A ref for the scrollable region within the overlay.
|
||||
* @default overlayRef
|
||||
* @default popoverRef
|
||||
*/
|
||||
scrollRef?: RefObject<HTMLElement>;
|
||||
/**
|
||||
@ -41,6 +38,11 @@ export interface Props extends HTMLNextUIProps<"div", PopoverVariantProps> {
|
||||
* @default 'top'
|
||||
*/
|
||||
placement?: OverlayPlacement;
|
||||
/**
|
||||
* Whether the element should render an arrow.
|
||||
* @default false
|
||||
*/
|
||||
showArrow?: boolean;
|
||||
/**
|
||||
* Type of overlay that is opened by the trigger.
|
||||
*/
|
||||
@ -62,11 +64,12 @@ export interface Props extends HTMLNextUIProps<"div", PopoverVariantProps> {
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<PopoverSlots>;
|
||||
/** Handler that is called when the overlay should close. */
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export type UsePopoverProps = Props & AriaOverlayProps & OverlayTriggerProps & OverlayOptions;
|
||||
export type UsePopoverProps = Props &
|
||||
Omit<AriaPopoverProps, "placement" | "triggerRef" | "popoverRef"> &
|
||||
OverlayTriggerProps &
|
||||
PopoverVariantProps;
|
||||
|
||||
export function usePopover(originalProps: UsePopoverProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, popover.variantKeys);
|
||||
@ -87,29 +90,25 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
showArrow = false,
|
||||
offset = 7,
|
||||
crossOffset = 0,
|
||||
isDismissable = true,
|
||||
shouldCloseOnBlur = true,
|
||||
isKeyboardDismissDisabled = true,
|
||||
shouldCloseOnInteractOutside,
|
||||
isKeyboardDismissDisabled,
|
||||
motionProps,
|
||||
className,
|
||||
styles,
|
||||
onClose,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "div";
|
||||
const popoverId = useId();
|
||||
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const domTriggerRef = useRef<HTMLElement>(null);
|
||||
|
||||
const triggerRef = triggerRefProp || domTriggerRef;
|
||||
|
||||
// Sync ref with overlayRef from passed ref.
|
||||
// Sync ref with popoverRef from passed ref.
|
||||
useImperativeHandle(ref, () =>
|
||||
// @ts-ignore
|
||||
createDOMRef(overlayRef),
|
||||
createDOMRef(popoverRef),
|
||||
);
|
||||
|
||||
const state = useOverlayTriggerState({
|
||||
@ -118,47 +117,25 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
onOpenChange,
|
||||
});
|
||||
|
||||
const {triggerProps, overlayProps: overlayTriggerProps} = useOverlayTrigger(
|
||||
{type: triggerType},
|
||||
state,
|
||||
triggerRef,
|
||||
);
|
||||
|
||||
const {overlayProps: positionProps, arrowProps, placement} = useOverlayPosition({
|
||||
overlayRef,
|
||||
scrollRef,
|
||||
isOpen: isOpen,
|
||||
targetRef: triggerRef,
|
||||
placement: toReactAriaPlacement(placementProp),
|
||||
offset: showArrow ? offset + 3 : offset,
|
||||
crossOffset,
|
||||
shouldFlip,
|
||||
containerPadding,
|
||||
});
|
||||
|
||||
const {overlayProps} = useOverlay(
|
||||
const {popoverProps, arrowProps, placement} = useReactAriaPopover(
|
||||
{
|
||||
isOpen: state.isOpen,
|
||||
onClose: chain(state.close, onClose),
|
||||
isDismissable,
|
||||
shouldCloseOnBlur,
|
||||
triggerRef,
|
||||
popoverRef,
|
||||
placement: toReactAriaPlacement(placementProp),
|
||||
offset: showArrow ? offset + 3 : offset,
|
||||
scrollRef,
|
||||
crossOffset,
|
||||
shouldFlip,
|
||||
containerPadding,
|
||||
isKeyboardDismissDisabled,
|
||||
shouldCloseOnInteractOutside,
|
||||
},
|
||||
overlayRef,
|
||||
state,
|
||||
);
|
||||
|
||||
const {triggerProps} = useOverlayTrigger({type: triggerType}, state, triggerRef);
|
||||
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const {modalProps} = useModal({isDisabled: true});
|
||||
|
||||
const {dialogProps} = useDialog(
|
||||
{
|
||||
role: "dialog",
|
||||
},
|
||||
overlayRef,
|
||||
);
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
popover({
|
||||
@ -171,21 +148,20 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getPopoverProps: PropGetter = (props = {}) => ({
|
||||
ref: overlayRef,
|
||||
...mergeProps(
|
||||
overlayTriggerProps,
|
||||
overlayProps,
|
||||
modalProps,
|
||||
dialogProps,
|
||||
positionProps,
|
||||
focusProps,
|
||||
otherProps,
|
||||
props,
|
||||
),
|
||||
className: slots.base({class: clsx(baseStyles, props.className)}),
|
||||
ref: popoverRef,
|
||||
...mergeProps(popoverProps, otherProps, props),
|
||||
id: popoverId,
|
||||
});
|
||||
|
||||
const getDialogProps: PropGetter = (props = {}) => ({
|
||||
className: slots.base({class: clsx(baseStyles, props.className)}),
|
||||
...mergeProps(focusProps, props),
|
||||
style: {
|
||||
// this prevent the dialog to have a default outline
|
||||
outline: "none",
|
||||
},
|
||||
});
|
||||
|
||||
const getTriggerProps = useCallback<PropGetter>(
|
||||
(props = {}, _ref: Ref<any> | null | undefined = null) => {
|
||||
return {
|
||||
@ -218,9 +194,11 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
onClose: state.close,
|
||||
disableAnimation: originalProps.disableAnimation ?? false,
|
||||
motionProps,
|
||||
focusProps,
|
||||
getPopoverProps,
|
||||
getTriggerProps,
|
||||
getArrowProps,
|
||||
getDialogProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {popover} from "@nextui-org/theme";
|
||||
import {popover, ButtonVariantProps} from "@nextui-org/theme";
|
||||
import {Button} from "@nextui-org/button";
|
||||
|
||||
import {Popover, PopoverTrigger, PopoverContent, PopoverProps} from "../src";
|
||||
@ -56,11 +56,6 @@ export default {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
showArrow: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
@ -91,35 +86,295 @@ const defaultProps = {
|
||||
placement: "top",
|
||||
offset: 7,
|
||||
defaultOpen: false,
|
||||
isDisabled: false,
|
||||
disableAnimation: false,
|
||||
};
|
||||
|
||||
const content = (
|
||||
<PopoverContent>
|
||||
<div className="px-1 py-2">
|
||||
<div className="text-sm font-bold">Popover Content</div>
|
||||
<div className="text-xs">This is a content of the popover</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
|
||||
const Template: ComponentStory<typeof Popover> = (args: PopoverProps) => {
|
||||
return (
|
||||
<Popover {...args}>
|
||||
<PopoverTrigger>
|
||||
<Button>Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="px-1 py-2">
|
||||
<div className="text-sm font-bold">Popover Content</div>
|
||||
<div className="text-xs">This is a content of the popover</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
{content}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenChangeTemplate: ComponentStory<typeof Popover> = (args: PopoverProps) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Popover {...args} onOpenChange={(open) => setIsOpen(open)}>
|
||||
<PopoverTrigger>
|
||||
<Button>Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="px-1 py-2">
|
||||
<div className="text-sm font-bold">Popover Content</div>
|
||||
<div className="text-xs">This is a content of the popover</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<p className="text-sm">isOpen: {isOpen ? "true" : "false"}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VariantsTemplate: ComponentStory<typeof Popover> = (args: PopoverProps) => {
|
||||
const buttonColor = args.color as ButtonVariantProps["color"];
|
||||
|
||||
const content = (
|
||||
<PopoverContent>
|
||||
<div className="px-1 py-2">
|
||||
<div className="text-sm font-bold">Popover Content</div>
|
||||
<div className="text-xs">This is a content of the popover</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Popover {...args} variant="solid">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor}>Solid</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} variant="bordered">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="bordered">
|
||||
Bordered
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} variant="light">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="light">
|
||||
Light
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} variant="flat">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Flat
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} variant="faded">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="faded">
|
||||
Faded
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} variant="shadow">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="shadow">
|
||||
Shadow
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PlacementsTemplate: ComponentStory<typeof Popover> = (args: PopoverProps) => {
|
||||
const buttonColor = args.color as ButtonVariantProps["color"];
|
||||
|
||||
return (
|
||||
<div className="inline-grid grid-cols-3 gap-4">
|
||||
<Popover {...args} placement="top-start">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Top Start
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args}>
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Top
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="top-end">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Top End
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="bottom-start">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Bottom Start
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="bottom">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Bottom
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="bottom-end">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Bottom End
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="right-start">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Right Start
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="right">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Right
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="right-end">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Right End
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="left-start">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Left Start
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="left">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Left
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
|
||||
<Popover {...args} placement="left-end">
|
||||
<PopoverTrigger>
|
||||
<Button color={buttonColor} variant="flat">
|
||||
Left End
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OffsetTemplate: ComponentStory<typeof Popover> = (args: PopoverProps) => (
|
||||
<div className="flex gap-2">
|
||||
<Popover {...args}>
|
||||
<PopoverTrigger>
|
||||
<Button color="warning" variant="faded">
|
||||
Default offset (7)
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} offset={15}>
|
||||
<PopoverTrigger>
|
||||
<Button color="warning" variant="faded">
|
||||
15 offset
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
<Popover {...args} offset={-7}>
|
||||
<PopoverTrigger>
|
||||
<Button color="warning" variant="faded">
|
||||
-7 offset
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{content}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
showArrow: true,
|
||||
};
|
||||
|
||||
export const DisableAnimation = Template.bind({});
|
||||
DisableAnimation.args = {
|
||||
...defaultProps,
|
||||
showArrow: true,
|
||||
disableAnimation: true,
|
||||
};
|
||||
|
||||
export const WithArrow = Template.bind({});
|
||||
WithArrow.args = {
|
||||
...defaultProps,
|
||||
showArrow: true,
|
||||
};
|
||||
|
||||
export const OpenChange = OpenChangeTemplate.bind({});
|
||||
OpenChange.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const Variants = VariantsTemplate.bind({});
|
||||
Variants.args = {
|
||||
...defaultProps,
|
||||
color: "primary",
|
||||
};
|
||||
|
||||
export const Placements = PlacementsTemplate.bind({});
|
||||
Placements.args = {
|
||||
...defaultProps,
|
||||
color: "secondary",
|
||||
};
|
||||
|
||||
export const WithOffset = OffsetTemplate.bind({});
|
||||
WithOffset.args = {
|
||||
...defaultProps,
|
||||
color: "warning",
|
||||
};
|
||||
|
||||
@ -84,7 +84,6 @@ const Tooltip = forwardRef<TooltipProps, "div">((props, ref) => {
|
||||
{content}
|
||||
{arrowContent}
|
||||
</Component>
|
||||
;
|
||||
</OverlayContainer>
|
||||
) : (
|
||||
<AnimatePresence initial={false}>
|
||||
|
||||
@ -303,6 +303,12 @@ Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const DisableAnimation = Template.bind({});
|
||||
DisableAnimation.args = {
|
||||
...defaultProps,
|
||||
disableAnimation: true,
|
||||
};
|
||||
|
||||
export const WithArrow = Template.bind({});
|
||||
WithArrow.args = {
|
||||
...defaultProps,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user