-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] #70 - 공통 모달(Modal/Dialog) 컴포넌트 시스템 구현 및 예제 추가 #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
23e949e
8f19e81
7210d85
1b1f2e9
6885721
49f73a2
a1f3727
ea784e3
6246a5f
6fbbf2c
7c9f23c
7023e07
93a890a
1534fc4
49ce40b
e605948
ba03d47
db3a535
6204420
48e33cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./modal"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| import { useState } from "react"; | ||
| import type { Meta, StoryObj } from "@storybook/react-vite"; | ||
| import { Modal, ModalHeader, ModalContent, ModalFooter } from "./modal"; | ||
| import { Button } from "@/shared/ui/button/button"; | ||
| import { Checkbox } from "@/shared/ui/checkbox/checkbox"; | ||
| import { FormField, FormLabel } from "@/shared/ui/form"; | ||
| import { Input, Textarea, Select } from "@/shared/ui/inputBox/inputBox"; | ||
|
|
||
| const meta = { | ||
| title: "Shared/UI/Modal", | ||
| component: Modal, | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| tags: ["autodocs"], | ||
| } satisfies Meta<typeof Modal>; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| // --- 1. Portfolio Filter Modal Example --- | ||
| const FilterModalExample = ({ | ||
| isOpen: propsIsOpen, | ||
| onClose: propsOnClose, | ||
| }: { | ||
| isOpen?: boolean; | ||
| onClose?: () => void; | ||
| }) => { | ||
| const [internalIsOpen, setInternalIsOpen] = useState(false); | ||
| const isOpen = propsIsOpen ?? internalIsOpen; | ||
| const onClose = propsOnClose ?? (() => setInternalIsOpen(false)); | ||
|
|
||
| const categories = ["웹 개발", "모바일 앱", "UI/UX 디자인", "데이터 분석", "AI/ML", "게임 개발"]; | ||
| const departments = ["소프트웨어학과", "미디어학과", "산업공학과", "경영학과"]; | ||
|
|
||
| return ( | ||
| <> | ||
| {!propsIsOpen && <Button onClick={() => setInternalIsOpen(true)}>필터 모달 열기</Button>} | ||
| <Modal isOpen={isOpen} onClose={onClose}> | ||
| <ModalHeader title="필터 선택" onClose={onClose} /> | ||
| <ModalContent className="space-y-8"> | ||
| <div className="space-y-4"> | ||
| <h3 className="text-[16px] font-semibold text-[var(--color-gray-800,#333)]"> | ||
| 카테고리 | ||
| </h3> | ||
| <div className="grid grid-cols-2 gap-3"> | ||
| <Checkbox label="전체" className="col-span-2" defaultChecked /> | ||
| {categories.map((cat) => ( | ||
| <Checkbox key={cat} label={cat} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="h-px bg-[var(--color-gray-100,#f2f2f2)]" /> | ||
|
|
||
| <div className="space-y-4"> | ||
| <h3 className="text-[16px] font-semibold text-[var(--color-gray-800,#333)]">학과</h3> | ||
| <div className="grid grid-cols-2 gap-3"> | ||
| <Checkbox label="전체" className="col-span-2" defaultChecked /> | ||
| {departments.map((dept) => ( | ||
| <Checkbox key={dept} label={dept} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </ModalContent> | ||
| <ModalFooter> | ||
| <Button variant="ghost" onClick={onClose}> | ||
| 초기화 | ||
| </Button> | ||
| <Button variant="secondary" onClick={onClose}> | ||
| 취소 | ||
| </Button> | ||
| <Button variant="primary" onClick={onClose} className="px-8"> | ||
| 적용 | ||
| </Button> | ||
| </ModalFooter> | ||
| </Modal> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export const FilterModal: Story = { | ||
| render: (args) => <FilterModalExample {...args} />, | ||
| args: { | ||
| isOpen: false, | ||
| onClose: () => {}, | ||
| children: null, // 필수 속성 추가 | ||
| }, | ||
| }; | ||
|
|
||
| // --- 2. Profile Edit Modal Example --- | ||
| const ProfileEditModalExample = ({ | ||
| isOpen: propsIsOpen, | ||
| onClose: propsOnClose, | ||
| }: { | ||
| isOpen?: boolean; | ||
| onClose?: () => void; | ||
| }) => { | ||
| const [internalIsOpen, setInternalIsOpen] = useState(false); | ||
| const isOpen = propsIsOpen ?? internalIsOpen; | ||
| const onClose = propsOnClose ?? (() => setInternalIsOpen(false)); | ||
|
|
||
| return ( | ||
| <> | ||
| {!propsIsOpen && ( | ||
| <Button variant="secondary" onClick={() => setInternalIsOpen(true)}> | ||
| 프로필 편집 열기 | ||
| </Button> | ||
| )} | ||
| <Modal isOpen={isOpen} onClose={onClose} className="max-w-[500px]"> | ||
| <ModalHeader title="프로필 편집" onClose={onClose} /> | ||
| <ModalContent className="space-y-5"> | ||
| <FormField> | ||
| <FormLabel>이름</FormLabel> | ||
| <Input defaultValue="김철수" /> | ||
| </FormField> | ||
|
|
||
| <FormField> | ||
| <FormLabel>이메일</FormLabel> | ||
| <Input type="email" defaultValue="chulsoo.kim@ajou.ac.kr" /> | ||
| </FormField> | ||
|
|
||
| <FormField> | ||
| <FormLabel>소속</FormLabel> | ||
| <Input defaultValue="아주대학교 소프트웨어학과" /> | ||
| </FormField> | ||
|
|
||
| <FormField> | ||
| <FormLabel>회원 종류</FormLabel> | ||
| <Select | ||
| options={[ | ||
| { label: "학생", value: "학생" }, | ||
| { label: "교수", value: "교수" }, | ||
| { label: "기업", value: "기업" }, | ||
| ]} | ||
| defaultValue="학생" | ||
| /> | ||
| </FormField> | ||
|
|
||
| <FormField> | ||
| <FormLabel>자기소개</FormLabel> | ||
| <Textarea defaultValue="웹 개발과 UI/UX 디자인에 관심이 많은 학생입니다." rows={4} /> | ||
| </FormField> | ||
| </ModalContent> | ||
| <ModalFooter> | ||
| <Button variant="secondary" onClick={onClose}> | ||
| 취소 | ||
| </Button> | ||
| <Button variant="primary" onClick={onClose} className="px-8"> | ||
| 저장 | ||
| </Button> | ||
| </ModalFooter> | ||
| </Modal> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export const ProfileEditModal: Story = { | ||
| render: (args) => <ProfileEditModalExample {...args} />, | ||
| args: { | ||
| isOpen: false, | ||
| onClose: () => {}, | ||
| children: null, // 필수 속성 추가 | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,135 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useEffect, useId } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createPortal } from "react-dom"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { XIcon } from "@/shared/ui/icons"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --- Global State for Nested Modals --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let modalOpenCount = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let originalOverflow = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --- Types --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type ModalProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOpen: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleId?: string; // 접근성을 위한 ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type ModalHeaderProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClose?: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children?: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id?: string; // h2에 부여될 ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type ModalContentProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type ModalFooterProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --- Styles --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const overlayBaseClasses = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fixed inset-0 z-[100] bg-black/50 backdrop-blur-sm transition-opacity duration-300 flex items-center justify-center p-4"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const modalBaseClasses = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "bg-white rounded-xl shadow-xl w-full max-w-[600px] max-h-[90vh] flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-200"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headerBaseClasses = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "flex items-center justify-between px-6 py-4 border-b border-[var(--color-gray-100,#f2f2f2)]"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const contentBaseClasses = "flex-1 overflow-y-auto px-6 py-6"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const footerBaseClasses = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "px-6 py-4 border-t border-[var(--color-gray-100,#f2f2f2)] flex justify-end gap-2"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const getClasses = (...classes: (string | undefined)[]) => classes.filter(Boolean).join(" "); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --- Components --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const Modal = ({ isOpen, onClose, children, className }: ModalProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const generatedId = useId(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const titleId = `modal-title-${generatedId}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isOpen) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ESC 키 대응 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleEsc = (e: KeyboardEvent) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (e.key === "Escape") onClose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 스크롤 잠금 (중첩 모달 대응) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (modalOpenCount === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| originalOverflow = document.body.style.overflow; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body.style.overflow = "hidden"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modalOpenCount++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.addEventListener("keydown", handleEsc); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modalOpenCount--; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (modalOpenCount === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body.style.overflow = originalOverflow; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.removeEventListener("keydown", handleEsc); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [isOpen, onClose]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isOpen) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 자식 요소들에 titleId를 주입하기 위해 React.Children.map 사용 (ModalHeader 탐색) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const childrenWithA11y = React.Children.map(children, (child) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (React.isValidElement(child) && child.type === ModalHeader) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return React.cloneElement(child as React.ReactElement<ModalHeaderProps>, { id: titleId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return child; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createPortal( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={overlayBaseClasses} onClick={(e) => e.target === e.currentTarget && onClose()}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| role="dialog" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-modal="true" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-labelledby={titleId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={getClasses(modalBaseClasses, className)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {childrenWithA11y} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ModalHeader = ({ title, onClose, children, className, id }: ModalHeaderProps) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={getClasses(headerBaseClasses, className)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {title && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2 id={id} className="text-[20px] font-bold text-[var(--color-gray-900,#111)]"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {title} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {onClose && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={onClose} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="p-1 rounded-md text-[var(--color-gray-400,#999)] hover:text-[var(--color-gray-600,#666)] hover:bg-[var(--color-gray-100,#f2f2f2)] transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="Close modal" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <XIcon size={24} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+109
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Consider applying the 🛡️ Proposed fix: apply id to wrapper export const ModalHeader = ({ title, onClose, children, className, id }: ModalHeaderProps) => (
- <div className={getClasses(headerBaseClasses, className)}>
+ <div id={id} className={getClasses(headerBaseClasses, className)}>
{title && (
- <h2 id={id} className="text-[20px] font-bold text-[var(--color-gray-900,`#111`)]">
+ <h2 className="text-[20px] font-bold text-[var(--color-gray-900,`#111`)]">
{title}
</h2>
)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ModalContent = ({ children, className }: ModalContentProps) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={getClasses(contentBaseClasses, className)}>{children}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ModalFooter = ({ children, className }: ModalFooterProps) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={getClasses(footerBaseClasses, className)}>{children}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.