Quipier

Comments 커스터마이징

테마 토큰(색·폰트·모양) · feature on/off · 슬롯으로 댓글 위젯을 내 서비스에 맞추기.

댓글 위젯은 아무 옵션 없이도 기본 형태로 동작하고, 필요한 만큼만 단계적으로 커스텀합니다. 세 가지 계층이 있습니다.

  1. appearance — 색·폰트·모서리·간격 토큰
  2. features — UI 요소 on/off
  3. slots — 특정 부분(또는 댓글 row 전체)을 직접 렌더

모두 init()(vanilla)과 @quipier/sdk/react(QuipierProvider/QuipierComments) 양쪽에서 동일하게 받습니다.

1. appearance — 테마 토큰

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  appearance: {
    accent: "#4f46e5",        // 버튼/강조
    accentText: "#ffffff",
    like: "#e11d48",          // 좋아요(활성)
    link: "#4f46e5",          // 답글 토글 등
    font: 'Inter, sans-serif',
    fontSize: 15,             // 숫자 → px
    radius: 8,                // 메뉴/입력
    pillRadius: 8,            // 버튼
    avatarShape: "square",    // "circle"(기본) | "rounded" | "square"
    gap: 24,                  // 댓글 간 간격
  },
});

지정하지 않은 키는 기본값을 유지합니다. 색 키: accent accentText text muted surface border link like danger.

CSS로 직접 (토큰)

appearance는 결국 컨테이너에 --quipier-* CSS 변수를 꽂는 것이라, CSS로도 같은 결과를 낼 수 있습니다. 다크 모드는 theme: "dark" | "auto".

.my-comments .quipier-root {
  --quipier-accent: #4f46e5;
  --quipier-font: Inter, sans-serif;
  --quipier-avatar-radius: 8px;
  --quipier-gap: 24px;
}

전체 토큰: --quipier-fg --quipier-muted --quipier-faint --quipier-surface --quipier-border --quipier-border-soft --quipier-hover --quipier-accent --quipier-accent-fg --quipier-link --quipier-like --quipier-danger --quipier-font --quipier-font-size --quipier-radius --quipier-radius-pill --quipier-avatar-radius --quipier-gap.

2. features — on/off

init({
  // …
  features: {
    sort: false,    // 정렬(필터) 버튼 숨김
    badge: false,   // "powered by Quipier" 숨김
    likes: true,
    replies: true,  // false = 답글 비활성(= maxDepth 1)
    report: true,
    menu: true,     // 댓글 ⋯ 메뉴
    composer: true, // 입력창
    avatars: true,
  },
});

모든 키 기본값은 true(현재 동작). 끄고 싶은 것만 false.

3. slots — 부분 또는 전체 교체

슬롯 함수는 HTMLElement | string | null | undefined(또는 Preact VNode)를 반환합니다. null/undefined를 반환하면 기본 렌더로 폴백하므로 조건부 커스텀이 가능합니다.

init({
  // …
  slots: {
    // 댓글 row '전체' 교체 — view(데이터) + ctx(동작/헬퍼) 제공
    comment: (view, ctx) => {
      if (view.isDeleted) return null;        // 삭제된 건 기본 처리
      const el = document.createElement("div");
      el.className = "my-comment";
      el.textContent = `${view.author.nickname}: ${view.content}`;
      const like = document.createElement("button");
      like.textContent = `♥ ${view.likes.count}`;
      like.onclick = () => ctx.actions.like(); // 동작은 ctx.actions로
      el.append(like);
      return el;
    },
    // 부분만 교체 — 나머지는 기본 row 유지(속성 상속)
    avatar: (view) => `<img src="/u/${view.author.id}.png" width="32" />`,
    authorLabel: (view) => `<b>${view.author.nickname}</b> <span class="vip">VIP</span>`,
  },
});

슬롯 종류

슬롯인자설명
header(ctx)상단(개수 + 정렬) 전체
composer(ctx)입력창 전체
empty(ctx)댓글 0개일 때
comment(view, ctx)댓글 row 전체 교체
avatar(view, ctx)아바타만
authorLabel(view, ctx)작성자 이름만
content(view, ctx)본문만
actions(view, ctx)좋아요/답글 줄만

comment(전체 교체)를 주면 그 안의 부분 슬롯(avatar 등)은 무시됩니다 — row 전체를 당신이 그리기 때문.

view (QuipierCommentView)

{
  id, content, createdAt,           // createdAt = ISO 문자열
  author: { id, nickname, isOwn, blocked },
  likes: { count, likedByMe },
  isDeleted, isHidden, parentId, replyCount,
  raw,                              // 원본 객체(가급적 의존 X)
}

ctx

  • ctx.actionslike() unlike() reply(content) edit(content) remove() report(reason) (해당 댓글에 바인딩)
  • ctx.helpersformatTime(iso), avatarColor(seed)
  • ctx.theme — 해석된 --quipier-* 값 맵
  • ctx.defaultNode()기본 렌더 결과를 DOM 노드로 돌려줘 감싸기/장식에 사용
