@conscience-technology/ctds 0.1.8 → 0.1.10
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/AIMessage/AIMessage.module.css +117 -0
- package/assets/components/AIMessage/AIMessage.tsx +90 -0
- package/assets/components/AIMessage/index.ts +2 -0
- package/assets/components/AttachmentChip/AttachmentChip.module.css +3 -1
- package/assets/components/AttachmentThumb/AttachmentThumb.module.css +13 -7
- package/assets/components/AttachmentThumb/AttachmentThumb.tsx +4 -1
- package/assets/components/Bubble/Bubble.module.css +65 -0
- package/assets/components/Bubble/Bubble.tsx +63 -0
- package/assets/components/Bubble/index.ts +2 -0
- package/assets/components/Button/Button.module.css +15 -2
- package/assets/components/Button/Button.tsx +13 -0
- package/assets/components/Calendar/Calendar.module.css +163 -0
- package/assets/components/Calendar/Calendar.tsx +323 -0
- package/assets/components/Calendar/index.ts +2 -0
- package/assets/components/ChatInput/ChatInput.module.css +81 -41
- package/assets/components/ChatInput/ChatInput.tsx +45 -32
- package/assets/components/ChatInput/ModelSelector.tsx +23 -27
- package/assets/components/ChatInput/index.ts +0 -1
- package/assets/components/CodeBlock/CodeBlock.module.css +79 -0
- package/assets/components/CodeBlock/CodeBlock.tsx +71 -0
- package/assets/components/CodeBlock/index.ts +2 -0
- package/assets/components/ContextMenu/ContextMenu.tsx +10 -1
- package/assets/components/DropdownMenu/DropdownMenu.module.css +26 -0
- package/assets/components/DropdownMenu/DropdownMenu.tsx +367 -0
- package/assets/components/DropdownMenu/index.ts +9 -0
- package/assets/components/EventChip/EventChip.module.css +57 -0
- package/assets/components/EventChip/EventChip.tsx +41 -0
- package/assets/components/EventChip/index.ts +2 -0
- package/assets/components/Icon/Icon.tsx +3 -0
- package/assets/components/IdentityButton/IdentityButton.module.css +119 -0
- package/assets/components/IdentityButton/IdentityButton.tsx +113 -0
- package/assets/components/IdentityButton/index.ts +6 -0
- package/assets/components/MenuItem/MenuItem.module.css +67 -2
- package/assets/components/MenuItem/MenuItem.tsx +22 -2
- package/assets/components/MessageActions/MessageActions.module.css +43 -0
- package/assets/components/MessageActions/MessageActions.tsx +94 -0
- package/assets/components/MessageActions/index.ts +6 -0
- package/assets/components/MessageQuote/MessageQuote.module.css +18 -0
- package/assets/components/MessageQuote/MessageQuote.tsx +33 -0
- package/assets/components/MessageQuote/index.ts +2 -0
- package/assets/components/MessageSources/MessageSources.module.css +63 -0
- package/assets/components/MessageSources/MessageSources.tsx +50 -0
- package/assets/components/MessageSources/index.ts +2 -0
- package/assets/components/MessageTable/MessageTable.module.css +46 -0
- package/assets/components/MessageTable/MessageTable.tsx +21 -0
- package/assets/components/MessageTable/index.ts +2 -0
- package/assets/components/QuotationBar/QuotationBar.module.css +2 -2
- package/assets/components/Sidebar/Sidebar.module.css +3 -57
- package/assets/components/Sidebar/Sidebar.tsx +18 -21
- package/assets/components/Spinner/LottiePlayer.tsx +75 -0
- package/assets/components/Spinner/Spinner.module.css +167 -0
- package/assets/components/Spinner/Spinner.tsx +119 -0
- package/assets/components/Spinner/index.ts +10 -0
- package/assets/components/Spinner/lottieColor.ts +76 -0
- package/assets/components/Spinner/lottieManifest.ts +99 -0
- package/assets/components/Tabs/Tabs.module.css +3 -3
- package/assets/components/UserMessage/UserMessage.module.css +41 -0
- package/assets/components/UserMessage/UserMessage.tsx +66 -0
- package/assets/components/UserMessage/index.ts +2 -0
- package/assets/components/_data.ts +18 -4
- package/assets/components/tokens.css +20 -0
- package/assets/prompts/components/ai-message.md +43 -0
- package/assets/prompts/components/bubble.md +56 -0
- package/assets/prompts/components/calendar.md +123 -0
- package/assets/prompts/components/code-block.md +35 -0
- package/assets/prompts/components/dropdown-menu.md +145 -0
- package/assets/prompts/components/identity-button.md +168 -0
- package/assets/prompts/components/menu-item.md +2 -1
- package/assets/prompts/components/message-actions.md +49 -0
- package/assets/prompts/components/message-quote.md +26 -0
- package/assets/prompts/components/message-sources.md +26 -0
- package/assets/prompts/components/message-table.md +27 -0
- package/assets/prompts/components/sidebar.md +106 -37
- package/assets/prompts/components/spinner.md +109 -0
- package/assets/prompts/components/user-message.md +45 -0
- package/assets/prompts/primitives/scroll.md +22 -11
- package/assets/skill/SKILL.md +1 -0
- package/package.json +1 -1
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
# Sidebar
|
|
2
2
|
|
|
3
3
|
### 원칙
|
|
4
|
-
좌측 세로 nav. **라우팅 전용** — Tabs/필터는 쓸 곳이 아님.
|
|
4
|
+
좌측 세로 nav. **라우팅 전용** — Tabs/필터는 쓸 곳이 아님. Header(고정) + Body(스크롤) + Footer(고정) 3영역 세로 쉘에, Brand + Section + Item + Separator + Profile을 compound로 조합. `collapsed`로 icon-only 축소. Item은 [[components/menu-item]]을 그대로 어댑트 — 시각·상태 규칙 공유.
|
|
5
|
+
|
|
6
|
+
### 3영역 구조 (중요)
|
|
7
|
+
Sidebar는 **Header / Body / Footer 세 영역으로 강제 분리**된다. 역할 혼동이 레이아웃 사고의 1순위 원인.
|
|
8
|
+
|
|
9
|
+
| 영역 | 스크롤 | 레이아웃 | 담는 것 |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `Sidebar.Header` | ❌ 고정 | **가로 flex 행** (brand symbol + wordmark 나란히) | `Sidebar.Brand` 전용 |
|
|
12
|
+
| `Sidebar.Body` | ✅ 세로 스크롤 | 세로 flex 열 | `Sidebar.Section` · `Sidebar.Separator` 전부 여기 |
|
|
13
|
+
| `Sidebar.Footer` | ❌ 고정 | 가로 flex 행 | `Sidebar.Profile` 또는 보조 액션 |
|
|
14
|
+
|
|
15
|
+
Header는 **가로 행**이기 때문에 Section을 넣으면 nav 전체가 가로 1줄로 눌려 겹친다. Section·Item·Separator는 반드시 `Sidebar.Body` 안에.
|
|
5
16
|
|
|
6
17
|
### Do
|
|
7
|
-
-
|
|
18
|
+
- **Header/Footer의 워크스페이스·프로필·조직 트리거는 반드시 [[components/identity-button]]** — Select·Input Dropdown·수제 Button+span 조합 전부 금지. Avatar + 2줄 라벨 + trailing 뼈대가 고정이라 전용 컴포넌트가 담당. 사이드바에서 "현재 X 선택 상태 + 전환 가능" 모양이 보이면 100% IdentityButton.
|
|
19
|
+
- Section·Separator는 반드시 **`Sidebar.Body` 안에** 둔다 — Body만 스크롤되는 영역.
|
|
20
|
+
- 라우트 이동은 Item에 `asChild` + Next `<Link>`. 단, `asChild`일 땐 라벨 truncation이 `<Link>` 쪽 책임이 된다 ([[components/menu-item]] Slot 동작) — 긴 라벨이면 Link에 `truncate` 유틸 또는 `title` 속성을 걸 것.
|
|
8
21
|
- 현재 라우트는 `active`로 명시 (MenuItem의 active 시각 그대로).
|
|
9
22
|
- Brand는 Home 링크 자리로. `symbol`(항상 노출) + `wordmark`(expanded만).
|
|
10
23
|
- Section 묶음은 `title`로 그룹. collapsed일 땐 title 자동 숨김.
|
|
11
24
|
- `collapsed` 상태에서는 `aria-label`과 `title`을 자동으로 걸어 접근성 유지.
|
|
25
|
+
- 콘텐츠 영역과의 **경계선은 [[components/app-frame]] `variant="outlined"`** 가 그린다 — Sidebar 자체는 border를 그리지 않음. 사이드바가 녹아 보이면 AppFrame variant부터 확인.
|
|
12
26
|
|
|
13
27
|
### Don't
|
|
28
|
+
- **워크스페이스/프로필 트리거에 Select·Input Dropdown 금지** — 폼 값 선택과 네비게이션 액션은 다른 역할. [[components/identity-button]] + DropdownMenu 조합이 유일한 표준 ([[antipattern_workspace_switcher]]).
|
|
29
|
+
- **수제 Button + span 조립 금지** — 매번 레이아웃·폰트·collapsed 전환이 어긋나서 일관성이 깨짐. IdentityButton 하나로 끝.
|
|
30
|
+
- **Header에 Section·Item·nav를 넣지 않는다** — Header는 가로 flex 행이라 세로 nav가 깔리면 전부 눌려 겹친다. Brand 전용.
|
|
14
31
|
- 페이지 안 뷰 전환에 쓰지 않는다 — 그건 [[components/tabs]].
|
|
15
32
|
- Item 안에 variant로 다른 톤을 쓰지 않는다. active 외의 강조 금지.
|
|
16
33
|
- Sidebar 자체에 padding/width를 CSS로 덮지 않는다. `--sidebar-w*` 토큰 경유.
|
|
34
|
+
- `.root`에 padding을 꽂지 않는다 — Body가 스크롤 컨테이너라 스크롤바가 안쪽으로 밀려 가장자리에 안 붙는다. 좌우 여백은 Header/Section/Footer가 각자 담당([[project_scroll_rule]]).
|
|
17
35
|
- Brand symbol·wordmark 크기 override 금지. `--sidebar-brand-symbol-max`·`--sidebar-brand-wordmark-max` 상한 안에서만.
|
|
18
36
|
|
|
19
37
|
### Fallback
|
|
20
|
-
collapsed 결정 애매하면 `false`. Section title 애매하면 생략.
|
|
38
|
+
collapsed 결정 애매하면 `false`. Section title 애매하면 생략. 경계선 애매하면 AppFrame `variant="outlined"`.
|
|
21
39
|
|
|
22
40
|
### 관련
|
|
41
|
+
- [[components/identity-button]] — Header/Footer 워크스페이스·프로필·조직 트리거. Sidebar.Profile은 이 컴포넌트의 preset 래퍼.
|
|
23
42
|
- [[components/menu-item]] — Item 시각·상태 전부 재사용
|
|
24
43
|
- [[components/avatar]] — Profile 왼쪽 아바타
|
|
25
44
|
- [[components/app-frame]] — Sidebar를 담는 grid 쉘
|
|
@@ -29,26 +48,31 @@ collapsed 결정 애매하면 `false`. Section title 애매하면 생략.
|
|
|
29
48
|
### Props 시그니처
|
|
30
49
|
```tsx
|
|
31
50
|
<Sidebar collapsed={boolean}> // default: false
|
|
51
|
+
{/* Header — 고정, 가로 행. Brand 전용. */}
|
|
32
52
|
<Sidebar.Header>
|
|
33
53
|
<Sidebar.Brand symbol={<Icon />} wordmark={<Logo /> | "CTDS"} />
|
|
34
54
|
</Sidebar.Header>
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
{/* Body — 세로 스크롤 트랙. Section·Separator 전부 여기. 여러 개 두면 각자 독립 스크롤. */}
|
|
57
|
+
<Sidebar.Body>
|
|
58
|
+
<Sidebar.Section title="워크스페이스">
|
|
59
|
+
<Sidebar.Item
|
|
60
|
+
icon={<Icon name="house" />}
|
|
61
|
+
label="대시보드"
|
|
62
|
+
active={boolean}
|
|
63
|
+
trailing={ReactNode} // 배지·카운트
|
|
64
|
+
shortcut="⌘1"
|
|
65
|
+
hasSubmenu={boolean}
|
|
66
|
+
disabled={boolean}
|
|
67
|
+
asChild={boolean}
|
|
68
|
+
onClick={(e) => void}
|
|
69
|
+
/>
|
|
70
|
+
</Sidebar.Section>
|
|
71
|
+
|
|
72
|
+
<Sidebar.Separator />
|
|
73
|
+
</Sidebar.Body>
|
|
74
|
+
|
|
75
|
+
{/* Footer — 고정, 하단. Profile 또는 보조 액션. */}
|
|
52
76
|
<Sidebar.Footer>
|
|
53
77
|
<Sidebar.Profile
|
|
54
78
|
name="희영"
|
|
@@ -69,39 +93,84 @@ collapsed 결정 애매하면 `false`. Section title 애매하면 생략.
|
|
|
69
93
|
|
|
70
94
|
### 조합 예시
|
|
71
95
|
```tsx
|
|
72
|
-
//
|
|
96
|
+
// 1. 워크스페이스 트리거 Header + Section 2개 + Footer Profile (가장 흔한 구성)
|
|
73
97
|
<Sidebar>
|
|
74
98
|
<Sidebar.Header>
|
|
75
|
-
<
|
|
99
|
+
<DropdownMenu>
|
|
100
|
+
<DropdownMenu.Trigger asChild>
|
|
101
|
+
<IdentityButton
|
|
102
|
+
size="lg"
|
|
103
|
+
avatar={{ initials: "T", shape: "rounded" }}
|
|
104
|
+
name="Taskify PM"
|
|
105
|
+
sub="Project Workspace"
|
|
106
|
+
trailing={<Icon name="caret-down" size={14} />}
|
|
107
|
+
aria-haspopup="menu"
|
|
108
|
+
/>
|
|
109
|
+
</DropdownMenu.Trigger>
|
|
110
|
+
<DropdownMenu.Content>
|
|
111
|
+
{/* 워크스페이스 리스트 + Separator + "+ 새 워크스페이스" */}
|
|
112
|
+
</DropdownMenu.Content>
|
|
113
|
+
</DropdownMenu>
|
|
76
114
|
</Sidebar.Header>
|
|
77
115
|
|
|
78
|
-
<Sidebar.
|
|
79
|
-
<Sidebar.
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
<Sidebar.Body>
|
|
117
|
+
<Sidebar.Section title="워크스페이스">
|
|
118
|
+
<Sidebar.Item asChild icon={<Icon name="house" />} label="대시보드" active>
|
|
119
|
+
<Link href="/">대시보드</Link>
|
|
120
|
+
</Sidebar.Item>
|
|
121
|
+
<Sidebar.Item asChild icon={<Icon name="chart-line" />} label="분석">
|
|
122
|
+
<Link href="/analytics">분석</Link>
|
|
123
|
+
</Sidebar.Item>
|
|
124
|
+
</Sidebar.Section>
|
|
86
125
|
|
|
87
|
-
|
|
126
|
+
<Sidebar.Separator />
|
|
88
127
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
<Sidebar.Section title="설정">
|
|
129
|
+
<Sidebar.Item icon={<Icon name="gear" />} label="환경설정" shortcut="⌘," />
|
|
130
|
+
</Sidebar.Section>
|
|
131
|
+
</Sidebar.Body>
|
|
92
132
|
|
|
93
133
|
<Sidebar.Footer>
|
|
134
|
+
{/* Sidebar.Profile은 IdentityButton의 preset 래퍼 — 커스텀하려면 IdentityButton 직접 사용. */}
|
|
94
135
|
<Sidebar.Profile name="희영" sub="admin@ctds.dev" avatar={{ status: "online" }} />
|
|
95
136
|
</Sidebar.Footer>
|
|
96
137
|
</Sidebar>
|
|
97
138
|
|
|
98
|
-
//
|
|
139
|
+
// 2. Brand 로고 전용 Header (워크스페이스 전환이 필요 없는 단일 제품)
|
|
140
|
+
<Sidebar>
|
|
141
|
+
<Sidebar.Header>
|
|
142
|
+
<Sidebar.Brand symbol={<Icon name="rocket-launch" />} wordmark="CTDS" />
|
|
143
|
+
</Sidebar.Header>
|
|
144
|
+
{/* ... Body / Footer */}
|
|
145
|
+
</Sidebar>
|
|
146
|
+
|
|
147
|
+
// 3. Footer 프로필 + 위로 열리는 메뉴 (caret-up)
|
|
148
|
+
<Sidebar.Footer>
|
|
149
|
+
<DropdownMenu>
|
|
150
|
+
<DropdownMenu.Trigger asChild>
|
|
151
|
+
<IdentityButton
|
|
152
|
+
avatar={{ src: user.avatar, status: "online" }}
|
|
153
|
+
name="희영"
|
|
154
|
+
sub="admin@ctds.dev"
|
|
155
|
+
trailing={<Icon name="caret-up" size={14} />}
|
|
156
|
+
aria-haspopup="menu"
|
|
157
|
+
/>
|
|
158
|
+
</DropdownMenu.Trigger>
|
|
159
|
+
<DropdownMenu.Content side="top" align="start">
|
|
160
|
+
{/* 계정 전환 · 설정 · 로그아웃 */}
|
|
161
|
+
</DropdownMenu.Content>
|
|
162
|
+
</DropdownMenu>
|
|
163
|
+
</Sidebar.Footer>
|
|
164
|
+
|
|
165
|
+
// 4. Collapsed — icon-only (IdentityButton·Profile 모두 자동으로 아바타만 남음)
|
|
99
166
|
<Sidebar collapsed>
|
|
100
167
|
<Sidebar.Header>
|
|
101
168
|
<Sidebar.Brand symbol={<Icon name="rocket-launch" />} />
|
|
102
169
|
</Sidebar.Header>
|
|
103
|
-
<Sidebar.
|
|
104
|
-
<Sidebar.
|
|
105
|
-
|
|
170
|
+
<Sidebar.Body>
|
|
171
|
+
<Sidebar.Section>
|
|
172
|
+
<Sidebar.Item icon={<Icon name="house" />} label="대시보드" active />
|
|
173
|
+
</Sidebar.Section>
|
|
174
|
+
</Sidebar.Body>
|
|
106
175
|
</Sidebar>
|
|
107
176
|
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Spinner
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
짧은 비동기 작업의 **로딩 신호**. 색은 currentColor로 묶여 부모 톤을 그대로 승계. **variant 미지정이 기본** — [[axes/motion]] 축의 `--loader-style`을 따라가서 bouncy=dots-bounce / standard=ring / calm=dots-fade로 자동 스왑. "버튼 안 로딩이 테마 바꾸면 같이 바뀌는" 축 추종 구조가 설계의 핵심이다. 긴 작업(파일 업로드 등 진행률이 있는 것)은 [[components/progress-bar]].
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- Button `loading` prop이 자동으로 Spinner를 중앙에 overlay — 소비자가 직접 Spinner 꽂을 필요 없음.
|
|
8
|
+
- 색이 필요하면 부모 요소에 `color: var(--accent)` 식으로 지정 — Spinner는 그대로 승계.
|
|
9
|
+
- 페이지 로딩이면 중앙에 size=`lg` 하나로 충분. 과한 fullscreen overlay는 피한다.
|
|
10
|
+
- variant 미지정이 default — Motion 축 바꾸면 모든 로더가 함께 바뀌는 게 설계 의도.
|
|
11
|
+
|
|
12
|
+
### Don't
|
|
13
|
+
- 진행률(퍼센트) 로딩에 쓰지 않는다 — [[components/progress-bar]].
|
|
14
|
+
- 스켈레톤 자리에 Spinner 쓰지 않는다 — 레이아웃이 이미 그려지는 자리면 shimmer skeleton이 맞음.
|
|
15
|
+
- 색을 하드코딩하지 않는다. currentColor가 부모 색을 승계하도록 두거나, 부모에 토큰 색을 걸 것.
|
|
16
|
+
- `size="xs"`는 아이콘 크기 로더 전용(Button xs 안쪽). 독립적으로 쓸 땐 sm 이상.
|
|
17
|
+
- variant를 소비 측에서 자꾸 override하지 않는다 — 축 추종이 "브랜드 톤 일관성"의 유일한 보장.
|
|
18
|
+
|
|
19
|
+
### Fallback
|
|
20
|
+
`size="sm"`, variant 미지정(= Motion 축 따라감), `label="Loading"`.
|
|
21
|
+
|
|
22
|
+
### 관련
|
|
23
|
+
- [[axes/motion]] — `--loader-style` 토큰을 축이 스왑
|
|
24
|
+
- [[components/button]] — `loading` prop이 Spinner를 자동 overlay
|
|
25
|
+
- [[components/progress-bar]] — 진행률 표시용 (Spinner와 역할 분리)
|
|
26
|
+
|
|
27
|
+
### Props 시그니처
|
|
28
|
+
```tsx
|
|
29
|
+
<Spinner
|
|
30
|
+
size="xs | sm | md | lg" // default: sm
|
|
31
|
+
variant="ring | dots-bounce | dots-fade | pulse | lottie-<name>" // default: Motion 축 따름
|
|
32
|
+
label={string} // SR 라벨. default: "Loading"
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Variant 매트릭스
|
|
37
|
+
| variant | 시각 | 쓰임 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| ring | 회전 반원 테두리 | 표준·중립. standard motion 자동 매핑 |
|
|
40
|
+
| dots-bounce | 3 dot 순차 bounce | 표현력. bouncy motion 자동 매핑 |
|
|
41
|
+
| dots-fade | 3 dot 순차 fade (위치 고정) | 미니멀. calm motion 자동 매핑 |
|
|
42
|
+
| pulse | 단일 원 scale pulse | 특수(온보딩·대기 신호). 자동 매핑 없음 — 명시 지정만 |
|
|
43
|
+
| `lottie-<name>` | 번들된 Lottie 로티 | 성격 있는 자리(AI 생성·온보딩·브랜드) |
|
|
44
|
+
|
|
45
|
+
### Lottie 로티 (10종 번들)
|
|
46
|
+
`lottie-react` 플레이어가 `/public/lottie/<name>.json`을 로드. 단색 로티는 `currentColor` 추종, 다색 로티는 원본 색 유지.
|
|
47
|
+
|
|
48
|
+
| name | 성격 | colorBind |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `lottie-rabbit` | 뛰어가는 토끼. 긴 작업·AI 생성 대기 | ✓ |
|
|
51
|
+
| `lottie-infiniti` | 무한 루프 선. 네트워크·동기화 | ✓ |
|
|
52
|
+
| `lottie-dots` | 그라데이션 dot 흐름. 포인트 로더 | — |
|
|
53
|
+
| `lottie-spinner` | 표준 라인 스피너 | ✓ |
|
|
54
|
+
| `lottie-clock` | 회전 시계 바늘. 처리 대기·타임아웃 | ✓ |
|
|
55
|
+
| `lottie-cosmos` | 그라데이션 궤도. 몽환적·온보딩 | ✓ |
|
|
56
|
+
| `lottie-diamond-star` | 다이아몬드 바운스. 성공·축하 | — |
|
|
57
|
+
| `lottie-image-gen` | 이미지 생성 대기. AI 이미지 | ✓ |
|
|
58
|
+
| `lottie-loading-square` | 회전하는 사각형. 미니멀 처리 | ✓ |
|
|
59
|
+
|
|
60
|
+
**colorBind=✓** 로티는 Palette/Hue 축 바꾸면 로더 색도 자동 추종. **✗** 로티는 작가 의도된 다색이 그대로 유지됨.
|
|
61
|
+
|
|
62
|
+
**사이즈 주의**: Lottie는 vectorized라 size가 작으면 디테일 뭉개짐. `size="md"` 이상 권장. Button loading 안에 Lottie 쓰는 건 비권장 — 기본 variant(dots/ring)가 적합.
|
|
63
|
+
|
|
64
|
+
### Size 매트릭스
|
|
65
|
+
IconButton 체계와 1:1 매칭. 로더는 캔버스를 다 채우지 않는 특성상 아이콘보다 한 단계 큰 bounds가 체감에 맞음.
|
|
66
|
+
|
|
67
|
+
| size | 실제 크기 | 쓰임 |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| xs | 16px | Button xs 내부, 조밀한 인라인 |
|
|
70
|
+
| sm | 20px | **기본**. 인라인 라벨 옆, 리스트 아이템, AIMessage generating |
|
|
71
|
+
| md | 24px | 섹션 로딩, 카드 중앙 |
|
|
72
|
+
| lg | 32px | 페이지 중앙 로딩, 모달 바디 로딩 |
|
|
73
|
+
|
|
74
|
+
Lottie 로더는 캔버스 충전율이 다르므로 `visualScale`로 bounds를 꽉 채우도록 보정됨 — size만 고르면 체감이 일관되게 맞아야 함. 특정 Lottie가 작아 보이면 `lottieManifest.ts`의 `visualScale`을 올려 글로벌 보정, 사용처에서 size를 올리지 말 것.
|
|
75
|
+
|
|
76
|
+
### 축 추종 매핑 (variant 미지정 시)
|
|
77
|
+
| Motion | Spinner 자동 variant |
|
|
78
|
+
|---|---|
|
|
79
|
+
| bouncy | dots-bounce |
|
|
80
|
+
| standard | ring |
|
|
81
|
+
| calm | dots-fade |
|
|
82
|
+
| none | ring (접근성 예외: 로딩 신호는 유지. `prefers-reduced-motion`은 상위 CSS가 처리) |
|
|
83
|
+
|
|
84
|
+
### 색 바인딩
|
|
85
|
+
Spinner는 `currentColor` 바인딩. 즉:
|
|
86
|
+
- Button primary 안에서는 Button의 fg 색(흰색 등)
|
|
87
|
+
- Button outline 안에서는 fg 기본색
|
|
88
|
+
- 커스텀 자리에 색이 필요하면 부모에 `color: var(--accent)` 식으로 걸 것
|
|
89
|
+
|
|
90
|
+
Palette·Hue 축이 바뀌어도 Spinner는 자동 추종 — 별도 색 지정 금지.
|
|
91
|
+
|
|
92
|
+
### 조합 예시
|
|
93
|
+
```tsx
|
|
94
|
+
// Button loading — 소비자가 신경쓸 거 없음 (Button이 자동 처리)
|
|
95
|
+
<Button loading>저장</Button>
|
|
96
|
+
|
|
97
|
+
// 페이지 중앙 로딩
|
|
98
|
+
<div style={{ display: "grid", placeItems: "center", minHeight: 240 }}>
|
|
99
|
+
<Spinner size="lg" />
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
// 리스트 아이템 inline 로딩
|
|
103
|
+
<li>
|
|
104
|
+
<Spinner size="xs" /> 불러오는 중
|
|
105
|
+
</li>
|
|
106
|
+
|
|
107
|
+
// 축 추종 끄고 명시 variant
|
|
108
|
+
<Spinner variant="pulse" size="md" />
|
|
109
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# User Message
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
사용자가 보낸 채팅 메시지. [[components/bubble]] + [[components/message-actions]] 조합. **오른쪽 정렬** 고정. 액션은 기본 호버 시만 노출 — 메시지 리스트가 액션 아이콘으로 어수선해지지 않게.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- 기본 구성: `shape=circular-drop, fill=translucent, tail=end` — 레퍼런스 톤.
|
|
8
|
+
- 액션은 `[copy, edit]` 기본. `actionsVisibility="show" | "hover"`로 상시/호버.
|
|
9
|
+
- 첨부 있으면 `before` slot에 [[components/attachment-thumb]] / [[components/attachment-chip]] 자유 조합. 상위에서 레이아웃 책임.
|
|
10
|
+
- 매우 긴 글은 bubble이 max-width 안에서 wrap, 그보다 넘으면 자연스레 아래로.
|
|
11
|
+
|
|
12
|
+
### Don't
|
|
13
|
+
- 왼쪽 정렬하지 않는다 — user는 **항상 오른쪽**. 왼쪽은 AI/시스템.
|
|
14
|
+
- 액션 라벨을 아이콘 옆에 노출하지 않는다 — title/aria-label만 (MessageActions 규약).
|
|
15
|
+
- bubble 안에 복잡한 레이아웃(table·code block) 넣지 않는다 — 그건 AIMessage 영역.
|
|
16
|
+
- 아바타·이름 넣지 않는다 — user 메시지는 암묵적으로 "나".
|
|
17
|
+
|
|
18
|
+
### Fallback
|
|
19
|
+
shape 애매하면 `circular-drop`, fill 애매하면 `translucent`, visibility 애매하면 `hover`.
|
|
20
|
+
|
|
21
|
+
### 관련
|
|
22
|
+
- [[components/bubble]] — 담는 그릇
|
|
23
|
+
- [[components/message-actions]] — 액션 바
|
|
24
|
+
- [[components/ai-message]] — 짝 컴포넌트
|
|
25
|
+
- [[components/attachment-thumb]] / [[components/attachment-chip]] — before slot 조합
|
|
26
|
+
|
|
27
|
+
### Props 시그니처
|
|
28
|
+
```tsx
|
|
29
|
+
<UserMessage
|
|
30
|
+
shape="rounded | circular-drop | circular | square" // default: circular-drop
|
|
31
|
+
fill="none | solid | translucent | outline" // default: translucent
|
|
32
|
+
size="md | lg" // default: md
|
|
33
|
+
actionsVisibility="show | hover" // default: hover
|
|
34
|
+
actions={[{ kind: "copy" }, { kind: "edit" }]} // 비우려면 []
|
|
35
|
+
before={ReactNode} // 첨부·인용 등 bubble 위
|
|
36
|
+
>
|
|
37
|
+
메시지 본문
|
|
38
|
+
</UserMessage>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 레이아웃
|
|
42
|
+
- root: `flex-direction: column; align-items: flex-end; gap: space-1; width: 100%`
|
|
43
|
+
- 메시지 리스트 안에서 각 UserMessage가 오른쪽에 자동 정렬됨.
|
|
44
|
+
- Bubble `tail="end"` 고정 — 오른쪽 상단 각짐.
|
|
45
|
+
- MessageActions `align="end"`.
|
|
@@ -99,14 +99,20 @@
|
|
|
99
99
|
|
|
100
100
|
### 정답 패턴 (ChatInput이 쓰는 구조)
|
|
101
101
|
|
|
102
|
+
핵심 결정: **wrapper에 padding 0, textarea에 상·하·좌·우 padding 전부**. 왜냐하면
|
|
103
|
+
`overflow: auto` 요소에 padding을 두면 그 padding은 **scroll 시 고정** — 스크롤해도
|
|
104
|
+
빈 띠가 상·하에 남아 "글이 padding에서 미리 잘린다"는 느낌을 줌. 반면 textarea의
|
|
105
|
+
padding은 **자신의 content box 일부**로 취급돼, textarea 전체가 wrapper 안에서 scroll
|
|
106
|
+
되면 padding도 함께 올라감.
|
|
107
|
+
|
|
102
108
|
1. **root는 `padding: 0; overflow: hidden`** — scroll clip이 root border에서 생기도록.
|
|
103
|
-
2.
|
|
104
|
-
3. **textarea는
|
|
109
|
+
2. **wrapper div(`.scroll`)는 `overflow-y: auto; padding: 0`** — padding 두면 "빈 띠" 생김.
|
|
110
|
+
3. **textarea는 상·하·좌·우 padding 전부 + `overflow: hidden`** — 자체 scroll 금지.
|
|
105
111
|
|
|
106
112
|
```css
|
|
107
113
|
.root {
|
|
108
114
|
padding: 0;
|
|
109
|
-
overflow: hidden;
|
|
115
|
+
overflow: hidden;
|
|
110
116
|
border-radius: var(--radius-sm);
|
|
111
117
|
border: var(--hairline) solid var(--border);
|
|
112
118
|
}
|
|
@@ -114,28 +120,33 @@
|
|
|
114
120
|
.scroll {
|
|
115
121
|
overflow-y: auto;
|
|
116
122
|
max-height: calc(var(--text-base) * 1.5 * 8);
|
|
117
|
-
padding:
|
|
123
|
+
padding: 0; /* 반드시 0 */
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
.textarea {
|
|
121
127
|
display: block;
|
|
122
128
|
width: 100%;
|
|
123
|
-
|
|
129
|
+
box-sizing: border-box;
|
|
130
|
+
padding: var(--space-3) var(--space-4); /* 상·하·좌·우 모두 */
|
|
124
131
|
background: transparent;
|
|
125
132
|
border: 0;
|
|
126
133
|
outline: none;
|
|
127
134
|
resize: none;
|
|
128
|
-
overflow: hidden;
|
|
135
|
+
overflow: hidden; /* textarea 자체 scroll 금지 */
|
|
129
136
|
font: inherit;
|
|
130
137
|
line-height: 1.5;
|
|
131
138
|
}
|
|
132
139
|
```
|
|
133
140
|
|
|
134
|
-
이
|
|
135
|
-
|
|
136
|
-
- wrapper max-height
|
|
137
|
-
-
|
|
138
|
-
-
|
|
141
|
+
이 구조에서:
|
|
142
|
+
- **짧은 글**: textarea가 자신의 padding 포함 높이 → wrapper 안에 스크롤 없음.
|
|
143
|
+
- **긴 글**: textarea height > wrapper max-height → wrapper 스크롤 시 textarea 전체가 올라감.
|
|
144
|
+
- 상단에서 textarea padding-top을 지나며 글이 **root 상단 border에서 자연스레 clip**.
|
|
145
|
+
- 하단에서 textarea padding-bottom을 지나며 글이 **root 하단 border에서 자연스레 clip**.
|
|
146
|
+
|
|
147
|
+
### 금지 패턴 추가 (이번 삽질로 확인)
|
|
148
|
+
|
|
149
|
+
❌ **"wrapper에 padding-top, textarea에 padding-bottom"** — 전에 썼던 패턴. wrapper의 padding-top이 scroll 시 "빈 띠"로 남아 글이 그 아래에서 clip돼 어색함.
|
|
139
150
|
|
|
140
151
|
### 함께 적용되는 레이아웃 룰
|
|
141
152
|
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -29,6 +29,7 @@ Layer 1(shadcn 방식 컴포넌트 코드) + Layer 2(AI 프롬프트 컨텍스
|
|
|
29
29
|
|---|---|
|
|
30
30
|
| 컴포넌트 신규 제작·variant/prop 수정 (`web/components/<Name>/` 또는 `prompts/components/<name>.md`) | `ctds-component` |
|
|
31
31
|
| 페이지·대시보드·레이아웃 조합 (`web/app/**/page.tsx`) | `ctds-page` |
|
|
32
|
+
| **와이어프레임/기존 페이지를 CTDS로 번역·교체** (사용자가 "CTDS로 바꿔줘/교체해줘/일괄 변경" 요청, 또는 와이어프레임 코드·이미지를 입력) | `ctds-wireframe` |
|
|
32
33
|
| 테마 프리셋 생성·수정 (`prompts/themes/*` 또는 `web/app/theme-demo/*`) | `ctds-theme` |
|
|
33
34
|
| 코드 검증 (하드코딩·유령 토큰·메모리 규칙·문서-코드 동기화) | `ctds-validate` |
|
|
34
35
|
| 3D 아이콘 일러스트 자산 생성 | `ctds-illust` |
|