Language : [영어](./EN_README.md) | 한국어
npm install @ilokesto/grunfeldLanguage : 영어 | 한국어
Grunfeld는 React 애플리케이션을 위한 간단하고 직관적인 대화상자 관리 라이브러리입니다. 복잡한 상태 관리 없이 몇 줄의 코드로 모달, 알림, 확인 대화상자를 구현할 수 있습니다.
- 🚀 간단한 API - 복잡한 설정 없이 바로 사용 가능
- 🎯 동기/비동기 지원 - 알림부터 사용자 입력까지 모든 시나리오 지원
- 📱 유연한 위치 설정 - 9분할 그리드로 정확한 위치 배치
- 🔄 스마트 스택 관리 - 논리적인 LIFO 순서로 대화상자 관리
- ⚡ Top-layer 지원 - 네이티브 요소 활용
- 🎨 완전한 커스터마이징 - 스타일과 동작 자유롭게 설정
``bash`
npm install grunfeld또는
yarn add grunfeld
앱의 최상위에 GrunfeldProvider를 추가하세요:
`tsx
import { GrunfeldProvider } from "grunfeld";
function App() {
return
}
`
`tsx
import { grunfeld } from "grunfeld";
function MyComponent() {
const showAlert = () => {
// 간단한 알림 - void 반환 (반환값 없음)
grunfeld.add(() =>
return ;
}
`
` 정말 삭제하시겠습니까?tsx
const showConfirm = async () => {
const result = await grunfeld.add
element: (
),
}));
if (result) {
console.log("사용자가 확인을 클릭했습니다");
} else {
console.log("사용자가 취소를 클릭했습니다");
}
};
`
매개변수 없는 팩토리 함수를 사용하면 간단한 알림으로 동작합니다 (void 반환):
` 저장이 완료되었습니다!tsx
// 기본 알림 - React 요소 직접 반환
grunfeld.add(() => (
style={{
padding: "20px",
background: "white",
borderRadius: "8px",
textAlign: "center",
}}
>
$3
사용자의 선택을 기다리는 확인 대화상자:
`tsx
const confirmed = await grunfeld.add((removeWith) => ({
element: (
style={{
padding: "20px",
background: "white",
borderRadius: "8px",
textAlign: "center",
}}
>
정말 삭제하시겠습니까?
if (confirmed) {
console.log("사용자가 삭제를 확인했습니다");
// 삭제 로직 실행
} else {
console.log("사용자가 취소했습니다");
}
`
사용자로부터 데이터를 입력받는 대화상자:
`tsx
const InputModal = ({ onClose }: { onClose: (name: string) => void }) => {
const [name, setName] = useState("");
return (
export default function GrunfeldPage() {
return (
onClick={async () => {
const value = await grunfeld.add
element:
}));
console.log(value);
}}
>
테스트 버튼
);
}
`
대화상자 생성 시 비동기 작업도 수행할 수 있습니다:
` 사용자 정보를 불러오는 중...tsx
const result = await grunfeld.add
// 로딩 표시
const loadingElement = (
⏳
);
// 먼저 로딩 다이얼로그 표시
setTimeout(() => {
// 실제 데이터 로드 후 내용 업데이트
fetch("/api/user")
.then((res) => res.json())
.then((data) => {
// 성공적으로 로드된 후의 UI로 업데이트하려면
// 새로운 다이얼로그를 생성하거나 상태 관리를 사용해야 합니다
})
.catch(() => {
removeWith("로드 실패");
});
}, 100);
return {
element: loadingElement,
};
});
// 더 실용적인 예제: 선택 리스트
const selectedItem = await grunfeld.add
const items = await fetch("/api/items").then((res) => res.json());
return {
element: (
⚙️ 설정 옵션
$3
`tsx
options={{
defaultPosition: "center", // 기본 위치
defaultLightDismiss: true, // 배경 클릭으로 닫기
defaultRenderMode: "inline", // 렌더링 모드
defaultBackdropStyle: { // 기본 백드롭 스타일
backgroundColor: "rgba(0, 0, 0, 0.5)"
}
}}
>
`$3
`tsx
grunfeld.add(() => ({
element: ,
position: "top-right", // 위치 (9분할 그리드)
lightDismiss: false, // 배경 클릭 비활성화
renderMode: "top-layer", // top-layer 렌더링
backdropStyle: {
// 커스텀 백드롭
backgroundColor: "rgba(0, 0, 0, 0.7)",
backdropFilter: "blur(5px)",
},
dismissCallback: () => {
// 닫힐 때 실행할 함수
console.log("대화상자가 닫혔습니다");
},
}));// 스타일링 예제
grunfeld.add(() => ({
element: (
<>
🎉 축하합니다!
작업이 성공적으로 완료되었습니다.
>
),
position: "center",
backdropStyle: {
backgroundColor: "rgba(102, 126, 234, 0.1)",
backdropFilter: "blur(8px)",
},
}));
`📍 위치 시스템
화면을 9분할로 나누어 정확한 위치에 대화상자를 배치할 수 있습니다:
`
top-left | top-center | top-right
center-left | center | center-right
bottom-left | bottom-center | bottom-right
`> 참고: 중앙 위치는
center 또는 center-center 모두 사용 가능합니다.사용 예시:
`tsx
// 중앙 배치 - 두 방식 모두 동일하게 작동
grunfeld.add(() => ({
element: ,
position: "center", // 또는 "center-center"
}));// 우상단 알림
grunfeld.add(() => ({
element: ,
position: "top-right",
}));
// 하단 액션 시트
grunfeld.add(() => ({
element: ,
position: "bottom-center",
}));
`🎨 렌더링 모드
$3
- z-index 기반의 안정적인 방식
- 모든 브라우저 지원
- 커스터마이징 유연함
- JavaScript 기반 ESC 키 처리
$3
- 네이티브
setMessage(result ? "확인됨" : "취소됨");
};
const showInput = async () => {
const input = await grunfeld.add
element:
}));
setMessage(input ? 입력값: ${input} : "취소됨");
};
return ( 상태: {message}
Grunfeld 예제
);
}
const InputDialog = ({ onSubmit }: { onSubmit: (value: string) => void }) => {
const [value, setValue] = useState("");
return (
🎬 시나리오 (워크플로우)
복잡한 사용자 플로우를 단계별로 정의하고 관리할 수 있는 시나리오 기능입니다. 로그인, 결제, 온보딩 등의 다단계 프로세스를 체계적으로 구성할 수 있습니다.
Grunfeld는 두 가지 시나리오 패턴을 지원합니다:
$3
가장 간단하고 직관적인 방식으로, 객체 형태로 시나리오를 정의합니다.
`tsx
// 로그인 시나리오 정의
const loginScenario = grunfeld.scenario("login", {
showLoginForm: () => {
grunfeld.add(() => ({
element: ,
position: "center",
}));
}, showLoading: () => {
grunfeld.remove(); // 이전 단계 정리
grunfeld.add(() => ({
element: "Loading...",
position: "center",
}));
},
showSuccess: () => {
grunfeld.remove();
grunfeld.add(() => ({
element: "로그인 성공!",
position: "top-right",
}));
},
});
// 사용법 - 동적 메서드 접근
await loginScenario.showLoginForm(); // 특정 단계 실행
await loginScenario.showLoading();
await loginScenario.showSuccess();
`$3
제어 로직과 구현을 분리하여 더 복잡한 시나리오를 구성할 수 있습니다.
`tsx
// 분리된 시나리오 정의
const advancedScenario = grunfeld.scenario(
"user-management",
// 제어 로직 (factory)
(cliche) => ({
processUser: async (user) => {
if (user.isPremium) {
await cliche.showPremiumContent();
} else {
await cliche.showBasicContent();
}
await cliche.logActivity();
},
}),
// 실제 구현 (implementation)
{
showPremiumContent: () => {
grunfeld.add(() => "프리미엄 콘텐츠");
},
showBasicContent: () => {
grunfeld.add(() => "기본 콘텐츠");
},
logActivity: () => {
console.log("사용자 활동 기록됨");
},
}
);// 사용법
await advancedScenario.processUser({ isPremium: true });
`$3
시나리오 단계에 매개변수를 전달하여 동적인 동작을 구현할 수 있습니다.
`tsx
// 매개변수를 받는 시나리오 정의
const userScenario = grunfeld.scenario("user-flow", {
welcomeUser: ({ userName, userType }) => {
grunfeld.add(() => ({
element: ${userName}님 (${userType}) 환영합니다!,
position: "center",
}));
}, showDashboard: ({ permissions = [] }) => {
grunfeld.add(() => ({
element:
대시보드 (권한: ${permissions.join(", ")}),
position: "center",
}));
},
});// 개별 단계에 매개변수 전달 (동적 메서드 접근)
await userScenario.welcomeUser({
userName: "홍길동",
userType: "관리자",
});
await userScenario.showDashboard({
permissions: ["read", "write", "admin"],
});
`$3
`tsx
const registrationScenario = grunfeld.scenario("registration", {
getUserName: async () => {
const name = await grunfeld.add((removeWith) => ({
element: (
이름을 입력하세요
type="text"
onKeyPress={(e) => {
if (e.key === "Enter") {
removeWith(e.target.value);
}
}}
/>
),
position: "center",
})); console.log("입력받은 이름:", name);
return name;
},
confirmData: async () => {
const confirmed = await grunfeld.add((removeWith) => ({
element: (
정보가 맞습니까?
),
position: "center",
}));
if (!confirmed) {
throw new Error("사용자가 취소했습니다");
}
},
});
`$3
`tsx
const advancedScenario = grunfeld.scenario(
"advanced",
{
step1: () => console.log("1단계"),
step2: () => {
throw new Error("오류 발생");
},
step3: () => console.log("3단계"),
},
{
stopOnError: false, // 오류 발생 시에도 계속 진행
stepDelay: 1000, // 단계 간 1초 지연
onStepStart: (stepName) => console.log(시작: ${stepName}),
onStepEnd: (stepName) => console.log(완료: ${stepName}),
onStepError: (stepName, error) => console.log(오류: ${stepName}),
}
);
`$3
-
scenario.step(stepName, params?) - 특정 단계 실행 (매개변수 선택적 전달)
- scenario.run(paramsMap?) - 모든 단계 순차 실행 (단계별 매개변수 맵 선택적 전달)
- scenario.getSteps() - 사용 가능한 단계 목록
- scenario.hasStep(stepName) - 단계 존재 여부 확인
- scenario.clone(newName?) - 시나리오 복제📋 API 참조
$3
두 가지 오버로드:
1. 간단한 알림 (매개변수 없음):
-
grunfeld.add(() => React.ReactNode | GrunfeldProps): void
- 즉시 실행되고 반환값 없음 (동기)
2. 사용자 응답 받기 (매개변수 있음):
- grunfeld.add
- 사용자 응답을 기다림 (비동기)사용 예시:
`tsx
// 1. 간단한 알림 - 반환값 없음
grunfeld.add(() => 간단한 알림);// 옵션과 함께
grunfeld.add(() => ({
element:
위치가 지정된 알림,
position: "top-right",
lightDismiss: false,
}));// 2. 사용자 응답 받기 - Promise 반환
const result = await grunfeld.add((removeWith) => ({
element: (
확인하시겠습니까?
),
}));
// 문자열 입력 받기
const input = await grunfeld.add((removeWith) => ({
element: ,
}));
`GrunfeldProps:
`typescript
{
element: React.ReactNode; // 표시할 내용
position?: Position; // 위치 (기본: "center")
lightDismiss?: boolean; // 배경 클릭으로 닫기 (기본: true)
backdropStyle?: React.CSSProperties; // 백드롭 스타일
dismissCallback?: () => unknown; // 닫힐 때 실행할 함수 (주의: 여기서 grunfeld.remove() 호출 금지)
renderMode?: "inline" | "top-layer"; // 렌더링 모드
}
`⚠️ 중요:
dismissCallback은 대화상자가 제거될 때 실행되므로, 이 함수 내에서 grunfeld.remove()나 grunfeld.clear()를 호출하면 안 됩니다. 자동으로 사라지는 알림을 만들려면 setTimeout을 대화상자 생성 후에 별도로 실행하세요:`tsx
// ❌ 잘못된 방법
grunfeld.add(() => ({
element: 알림,
dismissCallback: () => {
setTimeout(() => grunfeld.remove(), 2000); // 무한 루프 위험
},
}));// ✅ 올바른 방법
grunfeld.add(() => ({
element:
알림,
}));
setTimeout(() => grunfeld.remove(), 2000);
`$3
가장 최근 대화상자를 제거합니다.
$3
모든 대화상자를 제거합니다.
$3
`typescript
type PositionX = "left" | "center" | "right";
type PositionY = "top" | "center" | "bottom";type Position =
${PositionY}-${PositionX} | "center";
``Inline 렌더링: 모든 모던 브라우저 + IE 11+
Top-layer 렌더링: Chrome 37+, Firefox 98+, Safari 15.4+, Edge 79+