nej-react-components/Parts/DropDown.js
2023-12-30 02:10:51 +01:00

155 lines
4.9 KiB
JavaScript

import React, { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, useState } from "react";
import { createPopper } from "@popperjs/core";
import { usePopper } from 'react-popper';
import tw, { styled } from "twin.macro"
import "styled-components/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, onValueChanged, button, buttonProps, popper, placement, hover, ...props }, 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;
let click = item.props?.onClick;
let close = item.props?.closeOnClick ?? true;
return cloneElement(item, { key: children.indexOf ? 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.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>
</>
}