diff --git a/.changeset/little-students-tap.md b/.changeset/little-students-tap.md new file mode 100644 index 0000000..979089d --- /dev/null +++ b/.changeset/little-students-tap.md @@ -0,0 +1,6 @@ +--- +"@hensley-ui/ui": patch +"@hensley-ui/react-simple-dialog": patch +--- + +feat: react-simple-dialog 패키지 구현 및 Dialog 컴포넌트 아키텍처 개선 diff --git a/packages/ui/src/components/ui/dialog.tsx b/packages/ui/base-ui/ui/dialog.tsx similarity index 98% rename from packages/ui/src/components/ui/dialog.tsx rename to packages/ui/base-ui/ui/dialog.tsx index c4c1969..570428e 100644 --- a/packages/ui/src/components/ui/dialog.tsx +++ b/packages/ui/base-ui/ui/dialog.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' import { X } from 'lucide-react' -import { cn } from '../../../lib/utils' +import { cn } from '../../lib/utils' const Dialog = DialogPrimitive.Root @@ -117,4 +117,4 @@ export { DialogFooter, DialogTitle, DialogDescription, -} +} \ No newline at end of file diff --git a/packages/ui/eslint.config.js b/packages/ui/eslint.config.js index 69fee93..07e1e7e 100644 --- a/packages/ui/eslint.config.js +++ b/packages/ui/eslint.config.js @@ -10,6 +10,7 @@ export default [ 'dist/**/*', 'react-button/dist/**/*', 'react-heading/dist/**/*', + 'react-simple-dialog/dist/**/*', 'node_modules/**/*', 'tailwind.config.js', '.storybook/**/*', diff --git a/packages/ui/package.json b/packages/ui/package.json index 2e5fee6..b1431b5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -74,6 +74,7 @@ }, "dependencies": { "@hensley-ui/react-button": "workspace:*", + "@hensley-ui/react-simple-dialog": "workspace:*", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-slot": "^1.1.2", diff --git a/packages/ui/react-simple-dialog/README.kr.md b/packages/ui/react-simple-dialog/README.kr.md new file mode 100644 index 0000000..8b0a463 --- /dev/null +++ b/packages/ui/react-simple-dialog/README.kr.md @@ -0,0 +1,256 @@ +# @hensley-ui/react-simple-dialog + +> Promise 기반의 간단하고 직관적인 다이얼로그 컴포넌트 + +ShadCN Dialog의 편의성을 대폭 개선한 React 다이얼로그 라이브러리입니다. 복잡한 상태 관리 없이 간단한 함수 호출만으로 사용자 확인 다이얼로그를 구현할 수 있습니다. + +## 🌏 언어 +- [English](README.md) + +## 🚀 주요 특징 + +- **Promise 기반 API**: `await` 키워드로 사용자 응답을 직관적으로 처리 +- **명령형(Imperative) 접근**: 함수 호출만으로 다이얼로그 실행 +- **동적 콘텐츠**: 런타임에 제목, 설명, 버튼 텍스트 등을 자유롭게 설정 +- **상태 관리 자동화**: 내부적으로 모든 상태 관리가 처리됨 +- **한국어 지원**: 기본적으로 "확인", "취소" 한국어 버튼 지원 +- **TypeScript 완전 지원**: 타입 안전성 보장 + +## 📦 설치 + +```bash +npm install @hensley-ui/react-simple-dialog +# 또는 +pnpm add @hensley-ui/react-simple-dialog +``` + +## 🎯 ShadCN Dialog와의 비교 + +### ❌ ShadCN Dialog의 문제점 + +```tsx +// 복잡한 선언적 구조 +const [isOpen, setIsOpen] = useState(false) +const [title, setTitle] = useState("") +const [description, setDescription] = useState("") + +const handleDelete = async () => { + // 상태 업데이트 필요 + setTitle("삭제 확인") + setDescription("정말 삭제하시겠습니까?") + setIsOpen(true) + + // 사용자 응답 처리가 복잡함 + // 별도의 onConfirm, onCancel 핸들러 필요 +} + +return ( + <> + 삭제 + + {/* JSX에 모든 다이얼로그 구조를 미리 선언해야 함 */} + + + + {title} + {description} + + + setIsOpen(false)}> + 취소 + + { + // 실제 삭제 로직 + setIsOpen(false) + }}> + 확인 + + + + + > +) +``` + +### ✅ SimpleDialog의 편의성 + +```tsx +// 간단하고 직관적인 명령형 API +const openDialog = useSimpleDialog() + +const handleDelete = async () => { + const confirmed = await openDialog({ + title: "삭제 확인", + description: "정말 삭제하시겠습니까?", + confirmButton: "확인", + cancelButton: "취소", + }) + + if (confirmed) { + // 실제 삭제 로직 + console.log("삭제됨!") + } +} + +return 삭제 +``` + +### 🔄 편의성 개선 사항 + +| 항목 | ShadCN Dialog | SimpleDialog | +|------|---------------|--------------| +| **상태 관리** | 수동 (useState, onOpenChange) | 자동 (내부 처리) | +| **사용자 응답 처리** | 복잡 (별도 핸들러) | 직관적 (Promise resolve) | +| **동적 콘텐츠** | 어려움 (상태 업데이트) | 쉬움 (매개변수로 전달) | +| **코드 길이** | 길고 복잡 | 짧고 간결 | +| **JSX 구조** | 미리 선언 필요 | 불필요 | +| **비동기 처리** | 콜백 기반 | Promise 기반 | + +## 🛠 사용법 + +### 1. Provider 설정 + +```tsx +import { SimpleDialogProvider } from '@hensley-ui/react-simple-dialog' + +function App() { + return ( + + + + ) +} +``` + +### 2. 기본 사용 + +```tsx +import { useSimpleDialog } from '@hensley-ui/react-simple-dialog' + +function DeleteButton() { + const openDialog = useSimpleDialog() + + const handleClick = async () => { + const result = await openDialog({ + title: "삭제 확인", + description: "이 항목을 삭제하시겠습니까?", + confirmButton: "삭제", + cancelButton: "취소", + }) + + if (result) { + console.log("사용자가 확인을 눌렀습니다") + // 삭제 로직 실행 + } else { + console.log("사용자가 취소를 눌렀습니다") + } + } + + return 삭제 +} +``` + +### 3. 커스텀 버튼 + +문자열 대신 React 엘리먼트를 전달할 수 있습니다: + +```tsx +const result = await openDialog({ + title: "계정 삭제", + description: "이 작업은 되돌릴 수 없습니다.", + confirmButton: 영구 삭제, + cancelButton: 취소, +}) +``` + +## 📋 API 참조 + +### SimpleDialogType + +```typescript +type SimpleDialogType = { + title: string | ReactNode // 다이얼로그 제목 + description: string | ReactNode // 다이얼로그 설명 + confirmButton: string | ReactNode // 확인 버튼 (기본: "확인") + cancelButton: string | ReactNode // 취소 버튼 (기본: "취소") + onConfirm?: () => void // 확인 시 추가 콜백 (선택사항) + onCancel?: () => void // 취소 시 추가 콜백 (선택사항) +} +``` + +### useSimpleDialog() + +Promise를 반환하는 다이얼로그 열기 함수를 반환합니다. + +**반환값**: `(props: SimpleDialogType) => Promise` + +- `true`: 사용자가 확인 버튼을 클릭한 경우 +- `false`: 사용자가 취소 버튼을 클릭하거나 다이얼로그를 닫은 경우 + +## 🌟 사용 사례 + +### 삭제 확인 + +```tsx +const handleDelete = async () => { + const confirmed = await openDialog({ + title: "항목 삭제", + description: "선택한 항목을 삭제하시겠습니까?", + confirmButton: "삭제", + cancelButton: "취소", + }) + + if (confirmed) { + await deleteItem(itemId) + showSuccessMessage("항목이 삭제되었습니다") + } +} +``` + +### 폼 제출 확인 + +```tsx +const handleSubmit = async () => { + const confirmed = await openDialog({ + title: "양식 제출", + description: "입력한 정보로 양식을 제출하시겠습니까?", + confirmButton: "제출", + cancelButton: "계속 편집", + }) + + if (confirmed) { + await submitForm(formData) + navigate('/success') + } +} +``` + +### 로그아웃 확인 + +```tsx +const handleLogout = async () => { + const confirmed = await openDialog({ + title: "로그아웃", + description: "정말 로그아웃 하시겠습니까?", + confirmButton: "로그아웃", + cancelButton: "취소", + }) + + if (confirmed) { + await logout() + navigate('/login') + } +} +``` + +## 🎨 스타일링 + +이 컴포넌트는 TailwindCSS와 ShadCN 디자인 시스템을 기반으로 구축되었습니다. 필요에 따라 CSS 클래스를 오버라이드하여 스타일을 커스터마이징할 수 있습니다. + +## 🤝 기여 + +개선 사항이나 버그 리포트가 있으시면 언제든 이슈를 등록해 주세요. + +## 📄 라이선스 + +MIT License \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/README.md b/packages/ui/react-simple-dialog/README.md new file mode 100644 index 0000000..c81a156 --- /dev/null +++ b/packages/ui/react-simple-dialog/README.md @@ -0,0 +1,256 @@ +# @hensley-ui/react-simple-dialog + +> Simple and intuitive Promise-based dialog component + +A React dialog library that significantly improves upon ShadCN Dialog's convenience. Implement user confirmation dialogs with simple function calls without complex state management. + +## 🌏 Language +- [한국어](README.kr.md) + +## 🚀 Key Features + +- **Promise-based API**: Handle user responses intuitively with `await` keyword +- **Imperative Approach**: Execute dialogs with simple function calls +- **Dynamic Content**: Freely configure titles, descriptions, button texts at runtime +- **Automated State Management**: All state management handled internally +- **Korean Support**: Built-in Korean button labels ("확인", "취소") +- **Full TypeScript Support**: Type safety guaranteed + +## 📦 Installation + +```bash +npm install @hensley-ui/react-simple-dialog +# or +pnpm add @hensley-ui/react-simple-dialog +``` + +## 🎯 Comparison with ShadCN Dialog + +### ❌ Problems with ShadCN Dialog + +```tsx +// Complex declarative structure +const [isOpen, setIsOpen] = useState(false) +const [title, setTitle] = useState("") +const [description, setDescription] = useState("") + +const handleDelete = async () => { + // Need to update states + setTitle("Delete Confirmation") + setDescription("Are you sure you want to delete?") + setIsOpen(true) + + // Complex user response handling + // Separate onConfirm, onCancel handlers needed +} + +return ( + <> + Delete + + {/* Must declare entire dialog structure in JSX */} + + + + {title} + {description} + + + setIsOpen(false)}> + Cancel + + { + // Actual delete logic + setIsOpen(false) + }}> + Confirm + + + + + > +) +``` + +### ✅ SimpleDialog's Convenience + +```tsx +// Simple and intuitive imperative API +const openDialog = useSimpleDialog() + +const handleDelete = async () => { + const confirmed = await openDialog({ + title: "Delete Confirmation", + description: "Are you sure you want to delete?", + confirmButton: "Delete", + cancelButton: "Cancel", + }) + + if (confirmed) { + // Actual delete logic + console.log("Deleted!") + } +} + +return Delete +``` + +### 🔄 Convenience Improvements + +| Aspect | ShadCN Dialog | SimpleDialog | +|--------|---------------|--------------| +| **State Management** | Manual (useState, onOpenChange) | Automatic (handled internally) | +| **User Response Handling** | Complex (separate handlers) | Intuitive (Promise resolve) | +| **Dynamic Content** | Difficult (state updates) | Easy (pass as parameters) | +| **Code Length** | Long and complex | Short and concise | +| **JSX Structure** | Must be pre-declared | Not required | +| **Async Processing** | Callback-based | Promise-based | + +## 🛠 Usage + +### 1. Provider Setup + +```tsx +import { SimpleDialogProvider } from '@hensley-ui/react-simple-dialog' + +function App() { + return ( + + + + ) +} +``` + +### 2. Basic Usage + +```tsx +import { useSimpleDialog } from '@hensley-ui/react-simple-dialog' + +function DeleteButton() { + const openDialog = useSimpleDialog() + + const handleClick = async () => { + const result = await openDialog({ + title: "Delete Confirmation", + description: "Do you want to delete this item?", + confirmButton: "Delete", + cancelButton: "Cancel", + }) + + if (result) { + console.log("User confirmed") + // Execute delete logic + } else { + console.log("User cancelled") + } + } + + return Delete +} +``` + +### 3. Custom Buttons + +You can pass React elements instead of strings: + +```tsx +const result = await openDialog({ + title: "Account Deletion", + description: "This action cannot be undone.", + confirmButton: Permanently Delete, + cancelButton: Cancel, +}) +``` + +## 📋 API Reference + +### SimpleDialogType + +```typescript +type SimpleDialogType = { + title: string | ReactNode // Dialog title + description: string | ReactNode // Dialog description + confirmButton: string | ReactNode // Confirm button (default: "확인") + cancelButton: string | ReactNode // Cancel button (default: "취소") + onConfirm?: () => void // Additional confirm callback (optional) + onCancel?: () => void // Additional cancel callback (optional) +} +``` + +### useSimpleDialog() + +Returns a dialog opening function that returns a Promise. + +**Return value**: `(props: SimpleDialogType) => Promise` + +- `true`: User clicked the confirm button +- `false`: User clicked the cancel button or closed the dialog + +## 🌟 Use Cases + +### Delete Confirmation + +```tsx +const handleDelete = async () => { + const confirmed = await openDialog({ + title: "Delete Item", + description: "Do you want to delete the selected item?", + confirmButton: "Delete", + cancelButton: "Cancel", + }) + + if (confirmed) { + await deleteItem(itemId) + showSuccessMessage("Item deleted successfully") + } +} +``` + +### Form Submission Confirmation + +```tsx +const handleSubmit = async () => { + const confirmed = await openDialog({ + title: "Submit Form", + description: "Do you want to submit the form with the entered information?", + confirmButton: "Submit", + cancelButton: "Continue Editing", + }) + + if (confirmed) { + await submitForm(formData) + navigate('/success') + } +} +``` + +### Logout Confirmation + +```tsx +const handleLogout = async () => { + const confirmed = await openDialog({ + title: "Logout", + description: "Are you sure you want to logout?", + confirmButton: "Logout", + cancelButton: "Cancel", + }) + + if (confirmed) { + await logout() + navigate('/login') + } +} +``` + +## 🎨 Styling + +This component is built on TailwindCSS and ShadCN design system. You can customize styles by overriding CSS classes as needed. + +## 🤝 Contributing + +Feel free to submit issues for improvements or bug reports anytime. + +## 📄 License + +MIT License \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/package.json b/packages/ui/react-simple-dialog/package.json new file mode 100644 index 0000000..c5f2bb6 --- /dev/null +++ b/packages/ui/react-simple-dialog/package.json @@ -0,0 +1,56 @@ +{ + "name": "@hensley-ui/react-simple-dialog", + "version": "0.1.0", + "private": false, + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist/**" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "vite build", + "dev": "vite build --watch", + "clean": "rimraf .turbo node_modules dist", + "test": "vitest run --passWithNoTests", + "test:watch": "vitest --watch", + "type-check": "tsc --noEmit", + "lint": "eslint ." + }, + "dependencies": { + "@hensley-ui/react-button": "workspace:*", + "@radix-ui/react-dialog": "^1.1.6" + }, + "devDependencies": { + "@hensley-ui/typescript-config": "workspace:*", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "clsx": "^2.1.1", + "jsdom": "^26.1.0", + "lucide-react": "^0.474.0", + "tailwind-merge": "^2.6.0", + "typescript": "^5.0.0", + "vite": "^5.0.0", + "vite-plugin-dts": "^3.7.0", + "vitest": "^3.1.1" + }, + "peerDependencies": { + "@types/react": ">=18.0.0-rc <20.0.0", + "clsx": "^2.1.1", + "lucide-react": "^0.474.0", + "react": ">=18.0.0-rc <20.0.0", + "tailwind-merge": "^2.6.0" + } +} diff --git a/packages/ui/src/components/simpleDialog/SimpleDialog.tsx b/packages/ui/react-simple-dialog/src/SimpleDialog.tsx similarity index 98% rename from packages/ui/src/components/simpleDialog/SimpleDialog.tsx rename to packages/ui/react-simple-dialog/src/SimpleDialog.tsx index 3e6a806..167b580 100644 --- a/packages/ui/src/components/simpleDialog/SimpleDialog.tsx +++ b/packages/ui/react-simple-dialog/src/SimpleDialog.tsx @@ -10,7 +10,7 @@ import { DialogContent, DialogDescription, DialogTitle, -} from '@/components/ui/dialog' +} from '../../base-ui/ui/dialog' import { SimpleDialogType } from './SimpleDialog.types' export const SimpleDialogProvider = ({ @@ -57,4 +57,4 @@ export const SimpleDialogProvider = ({ ) -} +} \ No newline at end of file diff --git a/packages/ui/src/components/simpleDialog/SimpleDialog.types.ts b/packages/ui/react-simple-dialog/src/SimpleDialog.types.ts similarity index 99% rename from packages/ui/src/components/simpleDialog/SimpleDialog.types.ts rename to packages/ui/react-simple-dialog/src/SimpleDialog.types.ts index 4f19a28..fa35948 100644 --- a/packages/ui/src/components/simpleDialog/SimpleDialog.types.ts +++ b/packages/ui/react-simple-dialog/src/SimpleDialog.types.ts @@ -7,4 +7,4 @@ export type SimpleDialogType = { confirmButton: string | ReactNode onConfirm?: () => void onCancel?: () => void -} +} \ No newline at end of file diff --git a/packages/ui/src/components/simpleDialog/context.tsx b/packages/ui/react-simple-dialog/src/context.tsx similarity index 82% rename from packages/ui/src/components/simpleDialog/context.tsx rename to packages/ui/react-simple-dialog/src/context.tsx index bd11ab5..c66ca08 100644 --- a/packages/ui/src/components/simpleDialog/context.tsx +++ b/packages/ui/react-simple-dialog/src/context.tsx @@ -7,4 +7,4 @@ export type SimpleDialogContextType = { } export const SimpleDialogContext = - createContext(null) + createContext(null) \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/src/index.ts b/packages/ui/react-simple-dialog/src/index.ts new file mode 100644 index 0000000..deb7ae5 --- /dev/null +++ b/packages/ui/react-simple-dialog/src/index.ts @@ -0,0 +1,3 @@ +export { SimpleDialogProvider } from './SimpleDialog' +export { useSimpleDialog } from './useSimpleDialog' +export type { SimpleDialogType } from './SimpleDialog.types' \ No newline at end of file diff --git a/packages/ui/src/components/simpleDialog/simpleDialog.test.tsx b/packages/ui/react-simple-dialog/src/simpleDialog.test.tsx similarity index 99% rename from packages/ui/src/components/simpleDialog/simpleDialog.test.tsx rename to packages/ui/react-simple-dialog/src/simpleDialog.test.tsx index 94f10f8..bae7b77 100644 --- a/packages/ui/src/components/simpleDialog/simpleDialog.test.tsx +++ b/packages/ui/react-simple-dialog/src/simpleDialog.test.tsx @@ -118,4 +118,4 @@ const DialogOpener = ({ } return Open Dialog -} +} \ No newline at end of file diff --git a/packages/ui/src/components/simpleDialog/useSimpleDialog.tsx b/packages/ui/react-simple-dialog/src/useSimpleDialog.tsx similarity index 99% rename from packages/ui/src/components/simpleDialog/useSimpleDialog.tsx rename to packages/ui/react-simple-dialog/src/useSimpleDialog.tsx index e07e7b0..fa122b2 100644 --- a/packages/ui/src/components/simpleDialog/useSimpleDialog.tsx +++ b/packages/ui/react-simple-dialog/src/useSimpleDialog.tsx @@ -32,4 +32,4 @@ export const useSimpleDialog = () => { openDialog(content) }) } -} +} \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/tsconfig.json b/packages/ui/react-simple-dialog/tsconfig.json new file mode 100644 index 0000000..2d6d8cb --- /dev/null +++ b/packages/ui/react-simple-dialog/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@hensley-ui/typescript-config/react-library.json", + "compilerOptions": { + "outDir": "dist", + "jsx": "react-jsx", + "baseUrl": ".", + "paths": {}, + "types": ["vitest/globals", "@testing-library/jest-dom"] + }, + "include": ["src", "vitest.config.ts"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/vite.config.ts b/packages/ui/react-simple-dialog/vite.config.ts new file mode 100644 index 0000000..77e4915 --- /dev/null +++ b/packages/ui/react-simple-dialog/vite.config.ts @@ -0,0 +1,55 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + outDir: 'dist', + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'HensleyUI-React-SimpleDialog', + formats: ['es', 'cjs'], + fileName: (format) => `index.${format === 'es' ? 'mjs' : 'js'}`, + }, + rollupOptions: { + external: [ + 'react', + 'react-dom', + 'react/jsx-runtime', + 'clsx', + 'tailwind-merge', + 'lucide-react', + '@hensley-ui/react-button', + '@radix-ui/react-dialog', + ], + output: { + exports: 'named', + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + 'react/jsx-runtime': 'jsxRuntime', + 'lucide-react': 'LucideReact', + '@hensley-ui/react-button': 'HensleyUIReactButton', + '@radix-ui/react-dialog': 'RadixUIReactDialog', + }, + }, + }, + }, + resolve: { + alias: { + '@': resolve(__dirname, './src'), + }, + }, + plugins: [ + dts({ + entryRoot: 'src', + outDir: 'dist', + exclude: [ + '**/*.test.*', + '**/*.stories.*', + '**/*.spec.*', + '**/vitest.config.*', + ], + }), + ], +}) \ No newline at end of file diff --git a/packages/ui/react-simple-dialog/vitest.config.ts b/packages/ui/react-simple-dialog/vitest.config.ts new file mode 100644 index 0000000..a02a34c --- /dev/null +++ b/packages/ui/react-simple-dialog/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: '../test/setup', + }, +}) \ No newline at end of file diff --git a/packages/ui/src/components/dialog/Dialog.stories.tsx b/packages/ui/src/components/dialog/Dialog.stories.tsx deleted file mode 100644 index bdfe56c..0000000 --- a/packages/ui/src/components/dialog/Dialog.stories.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react' -import { - Dialog, - DialogTrigger, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from '@/components/ui/dialog' -import { Button } from '@hensley-ui/react-button' - -const meta: Meta = { - title: 'UI Component/Dialog', - component: Dialog, - parameters: { - layout: 'centered', - docs: { - description: { - component: 'Dialog는 모달 형태의 대화상자를 표시하는 컴포넌트입니다.', - }, - }, - }, - tags: ['autodocs'], -} satisfies Meta - -export default meta -type Story = StoryObj - -// 기본 다이얼로그 -export const Default: Story = { - render: () => ( - - - 다이얼로그 열기 - - - - 다이얼로그 제목 - - 이것은 기본 다이얼로그의 설명입니다. - - - 다이얼로그의 본문 내용이 여기에 들어갑니다. - - 취소 - 확인 - - - - ), -} - -// 경고 다이얼로그 -export const Alert: Story = { - render: () => ( - - - 삭제하기 - - - - 정말 삭제하시겠습니까? - 이 작업은 되돌릴 수 없습니다. - - - 취소 - 삭제 - - - - ), -} - -// 긴 내용의 다이얼로그 -export const LongContent: Story = { - render: () => ( - - - 자세히 보기 - - - - 상세 정보 - 아래는 상세한 내용입니다. - - - {Array.from({ length: 10 }).map((_, i) => ( - - 섹션 {i + 1}의 내용입니다. - - ))} - - - 닫기 - - - - ), -} - -// 커스텀 스타일 다이얼로그 -export const CustomStyle: Story = { - render: () => ( - - - 커스텀 스타일 - - - - - 커스텀 스타일 다이얼로그 - - - 배경과 테두리가 커스텀된 다이얼로그입니다. - - - - 커스텀 스타일이 적용된 다이얼로그의 내용입니다. - - - - 취소 - - 확인 - - - - ), -} diff --git a/packages/ui/src/components/simpleDialog/__stories__/SimpleDialog.stories.tsx b/packages/ui/src/components/simpleDialog/__stories__/SimpleDialog.stories.tsx deleted file mode 100644 index 96af564..0000000 --- a/packages/ui/src/components/simpleDialog/__stories__/SimpleDialog.stories.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react' -import { SimpleDialogDefaultExample } from './examples' -import { SimpleDialogProvider } from '../SimpleDialog' -import { useSimpleDialog } from '../useSimpleDialog' -import { SimpleDialogType } from '../SimpleDialog.types' - -const meta: Meta = { - title: 'UI Component/SimpleDialog', - component: SimpleDialogProvider, - parameters: { - layout: 'centered', - docs: { - description: { - component: ` -SimpleDialog는 사용자에게 확인이나 취소가 필요한 작업을 수행할 때 사용되는 다이얼로그 컴포넌트입니다. - -## Features -- Promise 기반의 확인/취소 처리 -- 커스텀 버튼 텍스트 지원 -- React Element를 버튼으로 사용 가능 -- 접근성 지원 - -## Usage -\`\`\`tsx -import { SimpleDialogProvider, useSimpleDialog } from '@components/simpleDialog' - -const MyComponent = () => { - const openDialog = useSimpleDialog() - - const handleClick = async () => { - const confirmed = await openDialog({ - title: '삭제 확인', - description: '정말 삭제하시겠습니까?', - confirmButton: '삭제', - cancelButton: '취소', - }) - - if (confirmed) { - // 삭제 로직 실행 - } - } - - return ( - - 삭제하기 - - ) -} -\`\`\` - `, - }, - }, - }, - argTypes: { - title: { - description: '다이얼로그의 제목', - control: 'text', - table: { - type: { summary: 'string' }, - defaultValue: { summary: 'undefined' }, - }, - }, - description: { - description: '다이얼로그의 설명', - control: 'text', - table: { - type: { summary: 'string' }, - defaultValue: { summary: 'undefined' }, - }, - }, - confirmButton: { - description: '확인 버튼의 텍스트 또는 React Element', - control: 'text', - table: { - type: { summary: 'string | ReactElement' }, - defaultValue: { summary: 'undefined' }, - }, - }, - cancelButton: { - description: '취소 버튼의 텍스트 또는 React Element', - control: 'text', - table: { - type: { summary: 'string | ReactElement' }, - defaultValue: { summary: 'undefined' }, - }, - }, - onConfirm: { - description: '확인 버튼 클릭 시 호출되는 콜백 함수', - control: false, - table: { - type: { summary: '() => void' }, - defaultValue: { summary: 'undefined' }, - }, - }, - onCancel: { - description: '취소 버튼 클릭 시 호출되는 콜백 함수', - control: false, - table: { - type: { summary: '() => void' }, - defaultValue: { summary: 'undefined' }, - }, - }, - }, - tags: ['autodocs'], -} - -export default meta -type Story = StoryObj - -export const Default: Story = { - args: { - title: '기본 다이얼로그', - description: '이것은 기본 다이얼로그입니다.', - confirmButton: '확인', - cancelButton: '취소', - }, - render: (args) => ( - - - - ), - parameters: { - docs: { - description: { - story: '기본적인 SimpleDialog 사용 예제입니다.', - }, - }, - }, -} - -const App = (args: SimpleDialogType) => { - const openModal = useSimpleDialog() - - const handleClick = async () => { - const res = await openModal({ - ...args, - }) - if (res) { - console.log('done') - } - } - - return ( - <> - 버튼 - > - ) -} - -export const PromiseTest: Story = { - args: { - title: '스토리북 확인', - description: 'Promise resolve 되는지 확인', - confirmButton: '확인', - cancelButton: '취소', - }, - render: (args) => ( - - - - ), - parameters: { - docs: { - description: { - story: 'Promise 기반의 확인/취소 처리를 보여주는 예제입니다.', - }, - }, - }, -} - -export const CustomButton: Story = { - args: { - title: '커스텀 버튼', - description: 'React Element를 버튼으로 사용할 수 있습니다.', - }, - render: (args) => ( - - - - ), - parameters: { - docs: { - description: { - story: '커스텀 버튼을 사용하는 예제입니다.', - }, - }, - }, -} - -const CustomButtonExample = (args: SimpleDialogType) => { - const openModal = useSimpleDialog() - - const handleClick = async () => { - const res = await openModal({ - ...args, - confirmButton: 삭제, - cancelButton: 취소, - }) - if (res) { - console.log('삭제됨') - } - } - - return ( - <> - 커스텀 버튼 다이얼로그 - > - ) -} diff --git a/packages/ui/src/components/simpleDialog/__stories__/examples.tsx b/packages/ui/src/components/simpleDialog/__stories__/examples.tsx deleted file mode 100644 index 74d6216..0000000 --- a/packages/ui/src/components/simpleDialog/__stories__/examples.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from '@hensley-ui/react-button' -import { useSimpleDialog } from '../useSimpleDialog' -import { SimpleDialogType } from '../SimpleDialog.types' - -export const SimpleDialogDefaultExample = ( - props: Partial, -) => { - const openModal = useSimpleDialog() - - const handleClick = async () => { - const res = await openModal({ - title: '스토리북 확인', - description: 'Promise resolve 되는지 확인', - confirmButton: '확인', - cancelButton: '취소', - ...props, - }) - if (res) { - console.log('done') - } - } - return 다이얼로그 열기 -} diff --git a/packages/ui/src/components/simpleDialog/index.ts b/packages/ui/src/components/simpleDialog/index.ts deleted file mode 100644 index 6c3fb0e..0000000 --- a/packages/ui/src/components/simpleDialog/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { SimpleDialogProvider } from '@/components/simpleDialog/SimpleDialog' -export { useSimpleDialog } from '@/components/simpleDialog/useSimpleDialog' -export type { SimpleDialogType } from '@/components/simpleDialog/SimpleDialog.types' diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 8ed136f..ccf4014 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -9,6 +9,13 @@ import { TextRef, } from '@/components/heading/Heading.types' +// SimpleDialog를 새로운 패키지에서 re-export +export { + SimpleDialogProvider, + useSimpleDialog, + type SimpleDialogType, +} from '@hensley-ui/react-simple-dialog' + export { Button, Heading } export type { ButtonProps, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac6448d..4c71b3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@hensley-ui/react-button': specifier: workspace:* version: link:react-button + '@hensley-ui/react-simple-dialog': + specifier: workspace:* + version: link:react-simple-dialog '@radix-ui/react-dialog': specifier: ^1.1.6 version: 1.1.6(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@19.1.0)(react@19.1.0) @@ -272,6 +275,55 @@ importers: specifier: ^3.1.1 version: 3.1.1(@types/node@22.13.4)(jsdom@26.1.0) + packages/ui/react-simple-dialog: + dependencies: + '@hensley-ui/react-button': + specifier: workspace:* + version: link:../react-button + '@radix-ui/react-dialog': + specifier: ^1.1.6 + version: 1.1.6(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@19.1.0)(react@19.1.0) + '@types/react': + specifier: '>=18.0.0-rc <20.0.0' + version: 18.3.18 + react: + specifier: '>=18.0.0-rc <20.0.0' + version: 19.1.0 + devDependencies: + '@hensley-ui/typescript-config': + specifier: workspace:* + version: link:../../typescript-config + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@19.1.0)(react@19.1.0) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + lucide-react: + specifier: ^0.474.0 + version: 0.474.0(react@19.1.0) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + typescript: + specifier: ^5.0.0 + version: 5.6.3 + vite: + specifier: ^5.0.0 + version: 5.4.19(@types/node@22.13.4) + vite-plugin-dts: + specifier: ^3.7.0 + version: 3.9.1(@types/node@22.13.4)(typescript@5.6.3)(vite@5.4.19) + vitest: + specifier: ^3.1.1 + version: 3.1.1(@types/node@22.13.4)(jsdom@26.1.0) + playground: dependencies: '@hensley-ui/react-heading': @@ -6374,7 +6426,6 @@ packages: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 dependencies: react: 19.1.0 - dev: false /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}