@conscience-technology/ctds 0.3.3 → 0.4.0
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/Button/Button.module.css +2 -2
- package/assets/components/Checkbox/Checkbox.module.css +1 -0
- package/assets/components/EventListItem/EventListItem.module.css +220 -0
- package/assets/components/EventListItem/EventListItem.tsx +205 -0
- package/assets/components/EventListItem/index.ts +8 -0
- package/assets/components/Icon/Icon.tsx +80 -41
- package/assets/components/ListItem/ListItem.module.css +91 -211
- package/assets/components/ListItem/ListItem.tsx +130 -154
- package/assets/components/ListItem/index.ts +2 -2
- package/assets/components/MenuItem/MenuItem.module.css +30 -5
- package/assets/components/MenuItem/MenuItem.tsx +86 -5
- package/assets/components/MetricCard/MetricCard.module.css +5 -5
- package/assets/components/Radio/Radio.module.css +1 -0
- package/assets/components/Sidebar/Sidebar.module.css +81 -4
- package/assets/components/Sidebar/Sidebar.tsx +15 -3
- package/assets/components/SplitButton/SplitButton.module.css +3 -3
- package/assets/components/Stepper/Stepper.module.css +196 -52
- package/assets/components/Stepper/Stepper.tsx +68 -13
- package/assets/components/Switch/Switch.module.css +1 -9
- package/assets/components/Topbar/Topbar.module.css +33 -5
- package/assets/components/Topbar/Topbar.tsx +62 -7
- package/assets/components/_data.ts +11 -4
- package/assets/components/index.ts +2 -0
- package/assets/components/tokens.css +23 -10
- package/assets/prompts/ASSEMBLY.md +219 -0
- package/assets/prompts/PRINCIPLES.md +70 -0
- package/assets/prompts/axes/icon.md +7 -1
- package/assets/prompts/axes/palette.md +11 -0
- package/assets/prompts/components/_variants.md +5 -1
- package/assets/prompts/components/callout.md +3 -3
- package/assets/prompts/components/event-list-item.md +103 -0
- package/assets/prompts/components/generated-image.md +80 -0
- package/assets/prompts/components/list-item.md +182 -204
- package/assets/prompts/components/menu-item.md +1 -0
- package/assets/prompts/components/metric-card.md +2 -2
- package/assets/prompts/components/toast.md +1 -0
- package/assets/prompts/components/topbar.md +77 -59
- package/assets/prompts/themes/focus.md +72 -0
- package/assets/skills/ctds/SKILL.md +1 -0
- package/assets/skills/ctds-page/SKILL.md +12 -2
- package/assets/skills/ctds-wireframe/SKILL.md +35 -1
- package/package.json +2 -4
|
@@ -1,235 +1,213 @@
|
|
|
1
1
|
# ListItem
|
|
2
2
|
|
|
3
3
|
### 원칙
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
같은 anatomy 안에서 **variant로 자리의 성격만 바꾼다** — `activity`(과거 서술), `notification`(처리 필요), `mention`(인용), `system`(사람 아님). variant를 늘리기 전에 slot(`leading`/`trailing`/`hoverActions`)으로 조립이 되는지 먼저 본다.
|
|
7
|
-
|
|
8
|
-
컴포넌트 자신은 **단일 row**다. border/divider/카드 chrome을 자체적으로 지지 않는다 — 리스트로 묶는 건 부모 레이아웃이 결정한다. 운영 콘솔은 border로, pure 좌표는 hairline으로, 카드 안 리스트는 Callout이 담당. ([[components/metric-card]]의 `bare` variant와 같은 원리.)
|
|
9
|
-
|
|
10
|
-
**단, "chrome 없음"은 완전한 공간 없음을 뜻하지 않는다** — ListItem은 `padding-block: var(--space-3) + margin-block: -var(--space-3)`의 **self-contained 상쇄 공식**을 가진다. 부모가 세로 padding을 안 걸어도 row hover hit area가 보장되게 하려는 안전장치다. 하지만 flex item 레이아웃에서는 음의 margin이 레이아웃 박스를 축소하지 못해 **ListItem이 "상쇄된 자리"를 실제로 차지한다** — 부모 li가 padding을 또 걸면 두 번 공간이 쌓여 과하게 보인다. 자세한 공식·완화법은 아래 "조립 — 부모 wrapper 공식" 섹션.
|
|
11
|
-
|
|
12
|
-
### Do
|
|
13
|
-
- **anatomy는 네 자리로 고정**: `[leading] [who] [body / verb+target] [meta/trailing]`. 어떤 variant든 이 뼈대를 깨지 않는다.
|
|
14
|
-
- **layout은 두 형태**: `inline`(한 줄, 기본) · `stacked`(두 줄 — from이 1줄, body가 2줄). 서술 모드 둘 다에서 작동 — action 모드 stacked는 "from · verb · target" + body 2줄째, 서술 모드 stacked는 from + body 2줄째. 긴 서술/인용/멘션은 stacked.
|
|
15
|
-
- **서술은 두 모드 중 하나로**: 자유 서술은 `from` + `body`("회계팀 — 외주비 결재 요청"), 액션 서술은 `from` + `verb` + `target`("박지훈 · 댓글 남김 · ERP 2.0 사이드바"). 둘 다 넘기지 않는다.
|
|
16
|
-
- **unread는 텍스트 톤으로만 신호** (dot/badge 없음): `unread={true}`면 body/from이 가장 진하고 meta는 `--primary` 컬러로 "지금"을 강조. `unread={false}`면 전체 톤이 한두 단계 뒤로 물러나 "이미 처리됨"을 시사. `unread` 생략(undefined)이면 중립 — **리드/언리드 개념 자체가 없는 자리**(활동 로그·히스토리·타임라인)에서 자유롭게 사용.
|
|
17
|
-
- **surface는 작은 inline 토큰**: `surface="GitHub"` 같은 서비스명은 target 뒤 작게 inline. [[components/badge]] variant="soft"로 렌더되지만 text-only로 유지.
|
|
18
|
-
- **meta는 상대시간 한 토큰**: "방금", "12분", "어제". ISO 날짜는 Tooltip으로 보조.
|
|
19
|
-
- **hover-only 액션은 명시적으로**: `hoverActions={[<IconButton name="archive"/>, ...]}` — Linear Inbox의 archive/snooze/mark-read 패턴. 평상시 숨김, row hover/focus/focus-within에서 등장. [[components/menu-item]]의 hoverOnly와 같은 메커니즘.
|
|
20
|
-
- **row 전체 클릭은 `href`로**: 라우팅 아이템이면 asChild로 `<Link>`에 위임. target만 링크로 쓰고 row는 비활성인 자리도 OK.
|
|
21
|
-
- **leading 정책은 리스트 단위로 통일**: 일부엔 Avatar, 일부엔 Icon처럼 섞으면 "누구"의 자리가 어긋난다. system 메시지가 섞이는 리스트면 전부 Avatar fallback(이니셜) + system은 서비스 로고 Avatar.
|
|
22
|
-
- **leading 타입은 세 종류**: Avatar(사람·팀), Icon(단색 글리프), `ListItemLeadingIcon`(배경색 + 아이콘 마크). 세 번째는 "서비스/시스템이 상태를 색으로 신호"하는 자리 — tone 프리셋(`primary · success · warning · danger · info · neutral`)으로 bg/fg 쌍을 한 번에, 혹은 `--leading-icon-bg` · `--leading-icon-fg` 변수로 세밀 조절. Avatar와 같은 사이즈 트랙(xs/sm/md/lg)이라 섞여도 정렬 유지. 추가로 `size="auto"`는 `layout="stacked"`와 짝 — content 두 줄 전체 높이에 맞춰 정사각으로 stretch, 상태 마크를 크게 보여야 하는 알림/시스템 메시지 자리.
|
|
23
|
-
- **density는 컨테이너에 맡긴다**: ListItem 자체는 size prop 없이, 부모 리스트의 density(또는 Density 축) 토큰을 상속한다.
|
|
24
|
-
|
|
25
|
-
### Don't
|
|
26
|
-
- `verb` + `body` 동시 사용 금지 — 서술 중복. 액션 모드와 서술 모드는 배타.
|
|
27
|
-
- 채팅뷰처럼 긴 메시지 본문을 그대로 노출하지 않는다 — ListItem은 "누가 어디서 뭘 했다" 요약 라인이지, 메시지 내용의 복사본이 아니다. 멘션/댓글이어도 `verb` + `target`("어디/뭐에")로 축약하고 상세는 target 클릭 뒤 전체 뷰에서. 인용부호를 둘러친 긴 body도 금지.
|
|
28
|
-
- `leading`을 생략하고 unread dot만 두지 않는다 — dot 혼자 떠서 "누구"의 자리가 사라진다. system 메시지는 서비스 로고/아이콘 Avatar를 세운다.
|
|
29
|
-
- body를 두 줄 이상으로 늘리지 않는다 — 길면 `layout="stacked"`로 자동 승격해 target 라인과 서술 라인을 분리하거나, [[components/callout]] 영역으로 넘긴다.
|
|
30
|
-
- `trailing` + `hoverActions`를 둘 다 넘기지 않는다 — 메타 자리 하나뿐. 상시 메타(카운트·시간)와 hover 액션이 공존해야 하면 `trailing`에 배열로.
|
|
31
|
-
- unread/urgent를 색으로 중복 신호하지 않는다 — dot만. body 색을 fg-strong으로 올리거나 배경을 깔면 과잉.
|
|
32
|
-
- 자체적으로 padding·border·bg는 두지 않지만 **hover bg는 예외** — row 레벨 상호작용 피드백은 "가두는 chrome"이 아니라 "지금 이 줄" 신호이므로 default로 켜져 있다. pure 좌표에서 끄려면 색상 훅 `--list-item-hover-bg: transparent`.
|
|
33
|
-
- "댓글 남김" 같은 동사를 긴 서술로 늘이지 않는다. verb는 **동사 토큰** 하나. 긴 설명은 target 아래 2줄 body로 승격.
|
|
34
|
-
|
|
35
|
-
### Fallback
|
|
36
|
-
variant 애매하면 `activity`. layout 애매하면 `inline`. leading 애매하면 `from` 기반 Avatar(이니셜).
|
|
37
|
-
|
|
38
|
-
### 색상 훅 (컨테이너 단위 톤 조절)
|
|
39
|
-
MenuItem과 같은 패턴. 리스트 wrapper에 변수를 얹으면 안쪽 ListItem이 일괄 반응.
|
|
4
|
+
리스트에 반복되는 **한 줄 단위**의 범용 anatomy. 피드·알림·활동 로그·작업 리스트·자원 목록이 전부 이 형태다. GitHub Timeline · Linear Inbox · Jira Issue history · Notion Updates · Figma Comments 모두 같은 뼈대 위에 서식과 톤만 바뀐다.
|
|
40
5
|
|
|
41
|
-
|
|
42
|
-
|---|---|---|
|
|
43
|
-
| `--list-item-fg` | 본문 서술 색 | `--fg` |
|
|
44
|
-
| `--list-item-from-fg` | from(누구) 색 | `--fg-strong` |
|
|
45
|
-
| `--list-item-meta-fg` | meta/상대시간 색 | `--fg-muted` |
|
|
46
|
-
| `--list-item-hover-bg` | row hover bg | `--hover` |
|
|
6
|
+
**3구역 뼈대**: `[leading] [content] [trailing]`. 가로축은 이 셋이 전부고 세 구역 전부 `ReactNode`라 **자유 조립**. content 안쪽은 자주 쓰는 조립을 위한 편의 서브컴포넌트(`Title · Description · Meta · MetaDivider`)가 세로 리듬을 자동 공급하지만 강제는 아님 — 아바타 그룹·프로그레스·커스텀 차트 등 아무거나 넣을 수 있다.
|
|
47
7
|
|
|
48
|
-
|
|
49
|
-
/* 예: 조용한 활동 로그 — 메타·본문 전부 한 단계 뒤로 */
|
|
50
|
-
.quiet-activity {
|
|
51
|
-
--list-item-fg: var(--fg-muted);
|
|
52
|
-
--list-item-meta-fg: var(--fg-subtle);
|
|
53
|
-
}
|
|
8
|
+
**일정·이벤트처럼 날짜가 leading 아이덴티티인 자리는 [[components/event-list-item]] 사용** — ListItem은 범용 한 줄, EventListItem은 시간축 중심 전용.
|
|
54
9
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
10
|
+
**chrome 없음** — bg·border·radius를 자체 공급하지 않는다. ListItem은 "리스트에 녹는 한 줄"이지, 블록이 아니다. 배경·테두리로 상태를 신호해야 하는 블록이 필요하면 [[components/callout]]로 승격한다.
|
|
11
|
+
|
|
12
|
+
변주 축은 두 개: `size`(md/sm)가 세로 여백감을, `align`(center/top)이 leading 세로 정렬을 담당. 기본은 `md`(작업·자원 리스트 밀도), `sm`은 활동 로그·추출 완료처럼 분리선 없이 조밀하게 쌓이는 스트림 자리. row hover bg는 전 리스트 기본, href/onClick이 있으면 cursor pointer + focus ring 추가.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 결정 가이드 — 뭘 만들려는 거지?
|
|
17
|
+
|
|
18
|
+
### 1. 자원·문서 목록 (내가 열어볼 것들)
|
|
19
|
+
문서·프로젝트·설정 항목처럼 **"열람 가능한 것들"의 목록**. 액션보다 네비게이션이 주.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<ListItem
|
|
23
|
+
href="/docs/abc"
|
|
24
|
+
leading={<ListItemLeadingIcon size="md"><Icon name="file-text" /></ListItemLeadingIcon>}
|
|
25
|
+
trailing={<Icon name="caret-right" />}
|
|
26
|
+
>
|
|
27
|
+
<ListItem.Title>채용 파이프라인 QBR</ListItem.Title>
|
|
28
|
+
<ListItem.Meta><time>2029년 7월 8일 작성</time></ListItem.Meta>
|
|
29
|
+
</ListItem>
|
|
59
30
|
```
|
|
60
31
|
|
|
61
|
-
|
|
32
|
+
- **leading**: `ListItemLeadingIcon`(배경색 아이콘) → 자원의 "타입 아이덴티티"
|
|
33
|
+
- **content**: `Title` + `Meta`(생성/수정 날짜). Description은 생략 — 자원의 "본문 요약"이 필요한 자리면 그건 ListItem이 아니라 Card
|
|
34
|
+
- **trailing**: `caret-right` → "클릭하면 열림" 신호
|
|
35
|
+
- **size**: `md`, **align**: `center`
|
|
36
|
+
|
|
37
|
+
### 2. 작업·할 일 리스트 (내가 처리해야 할 것들)
|
|
38
|
+
요청·승인 대기·검토 필요처럼 **상태가 있는 작업 단위**.
|
|
39
|
+
|
|
62
40
|
```tsx
|
|
63
41
|
<ListItem
|
|
64
|
-
|
|
65
|
-
leading={
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
meta={ReactNode} // "방금" · "12분". 오른쪽 끝 default
|
|
77
|
-
trailing={ReactNode} // meta 자리 커스텀 (ReactNode 또는 배열)
|
|
78
|
-
hoverActions={ReactNode[]} // row hover 시 등장. archive/snooze/⋯
|
|
79
|
-
|
|
80
|
-
// 레이아웃·상호작용
|
|
81
|
-
variant="activity | notification | mention | system" // default: activity
|
|
82
|
-
layout="inline | stacked" // default: inline. stacked는 body 2줄.
|
|
83
|
-
href={string} // row 전체 링크. asChild로 Link 위임.
|
|
84
|
-
onClick={(e) => void} // row 선택 핸들러
|
|
85
|
-
asChild={boolean} // <Link> 등에 위임
|
|
86
|
-
/>
|
|
42
|
+
align="top"
|
|
43
|
+
leading={<Icon name="list-dashes" />}
|
|
44
|
+
trailing={
|
|
45
|
+
<>
|
|
46
|
+
<Badge variant="soft" status="warning">검토 필요</Badge>
|
|
47
|
+
<IconButton size="sm" variant="ghost" icon={<Icon name="caret-right" />} aria-label="열기" />
|
|
48
|
+
</>
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
<ListItem.Title>개발 대상과 문서 범위 확인</ListItem.Title>
|
|
52
|
+
<ListItem.Meta><time>22분 전 요청</time></ListItem.Meta>
|
|
53
|
+
</ListItem>
|
|
87
54
|
```
|
|
88
55
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
56
|
+
- **leading**: raw `<Icon>` → 체크리스트·태스크의 "종류"만 가볍게
|
|
57
|
+
- **content**: `Title` + `Meta`(요청 시각·요청자). Description은 선택 — 제목이 짧으면 추가
|
|
58
|
+
- **trailing**: 상태 `Badge` + chevron. Badge가 "지금 상태"를 shout, chevron이 "열어서 처리하라"
|
|
59
|
+
- **size**: `md`, **align**: `top`
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
| `activity` | Avatar(from) | 없음 | verb+target | 최근 활동·히스토리 로그. GitHub/Linear timeline. 보통 unread 생략 |
|
|
96
|
-
| `notification` | Avatar(from) | archive/mark-read | from+body | 알림 센터·bell 드롭다운. unread 명시 권장 |
|
|
97
|
-
| `mention` | Avatar(from) | reply/resolve | from+body(인용) | 멘션·댓글 피드. body에 인용부호 자연스러움 |
|
|
98
|
-
| `system` | Icon(서비스 로고) | 없음 | from+body | ERP/CI/빌드 등 시스템 이벤트. 보통 unread 생략 |
|
|
61
|
+
### 3. 피드·활동 로그 (지나간 이벤트 스트림)
|
|
62
|
+
알림·활동 기록·이력처럼 **읽을 뿐 처리는 거의 없는 흐름**.
|
|
99
63
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- 한 리스트 안에 variant 혼용 — 리스트 톤은 하나로. 섞어야 하면 섹션으로 분리
|
|
64
|
+
```tsx
|
|
65
|
+
<ListItem align="top" size="sm" leading={<Avatar size="sm" name="박지훈" />}>
|
|
66
|
+
<ListItem.Title>박지훈 — ERP 2.0 사이드바 티켓에 코멘트</ListItem.Title>
|
|
67
|
+
<ListItem.Meta><time>12분 전</time></ListItem.Meta>
|
|
68
|
+
</ListItem>
|
|
69
|
+
```
|
|
107
70
|
|
|
108
|
-
|
|
71
|
+
- **leading**: Avatar(사람 이벤트) 또는 raw `<Icon>`(시스템 이벤트)
|
|
72
|
+
- **content**: `Title`에 "누가/뭘 했다"를 한 줄로. 긴 설명은 target 클릭 뒤 상세 뷰로
|
|
73
|
+
- **trailing**: 보통 없음 (hover-only 액션은 archive/mark-read 패턴)
|
|
74
|
+
- **size**: `sm` (스트림은 조밀하게), **align**: `top`
|
|
75
|
+
|
|
76
|
+
### 4. 멘션·댓글 (내가 응답해야 할 메시지)
|
|
77
|
+
멘션·@·댓글 리스트. 피드와 비슷하지만 **"응답 가능"이 핵심**.
|
|
109
78
|
|
|
110
79
|
```tsx
|
|
111
|
-
// 1) 최근 활동 피드 — verb+target 액션 서술
|
|
112
|
-
<ListItem
|
|
113
|
-
leading={<Avatar name="박지훈" />}
|
|
114
|
-
from="박지훈"
|
|
115
|
-
verb="댓글 남김"
|
|
116
|
-
target="ERP 2.0 사이드바 구조 확정"
|
|
117
|
-
surface="Figma"
|
|
118
|
-
meta="방금"
|
|
119
|
-
/>
|
|
120
|
-
|
|
121
|
-
// 2) 알림 센터 — unread + hoverActions
|
|
122
|
-
<ListItem
|
|
123
|
-
variant="notification"
|
|
124
|
-
leading={<Avatar name="회계팀" />}
|
|
125
|
-
from="회계팀"
|
|
126
|
-
body="외주비 세금계산서 결재 요청"
|
|
127
|
-
meta="방금"
|
|
128
|
-
unread
|
|
129
|
-
hoverActions={[
|
|
130
|
-
<IconButton name="archive" aria-label="보관" />,
|
|
131
|
-
<IconButton name="check" aria-label="읽음 처리" />,
|
|
132
|
-
]}
|
|
133
|
-
/>
|
|
134
|
-
|
|
135
|
-
// 3) 멘션 — body에 인용 (stacked로 2줄)
|
|
136
|
-
<ListItem
|
|
137
|
-
variant="mention"
|
|
138
|
-
layout="stacked"
|
|
139
|
-
leading={<Avatar name="장대표" />}
|
|
140
|
-
from="장대표"
|
|
141
|
-
body={<>“@김희영 1:1 전에 자료 공유 부탁”</>}
|
|
142
|
-
meta="오전 9:12"
|
|
143
|
-
unread
|
|
144
|
-
/>
|
|
145
|
-
|
|
146
|
-
// 4) 시스템 이벤트 — Icon leading, 사람 아님
|
|
147
|
-
<ListItem
|
|
148
|
-
variant="system"
|
|
149
|
-
leading={<Icon name="chart-line" />}
|
|
150
|
-
from="ERP"
|
|
151
|
-
body="주간 리포트 생성 완료"
|
|
152
|
-
meta="3시간"
|
|
153
|
-
/>
|
|
154
|
-
|
|
155
|
-
// 4-b) 상태 색을 띤 아이콘 마크 leading — 성공/경고/실패 신호
|
|
156
80
|
<ListItem
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
meta="방금"
|
|
166
|
-
/>
|
|
167
|
-
|
|
168
|
-
// 5) pure 좌표 — 부모 리스트가 hairline으로 분리 (조립 공식 "패턴 C" 참고)
|
|
169
|
-
// li에 세로 padding + ListItem self-space 중화 둘 다 필요하다.
|
|
170
|
-
<ul className="pureFeed">
|
|
171
|
-
{feed.map((f) => (
|
|
172
|
-
<li key={f.id}>
|
|
173
|
-
<ListItem
|
|
174
|
-
leading={<Avatar name={f.from} />}
|
|
175
|
-
from={f.from}
|
|
176
|
-
body={f.body}
|
|
177
|
-
meta={f.ago}
|
|
178
|
-
unread={f.unread}
|
|
179
|
-
/>
|
|
180
|
-
</li>
|
|
181
|
-
))}
|
|
182
|
-
</ul>
|
|
81
|
+
href="/thread/123"
|
|
82
|
+
align="top"
|
|
83
|
+
leading={<Avatar size="sm" name="장대표" />}
|
|
84
|
+
>
|
|
85
|
+
<ListItem.Title>장대표</ListItem.Title>
|
|
86
|
+
<ListItem.Description>@희영 1:1 전에 자료 공유 부탁</ListItem.Description>
|
|
87
|
+
<ListItem.Meta><time>오전 9:12</time></ListItem.Meta>
|
|
88
|
+
</ListItem>
|
|
183
89
|
```
|
|
184
90
|
|
|
185
|
-
|
|
186
|
-
|
|
91
|
+
- **content**: `Title`에 이름, `Description`에 **인용 한 줄**(전체 메시지 복사 금지), `Meta`에 시각
|
|
92
|
+
- **href**: row 전체가 thread 열기
|
|
93
|
+
- **align**: `top`
|
|
94
|
+
|
|
95
|
+
### 5. 데이터 레코드 (내가 보고 비교할 숫자)
|
|
96
|
+
가격·수량·기간이 핵심인 리스트. 숫자가 Title보다 중요한 자리.
|
|
97
|
+
|
|
98
|
+
→ **ListItem이 아니라 [[components/table]]의 자리**. ListItem은 "자원·텍스트 중심 한 줄", 숫자 비교·정렬·집계는 Table.
|
|
99
|
+
|
|
100
|
+
### 6. 상태 공지·보류 블록 (지금 당장 봐야 하는 알림)
|
|
101
|
+
"작업 보류", "다운타임 예정", "결제 실패"처럼 **배경 톤으로 shouts** 해야 하는 블록.
|
|
102
|
+
|
|
103
|
+
→ **ListItem이 아니라 [[components/callout]]의 자리**. ListItem은 chrome 없이 흐름에 녹는 한 줄이지, 상태 블록이 아니다.
|
|
104
|
+
|
|
105
|
+
### 7. 선택 가능한 목록 (여러 개 체크·drag)
|
|
106
|
+
멀티 셀렉션·drag reorder·bulk 작업 리스트.
|
|
187
107
|
|
|
188
|
-
|
|
189
|
-
그대로 둬도 ListItem이 자체 공간을 가진다. hover hit area는 보장되지만 **리스트처럼 세로로 쌓이면 아이템끼리 바로 붙는다** — 리스트 자리면 패턴 B/C로.
|
|
108
|
+
→ **ListItem leading에 `<Checkbox />`** 꽂아서 시작. drag handle은 leading 왼쪽에 추가 자리로. 복잡해지면 Table로 승격.
|
|
190
109
|
|
|
191
|
-
|
|
192
|
-
flex item 레이아웃에서 ListItem의 음의 margin이 레이아웃 박스를 축소시키지 못해 **공간이 두 번 쌓인다** (li padding + ListItem self-padding). 겉보기엔 세로가 과하게 커진다 — hairline과 콘텐츠 사이 호흡은 보이지만 리스트가 전반적으로 숨 막혀 보이면 이 상태다.
|
|
110
|
+
---
|
|
193
111
|
|
|
194
|
-
|
|
195
|
-
|
|
112
|
+
## 서브컴포넌트 역할
|
|
113
|
+
|
|
114
|
+
| 서브컴포넌트 | 뭘 담나 | 언제 쓰나 |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `ListItem.Title` | 메인 제목·행위 요약 | 항상. Title 없는 ListItem은 거의 없음. Badge를 inline으로 Title 앞에 섞어도 OK |
|
|
117
|
+
| `ListItem.Description` | 제목으로 안 담기는 **인용·부제** | 멘션/댓글 본문, 설명이 1줄 더 필요한 자리. 생성일 같은 메타는 절대 Description 아님 |
|
|
118
|
+
| `ListItem.Meta` | **메타데이터** (시각·작성자·버전·상태 텍스트) | 작성일·수정일·요청자·"n개 참여" 같은 보조 수치 전부 여기 |
|
|
119
|
+
| `ListItem.MetaDivider` | Meta 안 토큰 사이 hairline 구분자 | `<span>작성 10일 전</span><Divider /><span>박지훈</span>` 같을 때 |
|
|
120
|
+
|
|
121
|
+
### Description vs Meta
|
|
122
|
+
가장 자주 실수하는 자리. 구분 공식:
|
|
123
|
+
- **Description** = "제목으로 안 들어간 **본문 성격의 한 줄**" (댓글 본문, 부제, 요약)
|
|
124
|
+
- **Meta** = "**누가/언제/몇 개** 같은 메타데이터" (날짜·사람·수치·상태 텍스트)
|
|
125
|
+
|
|
126
|
+
"2029년 7월 8일 작성"은 **Meta**. "@희영 1:1 전에 자료 공유 부탁"은 **Description**.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Do
|
|
131
|
+
- **anatomy는 3구역 고정**: leading / children(content) / trailing.
|
|
132
|
+
- **세 구역은 전부 ReactNode — 완전 자유**: 전용 서브컴포넌트는 리듬 토큰을 자동 공급하는 **편의 레이어**지 강제가 아님.
|
|
133
|
+
- **leading 타입은 리스트 단위로 통일**: 자원 리스트는 전부 IconMark, 피드는 전부 Avatar 식.
|
|
134
|
+
- **2줄 이상 본문이면 `align="top"`**: leading이 Title 라인에 top-align.
|
|
135
|
+
- **스트림 자리는 `size="sm"`**: 활동 로그·알림 스트림은 조밀하게.
|
|
136
|
+
- **row 전체 클릭은 `href`/`onClick`**: cursor pointer + focus ring 자동.
|
|
137
|
+
- **Meta 안 토큰 사이는 MetaDivider**: 가운뎃점·bullet·파이프 금지. [[feedback_no_bullet_separator]].
|
|
138
|
+
- **날짜는 [[feedback_date_writing]] 규칙 준수**: "10월 13일" 또는 "12분 전", 숫자 패턴(10. 13) 금지.
|
|
139
|
+
|
|
140
|
+
## Don't
|
|
141
|
+
- **Description에 메타데이터 쓰기 금지**: "2029년 7월 8일 작성" 같은 날짜·사람·수치는 전부 `Meta`로.
|
|
142
|
+
- **Title에 전체 메시지/긴 서술 넣기 금지**: ListItem은 요약 라인, 메시지 뷰어가 아님.
|
|
143
|
+
- **content에 여러 Title 금지**: 메인 제목은 하나.
|
|
144
|
+
- **Description은 2줄 안쪽**: 3줄 이상이면 외부 Callout·Card로.
|
|
145
|
+
- **bg·border·radius 자체 공급 금지**: 블록 외형이 필요하면 [[components/callout]].
|
|
146
|
+
- **leading에 inline `<svg>` 금지**: 반드시 `<Icon />`/Badge/Avatar 경유. [[feedback_no_inline_svg]].
|
|
147
|
+
- **한 리스트 안 size 혼용 금지**: sm 리스트는 전부 sm.
|
|
148
|
+
- **자리마다 세로 간격 손 조정 금지**: 균형 안 맞으면 `size` 또는 리듬 토큰(`--li-gap-title`·`--li-gap-body`)으로.
|
|
149
|
+
|
|
150
|
+
## Fallback
|
|
151
|
+
leading 애매하면 Icon. size 애매하면 md. align 애매하면 center(한 줄)·top(2줄 이상).
|
|
152
|
+
|
|
153
|
+
## 축 (CSS 변수)
|
|
154
|
+
|
|
155
|
+
| 훅 | 대상 | 폴백 |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `--li-pad-y` | 세로 padding (hit area) | `var(--space-3)` |
|
|
158
|
+
| `--li-gap-x` | leading↔content↔trailing | `var(--space-3)` |
|
|
159
|
+
| `--li-gap-title` | Title↔Description·Meta ("제목+부제" 유닛감) | `var(--space-0_5)` |
|
|
160
|
+
| `--li-gap-body` | 큰 블록 간 간격 | `var(--space-2)` |
|
|
161
|
+
| `--li-gap-meta` | Meta 라인 아이템 간 가로 gap | `var(--space-2)` |
|
|
162
|
+
|
|
163
|
+
## Props 시그니처
|
|
164
|
+
```tsx
|
|
165
|
+
<ListItem
|
|
166
|
+
leading={ReactNode} // Icon | Avatar | Badge | ListItemLeadingIcon | 자유
|
|
167
|
+
trailing={ReactNode} // Badge | Button | IconButton | 조합 | 자유
|
|
168
|
+
size="md | sm" // default: md (sm은 스트림 자리)
|
|
169
|
+
align="center | top" // default: center. 2줄 이상이면 top.
|
|
170
|
+
href={string} // row 전체 링크 (클릭 가능)
|
|
171
|
+
onClick={(e) => void} // row 선택 핸들러 (클릭 가능)
|
|
172
|
+
>
|
|
173
|
+
<ListItem.Title>...</ListItem.Title>
|
|
174
|
+
<ListItem.Description>...</ListItem.Description>
|
|
175
|
+
<ListItem.Meta>
|
|
176
|
+
<time>...</time>
|
|
177
|
+
<ListItem.MetaDivider />
|
|
178
|
+
<span>...</span>
|
|
179
|
+
</ListItem.Meta>
|
|
180
|
+
</ListItem>
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 조립 — 부모 wrapper
|
|
185
|
+
리스트로 묶을 때는 부모 ul에 hairline border만 공급.
|
|
196
186
|
|
|
197
187
|
```css
|
|
198
|
-
|
|
199
|
-
.activityList {
|
|
188
|
+
.feed {
|
|
200
189
|
list-style: none;
|
|
201
|
-
margin: 0;
|
|
202
190
|
padding: 0;
|
|
203
|
-
|
|
204
|
-
flex-direction: column;
|
|
205
|
-
--list-item-hover-bg: transparent; /* 가두는 chrome 회피 */
|
|
206
|
-
}
|
|
207
|
-
.activityList > li {
|
|
208
|
-
padding-block: var(--space-3); /* 세로 호흡은 li가 전담 */
|
|
209
|
-
border-top: 1px solid var(--border-subtle, var(--border));
|
|
210
|
-
}
|
|
211
|
-
.activityList > li:last-child {
|
|
212
|
-
border-bottom: 1px solid var(--border-subtle, var(--border));
|
|
191
|
+
margin: 0;
|
|
213
192
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
padding-block: 0;
|
|
217
|
-
margin-block: 0;
|
|
193
|
+
.feed > li {
|
|
194
|
+
border-bottom: var(--hairline) solid var(--border);
|
|
218
195
|
}
|
|
196
|
+
.feed > li:last-child { border-bottom: none; }
|
|
219
197
|
```
|
|
220
198
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- [[components/
|
|
229
|
-
- [[components/
|
|
230
|
-
- [[components/
|
|
231
|
-
- [[components/
|
|
232
|
-
- [[
|
|
233
|
-
- [[
|
|
234
|
-
- [[
|
|
235
|
-
- [[
|
|
199
|
+
**interactive ListItem**(`href`/`onClick`)은 cursor pointer + focus ring 자동, hit area는 좌우 `space-2`만큼 자동 확장(negative margin으로 정렬 유지).
|
|
200
|
+
|
|
201
|
+
## 관련
|
|
202
|
+
- [[components/avatar]] — leading의 "사람" 자리.
|
|
203
|
+
- [[components/badge]] — Title 앞 inline 상태, leading 상태, trailing 상태 전부.
|
|
204
|
+
- [[components/button]] — trailing CTA.
|
|
205
|
+
- [[components/icon-button]] — trailing chevron/dots.
|
|
206
|
+
- [[components/callout]] — 배경·테두리로 상태 신호가 필요한 블록은 Callout.
|
|
207
|
+
- [[components/event-list-item]] — 날짜가 leading 아이덴티티인 일정·이벤트 리스트는 EventListItem.
|
|
208
|
+
- [[components/table]] — 숫자 비교·정렬이 핵심이면 Table.
|
|
209
|
+
- [[components/metric-card]] — chrome 없이 부모가 분리 담당하는 설계 철학 공유.
|
|
210
|
+
- [[axes/density]] — 전역 Density 축은 테마 레벨, ListItem의 size는 리스트 자리별 조절.
|
|
211
|
+
- [[feedback_no_bullet_separator]] — Meta 라인 구분자는 MetaDivider.
|
|
212
|
+
- [[feedback_no_inline_svg]] — leading 아이콘은 Icon 컴포넌트.
|
|
213
|
+
- [[feedback_date_writing]] — Meta 안 날짜는 "10월 13일" / "12분 전" 형식.
|
|
@@ -82,6 +82,7 @@ type MenuItemAction =
|
|
|
82
82
|
active={boolean} // 현재 페이지/선택 아이템 강조
|
|
83
83
|
disabled={boolean}
|
|
84
84
|
collapsed={boolean} // icon-only. leading 하나만 남기고 label/trailing/shortcut 전부 숨김. (Sidebar가 자동 주입)
|
|
85
|
+
truncateTooltip={boolean} // 라벨이 …로 잘리거나 collapsed일 때 호버 Tooltip으로 풀 라벨 노출. Sidebar.Item 자동 ON. Dropdown/Context/Select 어댑터 안에선 Radix asChild 체인 꼬임 방지로 끄는 걸 권장.
|
|
85
86
|
asChild={boolean} // Radix Slot — <Link>/<a>/DropdownMenu.Item 등에 위임
|
|
86
87
|
onClick={(e) => void}
|
|
87
88
|
/>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- `value`에 두 줄 텍스트, 긴 서술, 통화·단위를 라벨로 분리해 큰 숫자만 외롭게 두지 않는다.
|
|
15
15
|
- `trend`와 `delta`를 둘 다 넘기지 않는다 — `delta`를 넘기면 자동 매핑이 무시된다.
|
|
16
16
|
- `trend="down"`인데 숫자상 "좋은" 하락(예: 미결 결재 ↓)일 때도 색을 success로 뒤집지 않는다. 추세 색은 **방향**만 신호한다. 좋음/나쁨 해석은 `description`이나 라벨에서 한다.
|
|
17
|
-
- 그림자로 depth 쌓지 않는다.
|
|
17
|
+
- 그림자로 depth 쌓지 않는다. `line`만 아주 은은한 `--shadow-sm`을 깐다(hairline 보조). `soft`·`bare`는 그림자 금지.
|
|
18
18
|
- 아이콘을 숫자 옆에 두지 않는다. 아이콘은 라벨 왼쪽 보조 신호.
|
|
19
19
|
|
|
20
20
|
### Fallback
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
| variant | 표면 | 경계 | padding | 용도 |
|
|
51
51
|
|---|---|---|---|---|
|
|
52
|
-
| `line` | `--bg` | `1px solid --border` | 있음 | 운영 콘솔·조밀 그리드. hairline
|
|
52
|
+
| `line` | `--bg` | `1px solid --border` + `--shadow-sm` | 있음 | 운영 콘솔·조밀 그리드. hairline + 아주 은은한 lift. 기본값 |
|
|
53
53
|
| `soft` | `--bg-subtle` | 투명 | 있음 | 서술형 대시보드·분위기 있는 레이아웃. 경계 대신 표면 톤으로 depth |
|
|
54
54
|
| `bare` | 투명 | 없음 | 없음 | pure 좌표 전용. 부모가 hairline/톤으로 분리를 담당하는 strip 안에서만. 혼자 쓰지 않는다 |
|
|
55
55
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- 비동기 로딩 상태(spinner Toast)로 쓰지 않는다 — Toast는 완료 신호 전용.
|
|
25
25
|
- 플로팅이지만 `data-side` 기반 `.m-float` 유틸은 쓰지 않는다 — Toast는 placement별 slide-in 전용 애니.
|
|
26
26
|
- 영구 안내·설명·FAQ 유도에 쓰지 않는다 — [[components/callout]] 영역.
|
|
27
|
+
- `duration`을 [[axes/motion]] 스케일로 조절하지 않는다 — motion은 애니메이션 속도 축이고, duration은 "사용자 읽는 시간" 축. 둘은 독립. motion=`none`이어도 duration은 그대로(툭 나타나 4초 보고 툭 사라짐이 정상).
|
|
27
28
|
|
|
28
29
|
### Fallback
|
|
29
30
|
`tone` 애매하면 `neutral`. `surface` 애매하면 `inverse`. `placement` 애매하면 `bottom-right`. `duration` 애매하면 `4000`.
|