analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
355 lines (353 loc) • 10.8 kB
JavaScript
// src/components/Menu/Menu.tsx
import { create, useStore } from "zustand";
import {
useEffect,
useRef,
forwardRef,
isValidElement,
Children,
cloneElement,
useState
} from "react";
import { CaretLeft, CaretRight } from "phosphor-react";
// src/utils/utils.ts
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
function cn(...inputs) {
return twMerge(clsx(inputs));
}
// src/components/Menu/Menu.tsx
import { jsx, jsxs } from "react/jsx-runtime";
var createMenuStore = (onValueChange) => create((set) => ({
value: "",
setValue: (value) => {
set({ value });
onValueChange?.(value);
},
onValueChange
}));
var useMenuStore = (externalStore) => {
if (!externalStore) throw new Error("MenuItem must be inside Menu");
return externalStore;
};
var VARIANT_CLASSES = {
menu: "bg-background shadow-soft-shadow-1 px-6",
menu2: "",
"menu-overflow": "",
breadcrumb: "bg-transparent shadow-none !px-0"
};
var Menu = forwardRef(
({
className,
children,
defaultValue,
value: propValue,
variant = "menu",
onValueChange,
...props
}, ref) => {
const storeRef = useRef(null);
storeRef.current ??= createMenuStore(onValueChange);
const store = storeRef.current;
const { setValue } = useStore(store, (s) => s);
useEffect(() => {
setValue(propValue ?? defaultValue);
}, [defaultValue, propValue, setValue]);
const baseClasses = variant === "menu-overflow" ? "w-fit py-2 flex flex-row items-center justify-center" : "w-full py-2 flex flex-row items-center justify-center";
const variantClasses = VARIANT_CLASSES[variant];
return /* @__PURE__ */ jsx(
"div",
{
ref,
className: `
${baseClasses}
${variantClasses}
${className ?? ""}
`,
...props,
children: injectStore(children, store)
}
);
}
);
Menu.displayName = "Menu";
var MenuContent = forwardRef(
({ className, children, variant = "menu", ...props }, ref) => {
const baseClasses = "w-full flex flex-row items-center gap-2";
const variantClasses = variant === "menu2" || variant === "menu-overflow" ? "overflow-x-auto scroll-smooth" : "";
return /* @__PURE__ */ jsx(
"ul",
{
ref,
className: `
${baseClasses}
${variantClasses}
${variant == "breadcrumb" ? "flex-wrap" : ""}
${className ?? ""}
`,
style: variant === "menu2" || variant === "menu-overflow" ? { scrollbarWidth: "none", msOverflowStyle: "none" } : void 0,
...props,
children
}
);
}
);
MenuContent.displayName = "MenuContent";
var MenuItem = forwardRef(
({
className,
children,
value,
disabled = false,
store: externalStore,
variant = "menu",
separator = false,
...props
}, ref) => {
const store = useMenuStore(externalStore);
const { value: selectedValue, setValue } = useStore(store, (s) => s);
const handleClick = (e) => {
if (!disabled) {
setValue(value);
}
props.onClick?.(e);
};
const commonProps = {
role: "menuitem",
"aria-disabled": disabled,
ref,
onClick: handleClick,
onKeyDown: (e) => {
if (["Enter", " "].includes(e.key)) handleClick(e);
},
tabIndex: disabled ? -1 : 0,
onMouseDown: (e) => {
e.preventDefault();
},
...props
};
const variants = {
menu: /* @__PURE__ */ jsx(
"li",
{
"data-variant": "menu",
className: `
w-full flex flex-col items-center justify-center gap-0.5 py-1 px-2 rounded-sm font-medium text-xs
[&>svg]:size-6 cursor-pointer hover:bg-primary-600 hover:text-text
focus:outline-none focus:border-indicator-info focus:border-2
${selectedValue === value ? "bg-primary-50 text-primary-950" : "text-text-950"}
${className ?? ""}
`,
...commonProps,
children
}
),
menu2: /* @__PURE__ */ jsxs(
"li",
{
"data-variant": "menu2",
className: `
w-full flex flex-col items-center px-2 pt-4 gap-3 cursor-pointer focus:rounded-sm justify-center hover:bg-background-100 rounded-lg
focus:outline-none focus:border-indicator-info focus:border-2
${selectedValue === value ? "" : "pb-4"}
`,
...commonProps,
children: [
/* @__PURE__ */ jsx(
"span",
{
className: cn(
"flex flex-row items-center gap-2 px-4 text-text-950 text-xs font-bold",
className
),
children
}
),
selectedValue === value && /* @__PURE__ */ jsx("div", { className: "h-1 w-full bg-primary-950 rounded-lg" })
]
}
),
"menu-overflow": /* @__PURE__ */ jsxs(
"li",
{
"data-variant": "menu-overflow",
className: `
w-fit flex flex-col items-center px-2 pt-4 gap-3 cursor-pointer focus:rounded-sm justify-center hover:bg-background-100 rounded-lg
focus:outline-none focus:border-indicator-info focus:border-2
${selectedValue === value ? "" : "pb-4"}
`,
...commonProps,
children: [
/* @__PURE__ */ jsx(
"span",
{
className: cn(
"flex flex-row items-center gap-2 px-4 text-text-950 text-xs font-bold",
className
),
children
}
),
selectedValue === value && /* @__PURE__ */ jsx("div", { className: "h-1 w-full bg-primary-950 rounded-lg" })
]
}
),
breadcrumb: /* @__PURE__ */ jsxs(
"li",
{
"data-variant": "breadcrumb",
className: `
flex flex-row gap-2 items-center w-fit p-2 rounded-lg hover:text-primary-600 cursor-pointer font-bold text-xs
focus:outline-none focus:border-indicator-info focus:border-2
${selectedValue === value ? "text-text-950" : "text-text-600"}
${className ?? ""}
`,
...commonProps,
children: [
/* @__PURE__ */ jsx(
"span",
{
className: cn(
"border-b border-text-600 hover:border-primary-600 text-inherit text-xs",
selectedValue === value ? "border-b-0 font-bold" : "border-b-text-600"
),
children
}
),
separator && /* @__PURE__ */ jsx(
CaretRight,
{
size: 16,
className: "text-text-600",
"data-testid": "separator"
}
)
]
}
)
};
return variants[variant] ?? variants["menu"];
}
);
MenuItem.displayName = "MenuItem";
var MenuItemIcon = ({
className,
icon,
...props
}) => /* @__PURE__ */ jsx(
"span",
{
className: cn(
"bg-background-500 w-[21px] h-[21px] flex items-center justify-center [&>svg]:w-[17px] [&>svg]:h-[17px] rounded-sm",
className
),
...props,
children: icon
}
);
var internalScroll = (container, direction) => {
if (!container) return;
container.scrollBy({
left: direction === "left" ? -150 : 150,
behavior: "smooth"
});
};
var internalCheckScroll = (container, setShowLeftArrow, setShowRightArrow) => {
if (!container) return;
const { scrollLeft, scrollWidth, clientWidth } = container;
setShowLeftArrow(scrollLeft > 0);
setShowRightArrow(scrollLeft + clientWidth < scrollWidth);
};
var MenuOverflow = ({
children,
className,
defaultValue,
value,
onValueChange,
...props
}) => {
const containerRef = useRef(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(false);
useEffect(() => {
const checkScroll = () => internalCheckScroll(
containerRef.current,
setShowLeftArrow,
setShowRightArrow
);
checkScroll();
const container = containerRef.current;
container?.addEventListener("scroll", checkScroll);
window.addEventListener("resize", checkScroll);
return () => {
container?.removeEventListener("scroll", checkScroll);
window.removeEventListener("resize", checkScroll);
};
}, []);
return /* @__PURE__ */ jsxs(
"div",
{
"data-testid": "menu-overflow-wrapper",
className: cn("relative w-full overflow-hidden", className),
children: [
showLeftArrow && /* @__PURE__ */ jsxs(
"button",
{
onClick: () => internalScroll(containerRef.current, "left"),
className: "absolute left-0 top-1/2 -translate-y-1/2 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white shadow-md cursor-pointer",
"data-testid": "scroll-left-button",
children: [
/* @__PURE__ */ jsx(CaretLeft, { size: 16 }),
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Scroll left" })
]
}
),
/* @__PURE__ */ jsx(
Menu,
{
defaultValue,
onValueChange,
value,
variant: "menu2",
...props,
children: /* @__PURE__ */ jsx(MenuContent, { ref: containerRef, variant: "menu2", children })
}
),
showRightArrow && /* @__PURE__ */ jsxs(
"button",
{
onClick: () => internalScroll(containerRef.current, "right"),
className: "absolute right-0 top-1/2 -translate-y-1/2 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white shadow-md cursor-pointer",
"data-testid": "scroll-right-button",
children: [
/* @__PURE__ */ jsx(CaretRight, { size: 16 }),
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Scroll right" })
]
}
)
]
}
);
};
var injectStore = (children, store) => Children.map(children, (child) => {
if (!isValidElement(child)) return child;
const typedChild = child;
const shouldInject = typedChild.type === MenuItem;
return cloneElement(typedChild, {
...shouldInject ? { store } : {},
...typedChild.props.children ? { children: injectStore(typedChild.props.children, store) } : {}
});
});
var Menu_default = Menu;
export {
Menu,
MenuContent,
MenuItem,
MenuItemIcon,
MenuOverflow,
Menu_default as default,
internalCheckScroll,
internalScroll,
useMenuStore
};
//# sourceMappingURL=index.mjs.map