oliveyoung-restock-notification-popup
Version:
모바일 중심의 재입고 알림 신청 Bottom Sheet 웹컴포넌트
401 lines (311 loc) • 11.1 kB
Markdown
[](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)
[](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop)
**모바일 중심의 재입고 알림 신청 Bottom Sheet 웹컴포넌트**
프레임워크에 독립적이며, JSP, Next.js v12/v14 등 모든 환경에서 사용 가능한 네이티브 웹컴포넌트입니다.
- 📱 **모바일 우선 설계**: Bottom Sheet 형태로 하단에서 슬라이드업
- 💻 **반응형 지원**: PC에서는 중앙 모달로 자동 전환
- 🎭 **부드러운 애니메이션**: 슬라이드 업/다운 효과
- 👆 **스와이프 지원**: 모바일에서 드래그로 닫기
- ✅ **완전한 폼 기능**: 휴대폰/이메일 입력, 유효성 검사, 자동 포맷팅
- 🔄 **상태 관리**: 로딩/성공/에러 상태 처리
- 🎨 **스타일 격리**: Shadow DOM으로 CSS 충돌 방지
- 🌐 **프레임워크 독립적**: Vanilla JS, React, Vue, Angular 모두 지원
```bash
npm install oliveyoung-restock-notification-popup
```
```html
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/oliveyoung-restock-notification-popup"></script>
</head>
<body>
<restock-notification-popup
product-id="12345"
product-name="올리브영 베스트 크림"
product-price="29,000원"
api-endpoint="/api/restock-notifications"
>
</restock-notification-popup>
<button onclick="showPopup()">재입고 알림 신청</button>
<script>
function showPopup() {
document.querySelector("restock-notification-popup").show();
}
// 성공 이벤트 리스너
document.addEventListener("restock-notification-success", (e) => {
console.log("신청 완료:", e.detail);
alert("재입고 알림이 등록되었습니다!");
});
</script>
</body>
</html>
```
**⚠️ Next.js에서 올바른 사용법:**
```jsx
import { useEffect, useRef, useState } from "react";
export default function ProductPage() {
const popupRef = useRef(null);
const [isComponentLoaded, setIsComponentLoaded] = useState(false);
useEffect(() => {
// 웹컴포넌트 동적 로드 (Next.js SSR 고려)
const loadComponent = async () => {
if (typeof window !== "undefined") {
await import("oliveyoung-restock-notification-popup");
setIsComponentLoaded(true);
}
};
loadComponent();
}, []);
const showPopup = () => {
if (isComponentLoaded && popupRef.current) {
popupRef.current.show();
}
};
const handleSuccess = (e) => {
console.log("성공:", e.detail);
// 성공 처리 로직
};
useEffect(() => {
if (isComponentLoaded && popupRef.current) {
// 이벤트 리스너 등록
const popup = popupRef.current;
popup.addEventListener("restock-notification-success", handleSuccess);
return () => {
popup.removeEventListener(
"restock-notification-success",
handleSuccess
);
};
}
}, [isComponentLoaded]);
return (
<div>
<button onClick={showPopup}>재입고 알림 신청</button>
{/* 컴포넌트 로드 후에만 렌더링 */}
{isComponentLoaded && (
<restock-notification-popup
ref={popupRef}
product-id="12345"
product-name="올리브영 베스트 크림"
product-price="29,000원"
api-endpoint="/api/restock-notifications"
/>
)}
</div>
);
}
```
**또는 커스텀 훅 사용 (권장):**
```jsx
// hooks/useRestockPopup.js
import { useEffect, useRef, useState } from "react";
export function useRestockPopup() {
const popupRef = useRef(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const loadComponent = async () => {
if (typeof window !== "undefined") {
await import("oliveyoung-restock-notification-popup");
setIsLoaded(true);
}
};
loadComponent();
}, []);
const showPopup = () => {
if (isLoaded && popupRef.current) {
popupRef.current.show();
}
};
const hidePopup = () => {
if (isLoaded && popupRef.current) {
popupRef.current.hide();
}
};
return { popupRef, isLoaded, showPopup, hidePopup };
}
// ProductPage.js에서 사용
import { useRestockPopup } from "../hooks/useRestockPopup";
export default function ProductPage() {
const { popupRef, isLoaded, showPopup } = useRestockPopup();
return (
<div>
<button onClick={showPopup}>재입고 알림 신청</button>
{isLoaded && (
<restock-notification-popup
ref={popupRef}
product-id="12345"
product-name="올리브영 베스트 크림"
product-price="29,000원"
api-endpoint="/api/restock-notifications"
/>
)}
</div>
);
}
```
```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<script src="/node_modules/oliveyoung-restock-notification-popup/dist/restock-notification-popup.js"></script>
</head>
<body>
<div class="product-detail">
<h1>${product.name}</h1>
<p class="price">${product.price}원</p>
<c:if test="${product.soldOut}">
<button onclick="showRestockPopup()">재입고 알림 신청</button>
</c:if>
</div>
<restock-notification-popup
product-id="${product.id}"
product-name="${product.name}"
product-price="${product.price}원"
product-image="${product.imageUrl}"
api-endpoint="/api/restock-notifications">
</restock-notification-popup>
<script>
function showRestockPopup() {
document.querySelector('restock-notification-popup').show();
}
document.addEventListener('restock-notification-success', function(e) {
alert('재입고 알림 신청이 완료되었습니다!');
});
</script>
</body>
</html>
```
| 속성 | 타입 | 필수 | 기본값 | 설명 |
| --------------- | ------ | ---- | ------ | -------------------------- |
| `product-id` | string | ✅ | - | 상품 ID |
| `product-name` | string | ✅ | - | 상품명 |
| `product-price` | string | ✅ | - | 상품 가격 |
| `product-image` | string | ❌ | - | 상품 이미지 URL |
| `api-endpoint` | string | ✅ | - | 재입고 알림 API 엔드포인트 |
| 메서드 | 설명 |
| -------- | ----------------- |
| `show()` | 팝업을 표시합니다 |
| `hide()` | 팝업을 숨깁니다 |
```javascript
const popup = document.querySelector("restock-notification-popup");
popup.show(); // 팝업 열기
popup.hide(); // 팝업 닫기
```
| 이벤트 | 설명 | 데이터 |
| ------------------------------ | ---------------- | ------------------------------------ |
| `restock-notification-show` | 팝업이 표시될 때 | - |
| `restock-notification-hide` | 팝업이 숨겨질 때 | - |
| `restock-notification-success` | 신청 성공시 | `{ productId, phone, email, agree }` |
| `restock-notification-error` | 신청 실패시 | `{ error, data }` |
```javascript
document.addEventListener("restock-notification-success", (e) => {
console.log("신청 데이터:", e.detail);
// { productId: '12345', phone: '010-1234-5678', email: 'user@example.com', agree: true }
});
document.addEventListener("restock-notification-error", (e) => {
console.error("에러:", e.detail.error);
});
```
- 🔥 Bottom Sheet 스타일
- 📱 화면 하단에서 슬라이드업
- 👆 스와이프 다운으로 닫기
- 🎯 큰 터치 버튼
- 💻 중앙 모달 스타일
- ⚡ 페이드인/아웃 애니메이션
- 🖱️ 기존 데스크톱 UX
서버에서 다음과 같은 형태의 API를 제공해야 합니다:
```http
POST /api/restock-notifications
Content-Type: application/json
{
"productId": "12345",
"phone": "010-1234-5678",
"email": "user@example.com",
"agree": true
}
```
**성공시:**
```json
{
"success": true,
"message": "재입고 알림이 등록되었습니다.",
"notificationId": "abc123"
}
```
**실패시:**
```json
{
"success": false,
"message": "이미 등록된 상품입니다."
}
```
CSS 변수를 사용하여 스타일을 커스터마이징할 수 있습니다:
```css
restock-notification-popup {
--primary-color:
--border-radius: 12px;
--overlay-color: rgba(0, 0, 0, 0.4);
}
```
- ✅ Chrome 60+
- ✅ Firefox 63+
- ✅ Safari 13+
- ✅ Edge 79+
- ❌ IE (웹컴포넌트 미지원)
```html
<script src="https://unpkg.com/oliveyoung-restock-notification-popup"></script>
```
```javascript
import "oliveyoung-restock-notification-popup";
```
```javascript
require("oliveyoung-restock-notification-popup");
```
```bash
cp -r node_modules/oliveyoung-restock-notification-popup/dist ./public/libs/
```
```html
<script src="./public/libs/restock-notification-popup.js"></script>
```
- 📱 **테스트 페이지**: [GitHub Pages 데모](https://oy-daegyun.github.io/oliveyoung-mall-restock-noti-pop)
- 🔗 **NPM 패키지**: [npmjs.com](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)
- 💾 **GitHub 저장소**: [GitHub](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop)
1. 저장소를 Fork 합니다
2. 기능 브랜치를 만듭니다 (`git checkout -b feature/amazing-feature`)
3. 변경 사항을 커밋합니다 (`git commit -m 'Add amazing feature'`)
4. 브랜치에 푸시합니다 (`git push origin feature/amazing-feature`)
5. Pull Request를 생성합니다
MIT © [oy-daegyun](https://github.com/oy-daegyun)
- GitHub Issues: [Issues](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop/issues)
- NPM: [oliveyoung-restock-notification-popup](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)