@jogak/ui 0.1.0-alpha.3 → 0.1.0-alpha.5
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 +51 -0
- package/README.md +15 -0
- package/dist/host/index.js +1 -1
- package/dist/host/index.mjs +47 -47
- package/dist/index.js +1 -1
- package/dist/index.mjs +597 -912
- package/package.json +7 -3
- package/src/app/App.tsx +4 -16
- package/src/app/main.tsx +1 -0
- package/src/components/Actions/index.tsx +20 -50
- package/src/components/Controls/index.tsx +49 -70
- package/src/components/Preview/index.tsx +141 -250
- package/src/components/Sidebar/index.tsx +39 -66
- package/src/styles/jogak.css +108 -0
|
@@ -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,19 +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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
337
|
+
data-jogak-content
|
|
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
|
+
}
|
|
344
345
|
>
|
|
345
346
|
<JogakRenderer
|
|
346
347
|
key={`${entry.id}/${jogak.name}`}
|
|
@@ -354,24 +355,12 @@ function ReadyFrame({
|
|
|
354
355
|
|
|
355
356
|
{/* ── 컨트롤/액션 패널 ──────────────────────────────── */}
|
|
356
357
|
<div
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
flexShrink: 0,
|
|
360
|
-
display: 'flex',
|
|
361
|
-
flexDirection: 'column',
|
|
362
|
-
borderTop: '2px solid #e5e7eb',
|
|
363
|
-
}}
|
|
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)]"
|
|
364
360
|
>
|
|
365
361
|
<div
|
|
366
362
|
role="tablist"
|
|
367
|
-
|
|
368
|
-
display: 'flex',
|
|
369
|
-
gap: 4,
|
|
370
|
-
padding: '4px 12px 0',
|
|
371
|
-
background: '#fff',
|
|
372
|
-
borderBottom: '1px solid #e5e7eb',
|
|
373
|
-
flexShrink: 0,
|
|
374
|
-
}}
|
|
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"
|
|
375
364
|
>
|
|
376
365
|
{(['controls', 'actions'] as const).map((tab) => {
|
|
377
366
|
const active = bottomTab === tab
|
|
@@ -382,18 +371,12 @@ function ReadyFrame({
|
|
|
382
371
|
role="tab"
|
|
383
372
|
aria-selected={active}
|
|
384
373
|
onClick={() => { onBottomTabChange(tab) }}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
border: 'none',
|
|
392
|
-
borderBottom: active ? '2px solid #2563eb' : '2px solid transparent',
|
|
393
|
-
marginBottom: -1,
|
|
394
|
-
cursor: 'pointer',
|
|
395
|
-
textTransform: 'capitalize',
|
|
396
|
-
}}
|
|
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
|
+
)}
|
|
397
380
|
>
|
|
398
381
|
{tab}
|
|
399
382
|
</button>
|
|
@@ -401,7 +384,7 @@ function ReadyFrame({
|
|
|
401
384
|
})}
|
|
402
385
|
</div>
|
|
403
386
|
|
|
404
|
-
<div
|
|
387
|
+
<div className="jogak:flex-1 jogak:min-h-0 jogak:overflow-auto">
|
|
405
388
|
{bottomTab === 'controls' ? (
|
|
406
389
|
<Controls
|
|
407
390
|
args={mergedArgs}
|
|
@@ -441,51 +424,31 @@ function Toolbar({
|
|
|
441
424
|
onReset,
|
|
442
425
|
}: ToolbarProps): ReactElement {
|
|
443
426
|
return (
|
|
444
|
-
<div
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}}
|
|
454
|
-
>
|
|
455
|
-
<div style={{ flex: 1, fontSize: 13 }}>
|
|
456
|
-
<span style={{ color: '#9ca3af' }}>{title}</span>
|
|
457
|
-
<span style={{ color: '#d1d5db', margin: '0 6px' }}>/</span>
|
|
458
|
-
<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>
|
|
459
436
|
</div>
|
|
460
437
|
|
|
461
438
|
{/* 뷰포트 토글 */}
|
|
462
|
-
<div
|
|
463
|
-
style={{
|
|
464
|
-
display: 'flex',
|
|
465
|
-
gap: 2,
|
|
466
|
-
background: '#f3f4f6',
|
|
467
|
-
borderRadius: 6,
|
|
468
|
-
padding: 2,
|
|
469
|
-
}}
|
|
470
|
-
>
|
|
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">
|
|
471
440
|
{(['mobile', 'tablet', 'desktop'] as const).map((vp) => (
|
|
472
441
|
<button
|
|
473
442
|
key={vp}
|
|
474
443
|
type="button"
|
|
475
444
|
onClick={() => { onViewportChange(vp) }}
|
|
476
445
|
aria-pressed={viewport === vp}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
background: viewport === vp ? '#fff' : 'transparent',
|
|
484
|
-
color: viewport === vp ? '#111827' : '#6b7280',
|
|
485
|
-
fontWeight: viewport === vp ? 600 : 400,
|
|
486
|
-
boxShadow: viewport === vp ? '0 1px 2px rgba(0,0,0,0.08)' : 'none',
|
|
487
|
-
transition: 'all 0.1s',
|
|
488
|
-
}}
|
|
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
|
+
)}
|
|
489
452
|
>
|
|
490
453
|
{VIEWPORT_LABELS[vp]}
|
|
491
454
|
</button>
|
|
@@ -493,7 +456,7 @@ function Toolbar({
|
|
|
493
456
|
</div>
|
|
494
457
|
|
|
495
458
|
{/* 배경 토글 */}
|
|
496
|
-
<div
|
|
459
|
+
<div className="jogak:flex jogak:gap-1 jogak:items-center">
|
|
497
460
|
{(['white', 'dark', 'transparent'] as const).map((bg) => (
|
|
498
461
|
<button
|
|
499
462
|
key={bg}
|
|
@@ -501,16 +464,15 @@ function Toolbar({
|
|
|
501
464
|
onClick={() => { onBgModeChange(bg) }}
|
|
502
465
|
aria-pressed={bgMode === bg}
|
|
503
466
|
aria-label={`${bg} background`}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}}
|
|
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]}
|
|
514
476
|
/>
|
|
515
477
|
))}
|
|
516
478
|
</div>
|
|
@@ -520,15 +482,7 @@ function Toolbar({
|
|
|
520
482
|
<button
|
|
521
483
|
type="button"
|
|
522
484
|
onClick={onReset}
|
|
523
|
-
|
|
524
|
-
padding: '3px 10px',
|
|
525
|
-
fontSize: 12,
|
|
526
|
-
border: '1px solid #d1d5db',
|
|
527
|
-
borderRadius: 4,
|
|
528
|
-
background: '#fff',
|
|
529
|
-
cursor: 'pointer',
|
|
530
|
-
color: '#374151',
|
|
531
|
-
}}
|
|
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"
|
|
532
486
|
>
|
|
533
487
|
Reset
|
|
534
488
|
</button>
|
|
@@ -567,40 +521,24 @@ function JogakRenderer({ entry, args, source, theme }: JogakRendererProps): Reac
|
|
|
567
521
|
return (
|
|
568
522
|
<div>
|
|
569
523
|
{/* preview-content 영역 + 토글 버튼 */}
|
|
570
|
-
<div
|
|
524
|
+
<div className="jogak:relative">
|
|
571
525
|
<div
|
|
572
526
|
ref={containerRef}
|
|
573
527
|
data-testid="preview-content"
|
|
574
|
-
|
|
575
|
-
border: '1px dashed #e5e7eb',
|
|
576
|
-
borderRadius: 8,
|
|
577
|
-
padding: 16,
|
|
578
|
-
paddingBottom: 36,
|
|
579
|
-
}}
|
|
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"
|
|
580
529
|
/>
|
|
581
530
|
<button
|
|
582
531
|
type="button"
|
|
583
532
|
onClick={() => { setShowCode((v) => !v) }}
|
|
584
533
|
aria-pressed={showCode}
|
|
585
534
|
aria-label={showCode ? 'Hide source code' : 'Show source code'}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
594
|
-
fontWeight: 600,
|
|
595
|
-
letterSpacing: '0.02em',
|
|
596
|
-
background: showCode ? '#2563eb' : '#1e293b',
|
|
597
|
-
color: '#fff',
|
|
598
|
-
border: 'none',
|
|
599
|
-
borderRadius: 5,
|
|
600
|
-
cursor: 'pointer',
|
|
601
|
-
boxShadow: '0 1px 4px rgba(0,0,0,0.2)',
|
|
602
|
-
transition: 'background 0.15s',
|
|
603
|
-
}}
|
|
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
|
+
)}
|
|
604
542
|
>
|
|
605
543
|
{'</>'}
|
|
606
544
|
</button>
|
|
@@ -608,15 +546,7 @@ function JogakRenderer({ entry, args, source, theme }: JogakRendererProps): Reac
|
|
|
608
546
|
|
|
609
547
|
{/* 코드 패널 — preview-content 하단으로 펼쳐짐 */}
|
|
610
548
|
{showCode && (
|
|
611
|
-
<div
|
|
612
|
-
style={{
|
|
613
|
-
marginTop: 8,
|
|
614
|
-
borderRadius: 8,
|
|
615
|
-
overflow: 'hidden',
|
|
616
|
-
height: 320,
|
|
617
|
-
boxShadow: '0 0 0 1px rgba(0,0,0,0.08), 0 4px 16px rgba(0,0,0,0.12)',
|
|
618
|
-
}}
|
|
619
|
-
>
|
|
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)]">
|
|
620
550
|
<SourceViewer source={source} theme={theme} />
|
|
621
551
|
</div>
|
|
622
552
|
)}
|
|
@@ -638,15 +568,9 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
638
568
|
if (source === undefined) {
|
|
639
569
|
return (
|
|
640
570
|
<div
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
alignItems: 'center',
|
|
645
|
-
justifyContent: 'center',
|
|
646
|
-
background: bgColor,
|
|
647
|
-
color: '#94a3b8',
|
|
648
|
-
fontSize: 13,
|
|
649
|
-
}}
|
|
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}
|
|
650
574
|
>
|
|
651
575
|
Source not available
|
|
652
576
|
</div>
|
|
@@ -661,23 +585,11 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
661
585
|
}
|
|
662
586
|
|
|
663
587
|
return (
|
|
664
|
-
<div
|
|
588
|
+
<div className="jogak:relative jogak:h-full">
|
|
665
589
|
<button
|
|
666
590
|
type="button"
|
|
667
591
|
onClick={handleCopy}
|
|
668
|
-
|
|
669
|
-
position: 'absolute',
|
|
670
|
-
top: 10,
|
|
671
|
-
right: 12,
|
|
672
|
-
zIndex: 1,
|
|
673
|
-
padding: '3px 9px',
|
|
674
|
-
fontSize: 11,
|
|
675
|
-
background: 'rgba(255,255,255,0.1)',
|
|
676
|
-
color: '#e2e8f0',
|
|
677
|
-
border: '1px solid rgba(255,255,255,0.18)',
|
|
678
|
-
borderRadius: 4,
|
|
679
|
-
cursor: 'pointer',
|
|
680
|
-
}}
|
|
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"
|
|
681
593
|
>
|
|
682
594
|
{copied ? '✓ Copied' : 'Copy'}
|
|
683
595
|
</button>
|
|
@@ -685,40 +597,19 @@ function SourceViewer({ source, theme }: SourceViewerProps): ReactElement {
|
|
|
685
597
|
<Highlight code={source.trim()} language="tsx" theme={theme}>
|
|
686
598
|
{({ style, tokens, getLineProps, getTokenProps }) => (
|
|
687
599
|
<pre
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
padding: '12px 0',
|
|
692
|
-
fontSize: 12.5,
|
|
693
|
-
lineHeight: 1.7,
|
|
694
|
-
fontFamily:
|
|
695
|
-
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
696
|
-
height: '100%',
|
|
697
|
-
boxSizing: 'border-box',
|
|
698
|
-
overflow: 'auto',
|
|
699
|
-
}}
|
|
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}
|
|
700
603
|
>
|
|
701
604
|
{tokens.map((line, i) => (
|
|
702
605
|
<div
|
|
703
606
|
key={i}
|
|
704
607
|
{...getLineProps({ line })}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
paddingRight: 24,
|
|
709
|
-
}}
|
|
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}
|
|
710
611
|
>
|
|
711
|
-
<span
|
|
712
|
-
style={{
|
|
713
|
-
userSelect: 'none',
|
|
714
|
-
minWidth: 40,
|
|
715
|
-
paddingLeft: 14,
|
|
716
|
-
paddingRight: 14,
|
|
717
|
-
textAlign: 'right',
|
|
718
|
-
color: 'rgba(148,163,184,0.45)',
|
|
719
|
-
flexShrink: 0,
|
|
720
|
-
}}
|
|
721
|
-
>
|
|
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]">
|
|
722
613
|
{i + 1}
|
|
723
614
|
</span>
|
|
724
615
|
<span>
|