단일 Drag & Drop으로 160가지 다른 코드 조합을 생성하는 도구를 만들었습니다 — 방법은 이렇습니다
Source: Dev.to
위에 제공된 Source 라인만으로는 번역할 본문이 없습니다. 번역을 원하는 전체 텍스트(마크다운 형식 포함)를 제공해 주시면, 요청하신 대로 한국어로 번역해 드리겠습니다.
TL;DR
같은 이벤트 랜딩 페이지 코드를 계속 반복해서 작성하는 것이 지겨웠습니다. 그래서 PromoKit을 만들었습니다 – 이미지 위에 버튼을 끌어다 놓고, 프레임워크를 선택하면, 바로 배포 가능한 코드를 얻을 수 있습니다.
10개의 프레임워크 × 16개의 스타일 옵션.
제가 이 거대한 도구를 어떻게 만들었는지 차근차근 설명해 드리겠습니다.
기원 이야기: “버튼을 3 픽셀 위로 옮길 수 있나요?”
- HTML을 작성합니다.
- 위치 지정, 버튼 스타일, 호버 효과를 위한 CSS를 추가합니다.
- 완료.
그런데 이메일이 도착합니다:
- “CTA 버튼을 약간 오른쪽으로 옮길 수 있을까요?” → CSS 조정 → 완료.
- “사실, 20 픽셀 더 높게 해볼 수 있을까요?” → 다시 CSS 조정 → 완료.
제가 만든 것: PromoKit
- Upload 프로모션 이미지를 업로드하세요
- Drag & drop 버튼, 텍스트 및 이미지 오버레이
- Pick 프레임워크와 스타일링 방식을 선택하세요
- Copy 프로덕션 준비된 코드를 복사하세요
그게 전부입니다. 더 이상 수동 좌표 계산이 필요 없습니다. React, Vue, 그리고 Vanilla JS용으로 같은 코드를 다시 작성할 필요도 없습니다.
Live Demo
https://promotion-page-editor.netlify.app/
Source Code
GitHub – seadonggyun4/promo-kit
나를 자랑스럽게 만드는 숫자들
| Category | Details |
|---|---|
| Frameworks | React, Vue, Svelte, Angular, Solid.js, Preact, Astro, Qwik, Lit, Vanilla HTML |
| Styling Options | CSS, SCSS, Tailwind, Styled Components, Emotion, CSS Modules, UnoCSS, Panda CSS, … |
| Presets | 27개의 버튼 스타일 + 5개의 텍스트 스타일 + 12개의 이미지 오버레이 = 44 |
| Combinations | 모든 프레임워크 × 모든 스타일링 = 160개의 가능한 스택 |
기술 심층 분석: 어떻게 구축되었는가
src/
├── features/
│ ├── button-editor/ # Button creation & customization
│ ├── text-editor/ # Text element management
│ ├── image-overlay-editor/ # Image overlay handling
│ ├── download/ # The 2200+‑line code generator
│ └── version-history/ # Undo/redo system
└── shared/
└── store/ # Zustand state management
각 기능은 완전히 독립적입니다. 새로운 요소 유형을 추가하고 싶나요? 새로운 feature 폴더를 만들기만 하면 끝입니다. 기존 코드를 건드릴 필요가 없습니다.
상태 관리: 슈퍼파워를 가진 Zustand
// Every mutation automatically logs to history
const updateElement = (id, data) => {
set(state => {
const updated = state.elements.map(el =>
el.id === id ? { ...el, ...data } : el
);
// Automatic history tracking!
pushToHistory(updated, 'Style Changed', 'element_style');
return { elements: updated };
});
};
마법: 사용자는 상태 저장에 대해 전혀 생각하지 않습니다. 모든 동작이 자동으로 되돌릴 수 있게 됩니다. Ctrl+Z가 바로 작동합니다.
히스토리 시스템: 올바르게 구현된 커맨드 패턴
interface HistoryStore {
past: EditorSnapshot[]; // Previous states
present: EditorSnapshot; // Current state
future: EditorSnapshot[]; // Redo states
}
각 스냅샷은 다음을 캡처합니다:
- 모든 요소와 그 위치 및 스타일
- 배경 이미지(압축됨)
- 현재 선택된 요소
- 타임스탬프와 액션 설명
- 시각적 구분을 위한 액션 타입
디바운스된 히스토리
// During drag operations we don't want 60 snapshots per second
debouncedPushToHistory(
elements,
'Position Changed',
'element_move',
500 // ms of inactivity before saving
);
버튼을 드래그할 때, 저장 전 500 ms 동안 아무 동작이 없을 때만 저장합니다 – 부드러운 UX와 성능 저하 없음.
코드 생성 엔진: 2200+ 줄의 프레임워크 마법
function generateCode(
framework: Framework,
styling: StylingMethod,
elements: Element[],
options: CodeOptions
): string {
// 1. Detect element types (gradient buttons need special handling)
const hasGradients = elements.some(el => isGradientButton(el.style));
// 2. Generate framework‑specific imports
const imports = generateImports(framework, styling, hasGradients);
// 3. Generate element markup per framework
const markup = elements.map(el =>
generateElementMarkup(el, framework, styling)
);
// 4. Generate styles per styling method
const styles = generateStyles(elements, styling, options);
// 5. Assemble into framework structure
return assembleComponent(framework, imports, markup, styles);
}
출력은 모든 상황에 맞게 조정됩니다:
| 프레임워크 | 이벤트 구문 | 스타일링 통합 |
|---|---|---|
| React | onClick={...} | Styled Components, Tailwind 등 |
| Vue | @click="..." | Scoped CSS, CSS Modules |
| Svelte | on:click={...} | “ 블록 또는 Tailwind |
| Angular | (click)="..." | ngClass, ngStyle |
| Solid.js, Preact, Astro, Qwik, Lit, Vanilla | 유사한 규칙 | — |
예시 출력
React
export const PromoPage: React.FC = () => {
return (
<button onClick={() => {
window.location.href = '/deal';
}}>
Get 50% Off
</button>
);
};
Vue (Script Setup)
<template>
<button @click="handleClick">Get 50% Off</button>
</template>
<script setup>
const handleClick = () => {
window.location.href = '/deal';
};
</script>
시각적 히스토리 타임라인
단순히 실행 취소/다시 실행 버튼이 아니라, 전체 시각적 타임라인을 제공하며 다음을 할 수 있습니다:
- 각 작업이 무엇인지(아이콘과 함께) 확인하기
- 원하는 지점을 클릭하여 해당 시점으로 이동하기
- 과거/미래 상태가 몇 개인지 확인하기
Git처럼 디자인을 위한 버전 관리와 같습니다.
스마트 프리셋
44개의 프리셋이 카테고리별로 정리되었습니다:
| 카테고리 | 수량 | 예시 |
|---|---|---|
| Simple Buttons | 11 | 깨끗하고 미니멀한 디자인 |
| Gradient Buttons | 10 | 눈길을 끄는 그라디언트 |
| Animated Buttons | 6 | 바운스, 글로우, 펄스, 쉐이크, 슬라이드, 리플 |
각 프리셋은 배치 후 완전히 커스터마이즈할 수 있습니다.
실시간 미리보기
(편집할 때마다 라이브 미리보기 창이 업데이트됩니다.)
Accessibility Built‑In
- 의미론적 태그 (
<button>,<a>등) aria-label속성- 확장 설명을 위한
aria-describedby - 필요에 따라
role속성 - 키보드 사용자를 위한
:focus-visible스타일 - 스크린리더 전용 콘텐츠
반응형 코드 생성
“responsive” 토글을 켜면 생성된 코드에 미디어 쿼리가 자동으로 포함됩니다:
@media (max-width: 768px) {
.button {
transform: scale(0.85);
font-size: 0.9rem;
}
}
@media (max-width: 640px) {
.button {
transform: scale(0.7);
font-size: 0.8rem;
}
}
귀하의 랜딩 페이지는 모든 환경에서 자동으로 작동합니다.
SEO 메타 태그
Open Graph 태그가 필요하신가요? Twitter 카드? 정규화된 URL? 한 번의 토글로. 완료.
아키텍처 선택
Redux보다 Zustand
- 더 간단한 API
- 내장 히스토리 추적 (위에 표시된 대로)
Feature‑Sliced 설계
- 명확한 관심사 분리
- 확장하기 쉬움
훅에서 상속보다 컴포지션
// Base form logic
const baseForm = useButtonForm();
// Extended for animated buttons
const animatedForm = useAnimatedBtn(); // Internally uses useButtonForm
깨끗하고, 테스트 가능하며, DRY.
순환 의존성을 위한 동적 임포트
const getBackgroundImage = async () => {
const module = await import('./background');
return module.default;
};
기타 코드 스니펫
// Retrieve the uploaded image from the Zustand store
() => {
const { useUploadImageStore } = require('./uploadImageStore');
return useUploadImageStore.getState().uploadedImage;
};
가장 깔끔하진 않지만, 실제 문제를 우아하게 해결했습니다.
기술 스택
- React 18와 TypeScript
- Vite – 초고속 개발 서버
- Zustand – 상태 관리
- styled‑components – 스타일링
- dnd‑kit – 드래그‑앤‑드롭
- react‑i18next – 한국어, 영어, 일본어 지원
- JSZip + FileSaver – ZIP 다운로드
데모 및 소스
⭐️ 이게 멋지다고 생각한다면 별을 눌러 주세요.
🐞 보고 싶은 누락된 프레임워크가 있다면 이슈를 열어 주세요.
🔧 풀 리퀘스트를 언제든 환영합니다!
대량의 좌절감, 대량의 커피, 그리고 개발자는 같은 CSS를 두 번 작성해서는 안 된다는 확고한 믿음으로 만들어졌습니다.