nej-react-components/Parts/DropDown.tsx
2024-10-12 15:30:19 +02:00

167 lines
5.2 KiB
TypeScript

import React, { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, useState } from "react";
import { createPopper, Placement } from "@popperjs/core";
import { usePopper } from 'react-popper';
import tw, { styled } from "twin.macro"
import { colors, colorsDisabled, colorsHover, textColors, textColorsHover } from "./Colors";
const DropDownItem = styled.button(({ type }) => [
// The common button styles added with the tw import
tw`text-sm text-left py-2 px-4 focus:outline-none block w-full whitespace-nowrap bg-transparent hover:bg-secondary disabled:bg-secondary text-primary hover:text-primary`,
// Use props to conditionally style your components
textColors[type], colorsHover[type]
])
export { DropDownItem }
let Dropdown = React.forwardRef(({ children, dropdownCss = null, onValueChanged = null, button, buttonProps = null, popper = null, placement = null, hover = null, ...props }: {
children: React.ReactNode,
dropdownCss?: any,
onValueChanged?: (value: boolean) => void,
button?: React.ReactNode,
buttonProps?: any,
popper?: any,
placement?: Placement,
hover?: boolean,
}, ref) => {
// dropdown props
const [overButton, setOverButton] = React.useState(false);
const [overDropDown, setOverDropDown] = React.useState(false);
const [dropdownPopoverShow, setDropdownPopoverShow] = React.useState(false);
const [btnDropdownRef, setBtnDropdownRef] = React.useState(null);
const [popoverDropdownRef, setPopoverDropdownRef] = React.useState(null);
const { styles, attributes, update } = usePopper(btnDropdownRef, popoverDropdownRef,
{
placement: placement ?? "bottom",
strategy: "fixed",
...popper
});
const openDropdownPopover = async () => {
setDropdownPopoverShow(true);
if (update != null) {
await update();
}
try {
if (onValueChanged)
onValueChanged(true);
} catch (e) {
console.error(e);
}
};
const closeDropdownPopover = () => {
setDropdownPopoverShow(false);
try {
if (onValueChanged)
onValueChanged(false);
} catch (e) {
console.error(e);
}
};
function handleOutsideClick(event) {
let node = popoverDropdownRef;
if (node && !node?.contains(event.target) && !btnDropdownRef?.contains(event.target)) {
closeDropdownPopover();
}
}
useEffect(() => {
if ("ontouchend" in window) {
document.addEventListener("touchend", handleOutsideClick)
} else {
document.addEventListener("click", handleOutsideClick)
}
// Specify how to clean up after this effect:
return function cleanup() {
document.removeEventListener("touchend", handleOutsideClick)
document.removeEventListener("click", handleOutsideClick)
};
});
/*
useImperativeHandle(ref, () => ({
openDropdownPopover,
closeDropdownPopover
}))*/
let childrenWithProps = React.Children.map(children, (item) => {
if (!item)
return;
//check that item is not only ReactNode
if (!isValidElement(item))
return;
let click = item.props?.onClick;
let close = item.props?.closeOnClick ?? true;
return cloneElement(item as any, { key: Array.isArray(children) ? children.indexOf(item) : 0, onClick: () => { close && closeDropdownPopover(); click && click() } });
});
return (
<>
<button
ref={setBtnDropdownRef}
onClick={async (e) => {
e.preventDefault();
dropdownPopoverShow ? closeDropdownPopover() : await openDropdownPopover();
}}
onMouseEnter={async () => { setOverButton(true); hover && await openDropdownPopover() }}
onMouseLeave={() => { setOverButton(false); (!overDropDown && hover) && closeDropdownPopover() }}
{...buttonProps}
>
{button}
</button>
{dropdownPopoverShow && <div
{...props}
ref={setPopoverDropdownRef}
onMouseEnter={async () => { setOverDropDown(true); hover && await openDropdownPopover() }}
onMouseLeave={() => { setOverDropDown(false); (!overButton && hover) && closeDropdownPopover() }}
style={styles.popper}
{...attributes.popper}
css={[
dropdownPopoverShow ? tw`block` : tw`hidden`,
tw`bg-trinary text-base z-50 float-left rounded-xl overflow-hidden text-left shadow-lg`,
dropdownCss,
(props as any).css
]}
>
{childrenWithProps}
</div>}
</>
);
});
export default Dropdown;
export function DropdownMenu({ children, fallback, button, notSelectedOne = true, value, onChange, ...props }) {
let ref = useRef()
let btn = fallback ?? "";
if (children && children.length > 0)
btn = children[value > children.length ? 0 : value]
if (button != null) {
btn = button(btn, value > children.length ? 0 : value);
}
return <>
<Dropdown ref={ref} button={btn} {...props}>
{
children && children.map((i) => {
if (children.indexOf(i) == value)
return;
return <DropDownItem key={children.indexOf(i)} onClick={() => onChange(children.indexOf(i))}>{i}</DropDownItem>
})
}
{
(children == null || children.length <= (notSelectedOne ? 1 : 0)) && <p tw="p-4 text-secondary">Nothing to show</p>
}
</Dropdown>
</>
}