slots: {
  comment: (view, ctx) => {
    const wrap = document.createElement("div");
    wrap.style.borderLeft = "3px solid #4f46e5";
    wrap.appendChild(ctx.defaultNode()); // 기본 디자인 + 왼쪽 띠만 추가
    return wrap;
  },
}

⚠️ defaultNode()스냅샷입니다. 장식(테두리·배지 추가)에는 적합하지만, 그 안의 기본 좋아요 버튼 상태는 위젯 본체와 동기화되지 않습니다. 완전한 동작 제어가 필요하면 ctx.actions + view로 직접 렌더하세요.

CSS 훅 (data-part)

슬롯 없이 위치/스타일만 바꾸고 싶다면 모든 요소의 data-quipier-part를 CSS로 타겟하세요.

.quipier-root [data-quipier-part="actions"] { justify-content: flex-end; }
.quipier-root [data-quipier-part="avatar"] { box-shadow: 0 0 0 2px #4f46e5; }

파트: root header count sort composer item body head author content actions like reply replies list empty loadmore error badge.

React

<QuipierProvider
  config={{
    apiKey: "qp_…",
    projectId: "…",
    appearance: { accent: "#4f46e5", avatarShape: "square" },
    features: { sort: false },
  }}
>
  <QuipierComments pageId="/posts/1" />
</QuipierProvider>

appearance/features/slots<QuipierComments>에서 인스턴스별로 덮어쓸 수도 있습니다.

예시 모음 (레시피)

복붙해서 바로 쓰는 완성형 예시입니다. 모두 init() 형태이며, React는 같은 객체를 QuipierProvider config/QuipierComments props로 넘기면 됩니다. 아래 미리보기는 실제 위젯이 데모 데이터로 렌더된 모습입니다(좋아요·작성은 패스포트 연결이 필요).

1) 브랜드 컬러 매칭

가장 흔한 케이스 — 강조색과 폰트만 우리 브랜드로.

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  appearance: {
    accent: "#16a34a",
    accentText: "#ffffff",
    link: "#16a34a",
    like: "#16a34a",
    font: 'Pretendard, -apple-system, sans-serif',
    radius: 10,
    pillRadius: 10,
  },
});

2) 다크 카드 스타일

다크 테마 + 사이언 강조 + 컨테이너를 카드로(컨테이너 스타일은 호스트 CSS).

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  theme: "dark",
  appearance: {
    accent: "#22d3ee",
    accentText: "#04222a",
    surface: "#0b1020",
    gap: 24,
  },
});
#comments {
  background: #0b1020;
  border: 1px solid #1e293b;
  border-radius: 16px;
  padding: 20px;
}

3) 미니멀 / 컴팩트

정렬·배지·아바타를 끄고 간격을 좁혀 가볍게.

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  features: { sort: false, badge: false, avatars: false },
  appearance: { gap: 14, fontSize: 13 },
});

4) 읽기 전용 (작성·상호작용 끄기)

피드백을 받지 않고 보여주기만 할 때.

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  features: {
    composer: false,
    likes: false,
    replies: false,
    report: false,
    menu: false,
  },
});

5) 커스텀 아바타 + 작성자 뱃지 (부분 슬롯)

아바타와 작성자 라벨만 우리 디자인으로 — 본문·좋아요·답글은 기본 그대로(상속).

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  slots: {
    avatar: (view) => {
      const d = document.createElement("div");
      d.style.cssText =
        "width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;background:linear-gradient(135deg,#f97316,#db2777)";
      d.textContent = (view.author.nickname || "?").charAt(0).toUpperCase();
      return d;
    },
    authorLabel: (view) =>
      `<span style="font-weight:800">${view.author.nickname}</span>` +
      `<span style="font-size:11px;background:#fce7f3;color:#db2777;padding:1px 6px;border-radius:999px;margin-left:6px">VIP</span>`,
  },
});

실제 프로필 이미지를 쓰려면 avatar에서 <img src=…>를 반환하면 됩니다.

6) 인기 댓글 강조 (defaultNode 래핑)

기본 row를 그대로 쓰되, 좋아요가 많은 댓글만 띠+라벨로 강조.

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  slots: {
    comment: (view, ctx) => {
      if (view.likes.count < 5) return null; // 그 외는 기본 그대로
      const wrap = document.createElement("div");
      wrap.style.cssText =
        "border-left:3px solid #f59e0b;padding-left:10px;background:#fffbeb;border-radius:6px";
      const tag = document.createElement("div");
      tag.textContent = "⭐ 인기 댓글";
      tag.style.cssText = "font-size:11px;font-weight:700;color:#b45309;padding:4px 0";
      wrap.append(tag, ctx.defaultNode()); // 기본 렌더 + 강조
      return wrap;
    },
  },
});

defaultNode()는 스냅샷이라 장식용으로 적합합니다. 본문/좋아요 동작까지 직접 제어하려면 다음 예시(전체 교체)처럼 view+ctx.actions로 그리세요.

7) 완전 커스텀 카드형 row (전체 교체)

row를 처음부터 우리 디자인으로. 동작은 ctx.actions로 연결.

init({
  container: "#comments",
  apiKey: "qp_…",
  projectId: "…",
  slots: {
    comment: (view, ctx) => {
      if (view.isDeleted) return null; // 삭제는 기본 tombstone
      const card = document.createElement("article");
      card.style.cssText =
        "border:1px solid #e5e7eb;border-radius:12px;padding:14px;display:flex;flex-direction:column;gap:8px";

      const head = document.createElement("div");
      head.style.cssText = "display:flex;justify-content:space-between;align-items:center";
      head.innerHTML =
        `<strong style="font-size:13px">${view.author.nickname ?? "익명"}</strong>` +
        `<time style="color:#9ca3af;font-size:12px">${ctx.helpers.formatTime(view.createdAt)}</time>`;

      const body = document.createElement("p");
      body.textContent = view.content;
      body.style.cssText = "margin:0;font-size:14px;line-height:1.55";

      const like = document.createElement("button");
      like.textContent = `${view.likes.likedByMe ? "♥" : "♡"} ${view.likes.count}`;
      like.style.cssText =
        "align-self:flex-start;border:none;background:#f3f4f6;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer";
      like.onclick = () =>
        view.likes.likedByMe ? ctx.actions.unlike() : ctx.actions.like();

      card.append(head, body, like);
      return card;
    },
  },
});

8) CSS만으로 재배치 (슬롯 없이)

JS 슬롯 없이 위치/스타일만 손볼 땐 data-quipier-part를 타겟.

/* 액션(좋아요·답글)을 오른쪽 정렬 */
#comments [data-quipier-part="actions"] { justify-content: flex-end; }
/* 아바타에 링 */
#comments [data-quipier-part="avatar"] { box-shadow: 0 0 0 2px var(--quipier-accent); }
/* 본문 폰트만 키우기 */
#comments [data-quipier-part="content"] { font-size: 15px; }

9) React에서 인스턴스별 오버라이드

QuipierProvider에 공통 테마를 두고, 특정 위젯만 다르게.

<QuipierProvider config={{ apiKey: "qp_…", projectId: "…", appearance: { accent: "#16a34a" } }}>
  {/* 본문용 — 공통 테마 */}
  <QuipierComments pageId="/posts/1" />

  {/* 사이드바용 — 컴팩트 + 정렬/배지 off */}
  <QuipierComments
    pageId="/posts/1#sidebar"
    features={{ sort: false, badge: false }}
    appearance={{ gap: 12, fontSize: 13 }}
  />
</QuipierProvider>

React Native (@quipier/react-native)

모바일 SDK도 같은 appearance / features / slots 를 받습니다. 차이는 두 가지:

  • appearance 는 CSS 변수가 아니라 객체로 바로 적용됩니다(색·fontFamily·fontSize·radius·pillRadius·gap·avatarShape). 다크 모드는 theme="dark" | "auto".
  • 슬롯 함수는 DOM 이 아니라 React 노드를 반환합니다. null/undefined 반환 시 기본 렌더. data-part CSS 훅은 RN 에 없습니다(스타일은 슬롯으로).
import { QuipierComments } from "@quipier/react-native";
import { View, Text, Image } from "react-native";

<QuipierComments
  apiKey="qp_…"
  projectId="…"
  pageId="/posts/1"
  theme="auto"
  appearance={{ accent: "#4f46e5", like: "#e11d48", avatarShape: "rounded", radius: 12 }}
  features={{ sort: false, badge: false }}
  slots={{
    // 이미지 아바타 (부분 슬롯 — 나머지 row 는 기본)
    avatar: (view) => (
      <Image
        source={{ uri: `https://cdn.example.com/u/${view.author.id}.png` }}
        style={{ width: 32, height: 32, borderRadius: 8 }}
      />
    ),
    // row 전체 교체 — 동작은 ctx.actions
    comment: (view, ctx) => (
      <View style={{ padding: 12, borderRadius: 12, borderWidth: 1, borderColor: "#e5e7eb" }}>
        <Text style={{ fontWeight: "700" }}>{view.author.nickname}</Text>
        <Text style={{ marginTop: 4 }}>{view.content}</Text>
        <Text onPress={() => ctx.actions.like()} style={{ marginTop: 8, color: "#4f46e5" }}>
          ♥ {view.likes.count}
        </Text>
      </View>
    ),
  }}
/>

ctx 는 웹과 동일하게 defaultNode()(기본 노드를 그대로 반환 — 감싸기 가능) · actions · helpers · theme 를 제공합니다. view/슬롯 종류/features 키도 웹과 같습니다.

On this page