Skip to content

Commit

Permalink
Enhance tooltip & popover
Browse files Browse the repository at this point in the history
  • Loading branch information
lewisnguyen2804 committed Apr 23, 2024
1 parent af2150b commit 9c929a2
Show file tree
Hide file tree
Showing 13 changed files with 10,620 additions and 53 deletions.
9 changes: 9 additions & 0 deletions lib/components/menu/menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ const meta = {
],
control: { type: "radio" },
},
showArrow: {
control: "boolean",
},
trigger: {
description: "Menu trigger",
options: ["hover", "click"],
control: { type: "radio" },
},
items: {},
},
} satisfies Meta<typeof Menu>;
Expand Down Expand Up @@ -64,6 +72,7 @@ export const Primary: Story = {
key: 3,
title: "Logout",
icon: <Logout size={20} />,
isDisabled: true,
},
],
},
Expand Down
67 changes: 41 additions & 26 deletions lib/components/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AnimatePresence, motion } from "framer-motion";
import { useMemo, useState } from "react";
import { useLayer } from "react-laag";
import { Arrow, useHover, useLayer } from "react-laag";
import { twMerge } from "tailwind-merge";
import { useComponentStyle } from "../../customization/styles/theme.context";
import { IMenu, IMenuItem, IMenuKey } from "./menu.types";
Expand All @@ -13,13 +13,20 @@ export const Menu = ({
dropdownClassName,
items = [],
onMenuClick,
showArrow = true,
trigger = "click",
}: IMenu) => {
const isHoverTrigger = trigger === "hover";
const [isOpen, setOpen] = useState(false);
const [isHover, hoverProps] = useHover({
delayEnter: 200,
delayLeave: 200,
});

const theme = useComponentStyle("Menu");

const { triggerProps, layerProps, renderLayer } = useLayer({
isOpen: open || isOpen,
const { triggerProps, layerProps, renderLayer, arrowProps } = useLayer({
isOpen: open || isOpen || isHover,
placement,
auto: true,
triggerOffset: 10,
Expand All @@ -33,17 +40,13 @@ export const Menu = ({
return twMerge(theme.base(), dropdownClassName);
}, [dropdownClassName, theme]);

const itemClasses = useMemo(() => {
return twMerge(theme.item());
}, [theme]);

const itemIconClasses = useMemo(() => {
return twMerge(theme.itemIcon());
}, [theme]);

// const arrowClasses = useMemo(() => {
// return twMerge(theme.arrow());
// }, [theme]);
const arrowClasses = useMemo(() => {
return twMerge(theme.arrow());
}, [theme]);

const onMenuItemClick = (value: IMenuKey) => {
if (value) {
Expand All @@ -58,8 +61,10 @@ export const Menu = ({
<li
key={x.key}
value={x.key}
className={itemClasses}
onClick={() => onMenuItemClick(x.key)}
className={twMerge(theme.item({
isDisabled: x.isDisabled || false,
}))}
onClick={x.isDisabled ? undefined : () => onMenuItemClick(x.key)}
>
{!!x.icon && <div className={itemIconClasses}>{x.icon}</div>}
{x.title}
Expand All @@ -70,34 +75,44 @@ export const Menu = ({
return (
<>
<span
{...(!isHoverTrigger
? {
onClick: () => {
const _isOpen = !isOpen;
setOpen(_isOpen);
onOpenChange && onOpenChange(_isOpen);
},
}
: hoverProps)}
{...triggerProps}
onClick={() => {
const _isOpen = !isOpen;
setOpen(_isOpen);
onOpenChange && onOpenChange(_isOpen);
}}
>
{children}
</span>
{(open || isOpen) &&
{(isHover || open || isOpen) &&
renderLayer(
<AnimatePresence>
<motion.ul
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={classes}
transition={{ duration: 0.3 }}
{...layerProps}
{...(isHoverTrigger ? hoverProps : null)}
>
{renderMenuItems()}
{/* <Arrow
backgroundColor="var(--color-neutral-5)" // color-component-background
borderColor="transparent"
className={arrowClasses}
borderWidth={1}
size={8}
{...arrowProps}
/> */}
{showArrow &&
<Arrow
onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}
{...arrowProps}
backgroundColor="var(--color-neutral-5)" // color-component-background
borderColor="transparent"
className={arrowClasses}
borderWidth={1}
size={8}
/>
}
</motion.ul>
</AnimatePresence>
)}
Expand Down
3 changes: 3 additions & 0 deletions lib/components/menu/menu.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ export interface IMenu {
dropdownClassName?: string;
onMenuClick?: (key: IMenuKey) => void;
items?: IMenuItem[];
showArrow?: boolean;
trigger?: "hover" | "click";
}

export interface IMenuItem {
title?: React.ReactNode;
key: IMenuKey;
icon?: React.ReactNode;
isDisabled?: boolean;
}
5 changes: 4 additions & 1 deletion lib/components/popover/popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const meta = {
options: ["hover", "click"],
control: { type: "radio" },
},
showArrow: {
control: "boolean",
},
placement: {
description: "Popover placement",
options: [
Expand Down Expand Up @@ -51,7 +54,7 @@ export const Primary: Story = {
render: ({ children, content, ...rest }) => (
<Popover
{...rest}
content={<Box className="zn-max-w-[250px]">{content}</Box>}
content={<Box className="zn-max-w-[250px] zn-p-4">{content}</Box>}
>
<Text className="zn-underline zn-cursor-pointer">{children}</Text>
</Popover>
Expand Down
26 changes: 15 additions & 11 deletions lib/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ export const Popover = ({
onOpenChange,
popoverClassName,
trigger = "hover",
showArrow = true,
}: IPopover) => {
const isHoverTrigger = trigger === "hover";
const [isOpen, setOpen] = useState(false);
const [isHover, hoverProps] = useHover({
delayEnter: 200,
delayLeave: 200,
});

Expand All @@ -26,7 +28,6 @@ export const Popover = ({
isOpen: open || isOpen || isHover,
placement,
auto: true,

triggerOffset: 10,
onOutsideClick: () => {
onOpenChange && onOpenChange(false);
Expand Down Expand Up @@ -65,21 +66,24 @@ export const Popover = ({
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className={classes}
{...layerProps}
{...(isHoverTrigger ? hoverProps : null)}
>
{content}
<Arrow
onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}
{...arrowProps}
backgroundColor="var(--color-neutral-5)" // color-component-background
borderColor="transparent"
className={arrowClasses}
borderWidth={1}
size={8}
/>
{showArrow &&
<Arrow
onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}
{...arrowProps}
backgroundColor="var(--color-neutral-5)" // color-component-background
borderColor="transparent"
className={arrowClasses}
borderWidth={1}
size={8}
/>
}
</motion.div>
</AnimatePresence>
)}
Expand Down
1 change: 1 addition & 0 deletions lib/components/popover/popover.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface IPopover {
children?: React.ReactNode;
popoverClassName?: string;
trigger?: "hover" | "click";
showArrow?: boolean;
}
4 changes: 4 additions & 0 deletions lib/components/tooltip/tooltip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const meta = {
],
control: { type: "radio" },
},
showArrow: {
control: "boolean",
},
},
} satisfies Meta<typeof Tooltip>;

Expand All @@ -39,5 +42,6 @@ export const Primary: Story = {
children: "Display tooltip",
placement: "bottom-start",
content: "This is a tooltip",
showArrow: true,
},
};
23 changes: 14 additions & 9 deletions lib/components/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export const Tooltip = ({
content,
placement,
tooltipClassName,
showArrow = true
}: ITooltip) => {
const [isHover, hoverProps] = useHover({
delayEnter: 100,
delayLeave: 200,
});

Expand Down Expand Up @@ -44,20 +46,23 @@ export const Tooltip = ({
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className={classes}
{...layerProps}
{...hoverProps}
>
{content}
<Arrow
onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}
{...arrowProps}
backgroundColor="black"
className={arrowClasses}
borderWidth={1}
size={6}
/>
{showArrow &&
<Arrow
onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}
{...arrowProps}
backgroundColor="black"
className={arrowClasses}
borderWidth={1}
size={6}
/>
}
</motion.div>
</AnimatePresence>
)}
Expand Down
1 change: 1 addition & 0 deletions lib/components/tooltip/tooltip.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export interface ITooltip {
content?: string;
children?: React.ReactNode;
tooltipClassName?: string;
showArrow?: boolean;
}
11 changes: 8 additions & 3 deletions lib/customization/styles/components/menu.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ const item = cva([
"zn-px-3",
"zn-py-2",
"zn-rounded-base",
"hover:zn-bg-primary-10",
"zn-cursor-pointer",
"zn-text-sm",
"zn-font-medium",
"zn-flex",
"zn-items-center",
"zn-gap-2",
]);
], {
variants: {
isDisabled: {
true: ["!zn-cursor-not-allowed", "zn-text-neutral-40"],
false: ["hover:zn-bg-primary-10", "zn-cursor-pointer"],
},
},
});
const itemIcon = cva([
"zn-flex",
"zn-items-center",
Expand Down
2 changes: 0 additions & 2 deletions lib/customization/styles/components/popover.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { cva } from "class-variance-authority";

const base = cva([
"zn-rounded-base",
"zn-px-3",
"zn-py-2",
"zn-bg-color-component-background",
"zn-text-neutral-100",
"zn-text-sm",
Expand Down
2 changes: 1 addition & 1 deletion lib/customization/styles/components/tooltip.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const base = cva([
"zn-bg-black",
"zn-text-white",
"zn-rounded-base",
"zn-px-3",
"zn-px-2",
"zn-py-2",
"zn-text-sm",
"zn-shadow-base",
Expand Down
Loading

0 comments on commit 9c929a2

Please sign in to comment.