@conscience-technology/ctds 0.4.4 → 0.4.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/assets/components/ChatInput/ChatInput.module.css +54 -14
- package/assets/components/ChatInput/ChatInput.tsx +12 -1
- package/assets/components/ChatInput/SendButton.tsx +18 -1
- package/assets/components/ChatInput/index.ts +2 -0
- package/assets/components/Icon/Icon.tsx +1 -1
- package/assets/components/TabStrip/TabStrip.module.css +358 -0
- package/assets/components/TabStrip/TabStrip.tsx +396 -0
- package/assets/components/TabStrip/index.ts +7 -0
- package/assets/components/Toolbar/Toolbar.module.css +34 -7
- package/assets/components/Toolbar/Toolbar.tsx +9 -3
- package/assets/components/Toolbar/index.ts +1 -1
- package/assets/components/_data.ts +2 -1
- package/assets/components/index.ts +1 -0
- package/assets/prompts/ASSEMBLY.md +1 -0
- package/assets/prompts/components/chat-input.md +12 -2
- package/assets/prompts/components/tab-strip.md +157 -0
- package/assets/prompts/components/toolbar.md +5 -2
- package/assets/skills/ctds-page/SKILL.md +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# TabStrip
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
브라우저·에디터·디자인툴 상단에 늘어선 **"열려있는 문서/파일 탭"** 전용 컴포넌트. 각 탭은 **독립 문서·독립 라우트**를 가리킨다 — Chrome의 창 탭, Figma의 파일 탭, VSCode의 에디터 탭이 전부 같은 자리. 이름은 Chromium/VSCode/Telerik이 공통으로 쓰는 업계 표준어 `TabStrip`.
|
|
5
|
+
|
|
6
|
+
[[components/tabs]]와 역할이 **완전히 다르다**. Tabs는 "같은 화면 안 섹션 전환"(URL `?tab=activity`), TabStrip은 "열린 N개 중 하나 선택"(각 탭 = 라우트). 섞어 쓰지 말 것 — 한 화면에 TabStrip이 있고 그 아래 Tabs가 있는 건 OK (브라우저 탭 밑에 설정 섹션 탭 있는 꼴).
|
|
7
|
+
|
|
8
|
+
활성 표시는 **pill·underline이 아니라 bg-muted 배경 상시 유지** — hover 색(bg-muted)이 활성 탭에서 lock된 꼴. "hover되는 자리가 현재 자리"라는 직관.
|
|
9
|
+
|
|
10
|
+
### 발동 트리거 — 다중 탭 UI를 원할 때
|
|
11
|
+
사용자가 **"브라우저처럼 탭 열기"**, **"노션처럼 다중 탭"**, **"Figma/VSCode처럼 파일 탭"**, **"Chrome 탭 UI"**, **"탭으로 여러 문서/페이지 열기"** 등을 요구하면 **TabStrip이 정답**. 수제 조립·`<Tabs>` 전용화 하지 말 것. 신호 체크:
|
|
12
|
+
- 사용자가 **"열어두기"**·**"탭 추가"**·**"X 버튼으로 닫기"** 언급 → TabStrip (각 탭 = 열린 문서)
|
|
13
|
+
- 사용자가 **"섹션 전환"**·**"뷰 바꾸기"**·**"필터 토글"** 언급 → [[components/tabs]] (한 화면 안 뷰 전환)
|
|
14
|
+
- 둘 다 필요하면 **TabStrip(상단, 문서 탭) → PageHeader → Tabs(그 안 섹션)** 순으로 중첩. 한 화면에 TabStrip도 되고 Tabs도 되는 자리면 **TabStrip이 바깥**.
|
|
15
|
+
|
|
16
|
+
소비처 세팅은 보통 **라우팅 상태 + 열린 문서 배열**:
|
|
17
|
+
```tsx
|
|
18
|
+
const [docs, setDocs] = useState<Doc[]>(openDocs);
|
|
19
|
+
const [activeId, setActiveId] = useState<string>(openDocs[0].id);
|
|
20
|
+
// closeDoc, addDoc 핸들러는 소비처가 관리 (TabStrip은 상태 안 가짐).
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Do
|
|
24
|
+
- **각 탭 = 독립 문서/라우트**. 라우팅이나 외부 상태 저장소에 반영 (`?doc=portfolio` 또는 workspace store).
|
|
25
|
+
- **leadingIcon에 자유롭게** — [[components/icon]], `<img>`(파일 썸네일), `<svg>`(브랜드 로고) 전부 ReactNode로 받는다. Figma 파일 탭의 작은 썸네일, VSCode 파일 아이콘 패턴.
|
|
26
|
+
- **닫기는 `closable` prop으로**. x 버튼은 `absolute`로 오른쪽에 띄워 **왼쪽에서 bg-muted 그라데이션으로 라벨 위를 덮으며 등장** — Chrome 긴 탭 패턴. **hover 시에만** fade-in (활성 탭도 hover 아니면 숨김) — 평상시 시각 노이즈 최소화.
|
|
27
|
+
- **긴 라벨은 hover 시 marquee loop** — 라벨이 overflow되면 짧은 지연 후 왼쪽으로 **전광판처럼 끊김 없이** 흐른다 (텍스트 복제본이 뒤를 이어받음). ResizeObserver로 overflow 감지, 짧은 라벨은 자동으로 안 움직임. `prefers-reduced-motion`에선 비활성.
|
|
28
|
+
- **trailing slot은 자유 조립**. `trailing` prop에 ReactNode를 넣으면 탭 묶음 오른쪽에 자리잡음 — `<IconButton icon={<Icon name="plus" />} />` (새 탭), `<IconButton icon={<Icon name="dots-three" />} />` (더보기), 또는 아무것도 안 넣기. TabStrip hover·focus-within 시에만 fade-in.
|
|
29
|
+
- **자리 중립** — Topbar 바로 아래 2번째 줄에 flush로 두거나, Topbar의 center slot에 넣는다. 소비처가 자리를 결정.
|
|
30
|
+
- **키보드**: ← → Home End 지원. Enter/Space로 선택. 활성 탭만 `tabIndex=0` (roving).
|
|
31
|
+
- **라벨은 1줄 cap** — `max-width: 20ch`(md), `16ch`(sm)에서 ellipsis. 문서명이 길면 Tooltip으로 보완.
|
|
32
|
+
|
|
33
|
+
### Don't
|
|
34
|
+
- **섹션 전환**에 TabStrip 쓰지 말 것 — 그건 [[components/tabs]] underline/segmented/text. 역할 뒤집으면 "탭 안에 탭" 혼동.
|
|
35
|
+
- **활성 색을 `--primary`로 덮지 않는다** — 활성은 `--fg-strong` + `--bg-muted` 무채. 브랜드 컬러는 CTA와 위계가 충돌 ([[components/tabs]]와 동일 원칙).
|
|
36
|
+
- **라벨 가로 폭을 숫자(px)로 고정 금지** — 폰트 축이 바뀌면 깨짐. `ch` 단위 사용.
|
|
37
|
+
- **x 아이콘을 상시 노출하지 말 것** — 시각 노이즈. hover·active에만, 활성 탭은 늘 보이는 게 맞다.
|
|
38
|
+
- **`<svg>` 인라인 금지** — leadingIcon은 Icon·이미지·브랜드 svg는 OK지만 "아이콘용 raw svg"는 [[feedback_no_inline_svg]].
|
|
39
|
+
- **trailing에 생짜 `<button>` 넣지 말 것** — Button·IconButton·DropdownMenu 트리거 등 CTDS 컴포넌트 경유. 자리별 간격·디바이더는 wrapper가 담당하니 소비처는 버튼만 넘김.
|
|
40
|
+
- **탭 사이에 가운뎃점·대시 구분자 금지** — [[feedback_no_bullet_separator]]. 탭 자체가 구분된 블록.
|
|
41
|
+
|
|
42
|
+
### Fallback
|
|
43
|
+
자리 애매하면 **Topbar 아래 2번째 줄**(shell 2단), size 애매하면 **md** (shell chrome에서도 sm보다 md가 안정적 — Topbar와 같은 리듬).
|
|
44
|
+
|
|
45
|
+
### 관련
|
|
46
|
+
- [[components/tabs]] — 역할 분리. Tabs=섹션 전환, TabStrip=문서 전환
|
|
47
|
+
- [[components/topbar]] — TabStrip이 가장 자주 조립되는 shell. 아래 줄 또는 center slot
|
|
48
|
+
- [[components/icon]] · [[components/file-icon]] — leadingIcon 자리 (파일 타입 썸네일로 FileIcon 자주 조합)
|
|
49
|
+
- [[components/tooltip]] — 라벨이 ellipsis될 때 전체 문서명 노출
|
|
50
|
+
- [[axes/density]] — `--control-h-*`가 탭 높이에 매핑
|
|
51
|
+
- [[axes/shape]] — 탭 radius가 Shape 축에 반응
|
|
52
|
+
- [[axes/motion]] — hover·active 전환 duration
|
|
53
|
+
- [[tokens/color]] — `--bg-muted`(활성·hover 배경), `--fg-muted` → `--fg-strong` 대비
|
|
54
|
+
|
|
55
|
+
### Props 시그니처
|
|
56
|
+
```tsx
|
|
57
|
+
<TabStrip
|
|
58
|
+
variant="ghost | soft | segmented" // default: ghost
|
|
59
|
+
size="sm | md" // default: md
|
|
60
|
+
value={string} // controlled
|
|
61
|
+
defaultValue={string} // uncontrolled 초기값
|
|
62
|
+
onValueChange={(v) => void}
|
|
63
|
+
trailing={<ReactNode>} // 오른쪽 slot. IconButton·DropdownMenu 등 자유 조립
|
|
64
|
+
>
|
|
65
|
+
<TabStrip.Tab
|
|
66
|
+
value="revana"
|
|
67
|
+
leadingIcon={<Icon name="file-text" />}
|
|
68
|
+
closable
|
|
69
|
+
onClose={() => removeTab("revana")}
|
|
70
|
+
closeLabel="탭 닫기"
|
|
71
|
+
>
|
|
72
|
+
Revana (copy 2)
|
|
73
|
+
</TabStrip.Tab>
|
|
74
|
+
<TabStrip.Tab
|
|
75
|
+
value="portfolio"
|
|
76
|
+
leadingIcon={<img src="/figma.svg" alt="" />}
|
|
77
|
+
closable
|
|
78
|
+
onClose={() => removeTab("portfolio")}
|
|
79
|
+
>
|
|
80
|
+
Portfolio
|
|
81
|
+
</TabStrip.Tab>
|
|
82
|
+
</TabStrip>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 상태 매트릭스
|
|
86
|
+
| 상태 | 배경 | 라벨 색 | leading opacity | x + fade | 라벨 marquee |
|
|
87
|
+
|---|---|---|---|---|---|
|
|
88
|
+
| default (inactive) | transparent | `--fg-muted` | 0.75 | opacity 0 | 정지 |
|
|
89
|
+
| hover (inactive) | `--bg-muted` | `--fg` | 1 | fade-in | 지연 후 loop (오버플로우 시) |
|
|
90
|
+
| active (no hover) | `--bg-muted` 상시 | `--fg-strong` | 1 | opacity 0 | 정지 |
|
|
91
|
+
| active + hover | `--bg-muted` | `--fg-strong` | 1 | fade-in | 지연 후 loop (오버플로우 시) |
|
|
92
|
+
| disabled | transparent | opacity 0.5 | 0.5 | opacity 0 | 정지 |
|
|
93
|
+
| focus-visible | — | — | — | `--ring-shadow` | — |
|
|
94
|
+
|
|
95
|
+
### Variant 가이드
|
|
96
|
+
| variant | 결 | 언제 |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `ghost` (default) | 투명 + hover/active bg | shell chrome 기본. 가장 가벼움. 주변 UI와 조용히 어울림. |
|
|
99
|
+
| `soft` | 모든 탭에 연한 bg 상시, 활성이 조금 더 진함 | 탭이 **독립 블록**으로 보여야 할 때. 선택 자리가 또렷. Button 그룹과 리듬 맞음. |
|
|
100
|
+
| `segmented` | 탭 사이 세로 hairline으로 칸 구분 | 브라우저·에디터의 **문서 탭** 표준. 탭 경계가 또렷하고 전통적. |
|
|
101
|
+
|
|
102
|
+
### Size 가이드
|
|
103
|
+
| size | 언제 |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `sm` | 밀도 승부처 (사이드바 내부 docked, 좁은 패널) |
|
|
106
|
+
| `md` | 기본. Topbar 아래 shell 2번째 줄 |
|
|
107
|
+
|
|
108
|
+
### 조합 예시
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// Topbar 아래 줄 (Figma·Chrome 패턴)
|
|
112
|
+
<Topbar>
|
|
113
|
+
<Topbar.Leading><IdentityButton …/></Topbar.Leading>
|
|
114
|
+
<Topbar.Trailing>…</Topbar.Trailing>
|
|
115
|
+
</Topbar>
|
|
116
|
+
<div className="shell-row-2">
|
|
117
|
+
<TabStrip
|
|
118
|
+
value={active}
|
|
119
|
+
onValueChange={setActive}
|
|
120
|
+
trailing={
|
|
121
|
+
<IconButton
|
|
122
|
+
variant="ghost"
|
|
123
|
+
icon={<Icon name="plus" />}
|
|
124
|
+
aria-label="새 문서"
|
|
125
|
+
onClick={handleNewDoc}
|
|
126
|
+
/>
|
|
127
|
+
}
|
|
128
|
+
>
|
|
129
|
+
{docs.map((d) => (
|
|
130
|
+
<TabStrip.Tab
|
|
131
|
+
key={d.id}
|
|
132
|
+
value={d.id}
|
|
133
|
+
leadingIcon={<FileIcon ext={d.ext} />}
|
|
134
|
+
closable
|
|
135
|
+
onClose={() => closeDoc(d.id)}
|
|
136
|
+
>
|
|
137
|
+
{d.title}
|
|
138
|
+
</TabStrip.Tab>
|
|
139
|
+
))}
|
|
140
|
+
</TabStrip>
|
|
141
|
+
</div>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// Topbar center slot (탭 2~4개로 적을 때)
|
|
146
|
+
<Topbar>
|
|
147
|
+
<Topbar.Leading><IdentityButton …/></Topbar.Leading>
|
|
148
|
+
<Topbar.Center>
|
|
149
|
+
<TabStrip value={view} onValueChange={setView} size="sm">
|
|
150
|
+
<TabStrip.Tab value="design">Design</TabStrip.Tab>
|
|
151
|
+
<TabStrip.Tab value="code">Code</TabStrip.Tab>
|
|
152
|
+
<TabStrip.Tab value="specs">Specs</TabStrip.Tab>
|
|
153
|
+
</TabStrip>
|
|
154
|
+
</Topbar.Center>
|
|
155
|
+
<Topbar.Trailing>…</Topbar.Trailing>
|
|
156
|
+
</Topbar>
|
|
157
|
+
```
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# Toolbar
|
|
2
2
|
|
|
3
3
|
### 원칙
|
|
4
|
-
IconButton 중심의 action 줄. 에디터·캔버스·갤러리 상단, 또는 "뜨 있는" 알약으로 콘텐츠 위에 떠서 액션을 모으는 자리.
|
|
4
|
+
IconButton 중심의 action 줄. 에디터·캔버스·갤러리 상단, 또는 "뜨 있는" 알약으로 콘텐츠 위에 떠서 액션을 모으는 자리. toolbar 안의 버튼은 전부 같은 사이즈 — Toolbar의 `size` prop이 안쪽 IconButton 히트박스·아이콘을 한 번에 결정.
|
|
5
5
|
|
|
6
6
|
### Do
|
|
7
7
|
- `variant="floating"` 기본 — bg-elevated + shadow-md + radius-xl, 콘텐츠 위에 떠있는 알약. 캔버스·에디터·이미지 뷰어의 상단 중앙.
|
|
8
8
|
- `variant="attached"`는 콘텐츠에 붙은 bar — transparent + 하단 hairline. 페이지 상단에 "탭 아래 액션 바"로 쓸 때.
|
|
9
9
|
- 관련 액션은 `<Toolbar.Group>`으로 묶는다 — 내부는 좁은 gap, 그룹 간은 `<Toolbar.Divider>`.
|
|
10
10
|
- 좌·우 정렬이 필요하면 `<Toolbar.Spacer>`로 공간 밀기 — flex:1.
|
|
11
|
-
-
|
|
11
|
+
- `size="md"`(기본) — 히트박스 32·아이콘 20. 캔버스·에디터 상단 표준 자리.
|
|
12
|
+
- `size="sm"` — 히트박스 24·아이콘 16. 사이드바 바닥·인라인 bar처럼 밀도 승부처.
|
|
13
|
+
- 안쪽 IconButton은 size prop을 넘기지 않는다 — Toolbar가 CSS 변수로 자동 매칭.
|
|
12
14
|
|
|
13
15
|
### Don't
|
|
14
16
|
- toolbar 안에 label 긴 Button을 섞지 않는다 — IconButton + Tooltip이 toolbar 패턴. 긴 label이 필요하면 그 자리는 toolbar가 아니라 액션 영역.
|
|
@@ -20,6 +22,7 @@ IconButton 중심의 action 줄. 에디터·캔버스·갤러리 상단, 또는
|
|
|
20
22
|
```tsx
|
|
21
23
|
<Toolbar
|
|
22
24
|
variant?="floating | attached" // default: floating
|
|
25
|
+
size?="sm | md" // default: md (히트박스 32·아이콘 20)
|
|
23
26
|
stretch?={boolean} // default: variant로 자동 (attached=true, floating=false)
|
|
24
27
|
role?={string} // default: "toolbar"
|
|
25
28
|
>
|
|
@@ -33,6 +33,8 @@ description: CTDS 페이지·대시보드·레이아웃을 조합할 때 발동.
|
|
|
33
33
|
| 메타 라벨 가로 나열 | `prompts/recipes/meta-line.md` |
|
|
34
34
|
| 폼·모달 하단 액션 | `prompts/recipes/form-footer.md` |
|
|
35
35
|
| 페이지 폭 안내 바 | `prompts/recipes/page-banner.md` |
|
|
36
|
+
| 다중 탭 UI (브라우저·노션·Figma·VSCode식, 각 탭 = 열린 문서/라우트, 추가·닫기) | `prompts/components/tab-strip.md` — Tabs 아님 |
|
|
37
|
+
| 섹션/뷰 전환 탭 (한 화면 안에서 뷰만 토글, URL `?tab=…`) | `prompts/components/tabs.md` — TabStrip 아님 |
|
|
36
38
|
| 리스트 | `prompts/components/list-item.md` |
|
|
37
39
|
| 테이블 | `prompts/components/{table-row,table-row-edit,table-row-select,table-header}.md` |
|
|
38
40
|
| 폼 | `prompts/components/{form-field,input,select,checkbox,radio,switch}.md` |
|