@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.
@@ -1,149 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- forwardRef,
5
- type AnchorHTMLAttributes,
6
- type HTMLAttributes,
7
- type ReactNode,
8
- type Ref,
9
- } from "react";
10
-
11
- import { Badge } from "../Badge";
12
- import { Icon, type IconName } from "../Icon";
13
-
14
- import styles from "./EventCard.module.css";
15
-
16
- /**
17
- * EventCard — 리스트에 쌓이는 "하나의 일정" 아이템.
18
- * 좌: 날짜 블록 (큰 숫자 + 요일), 우: 제목 + status + meta.
19
- *
20
- * EventChip은 캘린더 셀 안 색 바 전용. 이 컴포넌트는 리스트 카드.
21
- */
22
-
23
- export type EventCardStatus = "upcoming" | "pending" | "completed" | "cancelled";
24
-
25
- export type EventCardMeta = {
26
- icon?: IconName;
27
- label: ReactNode;
28
- };
29
-
30
- type Common = {
31
- date: Date;
32
- title: ReactNode;
33
- status?: EventCardStatus;
34
- statusLabel?: ReactNode;
35
- meta?: EventCardMeta[];
36
- };
37
-
38
- type DivProps = Common &
39
- Omit<HTMLAttributes<HTMLDivElement>, keyof Common> & { href?: undefined };
40
-
41
- type AnchorProps = Common &
42
- Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof Common> & { href: string };
43
-
44
- export type EventCardProps = DivProps | AnchorProps;
45
-
46
- const statusMap: Record<
47
- EventCardStatus,
48
- { badgeStatus: "info" | "warning" | "success" | "destructive"; defaultLabel: string }
49
- > = {
50
- upcoming: { badgeStatus: "info", defaultLabel: "Upcoming" },
51
- pending: { badgeStatus: "warning", defaultLabel: "Pending" },
52
- completed: { badgeStatus: "success", defaultLabel: "Completed" },
53
- cancelled: { badgeStatus: "destructive", defaultLabel: "Cancelled" },
54
- };
55
-
56
- /* locale 고정 없이 사용자 로케일에 맡기면 리스트 안에서 길이가 들쭉날쭉.
57
- 요일 3글자 약어는 en-US 기준으로 맞추고, 대문자 금지 규칙에 따라 수동 소문자화. */
58
- function formatDay(date: Date): string {
59
- return String(date.getDate()).padStart(2, "0");
60
- }
61
- function formatWeekday(date: Date): string {
62
- return date
63
- .toLocaleDateString("en-US", { weekday: "short" })
64
- .toLowerCase();
65
- }
66
-
67
- const EventCard = forwardRef<HTMLElement, EventCardProps>((props, ref) => {
68
- const {
69
- className,
70
- date,
71
- title,
72
- status,
73
- statusLabel,
74
- meta,
75
- ...rest
76
- } = props as Common & { className?: string } & Record<string, unknown>;
77
-
78
- const isLink = typeof (props as AnchorProps).href === "string";
79
-
80
- const rootClass = [
81
- styles.root,
82
- isLink ? styles.interactive : "",
83
- className ?? "",
84
- ]
85
- .filter(Boolean)
86
- .join(" ");
87
-
88
- const mapped = status ? statusMap[status] : undefined;
89
- const effectiveStatusLabel = statusLabel ?? mapped?.defaultLabel;
90
-
91
- const content = (
92
- <>
93
- <div className={styles.dateBlock} aria-hidden>
94
- <span className={styles.day}>{formatDay(date)}</span>
95
- <span className={styles.weekday}>{formatWeekday(date)}</span>
96
- </div>
97
- <div className={styles.divider} aria-hidden />
98
- <div className={styles.body}>
99
- <h3 className={styles.title}>{title}</h3>
100
- {(mapped || meta?.length) && (
101
- <div className={styles.metaRow}>
102
- {mapped && effectiveStatusLabel && (
103
- <Badge variant="soft" status={mapped.badgeStatus}>
104
- {effectiveStatusLabel}
105
- </Badge>
106
- )}
107
- {meta?.map((m, i) => (
108
- <span key={i} className={styles.metaItem}>
109
- {m.icon && (
110
- <span className={styles.metaIcon} aria-hidden>
111
- <Icon name={m.icon} size={12} />
112
- </span>
113
- )}
114
- <span className={styles.metaLabel}>{m.label}</span>
115
- </span>
116
- ))}
117
- </div>
118
- )}
119
- </div>
120
- </>
121
- );
122
-
123
- if (isLink) {
124
- return (
125
- <a
126
- ref={ref as Ref<HTMLAnchorElement>}
127
- className={rootClass}
128
- {...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}
129
- href={(props as AnchorProps).href}
130
- >
131
- {content}
132
- </a>
133
- );
134
- }
135
-
136
- return (
137
- <div
138
- ref={ref as Ref<HTMLDivElement>}
139
- className={rootClass}
140
- {...(rest as HTMLAttributes<HTMLDivElement>)}
141
- >
142
- {content}
143
- </div>
144
- );
145
- });
146
-
147
- EventCard.displayName = "EventCard";
148
-
149
- export { EventCard };
@@ -1,2 +0,0 @@
1
- export { EventCard } from "./EventCard";
2
- export type { EventCardProps, EventCardStatus, EventCardMeta } from "./EventCard";
@@ -1,126 +0,0 @@
1
- # Accordion
2
-
3
- ### 원칙
4
- 여러 섹션을 **접었다 폈다** 하는 구조 컴포넌트. 긴 콘텐츠를 압축해 "지금 중요한 것"만 보여주고, 사용자가 원할 때 세부를 펼친다. 설정 그룹·FAQ·상세 사양·단계별 체크리스트가 전형적인 자리. Radix `@radix-ui/react-accordion`을 감싼 compound 어댑터 — 열림 상태·높이 측정·키보드 네비·ARIA는 Radix가 처리하고, CTDS는 Trigger 레이아웃·caret·divider·card variant만 담당. [[components/collapsible]]과 비슷하지만 **여러 아이템**인 점이 다르다. 하나짜리면 Collapsible로.
5
-
6
- ### Do
7
- - **"지금 보이는 것 vs 숨긴 것"의 비율이 1:3 이상일 때 쓴다.** 전부 다 중요하면 그냥 나열하는 게 낫다. Accordion은 "요약은 다 보이고, 세부가 선택적"인 리스트용.
8
- - **섹션 라벨은 명사 하나로 짧게.** "일반" "알림" "결제" — 긴 문장형은 Accordion 자리가 아님. 사용자는 라벨만 보고 "펼칠지 말지" 판단.
9
- - **`type="single" collapsible` 기본값.** 한 번에 한 섹션만 열림 + 열린 걸 다시 닫을 수 있음. 설정 UI의 표준 동작.
10
- - **여러 섹션 동시 열기는 `type="multiple"`**. 체크리스트·비교·FAQ(사용자가 여러 질문 동시 열람)에만.
11
- - **variant 선택**: 설명 블록 사이에 투명하게 녹여야 하면 `bare`(선·좌우 여백 전부 없음). 설정 그룹·FAQ 리스트는 `plain`(기본). 각 아이템이 독립 블록으로 보여야 하면 `card`.
12
- - **leading 아이콘은 섹션 성격 명시용**. 설정 그룹의 "⚙ 일반 / 🔔 알림 / 💳 결제"처럼 **섹션끼리 아이콘 세트가 균질할 때만** 쓴다. 하나만 아이콘 있고 나머지 없으면 혼란.
13
- - **trailing은 메타 정보**. "3개 변경됨" 배지, "4/8 완료" 숫자 등 사용자가 라벨만 보고 "열어볼 가치"를 판단하도록.
14
- - **첫 섹션 defaultOpen**: 사용자가 가장 자주 보는 섹션이 명확하면 `defaultValue`로 열어둔다. 모르면 전부 닫힌 상태 OK.
15
-
16
- ### Don't
17
- - **중첩 금지.** Accordion 안에 또 Accordion 금지. 깊이가 필요하면 섹션을 쪼개서 **별도 페이지**로 보내거나 좌측 Nav로 분리.
18
- - **본문에 또 다른 Primary CTA 넣지 말 것.** 사용자가 섹션을 "펼쳐야만" 중요한 액션을 볼 수 있는 구조는 발견 실패를 유발. 핵심 CTA는 Accordion 밖 상단에.
19
- - **길이가 들쭉날쭉한 본문 금지.** 어떤 섹션은 1줄, 어떤 섹션은 20줄이면 레이아웃이 요동친다. 긴 섹션은 아예 별도 페이지로.
20
- - **Accordion과 Tabs 혼용 금지.** 둘 다 "숨기기 + 보이기"이지만: **Accordion은 세로 리스트, Tabs는 가로 전환**. 섹션 수가 3개 이하 + 서로 배타적이면 Tabs가 맞음. 섹션 수가 많거나 동시 열람 가능하면 Accordion.
21
- - **애니메이션 커스터마이즈 금지.** Radix의 `--radix-accordion-content-height` 변수에 맞춰 CTDS 모션 토큰(`--duration-base` + `--ease-out`)으로 이미 처리. duration·ease 하드코딩은 [[feedback_no_hardcoded_values]] 위반.
22
- - **라벨에 아이콘만 넣고 텍스트 빼는 것 금지.** 접근성·스캐닝 모두 깨짐.
23
-
24
- ### Fallback
25
- type 애매하면 `single + collapsible`. variant 애매하면 `plain`. size 애매하면 `sm` ([[feedback_default_size_sm]]). defaultValue 애매하면 비워두기(전부 닫힘).
26
-
27
- ### 관련
28
- - [[components/collapsible]] — Accordion의 1-아이템 버전. "더 보기" 토글 하나짜리.
29
- - [[components/tabs]] — 섹션 수 ≤ 3 + 가로 전환이 자연스러우면 Tabs. Accordion은 세로 리스트.
30
- - [[components/menu-item]] — Section variant는 사이드바 섹션 접힘에 사용. 메뉴 트리의 접힘은 Accordion이 아님.
31
- - [[tokens/motion]] — `--duration-base` · `--ease-out`이 slide 애니메이션을 만든다.
32
- - [[tokens/color]] — `--fg` · `--fg-strong` · `--hover` · `--border` · `--border` hairline.
33
-
34
- ### Props 시그니처
35
- ```tsx
36
- <Accordion
37
- type="single | multiple" // Radix 통과. 기본은 소비자가 결정 (단일/다중)
38
- collapsible={boolean} // single에서 열린 걸 다시 닫을 수 있는지. 기본 undefined → false
39
- value={string | string[]} // controlled
40
- defaultValue={string | string[]} // uncontrolled 초기값
41
- onValueChange={(v) => void}
42
- size="sm | md" // default: sm — Trigger 높이 · 폰트 크기
43
- variant="bare | plain | card" // default: plain
44
- >
45
- <Accordion.Item value="unique-id" disabled={boolean}>
46
- <Accordion.Trigger
47
- leadingIcon="gear" // IconName — 간결 사용
48
- leadingActions={AccordionAction[]}// leadingIcon보다 우선
49
- trailingActions={AccordionAction[]}
50
- >
51
- 섹션 라벨
52
- </Accordion.Trigger>
53
- <Accordion.Content>
54
- {/* 자유 콘텐츠 */}
55
- </Accordion.Content>
56
- </Accordion.Item>
57
- </Accordion>
58
- ```
59
-
60
- ### 상태 매트릭스
61
- | 상태 | 시각 |
62
- |---|---|
63
- | closed | 라벨 `--fg`, caret 아래, 본문 숨김 |
64
- | open | 라벨 `--fg-strong`, caret 180도 회전, 본문 slide-down |
65
- | hover (closed) | Trigger 배경 `--hover` + 라벨 `--fg-strong` |
66
- | hover (open) | 배경 변화 없음 — 상태 signal 유지 ([[feedback_hover_over_active]]) |
67
- | focus-visible | `--focus-ring` outline |
68
- | disabled | `--fg-disabled`, cursor not-allowed, 클릭·키보드 모두 꺼짐 |
69
-
70
- ### Size 가이드
71
- | size | 언제 |
72
- |---|---|
73
- | `sm` | **기본.** 설정 패널·FAQ·관리 UI 밀도 높은 자리 |
74
- | `md` | 콘텐츠 사이트의 섹션 리스트·랜딩 FAQ — 라벨이 조금 더 커야 할 때 |
75
-
76
- ### 조합 예시
77
- ```tsx
78
- // 설정 패널 — plain + leading 아이콘 세트
79
- <Accordion type="single" collapsible defaultValue="general">
80
- <Accordion.Item value="general">
81
- <Accordion.Trigger leadingIcon="gear">일반</Accordion.Trigger>
82
- <Accordion.Content>
83
- <Input label="프로필 이름" />
84
- <Switch label="자동 저장" />
85
- </Accordion.Content>
86
- </Accordion.Item>
87
-
88
- <Accordion.Item value="notifications">
89
- <Accordion.Trigger
90
- leadingIcon="bell"
91
- trailingActions={[<Badge key="c" size="sm">3</Badge>]}
92
- >
93
- 알림
94
- </Accordion.Trigger>
95
- <Accordion.Content>...</Accordion.Content>
96
- </Accordion.Item>
97
-
98
- <Accordion.Item value="billing" disabled>
99
- <Accordion.Trigger leadingIcon="info">결제 (준비 중)</Accordion.Trigger>
100
- <Accordion.Content>...</Accordion.Content>
101
- </Accordion.Item>
102
- </Accordion>
103
-
104
- // FAQ — multiple + card variant (여러 질문 동시 열람 OK)
105
- <Accordion type="multiple" variant="card">
106
- <Accordion.Item value="q1">
107
- <Accordion.Trigger>CTDS는 어떻게 설치하나요?</Accordion.Trigger>
108
- <Accordion.Content>npm으로 설치 후 ctds init 실행...</Accordion.Content>
109
- </Accordion.Item>
110
- <Accordion.Item value="q2">
111
- <Accordion.Trigger>테마는 어떻게 바꾸나요?</Accordion.Trigger>
112
- <Accordion.Content>...</Accordion.Content>
113
- </Accordion.Item>
114
- </Accordion>
115
-
116
- // Controlled — 외부 상태에 묶기
117
- const [open, setOpen] = useState<string>("step-1");
118
- <Accordion type="single" value={open} onValueChange={setOpen}>
119
- <Accordion.Item value="step-1">
120
- <Accordion.Trigger trailingActions={[<span key="s">1/4</span>]}>
121
- 기본 정보
122
- </Accordion.Trigger>
123
- <Accordion.Content>...</Accordion.Content>
124
- </Accordion.Item>
125
- </Accordion>
126
- ```
@@ -1,148 +0,0 @@
1
- # CommandPalette
2
-
3
- ### 원칙
4
- ⌘K로 열리는 **전역 명령·검색 창**. 페이지 이동과 자주 쓰는 액션을 키보드만으로 끝내는 자리. 사이드바·메뉴·설정을 뒤지지 않고 "이름으로 바로 간다". VSCode·Linear·Notion·Figma가 공유하는 관용. CTDS는 쉘(Radix Dialog)·검색/필터/네비(cmdk)·시각(MenuItem)을 **얇게 묶은 어댑터**. 상호작용·접근성·fuzzy 매칭은 라이브러리에 맡기고, CTDS는 스킨과 API 다듬기에 집중한다. [[components/context-menu]]·[[components/dropdown-menu]]와 같은 메뉴 패밀리 — 아이템 시각 규칙은 [[components/menu-item]] 하나로 수렴.
5
-
6
- ### Do
7
- - **데이터·관리 UI에선 거의 필수로 넣는다.** 페이지 수가 10개 넘고 액션이 반복될수록 ⌘K 하나로 사용자 숙련도의 천장이 올라간다. 콘텐츠 사이트에선 선택적.
8
- - **두 종류를 섞는다 — 페이지 이동 + 액션.** 그룹 heading으로 명시 분리하면 사용자는 "어디로 갈까"와 "뭘 할까"를 같은 입구에서 결정한다.
9
- - **라벨은 동사·명사 짧게.** 리스트 폭이 넓긴 하지만 한 줄에 자연스레 읽히는 길이로. "선택한 항목을 휴지통으로 이동" 같은 문장형 금지 — 동사 하나로 압축.
10
- - **아이콘은 의미가 명확할 때만**. [[components/context-menu]]와 같은 규칙 — 억지 아이콘보다 라벨만 두는 게 낫다. 아이콘 이름은 [[web/components/Icon/Icon.tsx]] 세트에서만.
11
- - **단축키는 실제 심볼로 표시.** `⌘N` · `⇧⌘P` · `⌫`. uppercase 금지 — [[feedback_no_uppercase]].
12
- - **선택 후 자동 닫힘.** `onSelect` 안에서 라우팅/액션을 실행한 다음 `onOpenChange(false)`를 명시적으로 호출. cmdk는 자동으로 닫지 않는다.
13
- - **열기는 Hook으로 위임.** `useCommandPaletteHotkey(() => setOpen((v) => !v))` 한 줄이면 ⌘K/Ctrl+K가 어디서든 토글된다. 입력 중에도 발화 — 검색은 어디서나 열려야.
14
- - **Empty state는 꼭 넣는다.** 결과 0일 때 그냥 사라지면 사용자는 "뭐가 잘못됐나" 혼란. 짧은 한 줄로 "결과 없음" 명시, 필요하면 "다른 검색어" 유도.
15
- - **최근 사용·즐겨찾기는 상단 그룹**. 입력이 비었을 때만 노출. 입력이 생기면 그 그룹은 숨기거나 가중치 낮게.
16
- - **그룹은 3~5개 이하로.** 더 많으면 스캐닝이 무너진다. "페이지 / 액션 / 설정" 정도가 상한.
17
-
18
- ### Don't
19
- - **핵심 플로우를 ⌘K 뒤에만 두지 말 것.** 신규 가입·결제 같은 CTA는 눈에 보이는 자리에. 팔레트는 **알고 있는 사용자용 지름길**이지 발견 입구가 아니다.
20
- - 아이템에 큰 썸네일·멀티라인 설명 금지. 리스트 밀도가 핵심 — 설명이 필요하면 `description` prop 한 줄까지. 그보다 길면 결과 페이지로.
21
- - 트리거 버튼을 "Open Palette"라고 라벨 붙이지 말 것. 사용자는 그 용어를 모른다. **「검색…」 + ⌘K 힌트**가 관용.
22
- - 열림 상태로 영구 고정 금지 (`defaultOpen` 패턴 등). 팔레트는 **임시 입구** — 상주하는 UI는 사이드바·툴바.
23
- - Modal과 혼용 금지. 결정·입력 다이얼로그는 [[components/modal]], 전역 명령 검색은 Palette. 서로 포커스 레벨이 다르다.
24
- - 색·radius·shadow 하드코딩 금지 — [[feedback_no_hardcoded_values]]. 쉘 시각은 CommandPalette CSS가 토큰 경유로 이미 처리.
25
-
26
- ### Fallback
27
- size 애매하면 `md`. 그룹 분류 애매하면 "액션" 하나로. 트리거 UI 애매하면 상단 툴바의 **"검색… ⌘K" 버튼** — Button `variant="outline"` + `leftIcon={<Icon name="search" />}` + `rightIcon={<Shortcut>⌘K</Shortcut>}`.
28
-
29
- ### 관련
30
- - [[components/menu-item]] — 아이템 시각·슬롯·active 규칙의 원본. CommandPalette.Item은 이 위에 cmdk `data-selected`를 매핑.
31
- - [[components/modal]] — 쉘 상호작용 비교군. Modal은 바깥 클릭 안 닫힘·중앙 배치, Palette는 바깥 클릭 닫힘·상단 배치.
32
- - [[components/dropdown-menu]] · [[components/context-menu]] — 같은 메뉴 패밀리. 트리거만 다름(클릭 vs 우클릭 vs 전역 단축키).
33
- - [[components/shortcut]] — ⌘K 힌트는 Shortcut 컴포넌트로.
34
- - [[components/input]] — Palette의 Input은 전용 스타일. 일반 폼 Input과 섞지 말 것.
35
- - [[tokens/color]] · [[tokens/shadow]] — `--bg` · `--border` · `--shadow-xl` · `--scrim`이 쉘 시각을 만든다.
36
-
37
- ### Props 시그니처
38
- ```tsx
39
- <CommandPalette
40
- open={boolean}
41
- onOpenChange={(open: boolean) => void}
42
- size="sm | md" // default: md — 아이템 기본 size
43
- label="명령 검색" // 스크린리더용. 다이얼로그 접근성 네임
44
- >
45
- <CommandPalette.Input
46
- placeholder="검색..."
47
- leadingIcon="search | IconName | null" // default: search
48
- />
49
- <CommandPalette.List>
50
- <CommandPalette.Empty>결과가 없어요</CommandPalette.Empty>
51
-
52
- <CommandPalette.Group heading="페이지">
53
- <CommandPalette.Item
54
- leadingIcon="house" // 또는 leadingActions={[...]}
55
- shortcut="⌘,"
56
- description="보조 설명 한 줄"
57
- disabled={boolean}
58
- onSelect={() => void} // 자동 닫힘 ❌ — 안에서 onOpenChange(false) 명시
59
- >
60
- 대시보드
61
- </CommandPalette.Item>
62
- </CommandPalette.Group>
63
-
64
- <CommandPalette.Separator />
65
-
66
- <CommandPalette.Group heading="액션">...</CommandPalette.Group>
67
- </CommandPalette.List>
68
- </CommandPalette>
69
-
70
- // 전역 단축키 Hook
71
- useCommandPaletteHotkey(
72
- () => setOpen((v) => !v),
73
- { key: "k", mod: true, shift: false, alt: false, disabled: false },
74
- );
75
- ```
76
-
77
- ### 상태 매트릭스
78
- | 상태 | 시각 |
79
- |---|---|
80
- | 열림 · 닫힘 | 쉘은 Radix Dialog 애니메이션(scale 0.98→1 + fade). 상단 20vh 고정 배치. |
81
- | 아이템 highlight (↑↓·마우스) | cmdk `[data-selected="true"]` → MenuItem fill 스타일(`--hover` 배경 + `fg-strong`) 자동 매핑 |
82
- | 입력 필터링 | cmdk fuzzy 매칭. 매치되는 아이템만 표시, 그룹 heading은 매치된 아이템이 있을 때만 노출 |
83
- | Empty | 매치 0일 때 `Empty` children 렌더 — 다른 아이템 전부 숨김 |
84
- | disabled 아이템 | `fg-disabled` + 선택/엔터 모두 꺼짐 (cmdk + MenuItem 공통 처리) |
85
-
86
- ### Size 가이드
87
- | size | 언제 |
88
- |---|---|
89
- | `sm` | 밀도 높은 관리 UI · 결과가 많이 나와야 할 때 |
90
- | `md` | **기본.** 일반 ⌘K |
91
-
92
- ### 조합 예시
93
- ```tsx
94
- // 기본 — 페이지 + 액션 두 그룹
95
- const [open, setOpen] = useState(false);
96
- const router = useRouter();
97
- useCommandPaletteHotkey(() => setOpen((v) => !v));
98
-
99
- function go(path: string) {
100
- router.push(path);
101
- setOpen(false);
102
- }
103
-
104
- <CommandPalette open={open} onOpenChange={setOpen}>
105
- <CommandPalette.Input placeholder="검색 또는 명령어 입력" />
106
- <CommandPalette.List>
107
- <CommandPalette.Empty>결과가 없어요</CommandPalette.Empty>
108
- <CommandPalette.Group heading="페이지">
109
- <CommandPalette.Item leadingIcon="house" onSelect={() => go("/dashboard")}>
110
- 대시보드
111
- </CommandPalette.Item>
112
- <CommandPalette.Item leadingIcon="gear" shortcut="⌘," onSelect={() => go("/settings")}>
113
- 설정
114
- </CommandPalette.Item>
115
- </CommandPalette.Group>
116
- <CommandPalette.Separator />
117
- <CommandPalette.Group heading="액션">
118
- <CommandPalette.Item leadingIcon="plus" shortcut="⌘N" onSelect={newDoc}>
119
- 새 문서
120
- </CommandPalette.Item>
121
- </CommandPalette.Group>
122
- </CommandPalette.List>
123
- </CommandPalette>
124
-
125
- // 상단 툴바 트리거 — 「검색… ⌘K」 관용
126
- <Button
127
- variant="outline" size="sm"
128
- leftIcon={<Icon name="search" />}
129
- rightIcon={<Shortcut>⌘K</Shortcut>}
130
- onClick={() => setOpen(true)}
131
- >
132
- 검색
133
- </Button>
134
-
135
- // 최근 사용 — 입력이 비었을 때만
136
- <CommandPalette.List>
137
- {query === "" && (
138
- <CommandPalette.Group heading="최근">
139
- {recents.map((r) => (
140
- <CommandPalette.Item key={r.id} leadingIcon="clock" onSelect={() => go(r.href)}>
141
- {r.label}
142
- </CommandPalette.Item>
143
- ))}
144
- </CommandPalette.Group>
145
- )}
146
- <CommandPalette.Group heading="페이지">...</CommandPalette.Group>
147
- </CommandPalette.List>
148
- ```
@@ -1,78 +0,0 @@
1
- # EventCard
2
-
3
- ### 원칙
4
- 리스트에 쌓이는 **하나의 일정** 아이템. 좌측 날짜 블록(큰 숫자 + 요일) · 세로 hairline · 우측 제목 + status Badge + meta 라인의 고정 anatomy. 캘린더 셀 안 색 바는 [[components/event-chip]]의 몫이고, 이 컴포넌트는 "다가오는 일정 리스트" "결재/행사 리스트"처럼 시간을 축으로 수직 나열되는 자리의 1 레코드.
5
-
6
- 날짜 숫자는 2자리 기준으로 트랙이 고정되어 리스트가 세로로 쌓여도 제목 시작점이 흔들리지 않는다. status는 enum 4종(upcoming/pending/completed/cancelled)으로 받아 [[components/badge]] soft + 상태색으로 자동 매핑 — 호출부가 Badge를 직접 조립하지 않는다.
7
-
8
- ### Do
9
- - 한 아이템엔 **한 일정**. `date` 하나 + `title` 하나 + 선택적 `status` + `meta[]`.
10
- - status는 enum 4종으로 넘긴다 — 자유 색/텍스트 Badge 조립 금지. 라벨만 override 필요하면 `statusLabel`.
11
- - 메타 라인 아이템 사이는 **짧은 세로 hairline**으로 분리 (bullet·중점·|·하이픈 금지).
12
- - 행 전체를 라우팅으로 쓰려면 `href` — hover bg + focus ring 자동.
13
- - 리스트로 쌓을 때 세로 gap은 `--space-2` ~ `--space-3`. 카드 자체가 chrome(border+radius+padding)을 들고 있어 부모는 수직 flex만.
14
-
15
- ### Don't
16
- - [HARD] 안에 다시 Card chrome(추가 border/shadow) 쌓지 않는다 — "리스트 안 카드 안 카드"가 되어 depth가 깨진다.
17
- - [HARD] status를 자유 색으로 덮어쓰지 않는다. "좋음=green" 같은 가치판단 반전 금지. 색은 **상태 enum**이 결정.
18
- - [HARD] 날짜 요일 라벨을 UPPERCASE로 강제하지 않는다 — 소문자 유지("fri", "sun"). [[feedback_no_uppercase]].
19
- - [HARD] 메타 사이 bullet/중점/`|`/em-dash 삽입 금지. 짧은 세로 hairline만.
20
- - 날짜 숫자 외의 자리에 큰 숫자(가격·카운트) 추가 금지 — 좌측 트랙은 "날짜" 자리 하나.
21
- - 2줄 이상 서술/본문을 title에 넣지 않는다 — 1줄 ellipsis. 본문이 길면 상세 뷰로.
22
- - 날짜 블록을 Avatar/Icon으로 대체하지 않는다 — 그러면 이건 [[components/list-item]].
23
-
24
- ### Fallback
25
- `status` 애매하면 생략(Badge 안 뜸). `statusLabel`은 enum 기본 라벨(Upcoming/Pending/Completed/Cancelled) 사용. 라우팅 자리 아니면 `href` 생략.
26
-
27
- ### 관련
28
- - [[components/event-chip]] — 혼동 금지. EventChip은 캘린더 셀 안 색 바, EventCard는 리스트 카드.
29
- - [[components/list-item]] — 날짜 자리에 Avatar/Icon이 오는 "누가 뭘 했다" 피드는 ListItem.
30
- - [[components/metric-card]] — 같은 Card chrome 규칙(그림자 금지, line 기본).
31
- - [[components/badge]] — status 매핑은 Badge soft + status.
32
- - [[components/icon]] — meta 아이콘.
33
- - [[tokens/color]] — `--bg` · `--border` · `--fg-strong` · `--fg-muted` · `--hover`.
34
- - [[axes/density]] — compact에선 세로 padding 줄어 리스트가 조밀해짐.
35
- - [[feedback_no_bullet_separator]] — meta 사이 분리 규칙.
36
- - [[feedback_no_uppercase]] — 요일 소문자 유지.
37
- - [[feedback_font_weight_cap]] — title은 sm이라 medium, day는 2xl이라 semibold.
38
-
39
- ### Props 시그니처
40
- ```tsx
41
- <EventCard
42
- date={Date} // required — 날짜 블록으로 자동 포맷
43
- title={ReactNode} // required — 1줄 ellipsis
44
- status="upcoming | pending | completed | cancelled" // Badge soft + 상태색 자동
45
- statusLabel={ReactNode} // 기본 라벨 override (ex: 한국어 "예정")
46
- meta={{ icon?: IconName; label: ReactNode }[]} // 메타 라인. 사이는 세로 hairline으로 자동 분리
47
- href={string} // 있으면 <a> + hover/focus ring 활성
48
- />
49
- ```
50
-
51
- ### Variant 매트릭스
52
- variant 자체는 없음. 상태는 `status` enum이 담당.
53
-
54
- | status | Badge status | 기본 라벨 | 쓰임 |
55
- |---|---|---|---|
56
- | `upcoming` | `info` | Upcoming | 다가올 일정 (기본 info 톤) |
57
- | `pending` | `warning` | Pending | 승인·확정 대기 |
58
- | `completed` | `success` | Completed | 종료·완료 |
59
- | `cancelled` | `destructive` | Cancelled | 취소·무산 |
60
-
61
- ### 조합 예시
62
- ```tsx
63
- <section>
64
- {events.map((e) => (
65
- <EventCard
66
- key={e.id}
67
- href={`/events/${e.id}`}
68
- date={e.date}
69
- title={e.title}
70
- status={e.status}
71
- meta={[
72
- { icon: "calendar-dots", label: e.when },
73
- { icon: "compass", label: e.where },
74
- ]}
75
- />
76
- ))}
77
- </section>
78
- ```