단일 Drag & Drop으로 160가지 다른 코드 조합을 생성하는 도구를 만들었습니다 — 방법은 이렇습니다

발행: (2025년 12월 20일 오후 12:45 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

위에 제공된 Source 라인만으로는 번역할 본문이 없습니다. 번역을 원하는 전체 텍스트(마크다운 형식 포함)를 제공해 주시면, 요청하신 대로 한국어로 번역해 드리겠습니다.

TL;DR

같은 이벤트 랜딩 페이지 코드를 계속 반복해서 작성하는 것이 지겨웠습니다. 그래서 PromoKit을 만들었습니다 – 이미지 위에 버튼을 끌어다 놓고, 프레임워크를 선택하면, 바로 배포 가능한 코드를 얻을 수 있습니다.

10개의 프레임워크 × 16개의 스타일 옵션.
제가 이 거대한 도구를 어떻게 만들었는지 차근차근 설명해 드리겠습니다.

기원 이야기: “버튼을 3 픽셀 위로 옮길 수 있나요?”

  1. HTML을 작성합니다.
  2. 위치 지정, 버튼 스타일, 호버 효과를 위한 CSS를 추가합니다.
  3. 완료.

그런데 이메일이 도착합니다:

  • “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

나를 자랑스럽게 만드는 숫자들

CategoryDetails
FrameworksReact, Vue, Svelte, Angular, Solid.js, Preact, Astro, Qwik, Lit, Vanilla HTML
Styling OptionsCSS, SCSS, Tailwind, Styled Components, Emotion, CSS Modules, UnoCSS, Panda CSS, …
Presets27개의 버튼 스타일 + 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);
}

출력은 모든 상황에 맞게 조정됩니다:

프레임워크이벤트 구문스타일링 통합
ReactonClick={...}Styled Components, Tailwind 등
Vue@click="..."Scoped CSS, CSS Modules
Svelteon: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 Buttons11깨끗하고 미니멀한 디자인
Gradient Buttons10눈길을 끄는 그라디언트
Animated Buttons6바운스, 글로우, 펄스, 쉐이크, 슬라이드, 리플

각 프리셋은 배치 후 완전히 커스터마이즈할 수 있습니다.

실시간 미리보기

(편집할 때마다 라이브 미리보기 창이 업데이트됩니다.)

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 18TypeScript
  • Vite – 초고속 개발 서버
  • Zustand – 상태 관리
  • styled‑components – 스타일링
  • dnd‑kit – 드래그‑앤‑드롭
  • react‑i18next – 한국어, 영어, 일본어 지원
  • JSZip + FileSaver – ZIP 다운로드

데모 및 소스


⭐️ 이게 멋지다고 생각한다면 별을 눌러 주세요.
🐞 보고 싶은 누락된 프레임워크가 있다면 이슈를 열어 주세요.
🔧 풀 리퀘스트를 언제든 환영합니다!

대량의 좌절감, 대량의 커피, 그리고 개발자는 같은 CSS를 두 번 작성해서는 안 된다는 확고한 믿음으로 만들어졌습니다.

Back to Blog

관련 글

더 보기 »

어려운 선택: Angular, React 또는 Vue?

트렌드를 쫓지 않고 최신을 유지하기 기술 선택은 소프트웨어 개발에서 끊임없는 요소이다. 환경은 지속적으로 변한다. 새로운 프레임워크가 등장하고, ...

최고의 SaaS 대시보드 템플릿

현대 SaaS 대시보드 템플릿 – 개발자를 위한 의사결정 가이드 현대 SaaS 제품은 데이터를 얼마나 명확하게 보여주느냐에 따라 성공과 실패가 좌우됩니다. 창업자, 성장 팀, 그리고 고객…