@jogak/ui 0.1.0-alpha.4 → 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 +33 -0
- 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/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 +27 -7
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
|
-
import type { ReactElement } from 'react'
|
|
2
|
+
import type { CSSProperties, ReactElement } from 'react'
|
|
3
|
+
import clsx from 'clsx'
|
|
3
4
|
import type { CategoryMetaTree, RegistryEntryMeta } from '@jogak/core'
|
|
4
5
|
import { useRegistryMeta } from '@jogak/react'
|
|
5
6
|
|
|
7
|
+
// CSS custom property를 React style prop에 주입하기 위한 헬퍼 타입.
|
|
8
|
+
// React 18+는 string-keyed `--` prefix를 인식하나 TS는 명시적 cast 필요.
|
|
9
|
+
// spec §6.1 참조.
|
|
10
|
+
type CSSVarStyle = CSSProperties & Record<`--${string}`, string | number>
|
|
11
|
+
|
|
6
12
|
export interface SidebarProps {
|
|
7
13
|
readonly selectedEntryId: string | null
|
|
8
14
|
readonly selectedJogakName: string | null
|
|
@@ -26,15 +32,9 @@ export function Sidebar({
|
|
|
26
32
|
return (
|
|
27
33
|
<aside
|
|
28
34
|
data-testid="sidebar"
|
|
29
|
-
|
|
30
|
-
borderRight: '1px solid #e5e7eb',
|
|
31
|
-
height: '100%',
|
|
32
|
-
overflow: 'auto',
|
|
33
|
-
display: 'flex',
|
|
34
|
-
flexDirection: 'column',
|
|
35
|
-
}}
|
|
35
|
+
className="jogak:flex jogak:flex-col jogak:h-full jogak:overflow-auto jogak:border-r jogak:border-[var(--jogak-color-border)]"
|
|
36
36
|
>
|
|
37
|
-
<div
|
|
37
|
+
<div className="jogak:p-3 jogak:border-b jogak:border-[var(--jogak-color-border)]">
|
|
38
38
|
<input
|
|
39
39
|
type="search"
|
|
40
40
|
placeholder="Search components..."
|
|
@@ -42,16 +42,11 @@ export function Sidebar({
|
|
|
42
42
|
onChange={(e) => {
|
|
43
43
|
setQuery(e.target.value)
|
|
44
44
|
}}
|
|
45
|
-
|
|
46
|
-
width: '100%',
|
|
47
|
-
padding: '6px 8px',
|
|
48
|
-
border: '1px solid #d1d5db',
|
|
49
|
-
borderRadius: 4,
|
|
50
|
-
}}
|
|
45
|
+
className="jogak:w-full jogak:px-2 jogak:py-1.5 jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-md)]"
|
|
51
46
|
aria-label="Search components"
|
|
52
47
|
/>
|
|
53
48
|
</div>
|
|
54
|
-
<nav
|
|
49
|
+
<nav className="jogak:flex-1 jogak:overflow-auto jogak:py-2">
|
|
55
50
|
{filtered !== null ? (
|
|
56
51
|
<FlatList
|
|
57
52
|
metas={filtered}
|
|
@@ -87,13 +82,13 @@ function FlatList({
|
|
|
87
82
|
}: FlatListProps): ReactElement {
|
|
88
83
|
if (metas.length === 0) {
|
|
89
84
|
return (
|
|
90
|
-
<p
|
|
85
|
+
<p className="jogak:px-3 jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px]">
|
|
91
86
|
No results
|
|
92
87
|
</p>
|
|
93
88
|
)
|
|
94
89
|
}
|
|
95
90
|
return (
|
|
96
|
-
<ul
|
|
91
|
+
<ul className="jogak:list-none jogak:m-0 jogak:p-0">
|
|
97
92
|
{metas.map((meta) => (
|
|
98
93
|
<li key={meta.id}>
|
|
99
94
|
<EntryGroup
|
|
@@ -126,11 +121,9 @@ function TreeView({
|
|
|
126
121
|
}: TreeViewProps): ReactElement {
|
|
127
122
|
return (
|
|
128
123
|
<ul
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
padding: `0 0 0 ${depth * 12}px`,
|
|
133
|
-
}}
|
|
124
|
+
className="jogak:list-none jogak:m-0 jogak:pr-0 jogak:py-0 jogak:pl-[var(--jogak-tree-pl)]"
|
|
125
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: CSS var inject (--jogak-tree-pl)
|
|
126
|
+
style={{ '--jogak-tree-pl': `${depth * 12}px` } as CSSVarStyle}
|
|
134
127
|
>
|
|
135
128
|
{Object.entries(node).map(([key, child]) => (
|
|
136
129
|
<li key={key}>
|
|
@@ -183,21 +176,7 @@ function CategoryGroup({
|
|
|
183
176
|
onClick={() => {
|
|
184
177
|
setOpen((v) => !v)
|
|
185
178
|
}}
|
|
186
|
-
|
|
187
|
-
display: 'flex',
|
|
188
|
-
alignItems: 'center',
|
|
189
|
-
gap: 4,
|
|
190
|
-
width: '100%',
|
|
191
|
-
padding: '4px 12px',
|
|
192
|
-
background: 'none',
|
|
193
|
-
border: 'none',
|
|
194
|
-
cursor: 'pointer',
|
|
195
|
-
fontSize: 12,
|
|
196
|
-
fontWeight: 600,
|
|
197
|
-
color: '#6b7280',
|
|
198
|
-
textTransform: 'uppercase',
|
|
199
|
-
letterSpacing: '0.05em',
|
|
200
|
-
}}
|
|
179
|
+
className="jogak:flex jogak:items-center jogak:gap-1 jogak:w-full jogak:px-3 jogak:py-1 jogak:bg-transparent jogak:border-none jogak:cursor-pointer jogak:text-[12px] jogak:font-semibold jogak:text-[var(--jogak-color-fg-muted)] jogak:uppercase jogak:tracking-wider"
|
|
201
180
|
aria-expanded={open}
|
|
202
181
|
>
|
|
203
182
|
<span>{open ? '▾' : '▸'}</span>
|
|
@@ -254,29 +233,25 @@ function EntryGroup({
|
|
|
254
233
|
setOpen((v) => !v)
|
|
255
234
|
}
|
|
256
235
|
}}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
color: isCurrentEntry ? '#2563eb' : '#374151',
|
|
268
|
-
fontWeight: isCurrentEntry ? 500 : 400,
|
|
269
|
-
textAlign: 'left',
|
|
270
|
-
}}
|
|
236
|
+
className={clsx(
|
|
237
|
+
'jogak:flex jogak:items-center jogak:gap-1.5 jogak:w-full jogak:pr-3 jogak:py-[5px]',
|
|
238
|
+
'jogak:pl-[var(--jogak-entry-pl)]',
|
|
239
|
+
'jogak:border-none jogak:cursor-pointer jogak:text-left jogak:text-[13px]',
|
|
240
|
+
isCurrentEntry
|
|
241
|
+
? 'jogak:bg-[var(--jogak-color-accent-bg)] jogak:text-[var(--jogak-color-accent)] jogak:font-medium'
|
|
242
|
+
: 'jogak:bg-transparent jogak:text-[var(--jogak-color-fg)] jogak:font-normal',
|
|
243
|
+
)}
|
|
244
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: CSS var inject (--jogak-entry-pl)
|
|
245
|
+
style={{ '--jogak-entry-pl': `${paddingLeft}px` } as CSSVarStyle}
|
|
271
246
|
aria-expanded={open}
|
|
272
247
|
>
|
|
273
|
-
<span
|
|
248
|
+
<span className="jogak:text-[10px] jogak:shrink-0 jogak:leading-none">
|
|
274
249
|
{open ? '▾' : '▸'}
|
|
275
250
|
</span>
|
|
276
251
|
{label}
|
|
277
252
|
</button>
|
|
278
253
|
{open && (
|
|
279
|
-
<ul
|
|
254
|
+
<ul className="jogak:list-none jogak:m-0 jogak:p-0">
|
|
280
255
|
{meta.jogakNames.map((jogakName) => {
|
|
281
256
|
const isSelected = isCurrentEntry && jogakName === selectedJogakName
|
|
282
257
|
return (
|
|
@@ -286,18 +261,16 @@ function EntryGroup({
|
|
|
286
261
|
onClick={() => {
|
|
287
262
|
onSelect(meta.id, jogakName)
|
|
288
263
|
}}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
fontWeight: isSelected ? 500 : 400,
|
|
300
|
-
}}
|
|
264
|
+
className={clsx(
|
|
265
|
+
'jogak:block jogak:w-full jogak:text-left jogak:pr-3 jogak:py-1',
|
|
266
|
+
'jogak:pl-[var(--jogak-jogak-pl)]',
|
|
267
|
+
'jogak:border-none jogak:cursor-pointer jogak:text-[12px]',
|
|
268
|
+
isSelected
|
|
269
|
+
? 'jogak:bg-[var(--jogak-color-accent-bg-soft)] jogak:text-[var(--jogak-color-accent-fg)] jogak:font-medium'
|
|
270
|
+
: 'jogak:bg-transparent jogak:text-[var(--jogak-color-fg-muted)] jogak:font-normal',
|
|
271
|
+
)}
|
|
272
|
+
// eslint-disable-next-line no-restricted-syntax -- jogak: CSS var inject (--jogak-jogak-pl)
|
|
273
|
+
style={{ '--jogak-jogak-pl': `${paddingLeft + 18}px` } as CSSVarStyle}
|
|
301
274
|
aria-current={isSelected ? 'true' : undefined}
|
|
302
275
|
>
|
|
303
276
|
{jogakName}
|
package/src/styles/jogak.css
CHANGED
|
@@ -76,13 +76,33 @@
|
|
|
76
76
|
/* typography */
|
|
77
77
|
--jogak-font-sans: system-ui, sans-serif;
|
|
78
78
|
--jogak-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
/*
|
|
80
|
+
* NOTE 알파.5 PR 4: --jogak-text-{xs,sm,base,md,lg} 5개 변수 + --jogak-sidebar-width
|
|
81
|
+
* 1개 삭제. 사용처 zero (PR 1/3에서 픽셀 literal 채택). v4 함정 1 (font-size 페어링)
|
|
82
|
+
* 회귀 자체 차단 + 알파.6 사용자 globalCss 충돌 가능성 zero.
|
|
83
|
+
*/
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@layer components {
|
|
88
|
+
/*
|
|
89
|
+
* 알파.5 PR 4: skeleton 애니메이션을 inline <style>(Preview L250-252)에서 jogak.css로
|
|
90
|
+
* 이동. inline style forbid rule (no-restricted-syntax) 위반 1건 + inline <style> 1건
|
|
91
|
+
* 동시 제거. VR 영향 zero (선택자/타이밍/그라디언트 픽셀 동일).
|
|
92
|
+
*/
|
|
93
|
+
@keyframes jogakSkeleton {
|
|
94
|
+
0% { background-position: 200% 0; }
|
|
95
|
+
100% { background-position: -200% 0; }
|
|
96
|
+
}
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
.jogak-skeleton-shimmer {
|
|
99
|
+
background: linear-gradient(
|
|
100
|
+
90deg,
|
|
101
|
+
rgba(229, 231, 235, 0) 0%,
|
|
102
|
+
rgba(229, 231, 235, 0.45) 50%,
|
|
103
|
+
rgba(229, 231, 235, 0) 100%
|
|
104
|
+
);
|
|
105
|
+
background-size: 200% 100%;
|
|
106
|
+
animation: jogakSkeleton 1.4s ease-in-out infinite;
|
|
87
107
|
}
|
|
88
108
|
}
|