@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
|
@@ -166,6 +166,70 @@
|
|
|
166
166
|
color: var(--fg);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/* Menu trigger ─── segment 자체가 트리거. Link와 같은 xs · ghost 스펙 + ▾ caret.
|
|
170
|
+
active=true일 때는 Current와 같은 톤(--fg-strong) + hover는 그대로 유지. */
|
|
171
|
+
.menuTrigger {
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
gap: var(--space-1);
|
|
176
|
+
height: var(--control-h-xs);
|
|
177
|
+
padding-inline: var(--space-1);
|
|
178
|
+
border: 1px solid transparent;
|
|
179
|
+
border-radius: var(--radius-xs);
|
|
180
|
+
background: transparent;
|
|
181
|
+
color: var(--fg-muted);
|
|
182
|
+
font-family: var(--label-font);
|
|
183
|
+
font-weight: var(--label-weight);
|
|
184
|
+
font-size: var(--label-sm-size);
|
|
185
|
+
line-height: var(--lh-tight);
|
|
186
|
+
letter-spacing: var(--label-tracking);
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
user-select: none;
|
|
189
|
+
min-width: 0;
|
|
190
|
+
transition: var(--transition-colors), var(--transition-ring), var(--transition-press);
|
|
191
|
+
}
|
|
192
|
+
.menuTrigger:hover {
|
|
193
|
+
background: var(--hover);
|
|
194
|
+
color: var(--fg);
|
|
195
|
+
}
|
|
196
|
+
.menuTrigger:active {
|
|
197
|
+
transform: translateY(var(--space-0_5));
|
|
198
|
+
}
|
|
199
|
+
.menuTrigger:focus-visible {
|
|
200
|
+
outline: none;
|
|
201
|
+
box-shadow: var(--ring-shadow);
|
|
202
|
+
}
|
|
203
|
+
.menuTrigger[data-state="open"] {
|
|
204
|
+
background: var(--hover);
|
|
205
|
+
color: var(--fg);
|
|
206
|
+
}
|
|
207
|
+
.menuTriggerActive {
|
|
208
|
+
color: var(--fg-strong);
|
|
209
|
+
}
|
|
210
|
+
.menuTrigger:hover .linkIcon,
|
|
211
|
+
.menuTrigger[data-state="open"] .linkIcon {
|
|
212
|
+
opacity: 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ▾ caret ─── 트리거 끝에 붙는 작은 꼬리표. Separator처럼 반응 없음. */
|
|
216
|
+
.menuCaret {
|
|
217
|
+
display: inline-flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
flex-shrink: 0;
|
|
221
|
+
color: var(--fg-muted);
|
|
222
|
+
opacity: 0.5;
|
|
223
|
+
transition: opacity var(--duration-slow) var(--ease-out);
|
|
224
|
+
}
|
|
225
|
+
.menuCaret > svg {
|
|
226
|
+
display: block;
|
|
227
|
+
}
|
|
228
|
+
.menuTrigger:hover .menuCaret,
|
|
229
|
+
.menuTrigger[data-state="open"] .menuCaret {
|
|
230
|
+
opacity: 0.9;
|
|
231
|
+
}
|
|
232
|
+
|
|
169
233
|
/* Popover surface ─── Select · ContextMenu와 동일한 팝오버 표면.
|
|
170
234
|
ContextMenu.module.css의 .surface를 composes해 시각 규칙 원본을 재사용. */
|
|
171
235
|
.popoverContent {
|
|
@@ -139,72 +139,37 @@ const Current = forwardRef<HTMLSpanElement, BreadcrumbCurrentProps>(
|
|
|
139
139
|
Current.displayName = "Breadcrumb.Current";
|
|
140
140
|
|
|
141
141
|
// ────────────────────────────────────────────────────────────
|
|
142
|
-
//
|
|
143
|
-
// 숨긴 항목 리스트를 연다. items는 필수 — 클릭 불가한 정적 마커는 지원하지 않는다.
|
|
142
|
+
// Shared — Ellipsis·Menu가 공유하는 팝오버 아이템 모델/렌더.
|
|
144
143
|
// ────────────────────────────────────────────────────────────
|
|
145
144
|
|
|
146
|
-
export type
|
|
145
|
+
export type BreadcrumbMenuItem = {
|
|
147
146
|
label: ReactNode;
|
|
148
147
|
href?: string;
|
|
149
148
|
onSelect?: () => void;
|
|
150
149
|
leftIcon?: ReactNode;
|
|
151
150
|
disabled?: boolean;
|
|
151
|
+
/** 현재 선택된 항목. 체크 아이콘이 trailingActions에 자동 삽입. */
|
|
152
|
+
active?: boolean;
|
|
152
153
|
};
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/** 숨긴 항목 리스트. 최소 1개. */
|
|
157
|
-
items: BreadcrumbEllipsisItem[];
|
|
158
|
-
/** 팝오버 side offset (px). 기본 6. */
|
|
159
|
-
sideOffset?: number;
|
|
160
|
-
/** aria-label. 기본 "Show hidden pages". */
|
|
161
|
-
"aria-label"?: string;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const Ellipsis = forwardRef<HTMLButtonElement, BreadcrumbEllipsisProps>(
|
|
165
|
-
(
|
|
166
|
-
{
|
|
167
|
-
className,
|
|
168
|
-
items,
|
|
169
|
-
sideOffset = 6,
|
|
170
|
-
"aria-label": ariaLabel = "Show hidden pages",
|
|
171
|
-
...rest
|
|
172
|
-
},
|
|
173
|
-
ref,
|
|
174
|
-
) => {
|
|
175
|
-
return (
|
|
176
|
-
<RPopover.Root>
|
|
177
|
-
<RPopover.Trigger asChild>
|
|
178
|
-
<button
|
|
179
|
-
ref={ref}
|
|
180
|
-
type="button"
|
|
181
|
-
aria-label={ariaLabel}
|
|
182
|
-
className={[styles.ellipsisTrigger, className ?? ""].filter(Boolean).join(" ")}
|
|
183
|
-
{...rest}
|
|
184
|
-
>
|
|
185
|
-
…
|
|
186
|
-
</button>
|
|
187
|
-
</RPopover.Trigger>
|
|
188
|
-
<EllipsisContent items={items} sideOffset={sideOffset} />
|
|
189
|
-
</RPopover.Root>
|
|
190
|
-
);
|
|
191
|
-
},
|
|
192
|
-
);
|
|
193
|
-
Ellipsis.displayName = "Breadcrumb.Ellipsis";
|
|
155
|
+
/** 하위 호환 — 기존 Ellipsis.items 타입명. */
|
|
156
|
+
export type BreadcrumbEllipsisItem = BreadcrumbMenuItem;
|
|
194
157
|
|
|
195
158
|
/** Portal 하위에서 IconAxis 재주입 — Radix Portal이 트리 컨텍스트를 끊는다. */
|
|
196
|
-
function
|
|
159
|
+
function PopoverMenuContent({
|
|
197
160
|
items,
|
|
198
161
|
sideOffset,
|
|
162
|
+
align = "start",
|
|
199
163
|
}: {
|
|
200
|
-
items:
|
|
164
|
+
items: BreadcrumbMenuItem[];
|
|
201
165
|
sideOffset: number;
|
|
166
|
+
align?: "start" | "center" | "end";
|
|
202
167
|
}) {
|
|
203
168
|
const iconAxis = useIconAxis();
|
|
204
169
|
return (
|
|
205
170
|
<RPopover.Portal>
|
|
206
171
|
<RPopover.Content
|
|
207
|
-
align=
|
|
172
|
+
align={align}
|
|
208
173
|
sideOffset={sideOffset}
|
|
209
174
|
collisionPadding={8}
|
|
210
175
|
className={[styles.popoverContent, "m-float"].join(" ")}
|
|
@@ -222,8 +187,14 @@ function EllipsisContent({
|
|
|
222
187
|
: item.leftIcon,
|
|
223
188
|
]
|
|
224
189
|
: undefined;
|
|
190
|
+
const trailing = item.active ? [<Icon key="check" name="check" size={14} />] : undefined;
|
|
225
191
|
const inner = (
|
|
226
|
-
<MenuItem
|
|
192
|
+
<MenuItem
|
|
193
|
+
size="sm"
|
|
194
|
+
disabled={item.disabled}
|
|
195
|
+
leadingActions={leading}
|
|
196
|
+
trailingActions={trailing}
|
|
197
|
+
>
|
|
227
198
|
{item.label}
|
|
228
199
|
</MenuItem>
|
|
229
200
|
);
|
|
@@ -234,6 +205,7 @@ function EllipsisContent({
|
|
|
234
205
|
<a
|
|
235
206
|
href={item.href}
|
|
236
207
|
role="menuitem"
|
|
208
|
+
aria-current={item.active ? "page" : undefined}
|
|
237
209
|
className={styles.popoverItemLink}
|
|
238
210
|
aria-disabled={item.disabled || undefined}
|
|
239
211
|
>
|
|
@@ -245,6 +217,7 @@ function EllipsisContent({
|
|
|
245
217
|
<button
|
|
246
218
|
type="button"
|
|
247
219
|
role="menuitem"
|
|
220
|
+
aria-current={item.active ? "page" : undefined}
|
|
248
221
|
disabled={item.disabled}
|
|
249
222
|
onClick={() => item.onSelect?.()}
|
|
250
223
|
className={styles.popoverItemButton}
|
|
@@ -263,17 +236,117 @@ function EllipsisContent({
|
|
|
263
236
|
);
|
|
264
237
|
}
|
|
265
238
|
|
|
239
|
+
// ────────────────────────────────────────────────────────────
|
|
240
|
+
// Ellipsis — 항목이 많을 때 중간 축약. 클릭 시 팝오버로 숨긴 항목 리스트를 연다.
|
|
241
|
+
// ────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
export interface BreadcrumbEllipsisProps
|
|
244
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onSelect"> {
|
|
245
|
+
/** 숨긴 항목 리스트. 최소 1개. */
|
|
246
|
+
items: BreadcrumbMenuItem[];
|
|
247
|
+
/** 팝오버 side offset (px). 기본 6. */
|
|
248
|
+
sideOffset?: number;
|
|
249
|
+
/** aria-label. 기본 "Show hidden pages". */
|
|
250
|
+
"aria-label"?: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const Ellipsis = forwardRef<HTMLButtonElement, BreadcrumbEllipsisProps>(
|
|
254
|
+
(
|
|
255
|
+
{
|
|
256
|
+
className,
|
|
257
|
+
items,
|
|
258
|
+
sideOffset = 6,
|
|
259
|
+
"aria-label": ariaLabel = "Show hidden pages",
|
|
260
|
+
...rest
|
|
261
|
+
},
|
|
262
|
+
ref,
|
|
263
|
+
) => {
|
|
264
|
+
return (
|
|
265
|
+
<RPopover.Root>
|
|
266
|
+
<RPopover.Trigger asChild>
|
|
267
|
+
<button
|
|
268
|
+
ref={ref}
|
|
269
|
+
type="button"
|
|
270
|
+
aria-label={ariaLabel}
|
|
271
|
+
className={[styles.ellipsisTrigger, className ?? ""].filter(Boolean).join(" ")}
|
|
272
|
+
{...rest}
|
|
273
|
+
>
|
|
274
|
+
…
|
|
275
|
+
</button>
|
|
276
|
+
</RPopover.Trigger>
|
|
277
|
+
<PopoverMenuContent items={items} sideOffset={sideOffset} />
|
|
278
|
+
</RPopover.Root>
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
Ellipsis.displayName = "Breadcrumb.Ellipsis";
|
|
283
|
+
|
|
284
|
+
// ────────────────────────────────────────────────────────────
|
|
285
|
+
// Menu — segment 자체가 트리거. ▾ caret으로 형제 전환 UI 노출.
|
|
286
|
+
// active=true면 aria-current="page" 유지 (현재 페이지 자리로 쓸 때).
|
|
287
|
+
// ────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
export interface BreadcrumbMenuProps
|
|
290
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onSelect"> {
|
|
291
|
+
/** 트리거에 표시할 라벨. 아이콘 단독 모드 지원 안 함 — 메뉴 트리거는 라벨 필수. */
|
|
292
|
+
children: ReactNode;
|
|
293
|
+
/** 라벨 앞 아이콘. */
|
|
294
|
+
leftIcon?: ReactNode;
|
|
295
|
+
/** 메뉴에 넣을 형제/대체 페이지들. */
|
|
296
|
+
items: BreadcrumbMenuItem[];
|
|
297
|
+
/** 현재 페이지 자리에 쓸 때 true. aria-current="page" + fg-strong 톤. */
|
|
298
|
+
active?: boolean;
|
|
299
|
+
/** 팝오버 side offset (px). 기본 6. */
|
|
300
|
+
sideOffset?: number;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const Menu = forwardRef<HTMLButtonElement, BreadcrumbMenuProps>(
|
|
304
|
+
({ className, children, leftIcon, items, active = false, sideOffset = 6, ...rest }, ref) => {
|
|
305
|
+
const cls = [styles.menuTrigger, active ? styles.menuTriggerActive : "", className ?? ""]
|
|
306
|
+
.filter(Boolean)
|
|
307
|
+
.join(" ");
|
|
308
|
+
return (
|
|
309
|
+
<RPopover.Root>
|
|
310
|
+
<RPopover.Trigger asChild>
|
|
311
|
+
<button
|
|
312
|
+
ref={ref}
|
|
313
|
+
type="button"
|
|
314
|
+
aria-current={active ? "page" : undefined}
|
|
315
|
+
aria-haspopup="menu"
|
|
316
|
+
className={cls}
|
|
317
|
+
{...rest}
|
|
318
|
+
>
|
|
319
|
+
{leftIcon && (
|
|
320
|
+
<span className={styles.linkIcon} aria-hidden>
|
|
321
|
+
{sizedIcon(leftIcon)}
|
|
322
|
+
</span>
|
|
323
|
+
)}
|
|
324
|
+
<span className={styles.linkLabel}>{children}</span>
|
|
325
|
+
<span className={styles.menuCaret} aria-hidden>
|
|
326
|
+
<Icon name="caret-down" size={ICON_SIZE} />
|
|
327
|
+
</span>
|
|
328
|
+
</button>
|
|
329
|
+
</RPopover.Trigger>
|
|
330
|
+
<PopoverMenuContent items={items} sideOffset={sideOffset} />
|
|
331
|
+
</RPopover.Root>
|
|
332
|
+
);
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
Menu.displayName = "Breadcrumb.Menu";
|
|
336
|
+
|
|
266
337
|
// ────────────────────────────────────────────────────────────
|
|
267
338
|
|
|
268
339
|
type BreadcrumbCompound = typeof Root & {
|
|
269
340
|
Link: typeof Link;
|
|
270
341
|
Current: typeof Current;
|
|
271
342
|
Ellipsis: typeof Ellipsis;
|
|
343
|
+
Menu: typeof Menu;
|
|
272
344
|
};
|
|
273
345
|
|
|
274
346
|
const Breadcrumb = Root as BreadcrumbCompound;
|
|
275
347
|
Breadcrumb.Link = Link;
|
|
276
348
|
Breadcrumb.Current = Current;
|
|
277
349
|
Breadcrumb.Ellipsis = Ellipsis;
|
|
350
|
+
Breadcrumb.Menu = Menu;
|
|
278
351
|
|
|
279
352
|
export { Breadcrumb };
|