@jogak/ui 0.1.0-alpha.4 → 0.1.0-alpha.6
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/CHANGELOG.md +52 -0
- package/README.md +84 -8
- package/dist/index.js +1 -1
- package/dist/index.mjs +595 -912
- package/package.json +4 -3
- package/src/app/App.tsx +3 -16
- package/src/app/main.tsx +6 -0
- package/src/components/Actions/index.tsx +20 -50
- package/src/components/Controls/index.tsx +49 -70
- package/src/components/Preview/index.tsx +140 -250
- package/src/components/Sidebar/index.tsx +39 -66
- package/src/styles/jogak.css +47 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import type { ReactElement, CSSProperties } from 'react'
|
|
3
|
+
import clsx from 'clsx'
|
|
3
4
|
import { Highlight, themes } from 'prism-react-renderer'
|
|
4
5
|
import type { PrismTheme } from 'prism-react-renderer'
|
|
5
6
|
import { reactAdapter, useEntry } from '@jogak/react'
|
|
@@ -25,6 +26,12 @@ export interface PreviewProps {
|
|
|
25
26
|
type ViewportKey = 'mobile' | 'tablet' | 'desktop'
|
|
26
27
|
type BgMode = 'white' | 'dark' | 'transparent'
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* dynamic style + CSS variable 주입을 위한 React `CSSProperties` 확장 타입
|
|
31
|
+
* (api-contracts 알파.5 PR 2 §6.1).
|
|
32
|
+
*/
|
|
33
|
+
type CSSVarStyle = CSSProperties & Record<`--${string}`, string | number>
|
|
34
|
+
|
|
28
35
|
const VIEWPORT_WIDTHS: Record<ViewportKey, number | 'none'> = {
|
|
29
36
|
mobile: 375,
|
|
30
37
|
tablet: 768,
|
|
@@ -37,22 +44,46 @@ const VIEWPORT_LABELS: Record<ViewportKey, string> = {
|
|
|
37
44
|
desktop: 'Desktop',
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
/**
|
|
48
|
+
* bgMode별 캔버스 background 표현 — 4개 longhand CSS variable로 분해.
|
|
49
|
+
*
|
|
50
|
+
* v4 background shorthand arbitrary value(`bg-[...]`)는 ambiguous 하므로
|
|
51
|
+
* `bg-[image:...]`, `bg-[length:...]`, `bg-[position:...]` longhand hint를 사용해야 한다.
|
|
52
|
+
* 따라서 `BG_STYLES` (CSSProperties spread)를 폐기하고 mode별 변수 묶음만 정의한다
|
|
53
|
+
* (api-contracts 알파.5 PR 2 §3.2 결정 B).
|
|
54
|
+
*/
|
|
55
|
+
const BG_VARS: Record<BgMode, CSSVarStyle> = {
|
|
56
|
+
white: {
|
|
57
|
+
'--jogak-canvas-bg': '#ffffff',
|
|
58
|
+
'--jogak-canvas-bg-image': 'none',
|
|
59
|
+
'--jogak-canvas-bg-size': 'auto',
|
|
60
|
+
'--jogak-canvas-bg-position': '0 0',
|
|
61
|
+
},
|
|
62
|
+
dark: {
|
|
63
|
+
'--jogak-canvas-bg': '#1f2937',
|
|
64
|
+
'--jogak-canvas-bg-image': 'none',
|
|
65
|
+
'--jogak-canvas-bg-size': 'auto',
|
|
66
|
+
'--jogak-canvas-bg-position': '0 0',
|
|
67
|
+
},
|
|
43
68
|
transparent: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'linear-gradient(
|
|
47
|
-
'linear-gradient(45deg,
|
|
69
|
+
'--jogak-canvas-bg': '#ffffff',
|
|
70
|
+
'--jogak-canvas-bg-image':
|
|
71
|
+
'linear-gradient(45deg, #e2e8f0 25%, transparent 25%), ' +
|
|
72
|
+
'linear-gradient(-45deg, #e2e8f0 25%, transparent 25%), ' +
|
|
73
|
+
'linear-gradient(45deg, transparent 75%, #e2e8f0 75%), ' +
|
|
48
74
|
'linear-gradient(-45deg, transparent 75%, #e2e8f0 75%)',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
|
|
52
|
-
backgroundColor: '#ffffff',
|
|
75
|
+
'--jogak-canvas-bg-size': '16px 16px',
|
|
76
|
+
'--jogak-canvas-bg-position': '0 0, 0 8px, 8px -8px, -8px 0px',
|
|
53
77
|
},
|
|
54
78
|
}
|
|
55
79
|
|
|
80
|
+
/** 캔버스/미니버튼 공통 — 모드 무관. BG_VARS 가 변수 값을 mode별로 swap. */
|
|
81
|
+
const CANVAS_BG_CLASS =
|
|
82
|
+
'jogak:bg-[var(--jogak-canvas-bg)] ' +
|
|
83
|
+
'jogak:bg-[image:var(--jogak-canvas-bg-image)] ' +
|
|
84
|
+
'jogak:bg-[length:var(--jogak-canvas-bg-size)] ' +
|
|
85
|
+
'jogak:bg-[position:var(--jogak-canvas-bg-position)]'
|
|
86
|
+
|
|
56
87
|
/** 캔버스 영역 minHeight — loading/ready 사이 layout shift 방지 (계약 §10). */
|
|
57
88
|
const CANVAS_MIN_HEIGHT = 320
|
|
58
89
|
|
|
@@ -90,7 +121,10 @@ export function Preview({
|
|
|
90
121
|
// ── unknown ───────────────────────────────────────────────
|
|
91
122
|
if (state.status === 'unknown') {
|
|
92
123
|
return (
|
|
93
|
-
<div
|
|
124
|
+
<div
|
|
125
|
+
data-testid="preview-not-found"
|
|
126
|
+
className="jogak:p-6 jogak:text-[var(--jogak-color-error)]"
|
|
127
|
+
>
|
|
94
128
|
Entry not found: {entryId}
|
|
95
129
|
</div>
|
|
96
130
|
)
|
|
@@ -101,30 +135,10 @@ export function Preview({
|
|
|
101
135
|
return (
|
|
102
136
|
<div
|
|
103
137
|
data-testid="preview-error"
|
|
104
|
-
|
|
105
|
-
padding: 24,
|
|
106
|
-
color: '#b91c1c',
|
|
107
|
-
background: '#fef2f2',
|
|
108
|
-
height: '100%',
|
|
109
|
-
display: 'flex',
|
|
110
|
-
flexDirection: 'column',
|
|
111
|
-
gap: 12,
|
|
112
|
-
alignItems: 'flex-start',
|
|
113
|
-
}}
|
|
138
|
+
className="jogak:p-6 jogak:text-[var(--jogak-color-error-fg)] jogak:bg-[var(--jogak-color-bg-error)] jogak:h-full jogak:flex jogak:flex-col jogak:gap-3 jogak:items-start"
|
|
114
139
|
>
|
|
115
|
-
<div
|
|
116
|
-
<pre
|
|
117
|
-
style={{
|
|
118
|
-
margin: 0,
|
|
119
|
-
padding: 12,
|
|
120
|
-
background: '#fff',
|
|
121
|
-
border: '1px solid #fecaca',
|
|
122
|
-
borderRadius: 6,
|
|
123
|
-
fontSize: 12,
|
|
124
|
-
whiteSpace: 'pre-wrap',
|
|
125
|
-
maxWidth: '100%',
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
140
|
+
<div className="jogak:font-semibold">Failed to load entry: {entryId}</div>
|
|
141
|
+
<pre className="jogak:m-0 jogak:p-3 jogak:bg-[var(--jogak-color-bg)] jogak:border jogak:border-[var(--jogak-color-error-border)] jogak:rounded-[var(--jogak-radius-lg)] jogak:text-[12px] jogak:whitespace-pre-wrap jogak:max-w-full">
|
|
128
142
|
{state.error.message}
|
|
129
143
|
</pre>
|
|
130
144
|
</div>
|
|
@@ -190,7 +204,7 @@ function LoadingFrame({
|
|
|
190
204
|
return (
|
|
191
205
|
<div
|
|
192
206
|
data-testid="preview-loading"
|
|
193
|
-
|
|
207
|
+
className="jogak:flex jogak:flex-col jogak:h-full"
|
|
194
208
|
>
|
|
195
209
|
<Toolbar
|
|
196
210
|
title={meta.title}
|
|
@@ -203,44 +217,31 @@ function LoadingFrame({
|
|
|
203
217
|
onReset={() => {}}
|
|
204
218
|
/>
|
|
205
219
|
<div
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
overflow: 'auto',
|
|
210
|
-
...BG_STYLES[bgMode],
|
|
211
|
-
}}
|
|
220
|
+
className={`jogak:flex-1 jogak:overflow-auto jogak:min-h-[320px] ${CANVAS_BG_CLASS}`}
|
|
221
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: BG_VARS object inject
|
|
222
|
+
style={BG_VARS[bgMode]}
|
|
212
223
|
>
|
|
213
224
|
<div
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
225
|
+
className="jogak:mx-auto jogak:p-6 jogak:max-w-[var(--jogak-canvas-mw)]"
|
|
226
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: canvas-mw CSS var
|
|
227
|
+
style={
|
|
228
|
+
{
|
|
229
|
+
'--jogak-canvas-mw': maxWidth === 'none' ? '100%' : `${maxWidth}px`,
|
|
230
|
+
} as CSSVarStyle
|
|
231
|
+
}
|
|
219
232
|
>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
justifyContent: 'center',
|
|
229
|
-
color: '#9ca3af',
|
|
230
|
-
fontSize: 13,
|
|
231
|
-
background:
|
|
232
|
-
'linear-gradient(90deg, rgba(229,231,235,0) 0%, rgba(229,231,235,0.45) 50%, rgba(229,231,235,0) 100%)',
|
|
233
|
-
backgroundSize: '200% 100%',
|
|
234
|
-
animation: 'jogakSkeleton 1.4s ease-in-out infinite',
|
|
235
|
-
}}
|
|
236
|
-
>
|
|
233
|
+
{/*
|
|
234
|
+
* skeleton box — 알파.5 PR 4 마이그레이션: gradient + keyframe animation 을
|
|
235
|
+
* jogak.css `@layer components` 의 `.jogak-skeleton-shimmer` class 로 이동
|
|
236
|
+
* (api-contracts §6). inline `style={{...}}` 객체 + inline `<style>` 태그
|
|
237
|
+
* 동시 제거. 정적 부분(border / radius / padding / flex / color / fontSize /
|
|
238
|
+
* minHeight)은 jogak: utility 그대로 유지.
|
|
239
|
+
*/}
|
|
240
|
+
<div className="jogak-skeleton-shimmer jogak:border jogak:border-dashed jogak:border-[var(--jogak-color-border)] jogak:rounded-[var(--jogak-radius-xl)] jogak:p-4 jogak:flex jogak:items-center jogak:justify-center jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px] jogak:min-h-[256px]">
|
|
237
241
|
Loading {meta.title}…
|
|
238
242
|
</div>
|
|
239
243
|
</div>
|
|
240
244
|
</div>
|
|
241
|
-
<style>
|
|
242
|
-
{`@keyframes jogakSkeleton { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }`}
|
|
243
|
-
</style>
|
|
244
245
|
</div>
|
|
245
246
|
)
|
|
246
247
|
}
|
|
@@ -289,7 +290,7 @@ function ReadyFrame({
|
|
|
289
290
|
|
|
290
291
|
if (resolvedJogakName === null) {
|
|
291
292
|
return (
|
|
292
|
-
<div
|
|
293
|
+
<div className="jogak:p-6 jogak:text-[var(--jogak-color-error)]">
|
|
293
294
|
Entry has no jogaks: {entry.id}
|
|
294
295
|
</div>
|
|
295
296
|
)
|
|
@@ -298,7 +299,7 @@ function ReadyFrame({
|
|
|
298
299
|
const jogak = entry.jogaks.find((j) => j.name === resolvedJogakName)
|
|
299
300
|
if (jogak === undefined) {
|
|
300
301
|
return (
|
|
301
|
-
<div
|
|
302
|
+
<div className="jogak:p-6 jogak:text-[var(--jogak-color-error)]">
|
|
302
303
|
Jogak not found: {resolvedJogakName}
|
|
303
304
|
</div>
|
|
304
305
|
)
|
|
@@ -314,7 +315,7 @@ function ReadyFrame({
|
|
|
314
315
|
const maxWidth = VIEWPORT_WIDTHS[viewport]
|
|
315
316
|
|
|
316
317
|
return (
|
|
317
|
-
<div
|
|
318
|
+
<div className="jogak:flex jogak:flex-col jogak:h-full">
|
|
318
319
|
<Toolbar
|
|
319
320
|
title={entry.title}
|
|
320
321
|
jogakName={jogak.name}
|
|
@@ -328,20 +329,19 @@ function ReadyFrame({
|
|
|
328
329
|
|
|
329
330
|
{/* ── 캔버스 ───────────────────────────────────────── */}
|
|
330
331
|
<div
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
overflow: 'auto',
|
|
335
|
-
...BG_STYLES[bgMode],
|
|
336
|
-
}}
|
|
332
|
+
className={`jogak:flex-1 jogak:overflow-auto jogak:min-h-[320px] ${CANVAS_BG_CLASS}`}
|
|
333
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: BG_VARS object inject
|
|
334
|
+
style={BG_VARS[bgMode]}
|
|
337
335
|
>
|
|
338
336
|
<div
|
|
339
337
|
data-jogak-content
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
338
|
+
className="jogak:mx-auto jogak:p-6 jogak:max-w-[var(--jogak-canvas-mw)]"
|
|
339
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: canvas-mw CSS var
|
|
340
|
+
style={
|
|
341
|
+
{
|
|
342
|
+
'--jogak-canvas-mw': maxWidth === 'none' ? '100%' : `${maxWidth}px`,
|
|
343
|
+
} as CSSVarStyle
|
|
344
|
+
}
|
|
345
345
|
>
|
|
346
346
|
<JogakRenderer
|
|
347
347
|
key={`${entry.id}/${jogak.name}`}
|
|
@@ -355,24 +355,12 @@ function ReadyFrame({
|
|
|
355
355
|
|
|
356
356
|
{/* ── 컨트롤/액션 패널 ──────────────────────────────── */}
|
|
357
357
|
<div
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
flexShrink: 0,
|
|
361
|
-
display: 'flex',
|
|
362
|
-
flexDirection: 'column',
|
|
363
|
-
borderTop: '2px solid #e5e7eb',
|
|
364
|
-
}}
|
|
358
|
+
data-testid="bottom-panel"
|
|
359
|
+
className="jogak:h-[260px] jogak:shrink-0 jogak:flex jogak:flex-col jogak:border-t-2 jogak:border-[var(--jogak-color-border)]"
|
|
365
360
|
>
|
|
366
361
|
<div
|
|
367
362
|
role="tablist"
|
|
368
|
-
|
|
369
|
-
display: 'flex',
|
|
370
|
-
gap: 4,
|
|
371
|
-
padding: '4px 12px 0',
|
|
372
|
-
background: '#fff',
|
|
373
|
-
borderBottom: '1px solid #e5e7eb',
|
|
374
|
-
flexShrink: 0,
|
|
375
|
-
}}
|
|
363
|
+
className="jogak:flex jogak:gap-1 jogak:pt-1 jogak:px-3 jogak:pb-0 jogak:bg-[var(--jogak-color-bg)] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:shrink-0"
|
|
376
364
|
>
|
|
377
365
|
{(['controls', 'actions'] as const).map((tab) => {
|
|
378
366
|
const active = bottomTab === tab
|
|
@@ -383,18 +371,12 @@ function ReadyFrame({
|
|
|
383
371
|
role="tab"
|
|
384
372
|
aria-selected={active}
|
|
385
373
|
onClick={() => { onBottomTabChange(tab) }}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
border: 'none',
|
|
393
|
-
borderBottom: active ? '2px solid #2563eb' : '2px solid transparent',
|
|
394
|
-
marginBottom: -1,
|
|
395
|
-
cursor: 'pointer',
|
|
396
|
-
textTransform: 'capitalize',
|
|
397
|
-
}}
|
|
374
|
+
className={clsx(
|
|
375
|
+
'jogak:px-[14px] jogak:py-[6px] jogak:text-[12px] jogak:bg-transparent jogak:border-x-0 jogak:border-t-0 jogak:border-b-2 jogak:border-solid jogak:-mb-px jogak:cursor-pointer jogak:capitalize',
|
|
376
|
+
active
|
|
377
|
+
? 'jogak:font-semibold jogak:text-[var(--jogak-color-fg-strong)] jogak:border-[var(--jogak-color-accent)]'
|
|
378
|
+
: 'jogak:font-medium jogak:text-[var(--jogak-color-fg-muted)] jogak:border-transparent',
|
|
379
|
+
)}
|
|
398
380
|
>
|
|
399
381
|
{tab}
|
|
400
382
|
</button>
|
|
@@ -402,7 +384,7 @@ function ReadyFrame({
|
|
|
402
384
|
})}
|
|
403
385
|
</div>
|
|
404
386
|
|
|
405
|
-
<div
|
|
387
|
+
<div className="jogak:flex-1 jogak:min-h-0 jogak:overflow-auto">
|
|
406
388
|
{bottomTab === 'controls' ? (
|
|
407
389
|
<Controls
|
|
408
390
|
args={mergedArgs}
|
|
@@ -442,51 +424,31 @@ function Toolbar({
|
|
|
442
424
|
onReset,
|
|
443
425
|
}: ToolbarProps): ReactElement {
|
|
444
426
|
return (
|
|
445
|
-
<div
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}}
|
|
455
|
-
>
|
|
456
|
-
<div style={{ flex: 1, fontSize: 13 }}>
|
|
457
|
-
<span style={{ color: '#9ca3af' }}>{title}</span>
|
|
458
|
-
<span style={{ color: '#d1d5db', margin: '0 6px' }}>/</span>
|
|
459
|
-
<span style={{ color: '#111827', fontWeight: 600 }}>{jogakName}</span>
|
|
427
|
+
<div className="jogak:flex jogak:items-center jogak:gap-[10px] jogak:px-[14px] jogak:py-[7px] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:bg-[var(--jogak-color-bg)] jogak:shrink-0">
|
|
428
|
+
<div className="jogak:flex-1 jogak:text-[13px]">
|
|
429
|
+
<span className="jogak:text-[var(--jogak-color-fg-subtle)]">{title}</span>
|
|
430
|
+
<span className="jogak:text-[var(--jogak-color-border-strong)] jogak:mx-1.5 jogak:leading-none">
|
|
431
|
+
/
|
|
432
|
+
</span>
|
|
433
|
+
<span className="jogak:text-[var(--jogak-color-fg-strong)] jogak:font-semibold">
|
|
434
|
+
{jogakName}
|
|
435
|
+
</span>
|
|
460
436
|
</div>
|
|
461
437
|
|
|
462
438
|
{/* 뷰포트 토글 */}
|
|
463
|
-
<div
|
|
464
|
-
style={{
|
|
465
|
-
display: 'flex',
|
|
466
|
-
gap: 2,
|
|
467
|
-
background: '#f3f4f6',
|
|
468
|
-
borderRadius: 6,
|
|
469
|
-
padding: 2,
|
|
470
|
-
}}
|
|
471
|
-
>
|
|
439
|
+
<div className="jogak:flex jogak:gap-0.5 jogak:bg-[var(--jogak-color-bg-subtle)] jogak:rounded-[var(--jogak-radius-lg)] jogak:p-0.5">
|
|
472
440
|
{(['mobile', 'tablet', 'desktop'] as const).map((vp) => (
|
|
473
441
|
<button
|
|
474
442
|
key={vp}
|
|
475
443
|
type="button"
|
|
476
444
|
onClick={() => { onViewportChange(vp) }}
|
|
477
445
|
aria-pressed={viewport === vp}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
background: viewport === vp ? '#fff' : 'transparent',
|
|
485
|
-
color: viewport === vp ? '#111827' : '#6b7280',
|
|
486
|
-
fontWeight: viewport === vp ? 600 : 400,
|
|
487
|
-
boxShadow: viewport === vp ? '0 1px 2px rgba(0,0,0,0.08)' : 'none',
|
|
488
|
-
transition: 'all 0.1s',
|
|
489
|
-
}}
|
|
446
|
+
className={clsx(
|
|
447
|
+
'jogak:px-[9px] jogak:py-[3px] jogak:text-[12px] jogak:border-none jogak:rounded-[var(--jogak-radius-md)] jogak:cursor-pointer jogak:transition-all jogak:duration-100',
|
|
448
|
+
viewport === vp
|
|
449
|
+
? 'jogak:bg-[var(--jogak-color-bg-elevated)] jogak:text-[var(--jogak-color-fg-strong)] jogak:font-semibold jogak:shadow-[0_1px_2px_rgba(0,0,0,0.08)]'
|
|
450
|
+
: 'jogak:bg-transparent jogak:text-[var(--jogak-color-fg-muted)] jogak:font-normal jogak:shadow-none',
|
|
451
|
+
)}
|
|
490
452
|
>
|
|
491
453
|
{VIEWPORT_LABELS[vp]}
|
|
492
454
|
</button>
|
|
@@ -494,7 +456,7 @@ function Toolbar({
|
|
|
494
456
|
</div>
|
|
495
457
|
|
|
496
458
|
{/* 배경 토글 */}
|
|
497
|
-
<div
|
|
459
|
+
<div className="jogak:flex jogak:gap-1 jogak:items-center">
|
|
498
460
|
{(['white', 'dark', 'transparent'] as const).map((bg) => (
|
|
499
461
|
<button
|
|
500
462
|
key={bg}
|
|
@@ -502,16 +464,15 @@ function Toolbar({
|
|
|
502
464
|
onClick={() => { onBgModeChange(bg) }}
|
|
503
465
|
aria-pressed={bgMode === bg}
|
|
504
466
|
aria-label={`${bg} background`}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}}
|
|
467
|
+
className={clsx(
|
|
468
|
+
'jogak:w-5 jogak:h-5 jogak:rounded-[var(--jogak-radius-md)] jogak:border-2 jogak:cursor-pointer jogak:p-0 jogak:shrink-0',
|
|
469
|
+
CANVAS_BG_CLASS,
|
|
470
|
+
bgMode === bg
|
|
471
|
+
? 'jogak:border-[var(--jogak-color-accent)]'
|
|
472
|
+
: 'jogak:border-[var(--jogak-color-border-strong)]',
|
|
473
|
+
)}
|
|
474
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: BG_VARS object inject (3 mini buttons)
|
|
475
|
+
style={BG_VARS[bg]}
|
|
515
476
|
/>
|
|
516
477
|
))}
|
|
517
478
|
</div>
|
|
@@ -521,15 +482,7 @@ function Toolbar({
|
|
|
521
482
|
<button
|
|
522
483
|
type="button"
|
|
523
484
|
onClick={onReset}
|
|
524
|
-
|
|
525
|
-
padding: '3px 10px',
|
|
526
|
-
fontSize: 12,
|
|
527
|
-
border: '1px solid #d1d5db',
|
|
528
|
-
borderRadius: 4,
|
|
529
|
-
background: '#fff',
|
|
530
|
-
cursor: 'pointer',
|
|
531
|
-
color: '#374151',
|
|
532
|
-
}}
|
|
485
|
+
className="jogak:px-[10px] jogak:py-[3px] jogak:text-[12px] jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-md)] jogak:bg-[var(--jogak-color-bg)] jogak:cursor-pointer jogak:text-[var(--jogak-color-fg)] jogak:leading-none"
|
|
533
486
|
>
|
|
534
487
|
Reset
|
|
535
488
|
</button>
|
|
@@ -568,40 +521,24 @@ function JogakRenderer({ entry, args, source, theme }: JogakRendererProps): Reac
|
|
|
568
521
|
return (
|
|
569
522
|
<div>
|
|
570
523
|
{/* preview-content 영역 + 토글 버튼 */}
|
|
571
|
-
<div
|
|
524
|
+
<div className="jogak:relative">
|
|
572
525
|
<div
|
|
573
526
|
ref={containerRef}
|
|
574
527
|
data-testid="preview-content"
|
|
575
|
-
|
|
576
|
-
border: '1px dashed #e5e7eb',
|
|
577
|
-
borderRadius: 8,
|
|
578
|
-
padding: 16,
|
|
579
|
-
paddingBottom: 36,
|
|
580
|
-
}}
|
|
528
|
+
className="jogak:border jogak:border-dashed jogak:border-[var(--jogak-color-border)] jogak:rounded-[var(--jogak-radius-xl)] jogak:p-4 jogak:pb-9"
|
|
581
529
|
/>
|
|
582
530
|
<button
|
|
583
531
|
type="button"
|
|
584
532
|
onClick={() => { setShowCode((v) => !v) }}
|
|
585
533
|
aria-pressed={showCode}
|
|
586
534
|
aria-label={showCode ? 'Hide source code' : 'Show source code'}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
595
|
-
fontWeight: 600,
|
|
596
|
-
letterSpacing: '0.02em',
|
|
597
|
-
background: showCode ? '#2563eb' : '#1e293b',
|
|
598
|
-
color: '#fff',
|
|
599
|
-
border: 'none',
|
|
600
|
-
borderRadius: 5,
|
|
601
|
-
cursor: 'pointer',
|
|
602
|
-
boxShadow: '0 1px 4px rgba(0,0,0,0.2)',
|
|
603
|
-
transition: 'background 0.15s',
|
|
604
|
-
}}
|
|
535
|
+
className={clsx(
|
|
536
|
+
'jogak:absolute jogak:bottom-2 jogak:right-2 jogak:px-[9px] jogak:py-1',
|
|
537
|
+
'jogak:text-[11px] jogak:font-[family-name:var(--jogak-font-mono)] jogak:font-semibold jogak:tracking-[0.02em]',
|
|
538
|
+
'jogak:text-[var(--jogak-color-bg)] jogak:border-none jogak:rounded-[5px] jogak:cursor-pointer',
|
|
539
|
+
'jogak:shadow-[0_1px_4px_rgba(0,0,0,0.2)] jogak:transition-[background-color] jogak:duration-150 jogak:leading-none',
|
|
540
|
+
showCode ? 'jogak:bg-[var(--jogak-color-accent)]' : 'jogak:bg-[#1e293b]',
|
|
541
|
+
)}
|
|
605
542
|
>
|
|
606
543
|
{'</>'}
|
|
607
544
|
</button>
|
|
@@ -609,15 +546,7 @@ function JogakRenderer({ entry, args, source, theme }: JogakRendererProps): Reac
|
|
|
609
546
|
|
|
610
547
|
{/* 코드 패널 — preview-content 하단으로 펼쳐짐 */}
|
|
611
548
|
{showCode && (
|
|
612
|
-
<div
|
|
613
|
-
style={{
|
|
614
|
-
marginTop: 8,
|
|
615
|
-
borderRadius: 8,
|
|
616
|
-
overflow: 'hidden',
|
|
617
|
-
height: 320,
|
|
618
|
-
boxShadow: '0 0 0 1px rgba(0,0,0,0.08), 0 4px 16px rgba(0,0,0,0.12)',
|
|
619
|
-
}}
|
|
620
|
-
>
|
|
549
|
+
<div className="jogak:mt-2 jogak:rounded-[var(--jogak-radius-xl)] jogak:overflow-hidden jogak:h-[320px] jogak:shadow-[0_0_0_1px_rgba(0,0,0,0.08),_0_4px_16px_rgba(0,0,0,0.12)]">
|
|
621
550
|
<SourceViewer source={source} theme={theme} />
|
|
622
551
|
</div>
|
|
623
552
|
)}
|
|
@@ -639,15 +568,9 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
639
568
|
if (source === undefined) {
|
|
640
569
|
return (
|
|
641
570
|
<div
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
alignItems: 'center',
|
|
646
|
-
justifyContent: 'center',
|
|
647
|
-
background: bgColor,
|
|
648
|
-
color: '#94a3b8',
|
|
649
|
-
fontSize: 13,
|
|
650
|
-
}}
|
|
571
|
+
className="jogak:h-full jogak:flex jogak:items-center jogak:justify-center jogak:bg-[var(--jogak-source-bg)] jogak:text-[#94a3b8] jogak:text-[13px]"
|
|
572
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: source-bg CSS var (prism theme)
|
|
573
|
+
style={{ '--jogak-source-bg': bgColor } as CSSVarStyle}
|
|
651
574
|
>
|
|
652
575
|
Source not available
|
|
653
576
|
</div>
|
|
@@ -662,23 +585,11 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
662
585
|
}
|
|
663
586
|
|
|
664
587
|
return (
|
|
665
|
-
<div
|
|
588
|
+
<div className="jogak:relative jogak:h-full">
|
|
666
589
|
<button
|
|
667
590
|
type="button"
|
|
668
591
|
onClick={handleCopy}
|
|
669
|
-
|
|
670
|
-
position: 'absolute',
|
|
671
|
-
top: 10,
|
|
672
|
-
right: 12,
|
|
673
|
-
zIndex: 1,
|
|
674
|
-
padding: '3px 9px',
|
|
675
|
-
fontSize: 11,
|
|
676
|
-
background: 'rgba(255,255,255,0.1)',
|
|
677
|
-
color: '#e2e8f0',
|
|
678
|
-
border: '1px solid rgba(255,255,255,0.18)',
|
|
679
|
-
borderRadius: 4,
|
|
680
|
-
cursor: 'pointer',
|
|
681
|
-
}}
|
|
592
|
+
className="jogak:absolute jogak:top-[10px] jogak:right-3 jogak:z-[1] jogak:px-[9px] jogak:py-[3px] jogak:text-[11px] jogak:bg-[rgba(255,255,255,0.1)] jogak:text-[#e2e8f0] jogak:border jogak:border-[rgba(255,255,255,0.18)] jogak:rounded-[var(--jogak-radius-md)] jogak:cursor-pointer jogak:leading-none"
|
|
682
593
|
>
|
|
683
594
|
{copied ? '✓ Copied' : 'Copy'}
|
|
684
595
|
</button>
|
|
@@ -686,40 +597,19 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
686
597
|
<Highlight code={source.trim()} language="tsx" theme={theme}>
|
|
687
598
|
{({ style, tokens, getLineProps, getTokenProps }) => (
|
|
688
599
|
<pre
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
padding: '12px 0',
|
|
693
|
-
fontSize: 12.5,
|
|
694
|
-
lineHeight: 1.7,
|
|
695
|
-
fontFamily:
|
|
696
|
-
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
697
|
-
height: '100%',
|
|
698
|
-
boxSizing: 'border-box',
|
|
699
|
-
overflow: 'auto',
|
|
700
|
-
}}
|
|
600
|
+
className="jogak:m-0 jogak:py-3 jogak:px-0 jogak:text-[12.5px] jogak:leading-[1.7] jogak:font-[family-name:var(--jogak-font-mono)] jogak:h-full jogak:box-border jogak:overflow-auto"
|
|
601
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: prism-react-renderer external interface (pre)
|
|
602
|
+
style={style}
|
|
701
603
|
>
|
|
702
604
|
{tokens.map((line, i) => (
|
|
703
605
|
<div
|
|
704
606
|
key={i}
|
|
705
607
|
{...getLineProps({ line })}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
paddingRight: 24,
|
|
710
|
-
}}
|
|
608
|
+
className="jogak:flex jogak:pr-6"
|
|
609
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: prism-react-renderer external interface (line)
|
|
610
|
+
style={getLineProps({ line }).style}
|
|
711
611
|
>
|
|
712
|
-
<span
|
|
713
|
-
style={{
|
|
714
|
-
userSelect: 'none',
|
|
715
|
-
minWidth: 40,
|
|
716
|
-
paddingLeft: 14,
|
|
717
|
-
paddingRight: 14,
|
|
718
|
-
textAlign: 'right',
|
|
719
|
-
color: 'rgba(148,163,184,0.45)',
|
|
720
|
-
flexShrink: 0,
|
|
721
|
-
}}
|
|
722
|
-
>
|
|
612
|
+
<span className="jogak:select-none jogak:min-w-10 jogak:pl-[14px] jogak:pr-[14px] jogak:text-right jogak:text-[rgba(148,163,184,0.45)] jogak:shrink-0 jogak:leading-[1.7]">
|
|
723
613
|
{i + 1}
|
|
724
614
|
</span>
|
|
725
615
|
<span>
|