Skip to content

Instantly share code, notes, and snippets.

@hsnmrd
Created October 31, 2023 09:26
Show Gist options
  • Save hsnmrd/953e003e413130410cd9bd3193b57a87 to your computer and use it in GitHub Desktop.
Save hsnmrd/953e003e413130410cd9bd3193b57a87 to your computer and use it in GitHub Desktop.
kit-update
import {ReactNode, useEffect, useRef, useState} from "react";
import {Div, DivProps} from "@kit/component/div";
import {StylePopup, StylePopupProps} from "@kit/component/popup/style";
import {BreakpointProp} from "@kit/utils";
import {Property} from "csstype";
export interface PopupProps extends Omit<DivProps, "children" | "$top" | "$left" | "$right" | "$bottom">, Omit<StylePopupProps, "$open"> {
$holder: ReactNode
children: (closePopup: () => void) => ReactNode
open?: boolean
$hover?: "close-by-click" | "close-on-exit",
$holderWidth?: BreakpointProp<Property.Width>
}
export const Popup = (props: PopupProps) => {
const {$holder, $hover, open: forceOpen, $holderWidth, children} = props
const [open, setOpen] = useState(false)
const [bottomSpace, setBottomSpace] = useState(0);
const holderRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (open == (forceOpen ?? false)) return
setOpen(forceOpen ?? false)
}, [forceOpen])
const mouseHoverHandler = (enter: boolean, parent: boolean) => {
if ($hover == undefined) return
if (!parent && $hover == "close-by-click") return
setOpen(enter)
}
const closePopup = () => {
setOpen(false)
}
const calculateBottomSpace = () => {
if (holderRef.current) {
const holderRect = holderRef.current.getBoundingClientRect();
const windowHeight = window.innerHeight;
return windowHeight - holderRect.bottom;
}
return 0;
}
useEffect(() => {
const handleResize = () => {
setBottomSpace(calculateBottomSpace());
};
const handleScroll = () => {
setBottomSpace(calculateBottomSpace());
};
window.addEventListener('resize', handleResize);
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<StylePopup $open={open && (forceOpen ?? true)} $bottomSpace={bottomSpace} {...props}>
<Div $width={$holderWidth ?? "fit-content"}
onMouseEnter={() => mouseHoverHandler(true, true)}
onMouseLeave={() => mouseHoverHandler(false, true)}
>
<div className={"holder"} ref={holderRef}>
<div
onClick={() => {
setOpen(prevOpen => !prevOpen);
}}
>
{$holder}
</div>
<div className={"menu"}>
{children?.(closePopup)}
</div>
</div>
<span
onMouseEnter={() => mouseHoverHandler(false, false)}
className={"popup-backdrop"}
onClick={() => setOpen(false)}
/>
</Div>
</StylePopup>
)
}
import React, {ReactNode, useCallback, useEffect, useRef, useState} from "react";
import {Div} from "@kit/component/div";
import {Popup} from "@kit/component/popup";
import {Card} from "@kit/component";
import {BreakpointProp} from "@kit/utils";
import {Property} from "csstype";
export interface SelectProps<TYPE> {
$holder: (current: TYPE | undefined) => ReactNode
children: (option: { data: TYPE, current: TYPE | undefined }) => ReactNode
options: Array<TYPE>
defaultValue?: TYPE
open?: boolean
onChange?: (current: TYPE | undefined) => void
popupWrapper?: (children: ReactNode) => ReactNode
childrenWrapper?: (children: ReactNode) => ReactNode,
holderWidth?: BreakpointProp<Property.Width>
}
export const Select = <TYPE, >(props: SelectProps<TYPE>) => {
const {$holder, children, options, defaultValue, open, onChange, popupWrapper, holderWidth, childrenWrapper} = props
const [current, setCurrent] = useState<TYPE | undefined>(defaultValue)
const holderRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (Object.values(current ?? {}).filter(value => value != undefined).length != 0) return
setCurrent(defaultValue)
}, [current, defaultValue])
const optionClickHandler = (option: TYPE, closePopup: (() => void) | undefined) => {
setCurrent(option)
closePopup?.()
}
useEffect(() => {
onChange?.(current)
}, [current])
const Items = useCallback((props: { options: Array<TYPE>, closePopup: () => void }) => {
const {options, closePopup} = props
return (
React.Children.toArray(options.map(option => {
if (childrenWrapper) {
return (
<Div $width={"100%"}
onClick={() => optionClickHandler(option, closePopup)}>
{childrenWrapper(children({data: option, current: current}))}
</Div>
)
} else {
return (
<div onClick={() => optionClickHandler(option, closePopup)}>
{children({data: option, current: current})}
</div>
);
}
}))
)
}, [])
return (
<Popup
$holderWidth={holderWidth}
open={open}
$holder={
<div ref={holderRef}>
{$holder(current)}
</div>
}
>
{(closePopup) => {
if (popupWrapper) {
return (
<Div $width={holderRef.current?.clientWidth.toString() + "px"}>
{popupWrapper?.(
<Items options={options} closePopup={closePopup}/>
)}
</Div>
)
} else {
return (
<Card $variant={"shadow"} $backgroundColor={() => "transparent"} $borderRadius={"10px"}>
<Card $borderRadius={"10px"} $padding={"5px"} $variant={"outline"}
$backgroundColor={theme => theme.background.default}
$width={holderRef.current?.clientWidth.toString() + "px"}>
<Items options={options} closePopup={closePopup}/>
</Card>
</Card>
)
}
}}
</Popup>
)
}
import styled from "styled-components";
import {StyleDiv} from "@kit/component/div/style";
import {Property} from "csstype";
export interface StylePopupProps {
readonly $open: boolean
readonly $bottomSpace?: number
readonly $left?: Property.Left
readonly $top?: Property.Top
readonly $right?: Property.Right
readonly $bottom?: Property.Bottom
}
export const StylePopup = styled(StyleDiv)<StylePopupProps>`
.holder {
position: relative;
z-index: ${props => props?.$open ? (props.theme.zIndex?.popup.holder ?? 0) + 10 : props.theme.zIndex?.popup.holder};
.menu {
position: absolute;
left: ${props => props.$left ?? "0px"};
display: ${props => props?.$open ? 'inline-block' : 'none'};
z-index: ${props => props?.$open ? (props.theme.zIndex?.popup.menu ?? 0) + 10: props.theme.zIndex?.popup.menu};
top: ${props => props.$open && ((props.$bottomSpace ?? 0) >= 0) ? (`calc(100% + ${props.$top ?? "0px"})`) : 'auto'};
bottom: ${props => props.$open && ((props.$bottomSpace ?? 0) < 0) ? (`calc(100% + ${props.$top ?? "0px"})`) : 'auto'};
}
}
.popup-backdrop {
width: 100vw;
height: 100vh;
position: fixed;
inset: 0;
display: ${props => props?.$open ? 'block' : 'none'};
z-index: ${props => props?.$open ? (props.theme.zIndex?.popup.backdrop ?? 0) + 10 : props.theme.zIndex?.popup.backdrop};
}
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment