@leviosa-ai/detail-page-editor 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/detail-page/detail-page-download-dialog.tsx +22 -19
- package/components/detail-page/edit-quota-ui.tsx +6 -6
- package/components/detail-page/generation-progress-fill.tsx +4 -4
- package/components/detail-page/group-prompt-edit-panel.tsx +6 -6
- package/components/detail-page/prompt-edit-panel.tsx +6 -6
- package/components/detail-page/spec-prompt-edit-panel.tsx +7 -7
- package/components/detail-page/svg-prompt-edit-panel.tsx +6 -6
- package/components/ui/button.tsx +14 -1
- package/components/ui/dialog.tsx +6 -1
- package/components/ui/select.tsx +15 -2
- package/components/ui/slider.tsx +5 -0
- package/components/ui/switch.tsx +8 -1
- package/components/ui/tooltip.tsx +8 -1
- package/lib/detail-page/editor-profile.ts +14 -1
- package/package.json +1 -1
|
@@ -58,7 +58,8 @@ type ExportStoreLike = {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// 등록 플랫폼은 파일명 접미사에만 반영된다(상세페이지 폭은 750px 고정). 셀러가
|
|
61
|
-
// 어디에 올릴지 고르며 맥락을 잡도록 노출한다.
|
|
61
|
+
// 어디에 올릴지 고르며 맥락을 잡도록 노출한다. 캐러셀은 인스타그램 한 곳으로만
|
|
62
|
+
// 나가므로 이 목록을 아예 안 띄운다 — `profile.registerPlatform` 이 그것을 정한다.
|
|
62
63
|
const PLATFORMS = [
|
|
63
64
|
{ value: "naver", label: "네이버 스마트 스토어" },
|
|
64
65
|
{ value: "coupang", label: "쿠팡" },
|
|
@@ -220,7 +221,7 @@ export const DetailPageDownloadDialog = observer(function DetailPageDownloadDial
|
|
|
220
221
|
setError(null);
|
|
221
222
|
const mime = format === "png" ? "image/png" : "image/jpeg";
|
|
222
223
|
const ext = format === "png" ? "png" : "jpg";
|
|
223
|
-
const base = `${fileName}-${platform}
|
|
224
|
+
const base = profile.registerPlatform ? `${fileName}-${platform}` : fileName;
|
|
224
225
|
try {
|
|
225
226
|
if (format === "psd" || format === "svg" || format === "ai") {
|
|
226
227
|
// 문서 JSON 기반 내보내기. ag-psd 포함 모듈은 이 시점에만 로드한다.
|
|
@@ -384,23 +385,25 @@ export const DetailPageDownloadDialog = observer(function DetailPageDownloadDial
|
|
|
384
385
|
</Select>
|
|
385
386
|
</div>
|
|
386
387
|
|
|
387
|
-
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
<
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{p.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
388
|
+
{profile.registerPlatform && (
|
|
389
|
+
<div className="space-y-1.5">
|
|
390
|
+
<Label className="text-xs text-dpe-ink-500">
|
|
391
|
+
{t("editor.registerPlatform")}
|
|
392
|
+
</Label>
|
|
393
|
+
<Select value={platform} onValueChange={setPlatform}>
|
|
394
|
+
<SelectTrigger className="w-full">
|
|
395
|
+
<SelectValue />
|
|
396
|
+
</SelectTrigger>
|
|
397
|
+
<SelectContent>
|
|
398
|
+
{PLATFORMS.map((p) => (
|
|
399
|
+
<SelectItem key={p.value} value={p.value}>
|
|
400
|
+
{p.label}
|
|
401
|
+
</SelectItem>
|
|
402
|
+
))}
|
|
403
|
+
</SelectContent>
|
|
404
|
+
</Select>
|
|
405
|
+
</div>
|
|
406
|
+
)}
|
|
404
407
|
|
|
405
408
|
<div className="space-y-1.5">
|
|
406
409
|
<Label className="text-xs text-dpe-ink-500">{t("editor.pageScope")}</Label>
|
|
@@ -90,7 +90,7 @@ export function EditUsageBadge({
|
|
|
90
90
|
if (unlimited) {
|
|
91
91
|
return (
|
|
92
92
|
<span
|
|
93
|
-
className="ml-auto text-[11px] tabular-nums text-
|
|
93
|
+
className="ml-auto text-[11px] tabular-nums text-dpe-ink-500"
|
|
94
94
|
title={t("detailPage.editQuota.unlimitedTitle")}
|
|
95
95
|
>
|
|
96
96
|
{t("detailPage.editQuota.usedUnlimited", { used })}
|
|
@@ -104,10 +104,10 @@ export function EditUsageBadge({
|
|
|
104
104
|
className={
|
|
105
105
|
"ml-auto text-[11px] tabular-nums " +
|
|
106
106
|
(remaining === 0
|
|
107
|
-
? "text-
|
|
107
|
+
? "text-dpe-danger-600"
|
|
108
108
|
: warn
|
|
109
109
|
? "text-dpe-warn-500"
|
|
110
|
-
: "text-
|
|
110
|
+
: "text-dpe-ink-500")
|
|
111
111
|
}
|
|
112
112
|
title={t("detailPage.editQuota.remainingTitle", { remaining })}
|
|
113
113
|
>
|
|
@@ -130,17 +130,17 @@ export function EditQuotaBlock({
|
|
|
130
130
|
: t("detailPage.editQuota.textEdit");
|
|
131
131
|
return (
|
|
132
132
|
<div className="flex flex-col items-center gap-2 px-4 py-4 text-center">
|
|
133
|
-
<p className="text-[13px] font-dpe-medium text-
|
|
133
|
+
<p className="text-[13px] font-dpe-medium text-dpe-ink-900">
|
|
134
134
|
{t("detailPage.editQuota.quotaExhausted", { label })}
|
|
135
135
|
</p>
|
|
136
|
-
<p className="text-[11px] leading-4 text-
|
|
136
|
+
<p className="text-[11px] leading-4 text-dpe-ink-500">
|
|
137
137
|
{t("detailPage.editQuota.quotaHint")}
|
|
138
138
|
</p>
|
|
139
139
|
<button
|
|
140
140
|
type="button"
|
|
141
141
|
onClick={onBuyMore}
|
|
142
142
|
disabled={!onBuyMore}
|
|
143
|
-
className="mt-1 inline-flex items-center gap-1.5 rounded-dpe-lg bg-
|
|
143
|
+
className="mt-1 inline-flex items-center gap-1.5 rounded-dpe-lg bg-dpe-ink-900 px-3 py-1.5 text-xs font-dpe-medium text-dpe-on-accent disabled:opacity-40"
|
|
144
144
|
>
|
|
145
145
|
<Sparkles size={13} />
|
|
146
146
|
{t("detailPage.editQuota.buyCredits")}
|
|
@@ -38,18 +38,18 @@ export function progressPercent(progress: number): number {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* 밝은 배경 패널(카피·SVG 프롬프트 편집)의 하단에 얹는 얇은 프로그레스 라인.
|
|
41
|
-
*
|
|
42
|
-
* 하며, 이 요소는 컨테이너 하단에 절대배치된다.
|
|
41
|
+
* 색은 AI 표식 토큰(`dpe-ai`)이다 — 이 선이 도는 동안 일어나는 일이 생성이기
|
|
42
|
+
* 때문이다. 부모는 `relative`여야 하며, 이 요소는 컨테이너 하단에 절대배치된다.
|
|
43
43
|
*/
|
|
44
44
|
export function GenerationProgressLine({ progress }: { progress: number }) {
|
|
45
45
|
const pct = progressPercent(progress);
|
|
46
46
|
return (
|
|
47
47
|
<span
|
|
48
48
|
aria-hidden="true"
|
|
49
|
-
className="pointer-events-none absolute inset-x-0 bottom-0 h-[3px] bg-
|
|
49
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 h-[3px] bg-dpe-ai/15"
|
|
50
50
|
>
|
|
51
51
|
<span
|
|
52
|
-
className="block h-full bg-
|
|
52
|
+
className="block h-full bg-dpe-ai"
|
|
53
53
|
style={{ width: `${pct}%`, transition: "width 200ms ease-out" }}
|
|
54
54
|
/>
|
|
55
55
|
</span>
|
|
@@ -106,10 +106,10 @@ export function GroupPromptEditPanel({
|
|
|
106
106
|
const badgeLimit = hasText ? textLimit : svgLimit;
|
|
107
107
|
|
|
108
108
|
return (
|
|
109
|
-
<div className="flex flex-col overflow-hidden rounded-dpe-xl border border-
|
|
110
|
-
<div className="flex items-center gap-1.5 border-b border-
|
|
111
|
-
<Sparkles size={13} className="text-
|
|
112
|
-
<span className="text-xs font-dpe-medium text-
|
|
109
|
+
<div className="flex flex-col overflow-hidden rounded-dpe-xl border border-dpe-ink-200 bg-dpe-surface">
|
|
110
|
+
<div className="flex items-center gap-1.5 border-b border-dpe-ink-200 px-3 py-2">
|
|
111
|
+
<Sparkles size={13} className="text-dpe-ai" />
|
|
112
|
+
<span className="text-xs font-dpe-medium text-dpe-ink-900">프롬프트로 편집</span>
|
|
113
113
|
{unlimited || badgeLimit > 0 ? (
|
|
114
114
|
<EditUsageBadge used={badgeUsed} limit={badgeLimit} unlimited={unlimited} />
|
|
115
115
|
) : null}
|
|
@@ -132,13 +132,13 @@ export function GroupPromptEditPanel({
|
|
|
132
132
|
busy ? "수정 중…" : '어떻게 바꿀까요? (예: "더 힘있게", "톤을 통일해서")'
|
|
133
133
|
}
|
|
134
134
|
disabled={busy}
|
|
135
|
-
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-
|
|
135
|
+
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 disabled:opacity-60"
|
|
136
136
|
/>
|
|
137
137
|
<button
|
|
138
138
|
type="button"
|
|
139
139
|
onClick={send}
|
|
140
140
|
disabled={busy || !input.trim()}
|
|
141
|
-
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-
|
|
141
|
+
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-dpe-ink-900 text-dpe-on-accent disabled:opacity-40"
|
|
142
142
|
aria-label="프롬프트로 수정"
|
|
143
143
|
>
|
|
144
144
|
{busy ? (
|
|
@@ -97,10 +97,10 @@ export function PromptEditPanel({
|
|
|
97
97
|
}, [api, toast, input, busy, blocked, generatedId, slotRole, currentText, maxLength, renderKind, onApplied, onUsage, t]);
|
|
98
98
|
|
|
99
99
|
return (
|
|
100
|
-
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-
|
|
101
|
-
<div className="flex items-center gap-1.5 border-b border-
|
|
102
|
-
<Sparkles size={13} className="text-
|
|
103
|
-
<span className="text-xs font-dpe-medium text-
|
|
100
|
+
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-dpe-ink-200 bg-dpe-surface">
|
|
101
|
+
<div className="flex items-center gap-1.5 border-b border-dpe-ink-200 px-3 py-2">
|
|
102
|
+
<Sparkles size={13} className="text-dpe-ai" />
|
|
103
|
+
<span className="text-xs font-dpe-medium text-dpe-ink-900">{t("detailPage.promptEdit.header")}</span>
|
|
104
104
|
{unlimited || editLimit > 0 ? (
|
|
105
105
|
<EditUsageBadge used={editsUsed} limit={editLimit} unlimited={unlimited} />
|
|
106
106
|
) : null}
|
|
@@ -123,13 +123,13 @@ export function PromptEditPanel({
|
|
|
123
123
|
busy ? t("detailPage.promptEdit.editing") : t("detailPage.promptEdit.textPlaceholder")
|
|
124
124
|
}
|
|
125
125
|
disabled={busy}
|
|
126
|
-
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-
|
|
126
|
+
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 disabled:opacity-60"
|
|
127
127
|
/>
|
|
128
128
|
<button
|
|
129
129
|
type="button"
|
|
130
130
|
onClick={send}
|
|
131
131
|
disabled={busy || !input.trim()}
|
|
132
|
-
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-
|
|
132
|
+
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-dpe-ink-900 text-dpe-on-accent disabled:opacity-40"
|
|
133
133
|
aria-label={t("detailPage.promptEdit.send")}
|
|
134
134
|
>
|
|
135
135
|
{busy ? (
|
|
@@ -122,10 +122,10 @@ export function SpecPromptEditPanel({
|
|
|
122
122
|
]);
|
|
123
123
|
|
|
124
124
|
return (
|
|
125
|
-
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-
|
|
126
|
-
<div className="flex items-center gap-1.5 border-b border-
|
|
127
|
-
<Sparkles size={13} className="text-
|
|
128
|
-
<span className="text-xs font-dpe-medium text-
|
|
125
|
+
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-dpe-ink-200 bg-dpe-surface">
|
|
126
|
+
<div className="flex items-center gap-1.5 border-b border-dpe-ink-200 px-3 py-2">
|
|
127
|
+
<Sparkles size={13} className="text-dpe-ai" />
|
|
128
|
+
<span className="text-xs font-dpe-medium text-dpe-ink-900">
|
|
129
129
|
{t("detailPage.promptEdit.header")}
|
|
130
130
|
</span>
|
|
131
131
|
{unlimited || editLimit > 0 ? (
|
|
@@ -152,13 +152,13 @@ export function SpecPromptEditPanel({
|
|
|
152
152
|
: t(`detailPage.specPromptEdit.placeholder.${specKind}`)
|
|
153
153
|
}
|
|
154
154
|
disabled={busy}
|
|
155
|
-
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-
|
|
155
|
+
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 disabled:opacity-60"
|
|
156
156
|
/>
|
|
157
157
|
<button
|
|
158
158
|
type="button"
|
|
159
159
|
onClick={send}
|
|
160
160
|
disabled={busy || !input.trim()}
|
|
161
|
-
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-
|
|
161
|
+
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-dpe-ink-900 text-dpe-on-accent disabled:opacity-40"
|
|
162
162
|
aria-label={t("detailPage.promptEdit.send")}
|
|
163
163
|
>
|
|
164
164
|
{busy ? (
|
|
@@ -170,7 +170,7 @@ export function SpecPromptEditPanel({
|
|
|
170
170
|
</div>
|
|
171
171
|
)}
|
|
172
172
|
{busy ? <GenerationProgressLine progress={progress} /> : null}
|
|
173
|
-
<p className="px-3 pb-2 text-[11px] leading-relaxed text-
|
|
173
|
+
<p className="px-3 pb-2 text-[11px] leading-relaxed text-dpe-ink-500">
|
|
174
174
|
{t("detailPage.specPromptEdit.hint")}
|
|
175
175
|
</p>
|
|
176
176
|
</div>
|
|
@@ -90,10 +90,10 @@ export function SvgPromptEditPanel({
|
|
|
90
90
|
}, [api, toast, input, busy, blocked, generatedId, slotRole, currentSvg, onApplied, onUsage, t]);
|
|
91
91
|
|
|
92
92
|
return (
|
|
93
|
-
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-
|
|
94
|
-
<div className="flex items-center gap-1.5 border-b border-
|
|
95
|
-
<Sparkles size={13} className="text-
|
|
96
|
-
<span className="text-xs font-dpe-medium text-
|
|
93
|
+
<div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-dpe-ink-200 bg-dpe-surface">
|
|
94
|
+
<div className="flex items-center gap-1.5 border-b border-dpe-ink-200 px-3 py-2">
|
|
95
|
+
<Sparkles size={13} className="text-dpe-ai" />
|
|
96
|
+
<span className="text-xs font-dpe-medium text-dpe-ink-900">{t("detailPage.promptEdit.header")}</span>
|
|
97
97
|
{unlimited || editLimit > 0 ? (
|
|
98
98
|
<EditUsageBadge used={editsUsed} limit={editLimit} unlimited={unlimited} />
|
|
99
99
|
) : null}
|
|
@@ -116,13 +116,13 @@ export function SvgPromptEditPanel({
|
|
|
116
116
|
busy ? t("detailPage.promptEdit.editing") : t("detailPage.svgPromptEdit.placeholder")
|
|
117
117
|
}
|
|
118
118
|
disabled={busy}
|
|
119
|
-
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-
|
|
119
|
+
className="max-h-32 flex-1 resize-none bg-transparent px-2 py-1.5 text-[13px] leading-5 text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 disabled:opacity-60"
|
|
120
120
|
/>
|
|
121
121
|
<button
|
|
122
122
|
type="button"
|
|
123
123
|
onClick={send}
|
|
124
124
|
disabled={busy || !input.trim()}
|
|
125
|
-
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-
|
|
125
|
+
className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-dpe-ink-900 text-dpe-on-accent disabled:opacity-40"
|
|
126
126
|
aria-label={t("detailPage.promptEdit.send")}
|
|
127
127
|
>
|
|
128
128
|
{busy ? (
|
package/components/ui/button.tsx
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 편집기 버튼.
|
|
3
|
+
*
|
|
4
|
+
* 색은 **편집기 토큰(`dpe-*`)만** 쓴다. `bg-primary` · `text-primary-foreground` 는
|
|
5
|
+
* 소비자 앱의 CSS 변수를 읽는 이름이고, leviosa-agency 는 `--color-primary-foreground`
|
|
6
|
+
* 를 안 둔다. 그래서 글자색 클래스가 한 줄도 안 구워졌고, 글자가 본문
|
|
7
|
+
* (`--color-fg: #1a1a1a`)을 물려받아 호출부가 덮은 배경(`bg-dpe-ink-900`, 같은 앱에서
|
|
8
|
+
* `#1a1a1a`)과 정확히 같은 값이 됐다 — 다운로드 팝오버의 저장 버튼이 검은 판때기로
|
|
9
|
+
* 보인 이유다.
|
|
10
|
+
*
|
|
11
|
+
* 테두리도 같다. 색 없는 `border` 는 currentColor 라, 글자색이 어두우면 테두리가
|
|
12
|
+
* 통째로 먹으로 나온다. 색을 쓰는 자리마다 토큰을 적는다.
|
|
13
|
+
*/
|
|
1
14
|
import * as React from "react"
|
|
2
15
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
16
|
import { Slot } from "radix-ui"
|
|
@@ -13,7 +26,7 @@ const buttonVariants = cva(
|
|
|
13
26
|
destructive:
|
|
14
27
|
"bg-dpe-danger-500 text-dpe-on-accent hover:bg-dpe-danger-500/90 focus-visible:ring-dpe-danger-500/20 dark:focus-visible:ring-dpe-danger-500/40 dark:bg-dpe-danger-500/60",
|
|
15
28
|
outline:
|
|
16
|
-
"border
|
|
29
|
+
"border border-dpe-ink-200 bg-dpe-surface text-dpe-ink-900 shadow-xs hover:bg-dpe-ink-100 dark:bg-dpe-ink-100/30 dark:hover:bg-dpe-ink-100/50",
|
|
17
30
|
secondary:
|
|
18
31
|
"bg-dpe-ink-100 text-dpe-ink-900 hover:bg-dpe-ink-100/80",
|
|
19
32
|
ghost:
|
package/components/ui/dialog.tsx
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 편집기 모달. 포털이 body 로 나가므로 `data-dpe-root` 를 다시 달고(아래), 판의
|
|
5
|
+
* 글자색과 테두리색은 물려받지 않고 적는다 — 색 없는 `border` 는 currentColor 다.
|
|
6
|
+
* 이유는 select.tsx 주석에 있다.
|
|
7
|
+
*/
|
|
3
8
|
import * as React from "react";
|
|
4
9
|
import { XIcon } from "lucide-react";
|
|
5
10
|
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
@@ -65,7 +70,7 @@ function DialogContent({
|
|
|
65
70
|
data-slot="dialog-content"
|
|
66
71
|
data-dpe-root=""
|
|
67
72
|
className={cn(
|
|
68
|
-
"bg-dpe-surface data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[115] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-dpe-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
73
|
+
"bg-dpe-surface text-dpe-ink-900 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[115] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-dpe-lg border border-dpe-ink-200 p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
69
74
|
className,
|
|
70
75
|
)}
|
|
71
76
|
onOpenAutoFocus={(event) => {
|
package/components/ui/select.tsx
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Radix Select 위에 얹은 드롭다운.
|
|
5
|
+
*
|
|
6
|
+
* 색은 **편집기 토큰(`dpe-*`)만** 쓴다. shadcn 원본이 달고 온 `bg-popover` ·
|
|
7
|
+
* `text-popover-foreground` · `border-input` · `bg-accent` · `text-muted-foreground`
|
|
8
|
+
* 는 전부 소비자 앱의 팔레트를 읽는 이름이라, 이 패키지가 그 값을 정할 방법이 없다.
|
|
9
|
+
* leviosa-agency 는 `--color-popover` 를 아예 안 두므로 그 유틸리티가 한 줄도 안
|
|
10
|
+
* 구워졌고, 목록 판이 **투명해져** 뒤의 라벨과 글자가 겹쳐 보였다.
|
|
11
|
+
*
|
|
12
|
+
* 글자색은 물려받지 않고 판마다 적는다(`text-dpe-ink-900`). 이 버그가 아픈 이유가
|
|
13
|
+
* 정확히 "안 적으면 바깥 것을 물려받는다" 였다 — 판은 토큰으로 칠하면서 글자를
|
|
14
|
+
* 물려받게 두면 갈아입힌 앱에서 같은 자리에 같은 일이 다시 난다.
|
|
15
|
+
*/
|
|
3
16
|
import * as React from "react"
|
|
4
17
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
|
5
18
|
import { Select as SelectPrimitive } from "radix-ui"
|
|
@@ -37,7 +50,7 @@ function SelectTrigger({
|
|
|
37
50
|
data-slot="select-trigger"
|
|
38
51
|
data-size={size}
|
|
39
52
|
className={cn(
|
|
40
|
-
"border-dpe-ink-200 data-[placeholder]:text-dpe-ink-
|
|
53
|
+
"border-dpe-ink-200 text-dpe-ink-900 data-[placeholder]:text-dpe-ink-400 [&_svg:not([class*='text-'])]:text-dpe-ink-500 focus-visible:border-dpe-ink-400 focus-visible:ring-dpe-ink-300/50 aria-invalid:ring-dpe-danger-500/20 dark:aria-invalid:ring-dpe-danger-500/40 aria-invalid:border-dpe-danger-500 dark:bg-dpe-ink-100/30 dark:hover:bg-dpe-ink-100/50 flex w-fit items-center justify-between gap-2 rounded-dpe-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
41
54
|
className
|
|
42
55
|
)}
|
|
43
56
|
{...props}
|
|
@@ -114,7 +127,7 @@ function SelectItem({
|
|
|
114
127
|
<SelectPrimitive.Item
|
|
115
128
|
data-slot="select-item"
|
|
116
129
|
className={cn(
|
|
117
|
-
"
|
|
130
|
+
"text-dpe-ink-900 focus:bg-dpe-ink-100 [&_svg:not([class*='text-'])]:text-dpe-ink-500 relative flex w-full cursor-default items-center gap-2 rounded-dpe-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
118
131
|
className
|
|
119
132
|
)}
|
|
120
133
|
{...props}
|
package/components/ui/slider.tsx
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 해상도 배율 같은 값 슬라이더. 궤도·채움·손잡이 전부 편집기 토큰이다 — 원본의
|
|
5
|
+
* `bg-muted` · `bg-primary` 는 소비자 앱의 이름이라, 그 앱이 안 두면 채운 구간이
|
|
6
|
+
* 통째로 안 보인다(select.tsx 주석 참고).
|
|
7
|
+
*/
|
|
3
8
|
import * as React from "react"
|
|
4
9
|
import { Slider as SliderPrimitive } from "radix-ui"
|
|
5
10
|
|
package/components/ui/switch.tsx
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 켬/끔 스위치. 켠 색은 먹(`dpe-ink-900`), 끈 색은 `dpe-ink-300` 이다.
|
|
5
|
+
*
|
|
6
|
+
* 끈 색이 `ink-100` 이면 흰 패널(`dpe-surface`) 위에서 궤도가 거의 안 보인다 —
|
|
7
|
+
* 손잡이도 흰색이고 테두리는 투명이라, 꺼진 스위치가 통째로 사라진다. 한 단계
|
|
8
|
+
* 내려서 꺼짐이 꺼짐으로 읽히게 한다.
|
|
9
|
+
*/
|
|
3
10
|
import * as React from "react"
|
|
4
11
|
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
5
12
|
|
|
@@ -17,7 +24,7 @@ function Switch({
|
|
|
17
24
|
data-slot="switch"
|
|
18
25
|
data-size={size}
|
|
19
26
|
className={cn(
|
|
20
|
-
"peer data-[state=checked]:bg-dpe-ink-900 data-[state=unchecked]:bg-dpe-ink-
|
|
27
|
+
"peer data-[state=checked]:bg-dpe-ink-900 data-[state=unchecked]:bg-dpe-ink-300 focus-visible:border-dpe-ink-400 focus-visible:ring-dpe-ink-300/50 dark:data-[state=unchecked]:bg-dpe-ink-100/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
|
|
21
28
|
className
|
|
22
29
|
)}
|
|
23
30
|
{...props}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 말풍선 도움말. 어두운 판(`dpe-ink-900`) 위 `dpe-on-accent` 글자다.
|
|
5
|
+
*
|
|
6
|
+
* `text-background` 는 소비자 앱의 이름이라, 그 앱이 안 두면 클래스가 아예 안 구워지고
|
|
7
|
+
* 글자색이 물려받은 값(먹)으로 남는다 — 어두운 판 위 어두운 글자. 판만 토큰으로
|
|
8
|
+
* 옮기고 글자를 안 옮기면 오히려 원본보다 안 보인다.
|
|
9
|
+
*/
|
|
3
10
|
import * as React from "react"
|
|
4
11
|
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
12
|
|
|
@@ -43,7 +50,7 @@ function TooltipContent({
|
|
|
43
50
|
data-slot="tooltip-content"
|
|
44
51
|
sideOffset={sideOffset}
|
|
45
52
|
className={cn(
|
|
46
|
-
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-dpe-md bg-dpe-ink-900 px-3 py-1.5 text-xs text-balance text-
|
|
53
|
+
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-dpe-md bg-dpe-ink-900 px-3 py-1.5 text-xs text-balance text-dpe-on-accent fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
47
54
|
className
|
|
48
55
|
)}
|
|
49
56
|
{...props}
|
|
@@ -10,6 +10,13 @@ export type DetailPageEditorProfile = {
|
|
|
10
10
|
};
|
|
11
11
|
maxPages: number;
|
|
12
12
|
exports: readonly DetailPageEditorFormat[];
|
|
13
|
+
/**
|
|
14
|
+
* 다운로드 팝오버에 "등록 플랫폼"을 물어볼지. 그 답은 파일명 접미사에만 쓰이고,
|
|
15
|
+
* 셀러가 상품을 어디에 올릴지 고르며 맥락을 잡으라고 있는 것이다. 캐러셀은
|
|
16
|
+
* 인스타그램 한 곳으로 나가므로 물어볼 것이 없다 — 고를 수 없는 선택지를
|
|
17
|
+
* 남겨 두면 접미사만 거짓말을 한다(`-naver` 붙은 인스타 이미지).
|
|
18
|
+
*/
|
|
19
|
+
registerPlatform: boolean;
|
|
13
20
|
wording: "section" | "plate";
|
|
14
21
|
};
|
|
15
22
|
|
|
@@ -17,13 +24,19 @@ const DETAIL_PAGE_PROFILE: DetailPageEditorProfile = {
|
|
|
17
24
|
page: { width: "document", height: "auto", fixed: false },
|
|
18
25
|
maxPages: Number.POSITIVE_INFINITY,
|
|
19
26
|
exports: ["png", "jpeg", "psd", "ai", "svg"],
|
|
27
|
+
registerPlatform: true,
|
|
20
28
|
wording: "section",
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
const CAROUSEL_PROFILE: DetailPageEditorProfile = {
|
|
24
32
|
page: { width: 1080, height: 1350, fixed: true },
|
|
25
33
|
maxPages: 10,
|
|
26
|
-
|
|
34
|
+
// 상세페이지와 같은 다섯 가지다. 한때 JPG 하나로 좁혀 뒀던 것은 인스타그램이
|
|
35
|
+
// JPG 로 받는다는 이유였는데, 내보낸 파일이 곧장 업로드로만 가는 것이 아니다 —
|
|
36
|
+
// 투명 배경을 살리려면 PNG 가, 다른 도구로 넘겨 손보려면 PSD·AI·SVG 가 필요하다.
|
|
37
|
+
// 첫 항목이 기본값이므로 JPG 를 앞에 둬서 지금까지의 기본 동작은 그대로 둔다.
|
|
38
|
+
exports: ["jpeg", "png", "psd", "ai", "svg"],
|
|
39
|
+
registerPlatform: false,
|
|
27
40
|
wording: "plate",
|
|
28
41
|
};
|
|
29
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leviosa-ai/detail-page-editor",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface, and every URL it fetches comes from configureDetailPageEditor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|