From 340bd8c4bf0806b0264a8a8d186f30918c18be0f Mon Sep 17 00:00:00 2001 From: egagofur Date: Sat, 29 Mar 2025 17:26:54 +0700 Subject: [PATCH] feat: add modal login gacha page --- apps/gacha/src/app/page.tsx | 67 +++++++-- libs/ui/src/molecules/index.ts | 1 + libs/ui/src/molecules/modal/index.tsx | 190 ++++++++++++++++++++++++ libs/ui/src/organisms/navbar/navbar.tsx | 16 ++ 4 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 libs/ui/src/molecules/modal/index.tsx diff --git a/apps/gacha/src/app/page.tsx b/apps/gacha/src/app/page.tsx index eefefed..6cac5b2 100644 --- a/apps/gacha/src/app/page.tsx +++ b/apps/gacha/src/app/page.tsx @@ -1,9 +1,9 @@ import { ArrowDownOutlined } from '@ant-design/icons'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { ModalsGacha } from '@imphnen-frontend-service/ui/organisms'; +import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; import { cn } from '@imphnen-frontend-service/utils'; import { FC, Fragment, ReactElement, useEffect, useState } from 'react'; -import { createPortal } from 'react-dom'; +import { Link } from 'react-router'; interface GachaItemProps { src: string; @@ -14,7 +14,6 @@ interface GachaItemProps { export const Components: FC = (): ReactElement => { // Pinjem const [showModal, setShowModal] = useState(false); - // const scrollToRoulette = () => { const rouletteSection = document.getElementById('roulette'); @@ -222,16 +221,58 @@ export const Components: FC = (): ReactElement => {
{/**Izin pake untuk debug modals gacha */} -
-

Debug : Test Modals Gacha

- -
- {showModal && createPortal( - , - document.body - )} + + setShowModal(false)} + > + + +

+ Login +

+
+ + + + +

+ Lupa Password? +

+ +
+ + +
+

Belum punya akun?

+ + Daftar disini + +
+
+
{/**Izin pake untuk debug modals gacha */} ); diff --git a/libs/ui/src/molecules/index.ts b/libs/ui/src/molecules/index.ts index dbe5a6d..e13b7fd 100644 --- a/libs/ui/src/molecules/index.ts +++ b/libs/ui/src/molecules/index.ts @@ -1,2 +1,3 @@ export * from './input-form'; export * from './pagination'; +export * from './modal'; diff --git a/libs/ui/src/molecules/modal/index.tsx b/libs/ui/src/molecules/modal/index.tsx new file mode 100644 index 0000000..2f0d9ba --- /dev/null +++ b/libs/ui/src/molecules/modal/index.tsx @@ -0,0 +1,190 @@ +import { CloseOutlined } from '@ant-design/icons'; +import { cn } from '@imphnen-frontend-service/utils'; +import React, { useEffect, useMemo, useCallback } from 'react'; +import { createPortal } from 'react-dom'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; + className?: string; + overlayClassName?: string; + closeButtonClassName?: string; + disableEscapeKeyDown?: boolean; + 'aria-label'?: string; + 'aria-labelledby'?: string; + 'aria-describedby'?: string; +} + +const Modal = ({ + isOpen, + onClose, + children, + className, + overlayClassName, + closeButtonClassName, + disableEscapeKeyDown = false, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + 'aria-describedby': ariaDescribedBy, +}: ModalProps) => { + const handleEscapeKey = useCallback( + (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen && !disableEscapeKeyDown) { + onClose(); + } + }, + [isOpen, onClose, disableEscapeKeyDown] + ); + + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + window.addEventListener('keydown', handleEscapeKey); + } else { + document.body.style.overflow = ''; + } + + return () => { + document.body.style.overflow = ''; + window.removeEventListener('keydown', handleEscapeKey); + }; + }, [isOpen, handleEscapeKey]); + + const modalNode = useMemo(() => document.createElement('div'), []); + + useEffect(() => { + document.body.appendChild(modalNode); + return () => { + document.body.removeChild(modalNode); + }; + }, [modalNode]); + + if (!isOpen) return null; + + return createPortal( +
+
+
+ {children} + +
+
, + modalNode + ); +}; + +interface ModalHeaderProps { + className?: string; + children: React.ReactNode; +} + +const ModalHeader = ({ className, children }: ModalHeaderProps) => ( +
+ {children} +
+); + +interface ModalContentProps { + className?: string; + children: React.ReactNode; +} + +const ModalContent = ({ className, children }: ModalContentProps) => ( +
{children}
+); + +interface ModalFooterProps { + className?: string; + children: React.ReactNode; +} + +const ModalFooter = ({ className, children }: ModalFooterProps) => ( +
+ {children} +
+); + +interface ModalTitleProps { + className?: string; + children: React.ReactNode; + id?: string; +} + +const ModalTitle = ({ className, children, id }: ModalTitleProps) => ( +

+ {children} +

+); + +interface ModalDescriptionProps { + className?: string; + children: React.ReactNode; + id?: string; +} + +const ModalDescription = ({ + className, + children, + id, +}: ModalDescriptionProps) => ( +

+ {children} +

+); + +Modal.Header = ModalHeader; +Modal.Content = ModalContent; +Modal.Footer = ModalFooter; +Modal.Title = ModalTitle; +Modal.Description = ModalDescription; + +export { Modal }; +export type { + ModalProps, + ModalHeaderProps, + ModalContentProps, + ModalFooterProps, + ModalTitleProps, + ModalDescriptionProps, +}; diff --git a/libs/ui/src/organisms/navbar/navbar.tsx b/libs/ui/src/organisms/navbar/navbar.tsx index e6822e7..f431833 100644 --- a/libs/ui/src/organisms/navbar/navbar.tsx +++ b/libs/ui/src/organisms/navbar/navbar.tsx @@ -40,6 +40,14 @@ export const Navbar: FC = (): ReactElement => { Merch Gacha +
  • + +
  • )}