Skip to content

Commit

Permalink
Merge pull request #17 from tuzkituan/dev/menu
Browse files Browse the repository at this point in the history
New: Menu component
  • Loading branch information
tuzkituan authored Oct 31, 2023
2 parents 2a12279 + 5389030 commit 3f79b59
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 12 deletions.
2 changes: 1 addition & 1 deletion lib/components/center/center.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { twMerge } from "tailwind-merge";
import { useComponentStyle } from "../../customization/styles/theme.context";
import { ICenter, ICircle } from "./center.styles";
import { ICenter, ICircle } from "./center.types";
import { useMemo } from "react";

const defaultProps: Partial<ICenter> = {
Expand Down
File renamed without changes.
67 changes: 67 additions & 0 deletions lib/components/menu/menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Text } from "../text/text";
import { Menu } from "./menu";

const meta = {
title: "OVERLAY/Menu",
component: Menu,
tags: ["autodocs"],
parameters: {
layout: "centered",
},
argTypes: {
children: {
type: "string",
},
placement: {
description: "Menu placement",
options: [
"top-center",
"top-start",
"top-end",
"left-start",
"left-center",
"left-end",
"right-start",
"right-center",
"right-end",
"bottom-start",
"bottom-center",
"bottom-end",
],
control: { type: "radio" },
},
items: {},
},
} satisfies Meta<typeof Menu>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
render: ({
children,
items = [
{
key: 1,
title: "Option 1",
},
{
key: 2,
title: "Option 2",
},
{
key: 3,
title: "Option 3",
},
],
...rest
}) => (
<Menu items={items} {...rest}>
<Text className="underline cursor-pointer">{children}</Text>
</Menu>
),
args: {
children: "Open menu",
},
};
99 changes: 99 additions & 0 deletions lib/components/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { AnimatePresence, motion } from "framer-motion";
import { useMemo, useState } from "react";
import { Arrow, useLayer } from "react-laag";
import { twMerge } from "tailwind-merge";
import { useComponentStyle } from "../../customization/styles/theme.context";
import { IMenu, IMenuItem, IMenuKey } from "./menu.types";

export const Menu = ({
children,
placement,
open,
onOpenChange,
dropdownClassName,
items = [],
onMenuClick,
}: IMenu) => {
const [isOpen, setOpen] = useState(false);

const theme = useComponentStyle("Menu");

const { triggerProps, layerProps, arrowProps, renderLayer } = useLayer({
isOpen: open || isOpen,
placement,
auto: true,
triggerOffset: 10,
onOutsideClick: () => {
onOpenChange && onOpenChange(false);
setOpen(false);
},
});

const classes = useMemo(() => {
return twMerge(theme.base(), dropdownClassName);
}, [dropdownClassName, theme]);

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

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

const onMenuItemClick = (value: IMenuKey) => {
if (value) {
onMenuClick?.(value);
}
};

const renderMenuItems = () => {
return items.map((x: IMenuItem) => (
<li
key={x.key}
value={x.key}
className={itemClasses}
onClick={(e) => onMenuItemClick((e.target as HTMLInputElement).value)}
>
{x.title}
</li>
));
};

return (
<>
<span
{...triggerProps}
onClick={() => {
const _isOpen = !isOpen;
setOpen(_isOpen);
onOpenChange && onOpenChange(_isOpen);
}}
>
{children}
</span>
{(open || isOpen) &&
renderLayer(
<AnimatePresence>
<motion.ul
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={classes}
{...layerProps}
>
{renderMenuItems()}
<Arrow
{...arrowProps}
backgroundColor="var(--background-sec)"
borderColor="var(--line-primary)"
className={arrowClasses}
borderWidth={1}
size={8}
/>
</motion.ul>
</AnimatePresence>
)}
</>
);
};
27 changes: 27 additions & 0 deletions lib/components/menu/menu.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type IMenuKey = number | string;
export interface IMenu {
placement?:
| "top-center"
| "top-start"
| "top-end"
| "left-start"
| "left-center"
| "left-end"
| "right-start"
| "right-center"
| "right-end"
| "bottom-start"
| "bottom-center"
| "bottom-end";
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
dropdownClassName?: string;
onMenuClick?: (key: IMenuKey) => void;
items?: IMenuItem[];
}

export interface IMenuItem {
title?: React.ReactNode;
key: IMenuKey;
}
21 changes: 11 additions & 10 deletions lib/customization/styles/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
export * from "./button.styles";
export * from "./alert.styles";
export * from "./avatar.styles";
export * from "./box.styles";
export * from "./text.styles";
export * from "./tooltip.styles";
export * from "./popover.styles";
export * from "./button.styles";
export * from "./center.styles";
export * from "./checkbox.styles";
export * from "./flex.styles";
export * from "./avatar.styles";
export * from "./input.styles";
export * from "./number-input.styles";
export * from "./checkbox.styles";
export * from "./menu.styles";
export * from "./modal.styles";
export * from "./alert.styles";
export * from "./tabs.styles";
export * from "./number-input.styles";
export * from "./popover.styles";
export * from "./radio.styles";
export * from "./toast.styles";
export * from "./select.styles";
export * from "./tabs.styles";
export * from "./text.styles";
export * from "./toast.styles";
export * from "./tooltip.styles";
28 changes: 28 additions & 0 deletions lib/customization/styles/components/menu.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { cva } from "class-variance-authority";

const base = cva([
"rounded-lg",
"p-1",
"bg-sec-background",
"text-primary-text",
"text-base",
"border",
"border-line-primary",
"shadow-sm",
]);
const item = cva([
"w-full",
"px-2",
"py-1.5",
"rounded",
"hover:bg-line-primary",
"cursor-pointer",
]);
const arrow = cva([]);
const menuStyles = {
base,
arrow,
item,
};

export { menuStyles };
2 changes: 2 additions & 0 deletions lib/customization/styles/theme.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface StyleComponents {
Radio: typeof styles.radioStyles;
Toast: typeof styles.toastStyles;
Select: typeof styles.selectStyles;
Menu: typeof styles.menuStyles;
}

const defaultStyle: Style = {
Expand All @@ -44,6 +45,7 @@ const defaultStyle: Style = {
Radio: styles.radioStyles,
Toast: styles.toastStyles,
Select: styles.selectStyles,
Menu: styles.menuStyles,
},
};

Expand Down
17 changes: 16 additions & 1 deletion lib/main.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import "./customization/global.css";

export * from "./components/button/button";
export * from "./components/button/button.types";
export * from "./components/center/center";
export * from "./components/center/center.types";
export * from "./components/text/text";
export * from "./components/text/text.types";
export * from "./components/tooltip/tooltip";
export * from "./components/tooltip/tooltip.types";
export * from "./components/popover/popover";
export * from "./components/popover/popover.types";
export * from "./components/flex/flex";
export * from "./components/flex/flex.types";
export * from "./components/box/box";
export * from "./components/box/box.types";
export * from "./components/input/input";
export * from "./components/input/input.types";
export * from "./components/number-input/number-input";
export * from "./components/number-input/number-input.types";
export * from "./components/modal/modal";
export * from "./components/modal/modal.types";
export * from "./components/avatar/avatar";
export * from "./components/avatar/avatar.types";
export * from "./components/checkbox/checkbox";
export * from "./components/checkbox/checkbox.types";
export * from "./components/alert/alert";
export * from "./components/toast/index"
export * from "./components/alert/alert.types";
export * from "./components/toast";
export * from "./components/select/select";
export * from "./components/select/select.types";

// PROVIDERS
export * from "./providers/zeni-provider";
Expand Down

0 comments on commit 3f79b59

Please sign in to comment.