-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
587 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,30 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
/* eslint-disable react-refresh/only-export-components */ | ||
export * from './Dialog'; | ||
export * from './DialogContent'; | ||
export * from './DialogToggle'; | ||
export * from './DialogClose'; | ||
|
||
export const DialogHeader = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
`; | ||
|
||
export const DialogFooter = styled.div<{ justify?: React.CSSProperties['justifyContent'] }>` | ||
display: flex; | ||
flex-direction: row; | ||
gap: 0.5rem; | ||
align-items: flex-start; | ||
justify-content: ${({ justify }) => justify || 'flex-end'}; | ||
`; | ||
|
||
export const DialogTitle = styled.h2` | ||
font-weight: 600; | ||
line-height: 1; | ||
margin: 0px; | ||
`; | ||
export const DialogDescription = styled.p` | ||
margin: 0.375rem 0px; | ||
font-weight: 300; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as React from 'react'; | ||
|
||
import useModal from '@/hooks/useModal'; | ||
|
||
import DropdownContext, { DropdownAlignType } from './DropdownContext'; | ||
|
||
interface DropdownProps extends React.ComponentPropsWithoutRef<'div'> { | ||
align?: DropdownAlignType; | ||
} | ||
|
||
/** | ||
* Dropdown Context Provider | ||
* @returns | ||
*/ | ||
export const Dropdown = ({ children, align = 'center', ...props }: DropdownProps) => { | ||
const [showModal, setShowModal] = useModal(); | ||
const [toggleRect, setToggleRect] = React.useState<DOMRect>(); | ||
|
||
return ( | ||
<DropdownContext.Provider value={{ showModal, setShowModal, toggleRect, setToggleRect, align }} {...props}> | ||
{children} | ||
</DropdownContext.Provider> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { keyframes } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import * as React from 'react'; | ||
import { createPortal } from 'react-dom'; | ||
|
||
import useContext from '@/hooks/useContext'; | ||
import { composeRefs } from '@/libs/ref'; | ||
|
||
import DropdownContext from './DropdownContext'; | ||
|
||
interface ModalProps extends React.ComponentPropsWithoutRef<'div'> { | ||
width?: React.CSSProperties['width']; | ||
} | ||
|
||
type PositionType = { | ||
x: number; | ||
y: number; | ||
}; | ||
|
||
/** | ||
* DropdownContent | ||
* @returns | ||
*/ | ||
export const DropdownContent = React.forwardRef<HTMLDivElement, ModalProps>(({ width, ...props }, ref) => { | ||
const { showModal, setShowModal, toggleRect, align } = useContext(DropdownContext); | ||
const modalRef = React.useRef<HTMLDivElement | null>(null); | ||
const [reorgPos, setReorgPos] = React.useState<PositionType>({ x: 0, y: 0 }); | ||
|
||
const close = () => { | ||
setShowModal(false); | ||
}; | ||
|
||
React.useEffect(() => { | ||
if (modalRef.current && toggleRect) { | ||
const rect = modalRef.current.getBoundingClientRect(); | ||
|
||
const isOverflowing = rect.bottom > window.innerHeight; | ||
const reorgPos = { x: 0, y: 0 }; | ||
|
||
switch (align) { | ||
case 'center': | ||
reorgPos.x = toggleRect.x + toggleRect.width / 2 - rect.width / 2; | ||
break; | ||
case 'start': | ||
reorgPos.x = toggleRect.x; | ||
break; | ||
case 'end': | ||
reorgPos.x = toggleRect.x + toggleRect.width - rect.width; | ||
break; | ||
} | ||
|
||
if (isOverflowing) { | ||
reorgPos.y = toggleRect.y - rect.height; | ||
} else { | ||
reorgPos.y = toggleRect.y + toggleRect.height; | ||
} | ||
|
||
setReorgPos(reorgPos); | ||
} | ||
}, [align, showModal, toggleRect]); | ||
|
||
return ( | ||
<> | ||
{showModal && | ||
createPortal( | ||
<> | ||
<ModalBG onClick={close} className="bmates-modal-bg" /> | ||
<Modal ref={composeRefs(modalRef, ref)} width={width} position={reorgPos} {...props}> | ||
{props.children} | ||
</Modal> | ||
</>, | ||
document.body, | ||
)} | ||
</> | ||
); | ||
}); | ||
DropdownContent.displayName = 'DropdownContent'; | ||
|
||
const enter = keyframes` | ||
0% { opacity: 0; } | ||
100% { opacity: 1; } | ||
`; | ||
|
||
const ModalBG = styled.div` | ||
position: fixed; | ||
background-color: transparent; | ||
pointer-events: auto; | ||
z-index: 50; | ||
inset: 0; | ||
`; | ||
|
||
const Modal = styled.div<{ width?: React.CSSProperties['width']; position: PositionType }>` | ||
display: grid; | ||
min-width: max-content; | ||
${({ width }) => width && `width: ${typeof width === 'string' ? width : `${width}px`};`} | ||
padding: 0.25rem; | ||
border-radius: 0.5rem; | ||
position: fixed; | ||
top: 0px; | ||
left: 0px; | ||
transform: ${({ position }) => `translate(${position.x}px, ${position.y}px)`}; | ||
background-color: white; | ||
pointer-events: auto; | ||
z-index: 50; | ||
animation-name: ${enter}; | ||
animation-duration: 0.15s; | ||
border: 1px solid ${({ theme }) => theme.colors.gray['300']}; | ||
box-shadow: 0px 2px 2px 0px ${({ theme }) => theme.colors.gray['300']}; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import * as React from 'react'; | ||
|
||
export type DropdownAlignType = 'start' | 'center' | 'end'; | ||
|
||
interface DropdownContextType { | ||
showModal: boolean; | ||
setShowModal: (value: boolean) => void; | ||
toggleRect: DOMRect | undefined; | ||
setToggleRect: (value: DOMRect) => void; | ||
align: DropdownAlignType; | ||
} | ||
const DropdownContext = React.createContext<DropdownContextType | null>(null); | ||
export default DropdownContext; |
Oops, something went wrong.