@conscience-technology/ctds 0.1.9 → 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 +82 -45
- package/assets/components/ChatInput/ChatInput.tsx +46 -34
- 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/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 +48 -3
- 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/package.json +1 -1
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { forwardRef, type ComponentPropsWithoutRef } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Infinity as PhInfinity,
|
|
6
6
|
CaretDown as PhCaretDown,
|
|
7
7
|
} from "@phosphor-icons/react";
|
|
8
8
|
import styles from "./ChatInput.module.css";
|
|
9
9
|
|
|
10
|
-
interface ModelSelectorProps {
|
|
10
|
+
interface ModelSelectorProps extends ComponentPropsWithoutRef<"button"> {
|
|
11
11
|
label: string;
|
|
12
12
|
/** true면 라벨 숨기고 아이콘만 (SingleLine에서 공간 부족할 때). */
|
|
13
13
|
compact?: boolean;
|
|
14
|
-
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
15
|
-
disabled?: boolean;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
compact = false,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
}
|
|
16
|
+
/* forwardRef — DropdownMenu.Trigger asChild 위임을 위해 ref·props 전달 가능해야. */
|
|
17
|
+
export const ModelSelector = forwardRef<HTMLButtonElement, ModelSelectorProps>(
|
|
18
|
+
function ModelSelector({ label, compact = false, ...rest }, ref) {
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
ref={ref}
|
|
22
|
+
type="button"
|
|
23
|
+
className={styles.modelBtn}
|
|
24
|
+
data-compact={compact || undefined}
|
|
25
|
+
aria-label={compact ? `모델: ${label}` : undefined}
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
<PhInfinity className={styles.modelLead} weight="regular" aria-hidden />
|
|
29
|
+
{!compact ? <span className={styles.modelLabel}>{label}</span> : null}
|
|
30
|
+
{!compact ? (
|
|
31
|
+
<PhCaretDown className={styles.modelCaret} weight="bold" aria-hidden />
|
|
32
|
+
) : null}
|
|
33
|
+
</button>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* CodeBlock — AIMessage 안의 코드 블록. 상단 언어 라벨 + copy, 본문 pre+code. */
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
position: relative;
|
|
5
|
+
background: var(--primary-subtle);
|
|
6
|
+
border-radius: var(--radius-md);
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
width: 100%;
|
|
9
|
+
min-width: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.header {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
padding: var(--space-2) var(--space-3);
|
|
17
|
+
color: var(--fg-muted);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.lang {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--space-1);
|
|
24
|
+
font-family: var(--font-code);
|
|
25
|
+
font-size: var(--text-xs);
|
|
26
|
+
}
|
|
27
|
+
.langIcon {
|
|
28
|
+
width: var(--space-4);
|
|
29
|
+
height: var(--space-4);
|
|
30
|
+
}
|
|
31
|
+
.langLabel {
|
|
32
|
+
line-height: 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.copyBtn {
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
padding: 0;
|
|
40
|
+
border: 0;
|
|
41
|
+
background: var(--bg-muted);
|
|
42
|
+
color: var(--fg-muted);
|
|
43
|
+
border-radius: var(--radius-sm);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
width: var(--space-6);
|
|
46
|
+
height: var(--space-6);
|
|
47
|
+
transition: var(--transition-colors);
|
|
48
|
+
}
|
|
49
|
+
.copyBtn:hover {
|
|
50
|
+
background: var(--bg);
|
|
51
|
+
color: var(--fg);
|
|
52
|
+
}
|
|
53
|
+
.copyBtn:focus-visible {
|
|
54
|
+
outline: none;
|
|
55
|
+
box-shadow: var(--ring);
|
|
56
|
+
}
|
|
57
|
+
.copyIcon {
|
|
58
|
+
width: 60%;
|
|
59
|
+
height: 60%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* 코드 본문 — pre + mono font. 가로 스크롤 가능. */
|
|
63
|
+
.code {
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: var(--space-2) var(--space-4) var(--space-4);
|
|
66
|
+
font-family: var(--font-code);
|
|
67
|
+
font-size: var(--text-sm);
|
|
68
|
+
line-height: 1.6;
|
|
69
|
+
color: var(--fg-strong);
|
|
70
|
+
overflow-x: auto;
|
|
71
|
+
white-space: pre;
|
|
72
|
+
word-wrap: normal;
|
|
73
|
+
}
|
|
74
|
+
.code code {
|
|
75
|
+
font: inherit;
|
|
76
|
+
color: inherit;
|
|
77
|
+
background: transparent;
|
|
78
|
+
padding: 0;
|
|
79
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState, type HTMLAttributes, type ReactNode } from "react";
|
|
4
|
+
import {
|
|
5
|
+
CodeSimple as PhCodeSimple,
|
|
6
|
+
Copy as PhCopy,
|
|
7
|
+
Check as PhCheck,
|
|
8
|
+
} from "@phosphor-icons/react";
|
|
9
|
+
import { Tooltip } from "@/components/ctds/Tooltip";
|
|
10
|
+
import styles from "./CodeBlock.module.css";
|
|
11
|
+
|
|
12
|
+
export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
13
|
+
/** 보여줄 코드 문자열. 하이라이팅은 CodeBlock이 따로 하지 않음 — 사용처가 ReactNode(span 조합)로 전달해도 됨. */
|
|
14
|
+
code: string | ReactNode;
|
|
15
|
+
/** 상단 언어 라벨 (예: "html", "tsx"). 없으면 숨김. */
|
|
16
|
+
language?: string;
|
|
17
|
+
/** copy 버튼 클릭 시 호출. 미지정이면 clipboard API로 자동 복사. */
|
|
18
|
+
onCopyCode?: (code: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CodeBlock({
|
|
22
|
+
code,
|
|
23
|
+
language,
|
|
24
|
+
onCopyCode,
|
|
25
|
+
className,
|
|
26
|
+
...rest
|
|
27
|
+
}: CodeBlockProps) {
|
|
28
|
+
const [copied, setCopied] = useState(false);
|
|
29
|
+
const codeText = typeof code === "string" ? code : "";
|
|
30
|
+
|
|
31
|
+
const handleCopy = useCallback(() => {
|
|
32
|
+
if (onCopyCode) {
|
|
33
|
+
onCopyCode(codeText);
|
|
34
|
+
} else if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
35
|
+
navigator.clipboard.writeText(codeText).catch(() => {});
|
|
36
|
+
}
|
|
37
|
+
setCopied(true);
|
|
38
|
+
const t = setTimeout(() => setCopied(false), 1500);
|
|
39
|
+
return () => clearTimeout(t);
|
|
40
|
+
}, [codeText, onCopyCode]);
|
|
41
|
+
|
|
42
|
+
const cls = [styles.root, className].filter(Boolean).join(" ");
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={cls} {...rest}>
|
|
46
|
+
<div className={styles.header}>
|
|
47
|
+
<div className={styles.lang}>
|
|
48
|
+
<PhCodeSimple className={styles.langIcon} weight="regular" aria-hidden />
|
|
49
|
+
{language ? <span className={styles.langLabel}>{language}</span> : null}
|
|
50
|
+
</div>
|
|
51
|
+
<Tooltip content={copied ? "복사됨" : "복사"}>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
className={styles.copyBtn}
|
|
55
|
+
onClick={handleCopy}
|
|
56
|
+
aria-label={copied ? "복사됨" : "코드 복사"}
|
|
57
|
+
>
|
|
58
|
+
{copied ? (
|
|
59
|
+
<PhCheck className={styles.copyIcon} weight="bold" />
|
|
60
|
+
) : (
|
|
61
|
+
<PhCopy className={styles.copyIcon} weight="regular" />
|
|
62
|
+
)}
|
|
63
|
+
</button>
|
|
64
|
+
</Tooltip>
|
|
65
|
+
</div>
|
|
66
|
+
<pre className={styles.code}>
|
|
67
|
+
<code>{code}</code>
|
|
68
|
+
</pre>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -181,6 +181,8 @@ type ItemPassthrough = {
|
|
|
181
181
|
leadingActions?: MenuItemAction[];
|
|
182
182
|
trailingActions?: MenuItemAction[];
|
|
183
183
|
shortcut?: string;
|
|
184
|
+
/** 라벨 아래 보조 설명 한 줄. MenuItem.description으로 그대로 전달. */
|
|
185
|
+
description?: ReactNode;
|
|
184
186
|
disabled?: boolean;
|
|
185
187
|
children?: ReactNode;
|
|
186
188
|
};
|
|
@@ -196,6 +198,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(function ContextMenuItem(
|
|
|
196
198
|
leadingActions,
|
|
197
199
|
trailingActions,
|
|
198
200
|
shortcut,
|
|
201
|
+
description,
|
|
199
202
|
disabled,
|
|
200
203
|
children,
|
|
201
204
|
...rest
|
|
@@ -212,6 +215,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(function ContextMenuItem(
|
|
|
212
215
|
leadingActions={leadingActions}
|
|
213
216
|
trailingActions={trailingActions}
|
|
214
217
|
shortcut={shortcut}
|
|
218
|
+
description={description}
|
|
215
219
|
disabled={disabled}
|
|
216
220
|
>
|
|
217
221
|
{children}
|
|
@@ -233,6 +237,7 @@ const CheckboxItem = forwardRef<HTMLDivElement, CheckboxItemProps>(function Cont
|
|
|
233
237
|
leadingActions = [],
|
|
234
238
|
trailingActions,
|
|
235
239
|
shortcut,
|
|
240
|
+
description,
|
|
236
241
|
disabled,
|
|
237
242
|
checked,
|
|
238
243
|
onCheckedChange,
|
|
@@ -276,6 +281,7 @@ const CheckboxItem = forwardRef<HTMLDivElement, CheckboxItemProps>(function Cont
|
|
|
276
281
|
leadingActions={composedLeading}
|
|
277
282
|
trailingActions={trailingActions}
|
|
278
283
|
shortcut={shortcut}
|
|
284
|
+
description={description}
|
|
279
285
|
disabled={disabled}
|
|
280
286
|
>
|
|
281
287
|
{children}
|
|
@@ -297,6 +303,7 @@ const RadioItem = forwardRef<HTMLDivElement, RadioItemProps>(function ContextMen
|
|
|
297
303
|
leadingActions = [],
|
|
298
304
|
trailingActions,
|
|
299
305
|
shortcut,
|
|
306
|
+
description,
|
|
300
307
|
disabled,
|
|
301
308
|
children,
|
|
302
309
|
...rest
|
|
@@ -322,6 +329,7 @@ const RadioItem = forwardRef<HTMLDivElement, RadioItemProps>(function ContextMen
|
|
|
322
329
|
leadingActions={composedLeading}
|
|
323
330
|
trailingActions={trailingActions}
|
|
324
331
|
shortcut={shortcut}
|
|
332
|
+
description={description}
|
|
325
333
|
disabled={disabled}
|
|
326
334
|
>
|
|
327
335
|
{children}
|
|
@@ -337,7 +345,7 @@ export interface SubTriggerProps
|
|
|
337
345
|
Omit<ItemPassthrough, "shortcut"> {}
|
|
338
346
|
|
|
339
347
|
const SubTrigger = forwardRef<HTMLDivElement, SubTriggerProps>(function ContextMenuSubTrigger(
|
|
340
|
-
{ size, variant, leadingActions, trailingActions, disabled, children, ...rest },
|
|
348
|
+
{ size, variant, leadingActions, trailingActions, description, disabled, children, ...rest },
|
|
341
349
|
ref,
|
|
342
350
|
) {
|
|
343
351
|
const effectiveSize = useMenuSize(size);
|
|
@@ -349,6 +357,7 @@ const SubTrigger = forwardRef<HTMLDivElement, SubTriggerProps>(function ContextM
|
|
|
349
357
|
variant={variant}
|
|
350
358
|
leadingActions={leadingActions}
|
|
351
359
|
trailingActions={trailingActions}
|
|
360
|
+
description={description}
|
|
352
361
|
hasSubmenu
|
|
353
362
|
disabled={disabled}
|
|
354
363
|
>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* DropdownMenu — Radix DropdownMenu 위에 얹은 시각 레이어.
|
|
2
|
+
표면·분리선·indicator 전부 ContextMenu CSS에서 composes해 재사용 —
|
|
3
|
+
두 메뉴의 시각이 한 소스에서 흐르도록. 여기선 transform-origin·z-index 등
|
|
4
|
+
dropdown-menu primitive 전용 CSS 변수만 지정. */
|
|
5
|
+
|
|
6
|
+
.content {
|
|
7
|
+
composes: content from "../ContextMenu/ContextMenu.module.css";
|
|
8
|
+
z-index: var(--z-popover);
|
|
9
|
+
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.content[data-radix-dropdown-menu-sub-content] {
|
|
13
|
+
transform-origin: var(--radix-dropdown-menu-sub-content-transform-origin);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.separator {
|
|
17
|
+
composes: separator from "../ContextMenu/ContextMenu.module.css";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.checkIndicator {
|
|
21
|
+
composes: checkIndicator from "../ContextMenu/ContextMenu.module.css";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.radioIndicator {
|
|
25
|
+
composes: radioIndicator from "../ContextMenu/ContextMenu.module.css";
|
|
26
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DropdownMenu ─ 버튼 클릭으로 열리는 메뉴.
|
|
5
|
+
* Radix `@radix-ui/react-dropdown-menu` 위에 얇게 얹은 compound 컴포넌트.
|
|
6
|
+
*
|
|
7
|
+
* ContextMenu와의 관계
|
|
8
|
+
* - 시각 표면(.surface) · MenuItem 어댑터 패턴 · Checkbox/Radio 인디케이터 전부 동일.
|
|
9
|
+
* - 차이는 "트리거 방식"뿐: ContextMenu는 우클릭, DropdownMenu는 좌클릭/스페이스/엔터.
|
|
10
|
+
* - 두 컴포넌트의 Content·Item·CheckboxItem·RadioItem·SubTrigger API는 1:1 미러.
|
|
11
|
+
*
|
|
12
|
+
* 소비 표면
|
|
13
|
+
* <DropdownMenu>
|
|
14
|
+
* <DropdownMenu.Trigger asChild>
|
|
15
|
+
* <Button>Open</Button>
|
|
16
|
+
* </DropdownMenu.Trigger>
|
|
17
|
+
* <DropdownMenu.Content>
|
|
18
|
+
* <DropdownMenu.Item onSelect={...}>복사</DropdownMenu.Item>
|
|
19
|
+
* <DropdownMenu.Separator />
|
|
20
|
+
* <DropdownMenu.CheckboxItem checked={grid} onCheckedChange={setGrid}>
|
|
21
|
+
* 그리드
|
|
22
|
+
* </DropdownMenu.CheckboxItem>
|
|
23
|
+
* </DropdownMenu.Content>
|
|
24
|
+
* </DropdownMenu>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as RDM from "@radix-ui/react-dropdown-menu";
|
|
28
|
+
import {
|
|
29
|
+
createContext,
|
|
30
|
+
forwardRef,
|
|
31
|
+
useContext,
|
|
32
|
+
type ComponentPropsWithoutRef,
|
|
33
|
+
type ReactNode,
|
|
34
|
+
} from "react";
|
|
35
|
+
|
|
36
|
+
import { Icon, IconAxisProvider, useIconAxis } from "../Icon";
|
|
37
|
+
import { MenuItem, type MenuItemAction, type MenuItemSize, type MenuItemVariant } from "../MenuItem";
|
|
38
|
+
import styles from "./DropdownMenu.module.css";
|
|
39
|
+
|
|
40
|
+
/* Size context — Content가 하위 Item 기본 size 지정. 개별 아이템 size가 우선. */
|
|
41
|
+
const SizeContext = createContext<MenuItemSize>("md");
|
|
42
|
+
function useMenuSize(override?: MenuItemSize): MenuItemSize {
|
|
43
|
+
const ctx = useContext(SizeContext);
|
|
44
|
+
return override ?? ctx;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Root · Trigger ─────────────────────────────────────────── */
|
|
48
|
+
|
|
49
|
+
type RootProps = ComponentPropsWithoutRef<typeof RDM.Root>;
|
|
50
|
+
const Root = (props: RootProps) => <RDM.Root {...props} />;
|
|
51
|
+
|
|
52
|
+
type TriggerProps = ComponentPropsWithoutRef<typeof RDM.Trigger>;
|
|
53
|
+
const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(function DropdownMenuTrigger(
|
|
54
|
+
props,
|
|
55
|
+
ref,
|
|
56
|
+
) {
|
|
57
|
+
return <RDM.Trigger ref={ref} {...props} />;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/* Content ─────────────────────────────────────────────────────────── */
|
|
61
|
+
|
|
62
|
+
export interface ContentProps extends ComponentPropsWithoutRef<typeof RDM.Content> {
|
|
63
|
+
size?: MenuItemSize;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const Content = forwardRef<HTMLDivElement, ContentProps>(function DropdownMenuContent(
|
|
67
|
+
{ className, size = "md", collisionPadding = 8, sideOffset = 6, children, ...rest },
|
|
68
|
+
ref,
|
|
69
|
+
) {
|
|
70
|
+
const iconAxis = useIconAxis();
|
|
71
|
+
return (
|
|
72
|
+
<RDM.Portal>
|
|
73
|
+
<RDM.Content
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={[styles.content, "m-float", className ?? ""].filter(Boolean).join(" ")}
|
|
76
|
+
collisionPadding={collisionPadding}
|
|
77
|
+
sideOffset={sideOffset}
|
|
78
|
+
data-size={size}
|
|
79
|
+
{...rest}
|
|
80
|
+
>
|
|
81
|
+
<IconAxisProvider value={iconAxis}>
|
|
82
|
+
<SizeContext.Provider value={size}>{children}</SizeContext.Provider>
|
|
83
|
+
</IconAxisProvider>
|
|
84
|
+
</RDM.Content>
|
|
85
|
+
</RDM.Portal>
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/* SubContent ──────────────────────────────────────────────────────── */
|
|
90
|
+
|
|
91
|
+
export interface SubContentProps extends ComponentPropsWithoutRef<typeof RDM.SubContent> {
|
|
92
|
+
size?: MenuItemSize;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const SubContent = forwardRef<HTMLDivElement, SubContentProps>(function DropdownMenuSubContent(
|
|
96
|
+
{ className, size, collisionPadding = 8, children, ...rest },
|
|
97
|
+
ref,
|
|
98
|
+
) {
|
|
99
|
+
const parentSize = useContext(SizeContext);
|
|
100
|
+
const effective = size ?? parentSize;
|
|
101
|
+
const iconAxis = useIconAxis();
|
|
102
|
+
return (
|
|
103
|
+
<RDM.Portal>
|
|
104
|
+
<RDM.SubContent
|
|
105
|
+
ref={ref}
|
|
106
|
+
className={[styles.content, "m-float", className ?? ""].filter(Boolean).join(" ")}
|
|
107
|
+
collisionPadding={collisionPadding}
|
|
108
|
+
data-size={effective}
|
|
109
|
+
{...rest}
|
|
110
|
+
>
|
|
111
|
+
<IconAxisProvider value={iconAxis}>
|
|
112
|
+
<SizeContext.Provider value={effective}>{children}</SizeContext.Provider>
|
|
113
|
+
</IconAxisProvider>
|
|
114
|
+
</RDM.SubContent>
|
|
115
|
+
</RDM.Portal>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/* Label ───────────────────────────────────────────────────────────── */
|
|
120
|
+
|
|
121
|
+
const Label = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<typeof RDM.Label>>(
|
|
122
|
+
function DropdownMenuLabel({ className, children, ...rest }, ref) {
|
|
123
|
+
const size = useMenuSize();
|
|
124
|
+
return (
|
|
125
|
+
<RDM.Label ref={ref} asChild {...rest}>
|
|
126
|
+
<MenuItem variant="section" size={size} className={className}>
|
|
127
|
+
{children}
|
|
128
|
+
</MenuItem>
|
|
129
|
+
</RDM.Label>
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
/* Separator ───────────────────────────────────────────────────────── */
|
|
135
|
+
|
|
136
|
+
const Separator = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<typeof RDM.Separator>>(
|
|
137
|
+
function DropdownMenuSeparator({ className, ...rest }, ref) {
|
|
138
|
+
return (
|
|
139
|
+
<RDM.Separator
|
|
140
|
+
ref={ref}
|
|
141
|
+
className={[styles.separator, className ?? ""].filter(Boolean).join(" ")}
|
|
142
|
+
{...rest}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
/* Group · RadioGroup · Sub ───────────────────────────────────────── */
|
|
149
|
+
|
|
150
|
+
const Group = RDM.Group;
|
|
151
|
+
const RadioGroup = RDM.RadioGroup;
|
|
152
|
+
const Sub = RDM.Sub;
|
|
153
|
+
|
|
154
|
+
/* Item ───────────────────────────────────────────────────────────── */
|
|
155
|
+
|
|
156
|
+
type ItemPassthrough = {
|
|
157
|
+
size?: MenuItemSize;
|
|
158
|
+
variant?: MenuItemVariant;
|
|
159
|
+
leadingActions?: MenuItemAction[];
|
|
160
|
+
trailingActions?: MenuItemAction[];
|
|
161
|
+
shortcut?: string;
|
|
162
|
+
description?: ReactNode;
|
|
163
|
+
disabled?: boolean;
|
|
164
|
+
children?: ReactNode;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export interface ItemProps
|
|
168
|
+
extends Omit<ComponentPropsWithoutRef<typeof RDM.Item>, "asChild" | "children">,
|
|
169
|
+
ItemPassthrough {}
|
|
170
|
+
|
|
171
|
+
const Item = forwardRef<HTMLDivElement, ItemProps>(function DropdownMenuItem(
|
|
172
|
+
{
|
|
173
|
+
size,
|
|
174
|
+
variant,
|
|
175
|
+
leadingActions,
|
|
176
|
+
trailingActions,
|
|
177
|
+
shortcut,
|
|
178
|
+
description,
|
|
179
|
+
disabled,
|
|
180
|
+
children,
|
|
181
|
+
...rest
|
|
182
|
+
},
|
|
183
|
+
ref,
|
|
184
|
+
) {
|
|
185
|
+
const effectiveSize = useMenuSize(size);
|
|
186
|
+
return (
|
|
187
|
+
<RDM.Item asChild disabled={disabled} {...rest}>
|
|
188
|
+
<MenuItem
|
|
189
|
+
ref={ref}
|
|
190
|
+
size={effectiveSize}
|
|
191
|
+
variant={variant}
|
|
192
|
+
leadingActions={leadingActions}
|
|
193
|
+
trailingActions={trailingActions}
|
|
194
|
+
shortcut={shortcut}
|
|
195
|
+
description={description}
|
|
196
|
+
disabled={disabled}
|
|
197
|
+
>
|
|
198
|
+
{children}
|
|
199
|
+
</MenuItem>
|
|
200
|
+
</RDM.Item>
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/* CheckboxItem ────────────────────────────────────────────────────── */
|
|
205
|
+
|
|
206
|
+
export interface CheckboxItemProps
|
|
207
|
+
extends Omit<ComponentPropsWithoutRef<typeof RDM.CheckboxItem>, "asChild" | "children">,
|
|
208
|
+
ItemPassthrough {}
|
|
209
|
+
|
|
210
|
+
const CheckboxItem = forwardRef<HTMLDivElement, CheckboxItemProps>(function DropdownMenuCheckboxItem(
|
|
211
|
+
{
|
|
212
|
+
size,
|
|
213
|
+
variant,
|
|
214
|
+
leadingActions = [],
|
|
215
|
+
trailingActions,
|
|
216
|
+
shortcut,
|
|
217
|
+
description,
|
|
218
|
+
disabled,
|
|
219
|
+
checked,
|
|
220
|
+
onCheckedChange,
|
|
221
|
+
children,
|
|
222
|
+
...rest
|
|
223
|
+
},
|
|
224
|
+
ref,
|
|
225
|
+
) {
|
|
226
|
+
const effectiveSize = useMenuSize(size);
|
|
227
|
+
const indicatorSlot: MenuItemAction = {
|
|
228
|
+
node: (
|
|
229
|
+
<span
|
|
230
|
+
className={styles.checkIndicator}
|
|
231
|
+
data-checked={checked === true ? "true" : undefined}
|
|
232
|
+
aria-hidden
|
|
233
|
+
>
|
|
234
|
+
<Icon name="check" />
|
|
235
|
+
</span>
|
|
236
|
+
),
|
|
237
|
+
key: "__checkbox__",
|
|
238
|
+
};
|
|
239
|
+
const composedLeading: MenuItemAction[] = [indicatorSlot, ...leadingActions];
|
|
240
|
+
return (
|
|
241
|
+
<RDM.CheckboxItem
|
|
242
|
+
asChild
|
|
243
|
+
disabled={disabled}
|
|
244
|
+
checked={checked}
|
|
245
|
+
onCheckedChange={onCheckedChange}
|
|
246
|
+
{...rest}
|
|
247
|
+
>
|
|
248
|
+
<MenuItem
|
|
249
|
+
ref={ref}
|
|
250
|
+
size={effectiveSize}
|
|
251
|
+
variant={variant}
|
|
252
|
+
leadingActions={composedLeading}
|
|
253
|
+
trailingActions={trailingActions}
|
|
254
|
+
shortcut={shortcut}
|
|
255
|
+
description={description}
|
|
256
|
+
disabled={disabled}
|
|
257
|
+
>
|
|
258
|
+
{children}
|
|
259
|
+
</MenuItem>
|
|
260
|
+
</RDM.CheckboxItem>
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/* RadioItem ───────────────────────────────────────────────────────── */
|
|
265
|
+
|
|
266
|
+
export interface RadioItemProps
|
|
267
|
+
extends Omit<ComponentPropsWithoutRef<typeof RDM.RadioItem>, "asChild" | "children">,
|
|
268
|
+
ItemPassthrough {}
|
|
269
|
+
|
|
270
|
+
const RadioItem = forwardRef<HTMLDivElement, RadioItemProps>(function DropdownMenuRadioItem(
|
|
271
|
+
{
|
|
272
|
+
size,
|
|
273
|
+
variant,
|
|
274
|
+
leadingActions = [],
|
|
275
|
+
trailingActions,
|
|
276
|
+
shortcut,
|
|
277
|
+
description,
|
|
278
|
+
disabled,
|
|
279
|
+
children,
|
|
280
|
+
...rest
|
|
281
|
+
},
|
|
282
|
+
ref,
|
|
283
|
+
) {
|
|
284
|
+
const effectiveSize = useMenuSize(size);
|
|
285
|
+
const composedLeading: MenuItemAction[] = [
|
|
286
|
+
{ node: <span className={styles.radioIndicator} aria-hidden />, key: "__radio__" },
|
|
287
|
+
...leadingActions,
|
|
288
|
+
];
|
|
289
|
+
return (
|
|
290
|
+
<RDM.RadioItem asChild disabled={disabled} {...rest}>
|
|
291
|
+
<MenuItem
|
|
292
|
+
ref={ref}
|
|
293
|
+
size={effectiveSize}
|
|
294
|
+
variant={variant}
|
|
295
|
+
leadingActions={composedLeading}
|
|
296
|
+
trailingActions={trailingActions}
|
|
297
|
+
shortcut={shortcut}
|
|
298
|
+
description={description}
|
|
299
|
+
disabled={disabled}
|
|
300
|
+
>
|
|
301
|
+
{children}
|
|
302
|
+
</MenuItem>
|
|
303
|
+
</RDM.RadioItem>
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/* SubTrigger ──────────────────────────────────────────────────────── */
|
|
308
|
+
|
|
309
|
+
export interface SubTriggerProps
|
|
310
|
+
extends Omit<ComponentPropsWithoutRef<typeof RDM.SubTrigger>, "asChild" | "children">,
|
|
311
|
+
Omit<ItemPassthrough, "shortcut"> {}
|
|
312
|
+
|
|
313
|
+
const SubTrigger = forwardRef<HTMLDivElement, SubTriggerProps>(function DropdownMenuSubTrigger(
|
|
314
|
+
{ size, variant, leadingActions, trailingActions, description, disabled, children, ...rest },
|
|
315
|
+
ref,
|
|
316
|
+
) {
|
|
317
|
+
const effectiveSize = useMenuSize(size);
|
|
318
|
+
return (
|
|
319
|
+
<RDM.SubTrigger asChild disabled={disabled} {...rest}>
|
|
320
|
+
<MenuItem
|
|
321
|
+
ref={ref}
|
|
322
|
+
size={effectiveSize}
|
|
323
|
+
variant={variant}
|
|
324
|
+
leadingActions={leadingActions}
|
|
325
|
+
trailingActions={trailingActions}
|
|
326
|
+
description={description}
|
|
327
|
+
hasSubmenu
|
|
328
|
+
disabled={disabled}
|
|
329
|
+
>
|
|
330
|
+
{children}
|
|
331
|
+
</MenuItem>
|
|
332
|
+
</RDM.SubTrigger>
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
/* Compound export ─────────────────────────────────────────────────── */
|
|
337
|
+
|
|
338
|
+
type DropdownMenuComponent = typeof Root & {
|
|
339
|
+
Trigger: typeof Trigger;
|
|
340
|
+
Content: typeof Content;
|
|
341
|
+
Item: typeof Item;
|
|
342
|
+
CheckboxItem: typeof CheckboxItem;
|
|
343
|
+
RadioGroup: typeof RadioGroup;
|
|
344
|
+
RadioItem: typeof RadioItem;
|
|
345
|
+
Separator: typeof Separator;
|
|
346
|
+
Label: typeof Label;
|
|
347
|
+
Group: typeof Group;
|
|
348
|
+
Sub: typeof Sub;
|
|
349
|
+
SubTrigger: typeof SubTrigger;
|
|
350
|
+
SubContent: typeof SubContent;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const DropdownMenu = Root as DropdownMenuComponent;
|
|
354
|
+
DropdownMenu.Trigger = Trigger;
|
|
355
|
+
DropdownMenu.Content = Content;
|
|
356
|
+
DropdownMenu.Item = Item;
|
|
357
|
+
DropdownMenu.CheckboxItem = CheckboxItem;
|
|
358
|
+
DropdownMenu.RadioGroup = RadioGroup;
|
|
359
|
+
DropdownMenu.RadioItem = RadioItem;
|
|
360
|
+
DropdownMenu.Separator = Separator;
|
|
361
|
+
DropdownMenu.Label = Label;
|
|
362
|
+
DropdownMenu.Group = Group;
|
|
363
|
+
DropdownMenu.Sub = Sub;
|
|
364
|
+
DropdownMenu.SubTrigger = SubTrigger;
|
|
365
|
+
DropdownMenu.SubContent = SubContent;
|
|
366
|
+
|
|
367
|
+
export { DropdownMenu };
|