devnonla-ui 0.1.0 → 0.2.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/README.md +22 -10
- package/package.json +2 -2
- package/src/alert/Alert.tsx +1 -1
- package/src/app/App.tsx +38 -18
- package/src/app/context.ts +32 -0
- package/src/button/Button.tsx +2 -2
- package/src/calendar/Calendar.tsx +4 -4
- package/src/chat/AgentPanel.tsx +15 -6
- package/src/chat/common/types.ts +27 -2
- package/src/chat/common/useAgentStream.ts +78 -12
- package/src/chat/common/utils.ts +13 -0
- package/src/chat/index.ts +5 -3
- package/src/chat/message-ui/ChatAgentMessage.tsx +1 -1
- package/src/chat/message-ui/ChatInput.tsx +1 -1
- package/src/chat/message-ui/ChatMarkdown.tsx +6 -6
- package/src/chat/message-ui/ChatMarkdownTable.tsx +1 -1
- package/src/chat/message-ui/ChatThinking.tsx +4 -3
- package/src/chat/message-ui/ChatUserMessage.tsx +0 -8
- package/src/chat/message-ui/MermaidBlock.tsx +11 -1
- package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +2 -1
- package/src/chat/tool-ui/CallAgentToolUI.tsx +3 -2
- package/src/chat/tool-ui/ChatToolCall.tsx +3 -2
- package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -1
- package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -2
- package/src/chat/tool-ui/RunJsToolUI.tsx +3 -2
- package/src/chat/tool-ui/WebFetchToolUI.tsx +3 -2
- package/src/chat/tool-ui/registry.ts +24 -12
- package/src/checkbox/Checkbox.tsx +5 -4
- package/src/codeblock/CodeBlock.tsx +1 -0
- package/src/datepicker/DatePicker.tsx +15 -9
- package/src/desktop/DesktopHeader.tsx +1 -1
- package/src/desktop/DesktopIcon.tsx +2 -4
- package/src/desktop/DesktopWindow.tsx +19 -5
- package/src/desktop/MeadowDesktop.tsx +2 -2
- package/src/drawer/Drawer.tsx +8 -6
- package/src/dropdown/ContextMenu.tsx +3 -1
- package/src/dropdown/Dropdown.tsx +8 -6
- package/src/form/Form.tsx +2 -2
- package/src/form/FormItem.tsx +2 -2
- package/src/form/partials/FormItemHelps.tsx +2 -2
- package/src/form/variants/FieldColor.tsx +1 -1
- package/src/form/variants/FieldObject.tsx +2 -2
- package/src/form/variants/FieldRepeaterItem.tsx +4 -4
- package/src/form-layout/FormLayout.tsx +6 -6
- package/src/index.ts +20 -6
- package/src/input/Input.tsx +11 -10
- package/src/lib/sizes.ts +13 -3
- package/src/lib/surface.ts +2 -1
- package/src/menu/Menu.tsx +2 -2
- package/src/message/message.tsx +35 -2
- package/src/modal/Modal.tsx +49 -3
- package/src/pagination/Pagination.tsx +6 -5
- package/src/popconfirm/Popconfirm.tsx +1 -1
- package/src/popover/Popover.tsx +6 -8
- package/src/scroll/OverlayScroll.tsx +5 -3
- package/src/segmented/Segmented.tsx +6 -5
- package/src/select/Select.tsx +9 -6
- package/src/shimmer/Shimmer.tsx +17 -0
- package/src/skeleton/Skeleton.tsx +3 -3
- package/src/spin/Spin.tsx +8 -5
- package/src/styles.css +81 -27
- package/src/switch/Switch.tsx +6 -5
- package/src/table/Table.tsx +6 -7
- package/src/tag/Tag.tsx +2 -2
- package/src/theme.ts +23 -3
- package/src/timepicker/TimePicker.tsx +13 -11
- package/src/tooltip/Tooltip.tsx +6 -7
|
@@ -24,11 +24,11 @@ export const chatMarkdownClass = cn(
|
|
|
24
24
|
"[&_strong]:font-semibold [&_em]:italic",
|
|
25
25
|
"[&_blockquote]:m-0 [&_blockquote]:mb-(--chat-p-mb) [&_blockquote]:border-0 [&_blockquote]:p-0 [&_blockquote]:not-italic [&_blockquote]:text-inherit [&_blockquote:last-child]:mb-0",
|
|
26
26
|
"[&_ul]:mt-2 [&_ul]:mb-(--chat-p-mb)",
|
|
27
|
-
"[&_ul:not(.contains-task-list)]:list-disc [&_ul:not(.contains-task-list)]:pl-
|
|
28
|
-
"[&_ul.contains-task-list]:list-none [&_ul.contains-task-list]:pl-
|
|
27
|
+
"[&_ul:not(.contains-task-list)]:list-disc [&_ul:not(.contains-task-list)]:pl-6.5",
|
|
28
|
+
"[&_ul.contains-task-list]:list-none [&_ul.contains-task-list]:pl-6.5",
|
|
29
29
|
"[&_li.task-list-item]:relative",
|
|
30
|
-
"[&_li.task-list-item>.nonla-md-task-check]:absolute [&_li.task-list-item>.nonla-md-task-check]:top-0 [&_li.task-list-item>.nonla-md-task-check]
|
|
31
|
-
"[&_ol]:mt-2 [&_ol]:mb-(--chat-p-mb) [&_ol]:list-decimal [&_ol]:pl-
|
|
30
|
+
"[&_li.task-list-item>.nonla-md-task-check]:absolute [&_li.task-list-item>.nonla-md-task-check]:top-0 [&_li.task-list-item>.nonla-md-task-check]:-left-5.5",
|
|
31
|
+
"[&_ol]:mt-2 [&_ol]:mb-(--chat-p-mb) [&_ol]:list-decimal [&_ol]:pl-6.5",
|
|
32
32
|
"[&_li]:my-1.5 [&_li]:leading-(--chat-body-leading)",
|
|
33
33
|
"[&_a]:text-link [&_a]:no-underline [&_a]:hover:underline [&_a]:hover:underline-offset-[3px]",
|
|
34
34
|
);
|
|
@@ -78,14 +78,14 @@ export function createChatMarkdownComponents(getState?: () => ChatMarkdownStream
|
|
|
78
78
|
th({ children }) {
|
|
79
79
|
return (
|
|
80
80
|
<th>
|
|
81
|
-
<div className="inline-block max-w-
|
|
81
|
+
<div className="inline-block max-w-75 wrap-break-word">{children}</div>
|
|
82
82
|
</th>
|
|
83
83
|
);
|
|
84
84
|
},
|
|
85
85
|
td({ children }) {
|
|
86
86
|
return (
|
|
87
87
|
<td>
|
|
88
|
-
<div className="inline-block max-w-
|
|
88
|
+
<div className="inline-block max-w-75 wrap-break-word">{children}</div>
|
|
89
89
|
</td>
|
|
90
90
|
);
|
|
91
91
|
},
|
|
@@ -35,7 +35,7 @@ export function ChatMarkdownTable({ children }: { children: ReactNode }) {
|
|
|
35
35
|
<div className="overflow-x-auto">
|
|
36
36
|
<table
|
|
37
37
|
ref={tableRef}
|
|
38
|
-
className="w-max min-w-full wrap-normal border-separate border-spacing-0 text-[14px] [&_th]:bg-foreground/
|
|
38
|
+
className="w-max min-w-full wrap-normal border-separate border-spacing-0 text-[14px] [&_th]:bg-foreground/4 [&_th]:text-foreground [&_th]:font-semibold [&_th]:px-2.5 [&_th]:py-1.5 [&_th]:text-left [&_th]:align-top [&_th]:border-r [&_th]:border-b [&_th]:border-border [&_th:last-child]:border-r-0 [&_td]:px-2.5 [&_td]:py-1.5 [&_td]:align-top [&_td]:text-foreground [&_td]:bg-card [&_td]:border-r [&_td]:border-b [&_td]:border-border [&_td:last-child]:border-r-0 [&_tbody_tr:last-child_td]:border-b-0"
|
|
39
39
|
>
|
|
40
40
|
{children}
|
|
41
41
|
</table>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
4
|
|
|
4
5
|
export type ChatThinkingProps = {
|
|
5
6
|
thinking: string;
|
|
@@ -46,9 +47,9 @@ export function ChatThinking({ thinking, duration = 0, streaming = false, classN
|
|
|
46
47
|
if (el) el.scrollTop = el.scrollHeight;
|
|
47
48
|
}}
|
|
48
49
|
>
|
|
49
|
-
<summary className="cursor-pointer select-none text-sm font-medium
|
|
50
|
-
<
|
|
51
|
-
<svg className="w-3 h-3 shrink-0 opacity-0 transition-[opacity,transform] duration-150 group-hover/thinking:opacity-100 group-open/thinking:opacity-100 group-open/thinking:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
|
50
|
+
<summary className="cursor-pointer select-none text-sm font-medium flex items-center gap-1 py-0.5 list-none [&::-webkit-details-marker]:hidden">
|
|
51
|
+
<Shimmer active={streaming} className="text-tertiary-foreground">{label}</Shimmer>
|
|
52
|
+
<svg className="w-3 h-3 shrink-0 opacity-0 text-tertiary-foreground transition-[opacity,transform] duration-150 group-hover/thinking:opacity-100 group-open/thinking:opacity-100 group-open/thinking:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
|
52
53
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
53
54
|
</svg>
|
|
54
55
|
</summary>
|
|
@@ -25,21 +25,13 @@ export function ChatUserMessage({ content, className }: ChatUserMessageProps) {
|
|
|
25
25
|
|
|
26
26
|
const collapsed = isOverflow && !isExpanded;
|
|
27
27
|
|
|
28
|
-
const toggle = () => {
|
|
29
|
-
const sel = window.getSelection();
|
|
30
|
-
if (sel?.toString()) return;
|
|
31
|
-
setIsExpanded((v) => !v);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
28
|
return (
|
|
35
29
|
<div
|
|
36
30
|
className={cn(
|
|
37
31
|
"nonla-chat-user relative isolate mt-6 mb-3 mx-4 overflow-hidden rounded-xl px-3 py-2.5",
|
|
38
32
|
"border border-brand/40 bg-[color-mix(in_oklab,var(--brand)_14%,var(--glass))] backdrop-blur-xl backdrop-saturate-150",
|
|
39
|
-
isOverflow && "cursor-pointer",
|
|
40
33
|
className,
|
|
41
34
|
)}
|
|
42
|
-
onClick={isOverflow ? toggle : undefined}
|
|
43
35
|
>
|
|
44
36
|
<div
|
|
45
37
|
ref={contentRef}
|
|
@@ -257,7 +257,17 @@ export function MermaidBlock({ children, className }: MermaidBlockProps) {
|
|
|
257
257
|
) : null}
|
|
258
258
|
</div>
|
|
259
259
|
|
|
260
|
-
<div
|
|
260
|
+
<div
|
|
261
|
+
role="application"
|
|
262
|
+
aria-label="Pan and zoom diagram"
|
|
263
|
+
onWheel={handleWheel}
|
|
264
|
+
onMouseDown={handleMouseDown}
|
|
265
|
+
onMouseMove={handleMouseMove}
|
|
266
|
+
onMouseUp={handleMouseUp}
|
|
267
|
+
onMouseLeave={handleMouseLeave}
|
|
268
|
+
onDoubleClick={resetView}
|
|
269
|
+
className={cn("flex-1 overflow-hidden w-full h-full select-none", isDragging ? "cursor-grabbing" : "cursor-grab")}
|
|
270
|
+
>
|
|
261
271
|
<div ref={fullscreenRef} className="flex items-center justify-center w-full h-full origin-center [&_svg]:max-w-none [&_svg]:max-h-none pointer-events-none" style={{ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})` }} />
|
|
262
272
|
</div>
|
|
263
273
|
</dialog>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
2
3
|
import { formatBgElapsed, parseBgTaskRef } from "../common/bgTasks";
|
|
3
4
|
import { formatToolName, timestampMs } from "../common/utils";
|
|
4
5
|
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
@@ -24,7 +25,7 @@ export function BackgroundTaskToolUI({ msg, assistantLabel = "Assistant", assist
|
|
|
24
25
|
<div className="min-w-0 flex-1 pl-2">
|
|
25
26
|
<div className="flex items-center gap-2 py-1">
|
|
26
27
|
<ChatSpinner />
|
|
27
|
-
<
|
|
28
|
+
<Shimmer className="min-w-0 flex-1 truncate text-[14px] font-medium text-foreground">{label}</Shimmer>
|
|
28
29
|
<span className="shrink-0 rounded px-1 py-px text-[9px] font-semibold uppercase tracking-wider text-brand-700">Background</span>
|
|
29
30
|
<span className="shrink-0 text-[14px] tabular-nums text-muted-foreground">{formatBgElapsed(timestampMs(msg.timestamp), now)}</span>
|
|
30
31
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
4
5
|
import { formatBgElapsed, parseBgTaskRef } from "../common/bgTasks";
|
|
5
6
|
import { parseJsonObject, prettyJson, timestampMs } from "../common/utils";
|
|
6
7
|
import { ChatMarkdown } from "../message-ui/ChatMarkdown";
|
|
@@ -30,7 +31,7 @@ function parseCallAgentOutput(raw: unknown): CallAgentParsed | null {
|
|
|
30
31
|
|
|
31
32
|
function TypingDots() {
|
|
32
33
|
return (
|
|
33
|
-
<div className="flex items-center gap-1 px-0.5 py-0.5" aria-label="Typing">
|
|
34
|
+
<div className="flex items-center gap-1 px-0.5 py-0.5" role="status" aria-label="Typing">
|
|
34
35
|
{[0, 1, 2].map((i) => (
|
|
35
36
|
<span key={i} className="size-1.5 rounded-full bg-muted-foreground/50 animate-bounce" style={{ animationDelay: `${i * 150}ms` }} />
|
|
36
37
|
))}
|
|
@@ -121,7 +122,7 @@ export function CallAgentToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
121
122
|
<div className={cn("overflow-hidden rounded-xl border bg-card", failed ? "border-destructive/35" : "border-border")}>
|
|
122
123
|
<div className="flex items-center gap-2 border-b border-border-subtle px-3 py-2">
|
|
123
124
|
<FluentIcon name="chat-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
124
|
-
<
|
|
125
|
+
<Shimmer active={composing || awaitingReply} className="min-w-0 flex-1 truncate text-[14px] font-medium text-foreground">Calling {calleeName}</Shimmer>
|
|
125
126
|
{bgRunning ? <span className="text-[14px] tabular-nums text-muted-foreground">{formatBgElapsed(timestampMs(msg.timestamp), now)}</span> : null}
|
|
126
127
|
{statusLabel ? <span className="max-w-40 truncate text-[13px] italic text-destructive">{statusLabel}</span> : null}
|
|
127
128
|
<ToolUiTrailing running={composing || awaitingReply} failed={failed && !awaitingReply} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode, useState } from "react";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
4
|
import { formatToolName, hasMeaningfulInput, prettyJson } from "../common/utils";
|
|
4
5
|
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
5
6
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
@@ -37,7 +38,7 @@ export function ChatToolCall({ toolName = "Tool", label, toolInput, toolOutput,
|
|
|
37
38
|
const header = (
|
|
38
39
|
<>
|
|
39
40
|
{identityIcon}
|
|
40
|
-
<
|
|
41
|
+
<Shimmer active={running} className={cn("min-w-0 flex-1 truncate text-left text-[14px] font-medium", !running && "text-muted-foreground", expandable && !running && "transition-colors group-hover:text-foreground")}>{displayLabel}</Shimmer>
|
|
41
42
|
<ToolUiTrailing running={running} failed={hasError} chevron={expandable} chevronClassName={cn("group-hover:opacity-100", open && "opacity-100 rotate-90")} />
|
|
42
43
|
</>
|
|
43
44
|
);
|
|
@@ -60,7 +61,7 @@ export function ChatToolCall({ toolName = "Tool", label, toolInput, toolOutput,
|
|
|
60
61
|
{running ? (
|
|
61
62
|
<div className="flex items-center gap-2 border-t border-border bg-muted/40 px-3 py-2 text-muted-foreground">
|
|
62
63
|
<ChatSpinner />
|
|
63
|
-
<
|
|
64
|
+
<Shimmer className="italic">Running…</Shimmer>
|
|
64
65
|
</div>
|
|
65
66
|
) : null}
|
|
66
67
|
{!isPending ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
2
|
import { parseJsonObject } from "../common/utils";
|
|
3
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
4
|
import { ToolUiBadge } from "./ToolUiBadge";
|
|
4
5
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
5
6
|
import { isToolRunning, type ToolUIProps } from "./types";
|
|
@@ -40,7 +41,7 @@ export function GetCurrentTimeToolUI({ msg, assistantLabel = "Assistant", assist
|
|
|
40
41
|
<div className="px-4 pb-1">
|
|
41
42
|
<div className="flex items-center gap-2 py-1">
|
|
42
43
|
<FluentIcon name="clock-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
43
|
-
<
|
|
44
|
+
<Shimmer active={running} className="truncate text-[14px] font-medium tabular-nums text-muted-foreground">{hasError ? "Failed to get time" : running ? "Getting time…" : label}</Shimmer>
|
|
44
45
|
<ToolUiTrailing running={running} failed={hasError} />
|
|
45
46
|
</div>
|
|
46
47
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
4
|
import { parseJsonObject } from "../common/utils";
|
|
4
5
|
import { ToolUiBadge } from "./ToolUiBadge";
|
|
5
6
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
@@ -55,10 +56,10 @@ export function ReadSkillToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
55
56
|
<details className="group/readskill px-4 pb-2" style={{ overflowAnchor: "none" }}>
|
|
56
57
|
<summary className="flex cursor-pointer list-none items-center gap-2 py-0.5 text-[14px] leading-5.5 select-none [&::-webkit-details-marker]:hidden">
|
|
57
58
|
<FluentIcon name={reference ? "document-text-24" : "book-24"} size={13} className="shrink-0 text-muted-foreground" />
|
|
58
|
-
<
|
|
59
|
+
<Shimmer active={running} className="min-w-0 truncate font-medium text-muted-foreground">
|
|
59
60
|
{verb}
|
|
60
61
|
{targetLabel ? <span className="font-normal text-tertiary-foreground"> {targetLabel}</span> : null}
|
|
61
|
-
</
|
|
62
|
+
</Shimmer>
|
|
62
63
|
<ToolUiTrailing running={running} failed={failed} chevron chevronClassName="group-hover/readskill:opacity-100 group-open/readskill:opacity-100 group-open/readskill:rotate-90" />
|
|
63
64
|
</summary>
|
|
64
65
|
{body ? (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
3
|
import { CodeBlock } from "../../codeblock/CodeBlock";
|
|
4
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
4
5
|
import { parseBgTaskRef } from "../common/bgTasks";
|
|
5
6
|
import { parseJsonObject, prettyJson } from "../common/utils";
|
|
6
7
|
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
@@ -46,7 +47,7 @@ function SandboxOutput({
|
|
|
46
47
|
return (
|
|
47
48
|
<div className="flex items-center gap-2 border-t border-border px-3 py-2 text-[13px] text-muted-foreground">
|
|
48
49
|
<ChatSpinner />
|
|
49
|
-
<
|
|
50
|
+
<Shimmer>Running…</Shimmer>
|
|
50
51
|
</div>
|
|
51
52
|
);
|
|
52
53
|
}
|
|
@@ -87,7 +88,7 @@ export function RunJsToolUI({ msg, assistantLabel = "Assistant", assistantColor,
|
|
|
87
88
|
<details className="group/runjs px-4 pb-2" style={{ overflowAnchor: "none" }}>
|
|
88
89
|
<summary className="flex cursor-pointer list-none items-center gap-2 py-0.5 text-[14px] leading-5.5 select-none [&::-webkit-details-marker]:hidden">
|
|
89
90
|
<FluentIcon name="code-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
90
|
-
<
|
|
91
|
+
<Shimmer active={running} className="min-w-0 truncate font-medium text-muted-foreground">{verb}</Shimmer>
|
|
91
92
|
<ToolUiTrailing running={running} failed={failed} chevron chevronClassName="group-hover/runjs:opacity-100 group-open/runjs:opacity-100 group-open/runjs:rotate-90" />
|
|
92
93
|
</summary>
|
|
93
94
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
|
+
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
4
|
import { parseJsonObject } from "../common/utils";
|
|
4
5
|
import { ToolUiBadge } from "./ToolUiBadge";
|
|
5
6
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
@@ -39,9 +40,9 @@ export function WebFetchToolUI({ msg, assistantLabel = "Assistant", assistantCol
|
|
|
39
40
|
<details className="group/webfetch px-4 pb-2" style={{ overflowAnchor: "none" }}>
|
|
40
41
|
<summary className="flex cursor-pointer list-none items-center gap-2 py-0.5 text-[14px] leading-5.5 select-none [&::-webkit-details-marker]:hidden">
|
|
41
42
|
<FluentIcon name="globe-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
42
|
-
<
|
|
43
|
+
<Shimmer active={running} className="min-w-0 truncate font-medium text-muted-foreground">
|
|
43
44
|
{verb} <span className="font-normal text-tertiary-foreground">{url}</span>
|
|
44
|
-
</
|
|
45
|
+
</Shimmer>
|
|
45
46
|
<ToolUiTrailing running={running} failed={failed} chevron chevronClassName="group-hover/webfetch:opacity-100 group-open/webfetch:opacity-100 group-open/webfetch:rotate-90" />
|
|
46
47
|
</summary>
|
|
47
48
|
{body ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentType } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import type { AgentToolNameMatch } from "../common/types";
|
|
3
|
+
import { isCallAgentToolName, matchesToolName } from "../common/utils";
|
|
3
4
|
import { CallAgentToolUI } from "./CallAgentToolUI";
|
|
4
5
|
import { GetCurrentTimeToolUI } from "./GetCurrentTimeToolUI";
|
|
5
6
|
import { ReadSkillToolUI } from "./ReadSkillToolUI";
|
|
@@ -7,23 +8,34 @@ import { RunJsToolUI } from "./RunJsToolUI";
|
|
|
7
8
|
import type { ToolUIProps } from "./types";
|
|
8
9
|
import { WebFetchToolUI } from "./WebFetchToolUI";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/** Exact name, aliases, or a predicate (e.g. `call_agent__*`). */
|
|
12
|
+
export type AgentToolUIName = AgentToolNameMatch;
|
|
13
|
+
|
|
14
|
+
export type AgentToolUI = {
|
|
15
|
+
name: AgentToolUIName;
|
|
12
16
|
component: ComponentType<ToolUIProps>;
|
|
13
17
|
};
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{
|
|
19
|
+
export function matchesToolUIName(name: AgentToolUIName, toolName: string): boolean {
|
|
20
|
+
return matchesToolName(name, toolName);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const builtinToolUis: AgentToolUI[] = [
|
|
24
|
+
{ name: isCallAgentToolName, component: CallAgentToolUI },
|
|
25
|
+
{ name: ["web_fetch", "fetch_url", "browser"], component: WebFetchToolUI },
|
|
26
|
+
{ name: "get_current_time", component: GetCurrentTimeToolUI },
|
|
27
|
+
{ name: "read_skill", component: ReadSkillToolUI },
|
|
28
|
+
{ name: "run_js", component: RunJsToolUI },
|
|
21
29
|
];
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
/** App `toolUis` win on the same name; builtins are the fallback. */
|
|
32
|
+
export function resolveToolUI(toolName: string | null | undefined, extras?: readonly AgentToolUI[] | null): ComponentType<ToolUIProps> | null {
|
|
24
33
|
if (!toolName) return null;
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
const lists = extras?.length ? [extras, builtinToolUis] : [builtinToolUis];
|
|
35
|
+
for (const list of lists) {
|
|
36
|
+
for (const entry of list) {
|
|
37
|
+
if (matchesToolUIName(entry.name, toolName)) return entry.component;
|
|
38
|
+
}
|
|
27
39
|
}
|
|
28
40
|
return null;
|
|
29
41
|
}
|
|
@@ -5,7 +5,7 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
export type CheckboxProps = Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "onCheckedChange" | "checked" | "onChange"> & {
|
|
6
6
|
checked?: boolean | "indeterminate";
|
|
7
7
|
defaultChecked?: boolean;
|
|
8
|
-
/**
|
|
8
|
+
/** Receives next checked boolean (not a DOM event). */
|
|
9
9
|
onChange?: (checked: boolean) => void;
|
|
10
10
|
children?: ReactNode;
|
|
11
11
|
indeterminate?: boolean;
|
|
@@ -25,6 +25,7 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(function Ch
|
|
|
25
25
|
) {
|
|
26
26
|
const resolved = indeterminate ? "indeterminate" : checked;
|
|
27
27
|
return (
|
|
28
|
+
// biome-ignore lint/a11y/noLabelWithoutControl: wraps Radix checkbox button, not a native input
|
|
28
29
|
<label className={cn("inline-flex items-center gap-2 text-sm text-foreground cursor-pointer", disabled && "opacity-45 cursor-not-allowed")}>
|
|
29
30
|
<CheckboxPrimitive.Root
|
|
30
31
|
ref={ref}
|
|
@@ -33,9 +34,9 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(function Ch
|
|
|
33
34
|
disabled={disabled}
|
|
34
35
|
onCheckedChange={(v) => onChange?.(v === true)}
|
|
35
36
|
className={cn(
|
|
36
|
-
"size-4 shrink-0 rounded-
|
|
37
|
-
"focus-visible:outline-none data-[state=checked]:bg-brand data-[state=checked]:border-brand data-[state=checked]:text-
|
|
38
|
-
"data-[state=indeterminate]:bg-brand data-[state=indeterminate]:border-brand data-[state=indeterminate]:text-
|
|
37
|
+
"size-4 shrink-0 rounded-sm border border-input bg-(--control-bg) transition-colors duration-150 ease-[cubic-bezier(0.16,1,0.3,1)]",
|
|
38
|
+
"focus-visible:outline-none data-[state=checked]:bg-brand data-[state=checked]:border-brand data-[state=checked]:text-(--nonla-solid-fg)",
|
|
39
|
+
"data-[state=indeterminate]:bg-brand data-[state=indeterminate]:border-brand data-[state=indeterminate]:text-(--nonla-solid-fg)",
|
|
39
40
|
className,
|
|
40
41
|
)}
|
|
41
42
|
{...rest}
|
|
@@ -65,6 +65,7 @@ export function CodeBlock({ code, language, title, lineNumbers = false, wordWrap
|
|
|
65
65
|
<div className={cn("nonla-codeblock-body nonla-codeblock-well relative min-w-0 overflow-y-auto bg-card font-mono text-[13px] leading-5", children ? "max-h-96" : "mx-0.5 mb-0.5 max-h-96 rounded-lg")}>
|
|
66
66
|
<div className={cn("w-full", !wordWrap && "overflow-x-auto")}>
|
|
67
67
|
<pre className={cn("nonla-codeblock-pre m-0 whitespace-pre break-normal", lineNumbers && "nonla-codeblock-lines", wordWrap && "whitespace-pre-wrap wrap-break-word")}>
|
|
68
|
+
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: highlight.js emits escaped HTML */}
|
|
68
69
|
<code className={cn("block px-3 py-2.5", !wordWrap && "w-max min-w-full")} dangerouslySetInnerHTML={{ __html: html }} />
|
|
69
70
|
</pre>
|
|
70
71
|
</div>
|
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
import type { DateRange } from "react-day-picker";
|
|
17
17
|
import { Calendar } from "../calendar/Calendar";
|
|
18
18
|
import { Button } from "../button/Button";
|
|
19
|
+
import { usePopupContainer } from "../app/context";
|
|
19
20
|
import { cn } from "../lib/cn";
|
|
20
|
-
import { type ControlSize, controlFieldFocusBorder, controlFieldStyle, controlFieldSurface, controlFieldTransition, controlStatusClass, getSizeTokens } from "../lib/sizes";
|
|
21
|
+
import { type ControlSize, controlFieldFocusBorder, controlFieldStyle, controlFieldSurface, controlFieldTransition, controlStatusClass, getSizeTokens, useControlSize } from "../lib/sizes";
|
|
21
22
|
import { glassOverlayClass } from "../lib/surface";
|
|
22
23
|
|
|
23
24
|
export type DatePickerProps = {
|
|
@@ -29,7 +30,7 @@ export type DatePickerProps = {
|
|
|
29
30
|
allowClear?: boolean;
|
|
30
31
|
format?: string;
|
|
31
32
|
showTime?: boolean;
|
|
32
|
-
/** Keep the panel open until OK
|
|
33
|
+
/** Keep the panel open until OK. */
|
|
33
34
|
needConfirm?: boolean;
|
|
34
35
|
placeholder?: string;
|
|
35
36
|
disabled?: boolean;
|
|
@@ -127,6 +128,7 @@ const TriggerChrome = forwardRef<
|
|
|
127
128
|
children: ReactNode;
|
|
128
129
|
} & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "style">
|
|
129
130
|
>(function TriggerChrome({ size, status, disabled, className, style, icon, clear, children, ...rest }, ref) {
|
|
131
|
+
const resolvedSize = useControlSize(size);
|
|
130
132
|
return (
|
|
131
133
|
<button
|
|
132
134
|
ref={ref}
|
|
@@ -141,7 +143,7 @@ const TriggerChrome = forwardRef<
|
|
|
141
143
|
controlStatusClass(status),
|
|
142
144
|
className,
|
|
143
145
|
)}
|
|
144
|
-
style={{ ...controlFieldStyle(
|
|
146
|
+
style={{ ...controlFieldStyle(resolvedSize), ...style }}
|
|
145
147
|
{...rest}
|
|
146
148
|
>
|
|
147
149
|
{icon}
|
|
@@ -185,7 +187,9 @@ const DatePickerRoot = forwardRef<HTMLButtonElement, DatePickerProps>(function D
|
|
|
185
187
|
};
|
|
186
188
|
const [month, setMonth] = useState<Date>(() => selected ?? new Date());
|
|
187
189
|
const [draft, setDraft] = useState<Date | undefined>(selected);
|
|
188
|
-
const
|
|
190
|
+
const resolvedSize = useControlSize(size);
|
|
191
|
+
const tok = getSizeTokens(resolvedSize);
|
|
192
|
+
const portal = usePopupContainer()?.();
|
|
189
193
|
const confirm = Boolean(needConfirm);
|
|
190
194
|
|
|
191
195
|
useEffect(() => {
|
|
@@ -253,7 +257,7 @@ const DatePickerRoot = forwardRef<HTMLButtonElement, DatePickerProps>(function D
|
|
|
253
257
|
<PopoverPrimitive.Trigger asChild>
|
|
254
258
|
<TriggerChrome
|
|
255
259
|
ref={ref}
|
|
256
|
-
size={
|
|
260
|
+
size={resolvedSize}
|
|
257
261
|
status={status}
|
|
258
262
|
disabled={disabled}
|
|
259
263
|
className={className}
|
|
@@ -277,7 +281,7 @@ const DatePickerRoot = forwardRef<HTMLButtonElement, DatePickerProps>(function D
|
|
|
277
281
|
<span className={cn(!selected && "text-quaternary-foreground")}>{selected ? label : placeholder}</span>
|
|
278
282
|
</TriggerChrome>
|
|
279
283
|
</PopoverPrimitive.Trigger>
|
|
280
|
-
<PopoverPrimitive.Portal>
|
|
284
|
+
<PopoverPrimitive.Portal container={portal}>
|
|
281
285
|
<PopoverPrimitive.Content
|
|
282
286
|
align="start"
|
|
283
287
|
sideOffset={6}
|
|
@@ -351,7 +355,9 @@ const RangePicker = forwardRef<HTMLButtonElement, RangePickerProps>(function Ran
|
|
|
351
355
|
const selected = controlled ? parseRange(value) : inner;
|
|
352
356
|
const [open, setOpen] = useState(false);
|
|
353
357
|
const [month, setMonth] = useState<Date>(() => selected?.from ?? new Date());
|
|
354
|
-
const
|
|
358
|
+
const resolvedSize = useControlSize(size);
|
|
359
|
+
const tok = getSizeTokens(resolvedSize);
|
|
360
|
+
const portal = usePopupContainer()?.();
|
|
355
361
|
|
|
356
362
|
useEffect(() => {
|
|
357
363
|
if (selected?.from) setMonth(selected.from);
|
|
@@ -390,7 +396,7 @@ const RangePicker = forwardRef<HTMLButtonElement, RangePickerProps>(function Ran
|
|
|
390
396
|
<PopoverPrimitive.Trigger asChild>
|
|
391
397
|
<TriggerChrome
|
|
392
398
|
ref={ref}
|
|
393
|
-
size={
|
|
399
|
+
size={resolvedSize}
|
|
394
400
|
status={status}
|
|
395
401
|
disabled={disabled}
|
|
396
402
|
className={className}
|
|
@@ -418,7 +424,7 @@ const RangePicker = forwardRef<HTMLButtonElement, RangePickerProps>(function Ran
|
|
|
418
424
|
</span>
|
|
419
425
|
</TriggerChrome>
|
|
420
426
|
</PopoverPrimitive.Trigger>
|
|
421
|
-
<PopoverPrimitive.Portal>
|
|
427
|
+
<PopoverPrimitive.Portal container={portal}>
|
|
422
428
|
<PopoverPrimitive.Content
|
|
423
429
|
align="start"
|
|
424
430
|
sideOffset={6}
|
|
@@ -78,7 +78,7 @@ export function DesktopHeader({
|
|
|
78
78
|
profile?: ReactNode;
|
|
79
79
|
}) {
|
|
80
80
|
return (
|
|
81
|
-
<header className="fixed inset-x-0 top-0
|
|
81
|
+
<header className="nonla-header-layer fixed inset-x-0 top-0 flex h-desktop-bar items-center justify-between gap-3 border-0 bg-glass-bar px-3 backdrop-blur-lg">
|
|
82
82
|
<div className="flex min-w-0 items-center">
|
|
83
83
|
{logo}
|
|
84
84
|
{leading}
|
|
@@ -6,9 +6,7 @@ const iconButtonClass =
|
|
|
6
6
|
|
|
7
7
|
const labelClass = "text-xs leading-[18px] font-semibold text-center line-clamp-2 w-full";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return `w-11 h-11 flex items-center justify-center filter-[drop-shadow(0_1px_2px_rgba(0,0,0,0.45))] ${active ? "ring-2 ring-brand rounded-[10px]" : ""}`;
|
|
11
|
-
}
|
|
9
|
+
const plateClass = "nonla-window-glass nonla-desktop-icon-plate w-11 h-11 flex items-center justify-center";
|
|
12
10
|
|
|
13
11
|
export const DesktopIcon = forwardRef<
|
|
14
12
|
HTMLButtonElement,
|
|
@@ -24,7 +22,7 @@ export const DesktopIcon = forwardRef<
|
|
|
24
22
|
return (
|
|
25
23
|
<button ref={ref} type="button" title={label} aria-label={label} aria-current={active ? "true" : undefined} dir="ltr" onClick={onClick} className={iconButtonClass}>
|
|
26
24
|
{media ?? (
|
|
27
|
-
<span className={plateClass
|
|
25
|
+
<span className={plateClass}>
|
|
28
26
|
<FluentIcon name={icon ?? "sparkle-24"} size={32} />
|
|
29
27
|
</span>
|
|
30
28
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
|
+
import { OverlayScroll, type OverlayScrollVisibility } from "../scroll/OverlayScroll";
|
|
4
5
|
import { glassSurfaceClass } from "../lib/surface";
|
|
5
6
|
|
|
6
7
|
type Phase = "open" | "leaving";
|
|
@@ -211,12 +212,17 @@ export function DesktopWindow({
|
|
|
211
212
|
expanded,
|
|
212
213
|
onClose,
|
|
213
214
|
onToggleExpand,
|
|
215
|
+
scroll = true,
|
|
216
|
+
scrollbar = "hover",
|
|
214
217
|
children,
|
|
215
218
|
}: {
|
|
216
219
|
title?: ReactNode;
|
|
217
220
|
expanded: boolean;
|
|
218
221
|
onClose: () => void;
|
|
219
222
|
onToggleExpand: () => void;
|
|
223
|
+
/** Overlay auto-hide scrollbar on the body. Pass `false` when children manage their own scroll (split panes, AgentPanel). */
|
|
224
|
+
scroll?: boolean;
|
|
225
|
+
scrollbar?: OverlayScrollVisibility;
|
|
220
226
|
children: ReactNode;
|
|
221
227
|
}) {
|
|
222
228
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
@@ -444,7 +450,7 @@ export function DesktopWindow({
|
|
|
444
450
|
|
|
445
451
|
return (
|
|
446
452
|
<WindowHeaderSlotContext.Provider value={headerChrome}>
|
|
447
|
-
<div ref={overlayRef} className="pointer-events-none absolute inset-x-0 bottom-0 top-desktop-bar
|
|
453
|
+
<div ref={overlayRef} className="nonla-window-layer pointer-events-none absolute inset-x-0 bottom-0 top-desktop-bar">
|
|
448
454
|
<section
|
|
449
455
|
ref={frameRef}
|
|
450
456
|
aria-label={typeof title === "string" && title ? title : "Window"}
|
|
@@ -464,16 +470,16 @@ export function DesktopWindow({
|
|
|
464
470
|
opacity: visible ? 1 : 0,
|
|
465
471
|
transform: visible ? "scale(1)" : "scale(0.18)",
|
|
466
472
|
transition: [geom, fade].filter(Boolean).join(", ") || undefined,
|
|
467
|
-
["--glass" as string]: "color-mix(in srgb, white 72%, transparent)",
|
|
468
473
|
}}
|
|
469
474
|
data-expanded={expanded || undefined}
|
|
470
|
-
className={cn("absolute flex flex-col rounded-xl pointer-events-auto transform-gpu", glassSurfaceClass, leaving && "pointer-events-none")}
|
|
475
|
+
className={cn("absolute flex flex-col rounded-xl pointer-events-auto transform-gpu", glassSurfaceClass, "nonla-window-glass", leaving && "pointer-events-none")}
|
|
471
476
|
>
|
|
472
477
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl">
|
|
478
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: title bar is a pointer drag surface */}
|
|
473
479
|
<header
|
|
474
480
|
onPointerDown={startDrag}
|
|
475
481
|
onDoubleClick={onTitleBarDoubleClick}
|
|
476
|
-
className="flex h-8 shrink-0 cursor-default items-center gap-4 border-0 border-b border-solid border-
|
|
482
|
+
className="flex h-8 shrink-0 cursor-default items-center gap-4 border-0 border-b border-solid border-ink-line px-3 select-none touch-none"
|
|
477
483
|
>
|
|
478
484
|
<div className="group/traffic flex shrink-0 items-center gap-2">
|
|
479
485
|
<TrafficLight label="Close window" tone="close" onClick={requestClose} shortcut="Esc">
|
|
@@ -489,7 +495,15 @@ export function DesktopWindow({
|
|
|
489
495
|
</div>
|
|
490
496
|
</header>
|
|
491
497
|
|
|
492
|
-
<div className="min-h-0 flex-1 overflow-hidden">
|
|
498
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
499
|
+
{scroll ? (
|
|
500
|
+
<OverlayScroll visibility={scrollbar} className="h-full">
|
|
501
|
+
{children}
|
|
502
|
+
</OverlayScroll>
|
|
503
|
+
) : (
|
|
504
|
+
children
|
|
505
|
+
)}
|
|
506
|
+
</div>
|
|
493
507
|
</div>
|
|
494
508
|
|
|
495
509
|
{!expanded && !leaving
|
|
@@ -5,8 +5,8 @@ export function MeadowDesktop({ children, src }: { children?: ReactNode; src?: s
|
|
|
5
5
|
return (
|
|
6
6
|
<div className="absolute inset-0 bg-meadow">
|
|
7
7
|
<MeadowWallpaper src={src} />
|
|
8
|
-
<div className="absolute inset-x-0 bottom-0 top-desktop-bar
|
|
9
|
-
<nav aria-label="Desktop" className="absolute inset-0 overflow-auto
|
|
8
|
+
<div className="nonla-desktop-layer absolute inset-x-0 bottom-0 top-desktop-bar">
|
|
9
|
+
<nav aria-label="Desktop" className="absolute inset-0 overflow-auto pl-2 pr-3 pt-2 pb-3">
|
|
10
10
|
<div className="flex h-full flex-col flex-wrap content-start gap-x-2 gap-y-3">{children}</div>
|
|
11
11
|
</nav>
|
|
12
12
|
</div>
|
package/src/drawer/Drawer.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Dialog from "@radix-ui/react-dialog";
|
|
2
2
|
import { type AnimationEvent, type CSSProperties, type ReactNode, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { usePopupContainer } from "../app/context";
|
|
3
4
|
import { cn } from "../lib/cn";
|
|
4
5
|
import { glassSurfaceClass } from "../lib/surface";
|
|
5
6
|
|
|
@@ -17,7 +18,7 @@ export type DrawerProps = {
|
|
|
17
18
|
extra?: ReactNode;
|
|
18
19
|
children?: ReactNode;
|
|
19
20
|
width?: number | string;
|
|
20
|
-
/**
|
|
21
|
+
/** Width for left/right, height for top/bottom. */
|
|
21
22
|
size?: number | string;
|
|
22
23
|
placement?: DrawerPlacement;
|
|
23
24
|
destroyOnClose?: boolean;
|
|
@@ -66,6 +67,7 @@ export function Drawer({
|
|
|
66
67
|
}, [isOpen, shouldDestroy]);
|
|
67
68
|
|
|
68
69
|
const showBody = !shouldDestroy || present;
|
|
70
|
+
const portal = usePopupContainer()?.();
|
|
69
71
|
|
|
70
72
|
const onContentAnimationEnd = (e: AnimationEvent<HTMLDivElement>) => {
|
|
71
73
|
if (e.target !== e.currentTarget) return;
|
|
@@ -80,17 +82,17 @@ export function Drawer({
|
|
|
80
82
|
if (!next) onClose?.();
|
|
81
83
|
}}
|
|
82
84
|
>
|
|
83
|
-
<Dialog.Portal>
|
|
84
|
-
<Dialog.Overlay className="nonla-overlay
|
|
85
|
+
<Dialog.Portal container={portal}>
|
|
86
|
+
<Dialog.Overlay className="nonla-overlay" />
|
|
85
87
|
<Dialog.Content
|
|
86
88
|
data-side={placement}
|
|
87
89
|
className={cn(
|
|
88
|
-
"nonla-drawer-panel fixed
|
|
90
|
+
"nonla-drawer-panel fixed flex flex-col outline-none",
|
|
89
91
|
glassSurfaceClass,
|
|
90
92
|
placement === "right" && "top-0 right-0 bottom-0 max-w-[100vw]",
|
|
91
93
|
placement === "left" && "top-0 left-0 bottom-0 max-w-[100vw]",
|
|
92
|
-
placement === "bottom" && "right-0 bottom-0 left-0 max-h-
|
|
93
|
-
placement === "top" && "top-0 right-0 left-0 max-h-
|
|
94
|
+
placement === "bottom" && "right-0 bottom-0 left-0 max-h-screen",
|
|
95
|
+
placement === "top" && "top-0 right-0 left-0 max-h-screen",
|
|
94
96
|
className,
|
|
95
97
|
)}
|
|
96
98
|
style={{
|