@conscience-technology/ctds 0.3.1 → 0.3.3
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/assets/components/Breadcrumb/Breadcrumb.module.css +64 -0
- package/assets/components/Breadcrumb/Breadcrumb.tsx +120 -47
- package/assets/components/Icon/Icon.tsx +823 -114
- package/assets/components/Onboarding/Onboarding.module.css +186 -0
- package/assets/components/Sidebar/Sidebar.module.css +36 -74
- package/assets/components/Sidebar/Sidebar.tsx +15 -2
- package/assets/components/_data.ts +3 -7
- package/assets/components/_utils.css +105 -0
- package/assets/components/index.ts +0 -1
- package/assets/prompts/components/breadcrumb.md +44 -4
- package/assets/prompts/components/sidebar.md +14 -5
- package/assets/skills/ctds/SKILL.md +1 -3
- package/package.json +1 -1
- package/assets/components/Accordion/Accordion.module.css +0 -247
- package/assets/components/Accordion/Accordion.tsx +0 -207
- package/assets/components/Accordion/index.ts +0 -8
- package/assets/components/CommandPalette/CommandPalette.module.css +0 -203
- package/assets/components/CommandPalette/CommandPalette.tsx +0 -262
- package/assets/components/CommandPalette/index.ts +0 -9
- package/assets/components/CommandPalette/useCommandPaletteHotkey.ts +0 -49
- package/assets/components/EventCard/EventCard.module.css +0 -133
- package/assets/components/EventCard/EventCard.tsx +0 -149
- package/assets/components/EventCard/index.ts +0 -2
- package/assets/prompts/components/accordion.md +0 -126
- package/assets/prompts/components/command-palette.md +0 -148
- package/assets/prompts/components/event-card.md +0 -78
|
@@ -1351,3 +1351,189 @@
|
|
|
1351
1351
|
border-radius: var(--radius-sm);
|
|
1352
1352
|
}
|
|
1353
1353
|
|
|
1354
|
+
/* ── LayoutBuilder (shell preview) ────────────────────── */
|
|
1355
|
+
|
|
1356
|
+
.layoutGrid {
|
|
1357
|
+
display: grid;
|
|
1358
|
+
grid-template-columns: minmax(var(--space-12), var(--space-16)) 1fr;
|
|
1359
|
+
gap: var(--space-5);
|
|
1360
|
+
min-height: 0;
|
|
1361
|
+
}
|
|
1362
|
+
.layoutOptions {
|
|
1363
|
+
display: flex;
|
|
1364
|
+
flex-direction: column;
|
|
1365
|
+
gap: var(--space-4);
|
|
1366
|
+
min-width: 0;
|
|
1367
|
+
}
|
|
1368
|
+
.optionGroup {
|
|
1369
|
+
display: flex;
|
|
1370
|
+
flex-direction: column;
|
|
1371
|
+
gap: var(--space-2);
|
|
1372
|
+
min-width: 0;
|
|
1373
|
+
}
|
|
1374
|
+
.optionGroupLabel {
|
|
1375
|
+
font-size: var(--text-sm);
|
|
1376
|
+
font-weight: var(--font-weight-medium);
|
|
1377
|
+
color: var(--fg-strong);
|
|
1378
|
+
}
|
|
1379
|
+
.optionList {
|
|
1380
|
+
display: flex;
|
|
1381
|
+
flex-direction: column;
|
|
1382
|
+
gap: var(--space-1);
|
|
1383
|
+
}
|
|
1384
|
+
.optionItem {
|
|
1385
|
+
display: flex;
|
|
1386
|
+
align-items: center;
|
|
1387
|
+
min-height: var(--control-h-md);
|
|
1388
|
+
padding: 0 var(--space-3);
|
|
1389
|
+
border: var(--hairline) solid var(--border);
|
|
1390
|
+
border-radius: var(--radius-md);
|
|
1391
|
+
background: transparent;
|
|
1392
|
+
color: var(--fg);
|
|
1393
|
+
font: inherit;
|
|
1394
|
+
font-size: var(--text-sm);
|
|
1395
|
+
text-align: left;
|
|
1396
|
+
cursor: pointer;
|
|
1397
|
+
transition: var(--transition-colors);
|
|
1398
|
+
}
|
|
1399
|
+
.optionItem:hover {
|
|
1400
|
+
background: var(--hover);
|
|
1401
|
+
}
|
|
1402
|
+
.optionItem[data-active] {
|
|
1403
|
+
background: var(--secondary-bg);
|
|
1404
|
+
border-color: var(--border-strong);
|
|
1405
|
+
color: var(--fg-strong);
|
|
1406
|
+
}
|
|
1407
|
+
.field {
|
|
1408
|
+
display: flex;
|
|
1409
|
+
flex-direction: column;
|
|
1410
|
+
gap: var(--space-2);
|
|
1411
|
+
min-width: 0;
|
|
1412
|
+
}
|
|
1413
|
+
.fieldLabel {
|
|
1414
|
+
font-size: var(--text-sm);
|
|
1415
|
+
font-weight: var(--font-weight-medium);
|
|
1416
|
+
color: var(--fg-strong);
|
|
1417
|
+
}
|
|
1418
|
+
.fieldControl {
|
|
1419
|
+
min-height: var(--control-h-md);
|
|
1420
|
+
padding: 0 var(--space-3);
|
|
1421
|
+
border: var(--hairline) solid var(--border);
|
|
1422
|
+
border-radius: var(--radius-md);
|
|
1423
|
+
background: transparent;
|
|
1424
|
+
color: var(--fg);
|
|
1425
|
+
font: inherit;
|
|
1426
|
+
font-size: var(--text-sm);
|
|
1427
|
+
transition: var(--transition-colors);
|
|
1428
|
+
}
|
|
1429
|
+
.fieldControl:focus-visible {
|
|
1430
|
+
outline: none;
|
|
1431
|
+
box-shadow: var(--ring);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
.mini {
|
|
1435
|
+
display: grid;
|
|
1436
|
+
min-height: var(--space-16);
|
|
1437
|
+
border-radius: var(--radius-lg);
|
|
1438
|
+
background: var(--bg-subtle);
|
|
1439
|
+
overflow: hidden;
|
|
1440
|
+
grid-template-areas:
|
|
1441
|
+
"sidebar body"
|
|
1442
|
+
"sidebar body";
|
|
1443
|
+
grid-template-columns: var(--space-12) 1fr;
|
|
1444
|
+
grid-template-rows: 1fr;
|
|
1445
|
+
}
|
|
1446
|
+
.mini[data-topbar="true"] {
|
|
1447
|
+
grid-template-areas:
|
|
1448
|
+
"sidebar topbar"
|
|
1449
|
+
"sidebar body";
|
|
1450
|
+
grid-template-rows: auto 1fr;
|
|
1451
|
+
}
|
|
1452
|
+
.mini[data-sidebar="none"] {
|
|
1453
|
+
grid-template-areas:
|
|
1454
|
+
"body body"
|
|
1455
|
+
"body body";
|
|
1456
|
+
grid-template-columns: 1fr;
|
|
1457
|
+
}
|
|
1458
|
+
.mini[data-sidebar="none"][data-topbar="true"] {
|
|
1459
|
+
grid-template-areas:
|
|
1460
|
+
"topbar topbar"
|
|
1461
|
+
"body body";
|
|
1462
|
+
}
|
|
1463
|
+
.mini[data-sidebar="dual"] {
|
|
1464
|
+
grid-template-areas:
|
|
1465
|
+
"sidebar body sidebar-right"
|
|
1466
|
+
"sidebar body sidebar-right";
|
|
1467
|
+
grid-template-columns: var(--space-12) 1fr var(--space-12);
|
|
1468
|
+
}
|
|
1469
|
+
.mini[data-sidebar="dual"][data-topbar="true"] {
|
|
1470
|
+
grid-template-areas:
|
|
1471
|
+
"sidebar topbar sidebar-right"
|
|
1472
|
+
"sidebar body sidebar-right";
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.miniTopbar {
|
|
1476
|
+
grid-area: topbar;
|
|
1477
|
+
display: flex;
|
|
1478
|
+
align-items: center;
|
|
1479
|
+
gap: var(--space-2);
|
|
1480
|
+
padding: 0 var(--space-4);
|
|
1481
|
+
min-height: var(--control-h-md);
|
|
1482
|
+
background: var(--bg);
|
|
1483
|
+
border-bottom: var(--hairline) solid var(--border);
|
|
1484
|
+
}
|
|
1485
|
+
.miniSidebar {
|
|
1486
|
+
grid-area: sidebar;
|
|
1487
|
+
display: flex;
|
|
1488
|
+
flex-direction: column;
|
|
1489
|
+
gap: var(--space-1);
|
|
1490
|
+
padding: var(--space-3);
|
|
1491
|
+
background: var(--bg);
|
|
1492
|
+
border-right: var(--hairline) solid var(--border);
|
|
1493
|
+
}
|
|
1494
|
+
.miniSidebarRight {
|
|
1495
|
+
grid-area: sidebar-right;
|
|
1496
|
+
border-right: none;
|
|
1497
|
+
border-left: var(--hairline) solid var(--border);
|
|
1498
|
+
}
|
|
1499
|
+
.miniBody {
|
|
1500
|
+
grid-area: body;
|
|
1501
|
+
display: flex;
|
|
1502
|
+
align-items: center;
|
|
1503
|
+
justify-content: center;
|
|
1504
|
+
padding: var(--space-4);
|
|
1505
|
+
color: var(--fg-muted);
|
|
1506
|
+
font-size: var(--text-sm);
|
|
1507
|
+
text-align: center;
|
|
1508
|
+
}
|
|
1509
|
+
.miniBrand {
|
|
1510
|
+
font-size: var(--text-sm);
|
|
1511
|
+
font-weight: var(--font-weight-medium);
|
|
1512
|
+
color: var(--fg-strong);
|
|
1513
|
+
}
|
|
1514
|
+
.miniBrandSymbol {
|
|
1515
|
+
width: var(--space-5);
|
|
1516
|
+
height: var(--space-5);
|
|
1517
|
+
border-radius: var(--radius-sm);
|
|
1518
|
+
object-fit: cover;
|
|
1519
|
+
flex-shrink: 0;
|
|
1520
|
+
}
|
|
1521
|
+
.miniBrandImg {
|
|
1522
|
+
height: var(--space-5);
|
|
1523
|
+
max-width: var(--space-12);
|
|
1524
|
+
object-fit: contain;
|
|
1525
|
+
}
|
|
1526
|
+
.miniNavItem {
|
|
1527
|
+
display: flex;
|
|
1528
|
+
align-items: center;
|
|
1529
|
+
min-height: var(--control-h-sm);
|
|
1530
|
+
padding: 0 var(--space-2);
|
|
1531
|
+
border-radius: var(--radius-sm);
|
|
1532
|
+
color: var(--fg);
|
|
1533
|
+
font-size: var(--text-sm);
|
|
1534
|
+
}
|
|
1535
|
+
.miniNavItem[data-active] {
|
|
1536
|
+
background: var(--secondary-bg);
|
|
1537
|
+
color: var(--fg-strong);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/* Root ─────────────────────────────────────────────────
|
|
2
2
|
세 영역 구조: Header(상단 고정, flex:0) · Body(flex:1, 세로 스크롤) · Footer(하단 고정, flex:0).
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
|
|
4
|
+
패딩 공급 원칙 (중요):
|
|
5
|
+
Sidebar 벤더는 좌우/상하 패딩을 공급하지 않는다. root/Header/Body/Footer wrapper
|
|
6
|
+
어디에도 padding-inline·padding-block을 깔지 않음. 이유 — Body가 스크롤 컨테이너라
|
|
7
|
+
wrapper에 패딩을 깔면 스크롤바가 가장자리와 떨어지고, 벤더가 자동 공급하면
|
|
8
|
+
소비처가 그걸 모르고 또 깔아 이중 패딩이 나는 함정이 반복됨.
|
|
9
|
+
|
|
10
|
+
공급 책임은 소비처에 있음. 두 가지 패턴:
|
|
11
|
+
- 패턴 1 (단일 축): 세 영역이 모두 같은 축을 쓰면, 각 영역의 콘텐츠 래퍼에
|
|
12
|
+
동일한 padding-inline을 하나씩 깔거나 가장 큰 그룹 하나에 공급.
|
|
13
|
+
- 패턴 2 (섹션별 다른 축): 각 섹션(nav 묶음·profile 등)이 자기 padding을 깐다.
|
|
14
|
+
Minimal/Aurora 템플릿이 이 패턴.
|
|
15
|
+
|
|
16
|
+
Separator의 좌우 inset은 `--separator-inset-x` 변수로 노출(기본 0 = full-bleed).
|
|
17
|
+
소비처가 inset이 필요하면 토큰 override. */
|
|
14
18
|
.root {
|
|
15
|
-
/* 좌우/상하 축 토큰 — 전 영역이 공유. 두 값은 동일. */
|
|
16
|
-
--sidebar-pad-x: var(--space-4);
|
|
17
|
-
--sidebar-pad-y: var(--space-4);
|
|
18
19
|
display: flex;
|
|
19
20
|
flex-direction: column;
|
|
20
21
|
width: var(--sidebar-w);
|
|
@@ -27,21 +28,14 @@
|
|
|
27
28
|
percent-height가 auto로 떨어져 Body가 0px로 붕괴한다. */
|
|
28
29
|
align-self: stretch;
|
|
29
30
|
min-height: 0;
|
|
30
|
-
|
|
31
|
-
그 패딩만큼 빈 공간이 남는다. 상/하 숨돌림은 Header/Body/Footer가
|
|
32
|
-
각자 담당(Header padding-block, Body padding-top, Footer padding-block). */
|
|
33
|
-
transition:
|
|
34
|
-
width var(--duration-base) var(--ease-out),
|
|
35
|
-
padding var(--duration-base) var(--ease-out);
|
|
31
|
+
transition: width var(--duration-base) var(--ease-out);
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
.root[data-collapsed] {
|
|
39
|
-
--sidebar-pad-x: var(--space-2);
|
|
40
|
-
--sidebar-pad-y: var(--space-2);
|
|
41
35
|
width: var(--sidebar-w-collapsed);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
/* Header / Footer —
|
|
38
|
+
/* Header / Footer — 레이아웃만. 좌우/상하 padding은 소비처가 공급. */
|
|
45
39
|
.header,
|
|
46
40
|
.footer {
|
|
47
41
|
display: flex;
|
|
@@ -49,26 +43,12 @@
|
|
|
49
43
|
gap: var(--space-2);
|
|
50
44
|
min-height: var(--control-h-md);
|
|
51
45
|
flex: 0 0 auto;
|
|
52
|
-
padding-inline: var(--sidebar-pad-x);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* Header — 위 숨돌림을 더 크게(--space-3), 아래는 Brand와 Body 상단 fade mask가
|
|
56
|
-
받으므로 짧게(--space-1). Body `padding-top: --space-3`이 Header bottom을 보완. */
|
|
57
|
-
.header {
|
|
58
|
-
padding-top: var(--space-3);
|
|
59
|
-
padding-bottom: var(--space-1);
|
|
60
46
|
}
|
|
61
47
|
|
|
62
|
-
/* Footer — 상하 패딩을 좌우 축(--sidebar-pad-y = --sidebar-pad-x)과 동일하게.
|
|
63
|
-
Profile(IdentityButton)의 안쪽 콘텐츠가 Footer border와 사이드바 좌/우에서
|
|
64
|
-
모두 같은 거리를 두도록 사방 균형. */
|
|
65
48
|
/* Footer — 기본은 선 없음(사이드바 안쪽 ghost 리듬 유지). Body가 스크롤
|
|
66
49
|
가능할 때만(data-overflow) 상단 hairline을 켬 — 스크롤되는 콘텐츠가 Footer
|
|
67
|
-
뒤로 깔릴 때 분리가 필요하기 때문. 짧은 메뉴에서는 선이 없어 깔끔.
|
|
68
|
-
line-heavy theme(spare 등)은 소비처에서 항상 켜고 싶을 수 있는데, 그땐
|
|
69
|
-
`[data-sidebar-always-divider]` 등 상위 토글로 해결 — 기본은 overflow 조건부. */
|
|
50
|
+
뒤로 깔릴 때 분리가 필요하기 때문. 짧은 메뉴에서는 선이 없어 깔끔. */
|
|
70
51
|
.footer {
|
|
71
|
-
padding-block: var(--sidebar-pad-y);
|
|
72
52
|
margin-top: auto;
|
|
73
53
|
}
|
|
74
54
|
|
|
@@ -78,8 +58,7 @@
|
|
|
78
58
|
|
|
79
59
|
/* Body — 세로 스크롤 영역.
|
|
80
60
|
여러 개를 두면 각각 독립 스크롤 트랙. flex:1로 남은 공간을 균등 분배.
|
|
81
|
-
|
|
82
|
-
padding-bottom으로 Footer 바로 위에 숨돌릴 여백 — scroll 끝에서 마지막 항목이 border에 치이지 않도록. */
|
|
61
|
+
상하/좌우 padding은 소비처가 Body 안쪽 콘텐츠 래퍼에 공급. */
|
|
83
62
|
.body {
|
|
84
63
|
display: flex;
|
|
85
64
|
flex-direction: column;
|
|
@@ -88,43 +67,32 @@
|
|
|
88
67
|
min-height: 0;
|
|
89
68
|
overflow-y: auto;
|
|
90
69
|
overflow-x: hidden;
|
|
91
|
-
/* 'none': 스크롤 체인 차단 + 바운스/러버밴드 자체도 억제.
|
|
92
|
-
스크롤이 생길 콘텐츠면 자기 영역에서만 스크롤, 콘텐츠가 짧으면 바운스 없음. */
|
|
70
|
+
/* 'none': 스크롤 체인 차단 + 바운스/러버밴드 자체도 억제. */
|
|
93
71
|
overscroll-behavior-y: none;
|
|
94
|
-
/*
|
|
95
|
-
|
|
96
|
-
padding-top
|
|
97
|
-
|
|
98
|
-
좌우 축은 Section·Separator·외부 삽입 블록이 `--sidebar-pad-x`로 각자 담당. */
|
|
72
|
+
/* padding-inline 금지 — 스크롤바가 가장자리와 떨어짐. 좌우 축은 소비처가
|
|
73
|
+
Body 안쪽 콘텐츠 래퍼(nav/섹션 리스트 등)에 깐다. */
|
|
74
|
+
/* padding-top·padding-bottom도 두지 않음 — Header 아래 숨돌림, Footer 위
|
|
75
|
+
마지막 항목 숨돌림 모두 소비처가 안쪽 래퍼의 padding-block으로 공급. */
|
|
99
76
|
/* mask-image 금지 — Body 안에 "고정 상단 블록(railTop 등 primary CTA + 검색)"을
|
|
100
77
|
꽂는 소비처가 많아서 fade가 걸리면 그 블록 상단이 흐려져 "밝은 입체 바"
|
|
101
|
-
착시를 만듦(특히 primary solid 버튼에서 심함).
|
|
102
|
-
Header border 혹은 Header padding-bottom으로 처리. */
|
|
103
|
-
/* padding-bottom은 두지 않음 — Footer가 있으면 Footer 자체 padding이 받고,
|
|
104
|
-
없으면 콘텐츠가 사이드바 끝까지 자연스럽게 흐르게. */
|
|
78
|
+
착시를 만듦(특히 primary solid 버튼에서 심함). */
|
|
105
79
|
}
|
|
106
80
|
|
|
107
81
|
/* Body 자식이 flex column에서 수축돼 header가 뭉개지는 걸 방지.
|
|
108
|
-
|
|
109
|
-
좌우 축도 여기서 공급 — Body 자체엔 padding-inline 금지(스크롤바 가장자리
|
|
110
|
-
밀어냄). SidebarSection 안 쓰고 MenuItem.Section 등을 Body 직접 자식으로
|
|
111
|
-
꽂는 소비처(docs 사이드바)에서도 좌우 리듬 유지. SidebarSection은 자기
|
|
112
|
-
.section에서 같은 토큰 값으로 padding-inline을 걸므로 결과가 동일 — 중복 아님. */
|
|
82
|
+
좌우/상하 padding 공급은 **하지 않음** — 소비처 책임. */
|
|
113
83
|
.body > * {
|
|
114
84
|
flex: 0 0 auto;
|
|
115
|
-
padding-inline: var(--sidebar-pad-x);
|
|
116
85
|
}
|
|
117
86
|
|
|
118
|
-
/* Brand
|
|
119
|
-
symbol은 항상, wordmark는 expanded일 때만 렌더.
|
|
87
|
+
/* Brand preset ─────────────────────────────────────────
|
|
88
|
+
Header 안에 꽂히는 preset. symbol은 항상, wordmark는 expanded일 때만 렌더.
|
|
120
89
|
상한선 토큰으로 max-height 강제 — 사용처가 그 안에서 자유롭게 크기 지정.
|
|
121
|
-
|
|
90
|
+
좌우/상하 padding은 소비처가 공급. */
|
|
122
91
|
.brand {
|
|
123
92
|
display: flex;
|
|
124
93
|
align-items: center;
|
|
125
94
|
gap: var(--space-2);
|
|
126
95
|
min-height: var(--control-h-md);
|
|
127
|
-
/* padding-inline 없음 — Header가 `--sidebar-pad-x`로 좌우 축 담당. */
|
|
128
96
|
}
|
|
129
97
|
|
|
130
98
|
.brandSymbol {
|
|
@@ -161,26 +129,19 @@
|
|
|
161
129
|
height: auto;
|
|
162
130
|
}
|
|
163
131
|
|
|
164
|
-
/* Profile
|
|
165
|
-
IdentityButton의 preset 래퍼. Footer의 좌우/상하 padding(`--sidebar-pad-x/y`)은
|
|
166
|
-
그대로 유지 — 안쪽 Avatar/라벨이 Section 아이템과 동일 축에 정렬됨.
|
|
132
|
+
/* Profile preset — Footer 안에 꽂히는 preset. 좌우/상하 padding은 소비처가 공급.
|
|
167
133
|
IdentityButton 기본이 inline-flex라 Footer flex 안에서 콘텐츠 폭에 머무름 →
|
|
168
|
-
display: flex + width 100%로 가로만 끝까지 받게 하고, 세로는 늘리지
|
|
169
|
-
(flex: 1 1 auto로 세로 당기면 Footer bottom padding이 Profile 안으로 흡수돼
|
|
170
|
-
아래 패딩이 "없어 보임"). */
|
|
134
|
+
display: flex + width 100%로 가로만 끝까지 받게 하고, 세로는 늘리지 않음. */
|
|
171
135
|
.profile {
|
|
172
136
|
display: flex;
|
|
173
137
|
width: 100%;
|
|
174
138
|
}
|
|
175
139
|
|
|
176
|
-
/* Section
|
|
177
|
-
스크롤 트랙(.body) 안쪽의 "items" 레이어. 좌우 padding은 여기가 담당.
|
|
178
|
-
.body 자체에 두면 스크롤바가 padding 안쪽으로 밀려 좌/우 끝에 안 붙음. */
|
|
140
|
+
/* Section preset — Body 안쪽에 꽂히는 preset. 좌우/상하 padding은 소비처가 공급. */
|
|
179
141
|
.section {
|
|
180
142
|
display: flex;
|
|
181
143
|
flex-direction: column;
|
|
182
144
|
gap: var(--space-1);
|
|
183
|
-
padding-inline: var(--sidebar-pad-x);
|
|
184
145
|
}
|
|
185
146
|
|
|
186
147
|
.sectionItems {
|
|
@@ -190,11 +151,12 @@
|
|
|
190
151
|
}
|
|
191
152
|
|
|
192
153
|
/* Separator ──────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
|
|
154
|
+
기본은 full-bleed(좌우 가장자리까지). 소비처가 inset이 필요하면
|
|
155
|
+
`--separator-inset-x` 토큰 override. */
|
|
195
156
|
.separator {
|
|
196
157
|
height: var(--hairline);
|
|
197
|
-
margin: var(--space-1)
|
|
158
|
+
margin-block: var(--space-1);
|
|
159
|
+
margin-inline: var(--separator-inset-x, 0);
|
|
198
160
|
background: var(--border);
|
|
199
161
|
}
|
|
200
162
|
|
|
@@ -61,7 +61,11 @@ const SidebarRoot = forwardRef<HTMLElement, SidebarProps>(function SidebarRoot(
|
|
|
61
61
|
/* Header ───────────────────────────────────────────── */
|
|
62
62
|
function SidebarHeader({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
63
63
|
return (
|
|
64
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
data-sidebar-slot="header"
|
|
66
|
+
className={[styles.header, className ?? ""].filter(Boolean).join(" ")}
|
|
67
|
+
{...rest}
|
|
68
|
+
>
|
|
65
69
|
{children}
|
|
66
70
|
</div>
|
|
67
71
|
);
|
|
@@ -95,6 +99,7 @@ function SidebarBody({ children, className, ...rest }: HTMLAttributes<HTMLDivEle
|
|
|
95
99
|
return (
|
|
96
100
|
<div
|
|
97
101
|
ref={ref}
|
|
102
|
+
data-sidebar-slot="body"
|
|
98
103
|
className={[styles.body, className ?? ""].filter(Boolean).join(" ")}
|
|
99
104
|
{...rest}
|
|
100
105
|
>
|
|
@@ -116,7 +121,11 @@ export interface SidebarBrandProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
116
121
|
function SidebarBrand({ symbol, wordmark, className, ...rest }: SidebarBrandProps) {
|
|
117
122
|
const { collapsed } = useSidebar();
|
|
118
123
|
return (
|
|
119
|
-
<div
|
|
124
|
+
<div
|
|
125
|
+
data-sidebar-slot="brand"
|
|
126
|
+
className={[styles.brand, className ?? ""].filter(Boolean).join(" ")}
|
|
127
|
+
{...rest}
|
|
128
|
+
>
|
|
120
129
|
<div className={styles.brandSymbol}>{symbol}</div>
|
|
121
130
|
{!collapsed && wordmark && (
|
|
122
131
|
<div className={styles.brandWordmark}>{wordmark}</div>
|
|
@@ -132,6 +141,7 @@ function SidebarFooter({ children, className, ...rest }: HTMLAttributes<HTMLDivE
|
|
|
132
141
|
const { bodyOverflow } = useSidebar();
|
|
133
142
|
return (
|
|
134
143
|
<div
|
|
144
|
+
data-sidebar-slot="footer"
|
|
135
145
|
className={[styles.footer, className ?? ""].filter(Boolean).join(" ")}
|
|
136
146
|
data-overflow={bodyOverflow || undefined}
|
|
137
147
|
{...rest}
|
|
@@ -177,6 +187,7 @@ function SidebarProfile({
|
|
|
177
187
|
className={[styles.profile, className ?? ""].filter(Boolean).join(" ")}
|
|
178
188
|
aria-label={collapsed ? name : undefined}
|
|
179
189
|
onClick={onClick}
|
|
190
|
+
data-sidebar-slot="profile"
|
|
180
191
|
/>
|
|
181
192
|
);
|
|
182
193
|
}
|
|
@@ -191,6 +202,7 @@ function SidebarSection({ title, className, children, ...rest }: SidebarSectionP
|
|
|
191
202
|
const { collapsed } = useSidebar();
|
|
192
203
|
return (
|
|
193
204
|
<div
|
|
205
|
+
data-sidebar-slot="section"
|
|
194
206
|
className={[styles.section, className ?? ""].filter(Boolean).join(" ")}
|
|
195
207
|
role="group"
|
|
196
208
|
aria-label={title}
|
|
@@ -210,6 +222,7 @@ function SidebarSection({ title, className, children, ...rest }: SidebarSectionP
|
|
|
210
222
|
function SidebarSeparator({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
211
223
|
return (
|
|
212
224
|
<div
|
|
225
|
+
data-sidebar-slot="separator"
|
|
213
226
|
role="separator"
|
|
214
227
|
className={[styles.separator, className ?? ""].filter(Boolean).join(" ")}
|
|
215
228
|
{...rest}
|
|
@@ -488,7 +488,7 @@ export const shadows: [string, string, string][] = [
|
|
|
488
488
|
["shadow-sm", "var(--shadow-sm)", "Card 기본"],
|
|
489
489
|
["shadow-md", "var(--shadow-md)", "Dropdown, Popover"],
|
|
490
490
|
["shadow-lg", "var(--shadow-lg)", "Modal"],
|
|
491
|
-
["shadow-xl", "var(--shadow-xl)",
|
|
491
|
+
["shadow-xl", "var(--shadow-xl)",],
|
|
492
492
|
["shadow-inner", "var(--shadow-inner)", "눌린 상태"],
|
|
493
493
|
];
|
|
494
494
|
|
|
@@ -554,8 +554,6 @@ export const componentSlugs: Record<string, string> = {
|
|
|
554
554
|
"App Frame": "layout/app-frame",
|
|
555
555
|
Stepper: "nav/stepper",
|
|
556
556
|
"Context Menu": "overlay/context-menu",
|
|
557
|
-
"Command Palette": "overlay/command-palette",
|
|
558
|
-
"Accordion": "overlay/accordion",
|
|
559
557
|
Callout: "feedback/callout",
|
|
560
558
|
Modal: "feedback/modal",
|
|
561
559
|
Drawer: "feedback/drawer",
|
|
@@ -564,7 +562,6 @@ export const componentSlugs: Record<string, string> = {
|
|
|
564
562
|
"Table Row": "data/table-row",
|
|
565
563
|
"Table Row Select": "data/table-row-select",
|
|
566
564
|
"Table Row Edit": "data/table-row-edit",
|
|
567
|
-
"Event Card": "data/event-card",
|
|
568
565
|
"Event Chip": "data/event-chip",
|
|
569
566
|
"List Item": "data/list-item",
|
|
570
567
|
"Metric Card": "data/metric-card",
|
|
@@ -619,7 +616,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
619
616
|
title: "Data Display",
|
|
620
617
|
items: [
|
|
621
618
|
"Table Header", "Table Row", "Table Row Select", "Table Row Edit",
|
|
622
|
-
"List Item", "Metric Card", "Gallery Item", "File Icon", "Attachment Item", "Event Chip",
|
|
619
|
+
"List Item", "Metric Card", "Gallery Item", "File Icon", "Attachment Item", "Event Chip",
|
|
623
620
|
"Empty State", "Description List", "Timeline",
|
|
624
621
|
],
|
|
625
622
|
},
|
|
@@ -643,8 +640,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
643
640
|
id: "overlay",
|
|
644
641
|
title: "Overlay / Menu",
|
|
645
642
|
items: [
|
|
646
|
-
"Context Menu", "
|
|
647
|
-
"Accordion", "Collapsible", "Toggle Group",
|
|
643
|
+
"Context Menu", "Collapsible", "Toggle Group",
|
|
648
644
|
],
|
|
649
645
|
},
|
|
650
646
|
{
|
|
@@ -17,3 +17,108 @@
|
|
|
17
17
|
white-space: nowrap;
|
|
18
18
|
border: 0;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
/* ── emerge — 앵커(트리거)에서 튀어나오는 등장 ──────────────
|
|
22
|
+
overlay(Tooltip/Popover/Dropdown/Menu 등)가 data-side 값에 따라
|
|
23
|
+
"버튼에서 멀어지는 방향으로" 슬라이드 + 스케일 인.
|
|
24
|
+
Radix의 --radix-*-content-transform-origin과 결합해 앵커 엣지에서 자라남.
|
|
25
|
+
컴포넌트가 아래 클래스를 붙이거나, [data-state="open"][data-side] 셀렉터로 직접 매칭. */
|
|
26
|
+
@keyframes m-emerge-from-top {
|
|
27
|
+
from { opacity: 0; transform: translateY(calc(-4px * var(--motion-distance-scale))) scale(0.96); }
|
|
28
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
29
|
+
}
|
|
30
|
+
@keyframes m-emerge-from-bottom {
|
|
31
|
+
from { opacity: 0; transform: translateY(calc(4px * var(--motion-distance-scale))) scale(0.96); }
|
|
32
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
33
|
+
}
|
|
34
|
+
@keyframes m-emerge-from-left {
|
|
35
|
+
from { opacity: 0; transform: translateX(calc(-4px * var(--motion-distance-scale))) scale(0.96); }
|
|
36
|
+
to { opacity: 1; transform: translateX(0) scale(1); }
|
|
37
|
+
}
|
|
38
|
+
@keyframes m-emerge-from-right {
|
|
39
|
+
from { opacity: 0; transform: translateX(calc(4px * var(--motion-distance-scale))) scale(0.96); }
|
|
40
|
+
to { opacity: 1; transform: translateX(0) scale(1); }
|
|
41
|
+
}
|
|
42
|
+
.m-emerge-from-top { animation: m-emerge-from-top var(--duration-fast) var(--ease-out); }
|
|
43
|
+
.m-emerge-from-bottom { animation: m-emerge-from-bottom var(--duration-fast) var(--ease-out); }
|
|
44
|
+
.m-emerge-from-left { animation: m-emerge-from-left var(--duration-fast) var(--ease-out); }
|
|
45
|
+
.m-emerge-from-right { animation: m-emerge-from-right var(--duration-fast) var(--ease-out); }
|
|
46
|
+
|
|
47
|
+
/* ── recede — emerge의 대칭 프리미티브. 앵커 쪽으로 살짝 물러나며 사라짐 ──
|
|
48
|
+
overlay 닫힘용. 이동량은 emerge의 절반(2px) — 닫힘은 짧고 담백하게. */
|
|
49
|
+
@keyframes m-recede-to-top {
|
|
50
|
+
from { opacity: 1; transform: translateY(0) scale(1); }
|
|
51
|
+
to { opacity: 0; transform: translateY(calc(-2px * var(--motion-distance-scale))) scale(0.98); }
|
|
52
|
+
}
|
|
53
|
+
@keyframes m-recede-to-bottom {
|
|
54
|
+
from { opacity: 1; transform: translateY(0) scale(1); }
|
|
55
|
+
to { opacity: 0; transform: translateY(calc(2px * var(--motion-distance-scale))) scale(0.98); }
|
|
56
|
+
}
|
|
57
|
+
@keyframes m-recede-to-left {
|
|
58
|
+
from { opacity: 1; transform: translateX(0) scale(1); }
|
|
59
|
+
to { opacity: 0; transform: translateX(calc(-2px * var(--motion-distance-scale))) scale(0.98); }
|
|
60
|
+
}
|
|
61
|
+
@keyframes m-recede-to-right {
|
|
62
|
+
from { opacity: 1; transform: translateX(0) scale(1); }
|
|
63
|
+
to { opacity: 0; transform: translateX(calc(2px * var(--motion-distance-scale))) scale(0.98); }
|
|
64
|
+
}
|
|
65
|
+
.m-recede-to-top { animation: m-recede-to-top var(--duration-fast) var(--ease-in); }
|
|
66
|
+
.m-recede-to-bottom { animation: m-recede-to-bottom var(--duration-fast) var(--ease-in); }
|
|
67
|
+
.m-recede-to-left { animation: m-recede-to-left var(--duration-fast) var(--ease-in); }
|
|
68
|
+
.m-recede-to-right { animation: m-recede-to-right var(--duration-fast) var(--ease-in); }
|
|
69
|
+
|
|
70
|
+
/* ── m-float — 플로팅 서페이스(오버레이·팝오버·툴팁·메뉴·셀렉트·데이트픽커…) 공통 in/out ──
|
|
71
|
+
원칙: "떠오르는 표면"은 항상 이 유틸 하나로 등장·퇴장한다. 컴포넌트마다 다른 애니메이션 규칙을
|
|
72
|
+
만들지 말고, 반드시 Radix Content(또는 그 상응 요소)의 className에 "m-float"을 추가한다.
|
|
73
|
+
|
|
74
|
+
Radix는 오버레이 Content에 자동으로 다음 attribute를 부여한다:
|
|
75
|
+
data-state = "open" | "closed" | "delayed-open" (Tooltip은 hover 지연으로 delayed-open 사용)
|
|
76
|
+
data-side = "top" | "bottom" | "left" | "right" (position=popper일 때)
|
|
77
|
+
→ 이 유틸은 두 attribute 조합만으로 매칭되므로 컴포넌트 CSS Module 안에서 반복 정의할 필요 없음.
|
|
78
|
+
|
|
79
|
+
왜 전역 클래스인가:
|
|
80
|
+
Turbopack의 CSS Modules는 `animation: <name>` shorthand의 keyframe identifier를 로컬 스코프로
|
|
81
|
+
rewrite한다(예: m-emerge-from-top → xItvXq__m-emerge-from-top). 우회하려면 컴포넌트마다
|
|
82
|
+
`--anim` 변수로 이름을 넘기는 보일러플레이트가 필요했다. 규칙 자체를 _utils.css로 올리면
|
|
83
|
+
스코핑을 아예 회피할 수 있어 각 컴포넌트는 클래스 문자열 하나만 붙이면 된다.
|
|
84
|
+
|
|
85
|
+
왜 globals.css가 아니라 _utils.css인가:
|
|
86
|
+
globals.css는 로컬 Next 앱 전용이고 ctds npm 패키지엔 번들되지 않는다. 반면 _utils.css는
|
|
87
|
+
tokens.css가 import하고 tokens.css는 소비자 globals.css에 자동 패치로 끼워지므로,
|
|
88
|
+
`ctds init`로 설치한 소비자 앱에서도 .m-float이 그대로 로드된다.
|
|
89
|
+
|
|
90
|
+
방향 규칙:
|
|
91
|
+
- open → 앵커 반대편에서 자라남 (data-side=bottom → 트리거 아래 → 위쪽에서 내려옴)
|
|
92
|
+
- close → 앵커 쪽으로 되감김 (data-side=bottom → 위쪽으로 물러남)
|
|
93
|
+
시간·곡선(모두 Motion 축 토큰 경유):
|
|
94
|
+
- open : --duration-base + --ease-emphasized (정착감 있는 등장)
|
|
95
|
+
- close : --duration-fast + --ease-in (담백한 이탈, 이동량 절반) */
|
|
96
|
+
.m-float {
|
|
97
|
+
will-change: transform, opacity;
|
|
98
|
+
}
|
|
99
|
+
.m-float[data-state="open"][data-side="top"],
|
|
100
|
+
.m-float[data-state="delayed-open"][data-side="top"] { animation: m-emerge-from-bottom var(--duration-base) var(--ease-emphasized); }
|
|
101
|
+
.m-float[data-state="open"][data-side="bottom"],
|
|
102
|
+
.m-float[data-state="delayed-open"][data-side="bottom"] { animation: m-emerge-from-top var(--duration-base) var(--ease-emphasized); }
|
|
103
|
+
.m-float[data-state="open"][data-side="left"],
|
|
104
|
+
.m-float[data-state="delayed-open"][data-side="left"] { animation: m-emerge-from-right var(--duration-base) var(--ease-emphasized); }
|
|
105
|
+
.m-float[data-state="open"][data-side="right"],
|
|
106
|
+
.m-float[data-state="delayed-open"][data-side="right"] { animation: m-emerge-from-left var(--duration-base) var(--ease-emphasized); }
|
|
107
|
+
|
|
108
|
+
.m-float[data-state="closed"][data-side="top"] { animation: m-recede-to-bottom var(--duration-fast) var(--ease-in); }
|
|
109
|
+
.m-float[data-state="closed"][data-side="bottom"] { animation: m-recede-to-top var(--duration-fast) var(--ease-in); }
|
|
110
|
+
.m-float[data-state="closed"][data-side="left"] { animation: m-recede-to-right var(--duration-fast) var(--ease-in); }
|
|
111
|
+
.m-float[data-state="closed"][data-side="right"] { animation: m-recede-to-left var(--duration-fast) var(--ease-in); }
|
|
112
|
+
|
|
113
|
+
/* data-side가 없는 경우(Tooltip이 아직 위치 계산 전, 또는 popper가 아닌 경우) 폴백: 페이드+살짝 스케일 */
|
|
114
|
+
.m-float[data-state="open"]:not([data-side]),
|
|
115
|
+
.m-float[data-state="delayed-open"]:not([data-side]) {
|
|
116
|
+
animation: m-emerge-from-top var(--duration-base) var(--ease-emphasized);
|
|
117
|
+
}
|
|
118
|
+
.m-float[data-state="closed"]:not([data-side]) {
|
|
119
|
+
animation: m-recede-to-top var(--duration-fast) var(--ease-in);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (prefers-reduced-motion: reduce) {
|
|
123
|
+
.m-float { animation: none !important; }
|
|
124
|
+
}
|