devnonla-ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +2 -1
- package/src/app/App.tsx +4 -4
- package/src/button/Button.tsx +1 -1
- package/src/button/ButtonCopy.tsx +11 -21
- package/src/calendar/Calendar.tsx +1 -1
- package/src/chat/AgentPanel.tsx +3 -3
- package/src/chat/index.ts +35 -51
- package/src/chat/message-ui/ChatError.tsx +2 -4
- package/src/chat/message-ui/ChatInput.tsx +5 -9
- package/src/chat/message-ui/ChatThinking.tsx +2 -3
- package/src/chat/message-ui/ChatUserMessage.tsx +4 -5
- package/src/chat/message-ui/ChatWelcome.tsx +3 -8
- package/src/chat/message-ui/MermaidBlock.tsx +7 -47
- package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +8 -11
- package/src/chat/tool-ui/BackgroundTasksBar.tsx +13 -14
- package/src/chat/tool-ui/CallAgentToolUI.tsx +10 -4
- package/src/chat/tool-ui/ChatToolCall.tsx +11 -17
- package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -2
- package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -3
- package/src/chat/tool-ui/RunJsToolUI.tsx +2 -2
- package/src/chat/tool-ui/ToolUiTrailing.tsx +3 -7
- package/src/chat/tool-ui/WebFetchToolUI.tsx +22 -13
- package/src/checkbox/Checkbox.tsx +1 -1
- package/src/codeblock/CodeBlock.tsx +17 -19
- package/src/colorpicker/ColorPicker.tsx +694 -30
- package/src/colorpicker/color.ts +246 -0
- package/src/datepicker/DatePicker.tsx +3 -3
- package/src/desktop/DesktopHeader.tsx +15 -20
- package/src/desktop/DesktopIcon.tsx +1 -1
- package/src/desktop/DesktopWindow.tsx +54 -30
- package/src/form/Form.tsx +1 -1
- package/src/form/FormItem.tsx +2 -2
- package/src/form/index.ts +23 -24
- package/src/form/variants/FieldColor.tsx +16 -51
- package/src/index.ts +167 -196
- package/src/input/Input.tsx +14 -12
- package/src/input/SearchInput.tsx +5 -2
- package/src/lib/sizes.ts +1 -1
- package/src/modal/Modal.tsx +3 -0
- package/src/pagination/Pagination.tsx +528 -51
- package/src/scroll/OverlayScroll.tsx +58 -37
- package/src/select/Select.tsx +2 -2
- package/src/spin/Spin.tsx +14 -17
- package/src/splitter/Splitter.tsx +330 -0
- package/src/splitter/sizes.ts +67 -0
- package/src/styles.css +28 -3
- package/src/switch/Switch.tsx +1 -1
- package/src/table/Table.tsx +43 -18
- package/src/timepicker/TimePicker.tsx +2 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ReactNode, useState } from "react";
|
|
2
|
+
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
4
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
4
5
|
import { formatToolName, hasMeaningfulInput, prettyJson } from "../common/utils";
|
|
5
|
-
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
6
6
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
7
7
|
|
|
8
8
|
export type ChatToolCallProps = {
|
|
@@ -11,7 +11,7 @@ export type ChatToolCallProps = {
|
|
|
11
11
|
toolInput?: unknown;
|
|
12
12
|
toolOutput?: unknown;
|
|
13
13
|
toolError?: boolean | string;
|
|
14
|
-
/**
|
|
14
|
+
/** Pending execution — label shimmers, no spinner. */
|
|
15
15
|
running?: boolean;
|
|
16
16
|
/** Optional icon node (left of label). */
|
|
17
17
|
icon?: ReactNode;
|
|
@@ -28,44 +28,38 @@ export function ChatToolCall({ toolName = "Tool", label, toolInput, toolOutput,
|
|
|
28
28
|
const [open, setOpen] = useState(defaultOpen);
|
|
29
29
|
const displayLabel = label ?? formatToolName(toolName);
|
|
30
30
|
|
|
31
|
-
const identityIcon = icon ??
|
|
32
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" className="shrink-0 text-muted-foreground" aria-hidden>
|
|
33
|
-
<path d="M8 7h8M8 12h8M8 17h5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
|
|
34
|
-
<rect x="4" y="4" width="16" height="16" rx="3" stroke="currentColor" strokeWidth="1.75" />
|
|
35
|
-
</svg>
|
|
36
|
-
);
|
|
31
|
+
const identityIcon = icon ?? <FluentIcon name="board-24" size={13} className="shrink-0" />;
|
|
37
32
|
|
|
38
33
|
const header = (
|
|
39
34
|
<>
|
|
40
35
|
{identityIcon}
|
|
41
36
|
<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>
|
|
42
|
-
<ToolUiTrailing
|
|
37
|
+
<ToolUiTrailing failed={hasError} chevron={expandable} chevronClassName={cn("group-hover:opacity-100", open && "opacity-100 rotate-90")} />
|
|
43
38
|
</>
|
|
44
39
|
);
|
|
45
40
|
|
|
46
41
|
return (
|
|
47
42
|
<div className={cn("nonla-chat-tool mt-1", className)}>
|
|
48
43
|
<div className="px-4 pb-0.5">
|
|
49
|
-
<div className={cn("mb-1 overflow-hidden rounded-
|
|
44
|
+
<div className={cn("mb-1 overflow-hidden rounded-md", open && "bg-card")}>
|
|
50
45
|
{expandable ? (
|
|
51
|
-
<button type="button" onClick={() => setOpen((v) => !v)} className=
|
|
46
|
+
<button type="button" onClick={() => setOpen((v) => !v)} className="group flex w-full cursor-pointer items-center gap-2 border-0 bg-transparent px-2.5 py-1 outline-none">
|
|
52
47
|
{header}
|
|
53
48
|
</button>
|
|
54
49
|
) : (
|
|
55
|
-
<div className="flex w-full items-center gap-2 px-2.5 py-1
|
|
50
|
+
<div className="flex w-full items-center gap-2 bg-transparent px-2.5 py-1">{header}</div>
|
|
56
51
|
)}
|
|
57
52
|
|
|
58
53
|
{open && expandable ? (
|
|
59
|
-
<div className="
|
|
60
|
-
{hasInput ? <pre className="m-0 max-h-27.5 overflow-y-auto bg-
|
|
54
|
+
<div className="font-mono text-[13px]">
|
|
55
|
+
{hasInput ? <pre className="m-0 max-h-27.5 overflow-y-auto bg-well px-3 py-1.5 break-all whitespace-pre-wrap font-normal leading-[1.65] text-muted-foreground">{prettyJson(toolInput)}</pre> : null}
|
|
61
56
|
{running ? (
|
|
62
|
-
<div className="
|
|
63
|
-
<ChatSpinner />
|
|
57
|
+
<div className="bg-well-strong px-3 py-1.5 text-muted-foreground">
|
|
64
58
|
<Shimmer className="italic">Running…</Shimmer>
|
|
65
59
|
</div>
|
|
66
60
|
) : null}
|
|
67
61
|
{!isPending ? (
|
|
68
|
-
<pre className={cn("m-0 max-h-75 overflow-y-auto px-3 py-
|
|
62
|
+
<pre className={cn("m-0 max-h-75 overflow-y-auto px-3 py-1.5 break-all whitespace-pre-wrap font-normal leading-[1.65] text-muted-foreground", hasError ? "bg-destructive/6" : "nonla-chat-tool-result-ok")}>
|
|
69
63
|
{hasOutput ? prettyJson(toolOutput) : typeof toolError === "string" ? toolError : "Tool execution failed"}
|
|
70
64
|
</pre>
|
|
71
65
|
) : null}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
|
-
import { parseJsonObject } from "../common/utils";
|
|
3
2
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
|
+
import { parseJsonObject } from "../common/utils";
|
|
4
4
|
import { ToolUiBadge } from "./ToolUiBadge";
|
|
5
5
|
import { ToolUiTrailing } from "./ToolUiTrailing";
|
|
6
6
|
import { isToolRunning, type ToolUIProps } from "./types";
|
|
@@ -42,7 +42,7 @@ export function GetCurrentTimeToolUI({ msg, assistantLabel = "Assistant", assist
|
|
|
42
42
|
<div className="flex items-center gap-2 py-1">
|
|
43
43
|
<FluentIcon name="clock-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
44
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>
|
|
45
|
-
<ToolUiTrailing
|
|
45
|
+
<ToolUiTrailing failed={hasError} />
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
@@ -60,16 +60,16 @@ export function ReadSkillToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
60
60
|
{verb}
|
|
61
61
|
{targetLabel ? <span className="font-normal text-tertiary-foreground"> {targetLabel}</span> : null}
|
|
62
62
|
</Shimmer>
|
|
63
|
-
<ToolUiTrailing
|
|
63
|
+
<ToolUiTrailing failed={failed} chevron chevronClassName="group-hover/readskill:opacity-100 group-open/readskill:opacity-100 group-open/readskill:rotate-90" />
|
|
64
64
|
</summary>
|
|
65
65
|
{body ? (
|
|
66
|
-
<pre className={cn("m-0 mt-1.5 mb-1 max-h-40 overflow-y-auto rounded-lg border px-3 py-2 font-mono text-[
|
|
66
|
+
<pre className={cn("m-0 mt-1.5 mb-1 max-h-40 overflow-y-auto rounded-lg border px-3 py-2 font-mono text-[13px] font-normal leading-[1.65] break-all whitespace-pre-wrap", failed ? "border-destructive/35 bg-destructive/6 text-destructive" : "border-(--popper-border) bg-(--nonla-elevated) text-muted-foreground")}>{body}</pre>
|
|
67
67
|
) : null}
|
|
68
68
|
{!running && !reference && available.length > 0 ? (
|
|
69
69
|
<div className="mt-0.5 mb-1 flex flex-wrap items-center gap-1">
|
|
70
70
|
<span className="text-[10px] font-medium tracking-wide text-quaternary-foreground uppercase">Refs</span>
|
|
71
71
|
{available.map((name) => (
|
|
72
|
-
<span key={name} className="inline-flex max-w-full items-center rounded-md border border-border-subtle bg-muted/40 px-1.5 py-0.5 font-mono text-[
|
|
72
|
+
<span key={name} className="inline-flex max-w-full items-center rounded-md border border-border-subtle bg-muted/40 px-1.5 py-0.5 font-mono text-[13px] leading-[1.4] text-tertiary-foreground">
|
|
73
73
|
{skillName ? `${skillName} / ${name}` : name}
|
|
74
74
|
</span>
|
|
75
75
|
))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { CodeBlock } from "../../codeblock/CodeBlock";
|
|
1
2
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
|
-
import { CodeBlock } from "../../codeblock/CodeBlock";
|
|
4
4
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
5
5
|
import { parseBgTaskRef } from "../common/bgTasks";
|
|
6
6
|
import { parseJsonObject, prettyJson } from "../common/utils";
|
|
@@ -87,7 +87,7 @@ export function RunJsToolUI({ msg, assistantLabel = "Assistant", assistantColor,
|
|
|
87
87
|
<ToolUiBadge show={showAvatar} label={assistantLabel} color={assistantColor} />
|
|
88
88
|
<details className="group/runjs px-4 pb-2" style={{ overflowAnchor: "none" }}>
|
|
89
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">
|
|
90
|
-
<FluentIcon name="code-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
90
|
+
<FluentIcon name="code-block-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
91
91
|
<Shimmer active={running} className="min-w-0 truncate font-medium text-muted-foreground">{verb}</Shimmer>
|
|
92
92
|
<ToolUiTrailing running={running} failed={failed} chevron chevronClassName="group-hover/runjs:opacity-100 group-open/runjs:opacity-100 group-open/runjs:rotate-90" />
|
|
93
93
|
</summary>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChevronRight, CircleX } from "lucide-react";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
3
|
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
4
4
|
|
|
@@ -14,11 +14,7 @@ export function ToolUiTrailing({
|
|
|
14
14
|
chevronClassName?: string;
|
|
15
15
|
}) {
|
|
16
16
|
if (running) return <ChatSpinner />;
|
|
17
|
-
if (failed) return <
|
|
17
|
+
if (failed) return <CircleX size={13} className="text-destructive" aria-hidden />;
|
|
18
18
|
if (!chevron) return null;
|
|
19
|
-
return (
|
|
20
|
-
<svg className={cn("h-3 w-3 shrink-0 opacity-0 transition-[opacity,transform] duration-150", chevronClassName)} fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
|
21
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
22
|
-
</svg>
|
|
23
|
-
);
|
|
19
|
+
return <ChevronRight size={12} className={cn("shrink-0 opacity-0 transition-[opacity,transform] duration-150", chevronClassName)} aria-hidden />;
|
|
24
20
|
}
|
|
@@ -32,23 +32,32 @@ export function WebFetchToolUI({ msg, assistantLabel = "Assistant", assistantCol
|
|
|
32
32
|
const running = isToolRunning(msg, generating);
|
|
33
33
|
const url = output?.url || inputUrl(msg.toolInput) || "—";
|
|
34
34
|
const body = failed ? (output?.error ?? (typeof msg.toolError === "string" ? msg.toolError : null) ?? "Tool execution failed") : output?.text?.trim() || "";
|
|
35
|
-
const
|
|
35
|
+
const expandable = Boolean(body);
|
|
36
|
+
const verb = running ? "Fetching page" : failed ? "Fetch page" : "Fetched page";
|
|
37
|
+
|
|
38
|
+
const header = (
|
|
39
|
+
<>
|
|
40
|
+
<FluentIcon name="globe-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
41
|
+
<Shimmer active={running} className="min-w-0 truncate font-medium text-muted-foreground">
|
|
42
|
+
{verb} <span className="font-normal text-tertiary-foreground">{url}</span>
|
|
43
|
+
</Shimmer>
|
|
44
|
+
<ToolUiTrailing failed={failed} chevron={expandable} chevronClassName="group-hover/webfetch:opacity-100 group-open/webfetch:opacity-100 group-open/webfetch:rotate-90" />
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
36
47
|
|
|
37
48
|
return (
|
|
38
49
|
<div className="mt-1 animate-fadeIn">
|
|
39
50
|
<ToolUiBadge show={showAvatar} label={assistantLabel} color={assistantColor} />
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
) : null}
|
|
51
|
-
</details>
|
|
51
|
+
{expandable ? (
|
|
52
|
+
<details className="group/webfetch px-4 pb-2" style={{ overflowAnchor: "none" }}>
|
|
53
|
+
<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">{header}</summary>
|
|
54
|
+
<pre className={cn("m-0 mt-1.5 mb-1 max-h-40 overflow-y-auto rounded-lg border px-3 py-2 font-mono text-[13px] font-normal leading-[1.65] break-all whitespace-pre-wrap", failed ? "border-destructive/35 bg-destructive/6 text-destructive" : "border-(--popper-border) bg-(--nonla-elevated) text-muted-foreground")}>{body}</pre>
|
|
55
|
+
</details>
|
|
56
|
+
) : (
|
|
57
|
+
<div className="px-4 pb-2">
|
|
58
|
+
<div className="flex items-center gap-2 py-0.5 text-[14px] leading-5.5">{header}</div>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
52
61
|
</div>
|
|
53
62
|
);
|
|
54
63
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
2
|
-
import { type ComponentPropsWithoutRef, type ReactNode
|
|
2
|
+
import { type ComponentPropsWithoutRef, forwardRef, type ReactNode } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
|
|
5
5
|
export type CheckboxProps = Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "onCheckedChange" | "checked" | "onChange"> & {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FileText } from "lucide-react";
|
|
2
|
+
import { type ComponentProps, createContext, type ReactNode, useContext, useMemo } from "react";
|
|
2
3
|
import { ButtonCopy } from "../button/ButtonCopy";
|
|
3
4
|
import { cn } from "../lib/cn";
|
|
5
|
+
import type { ControlSize } from "../lib/sizes";
|
|
6
|
+
import { OverlayScroll } from "../scroll/OverlayScroll";
|
|
4
7
|
import { highlightCode, languageLabel, wrapHljsLines } from "./highlight";
|
|
5
8
|
|
|
6
9
|
type CodeBlockContextValue = {
|
|
@@ -22,16 +25,9 @@ export type CodeBlockProps = ComponentProps<"div"> & {
|
|
|
22
25
|
wordWrap?: boolean;
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<svg width={size} height={size} viewBox="0 0 256 256" aria-hidden>
|
|
28
|
-
<path fill="none" stroke="currentColor" strokeWidth="25" strokeLinecap="round" d="M208 128l-80 80M192 40 40 192" />
|
|
29
|
-
</svg>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type CodeBlockCopyButtonProps = ComponentProps<"button"> & {
|
|
28
|
+
export type CodeBlockCopyButtonProps = Omit<ComponentProps<"button">, "size"> & {
|
|
34
29
|
content?: string;
|
|
30
|
+
size?: ControlSize;
|
|
35
31
|
};
|
|
36
32
|
|
|
37
33
|
export function CodeBlockCopyButton({ content, className, ...props }: CodeBlockCopyButtonProps) {
|
|
@@ -54,7 +50,7 @@ export function CodeBlock({ code, language, title, lineNumbers = false, wordWrap
|
|
|
54
50
|
<div className="flex min-w-0 items-center gap-2">
|
|
55
51
|
{label ? (
|
|
56
52
|
<>
|
|
57
|
-
<
|
|
53
|
+
<FileText size={14} className="shrink-0" aria-hidden />
|
|
58
54
|
<span className="truncate font-medium">{label}</span>
|
|
59
55
|
</>
|
|
60
56
|
) : null}
|
|
@@ -62,14 +58,16 @@ export function CodeBlock({ code, language, title, lineNumbers = false, wordWrap
|
|
|
62
58
|
<CodeBlockCopyButton />
|
|
63
59
|
</div>
|
|
64
60
|
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
<OverlayScroll
|
|
62
|
+
autoHeight
|
|
63
|
+
className={cn("nonla-codeblock-body nonla-codeblock-well min-w-0 bg-card font-mono text-[13px] leading-5 max-h-96", !children && "mx-0.5 mb-0.5 rounded-lg")}
|
|
64
|
+
innerClassName={wordWrap ? undefined : "overflow-x-auto"}
|
|
65
|
+
>
|
|
66
|
+
<pre className={cn("nonla-codeblock-pre m-0 whitespace-pre break-normal", lineNumbers && "nonla-codeblock-lines", wordWrap && "whitespace-pre-wrap wrap-break-word")}>
|
|
67
|
+
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: highlight.js emits escaped HTML */}
|
|
68
|
+
<code className={cn("block px-3 py-2.5", !wordWrap && "w-max min-w-full")} dangerouslySetInnerHTML={{ __html: html }} />
|
|
69
|
+
</pre>
|
|
70
|
+
</OverlayScroll>
|
|
73
71
|
{children}
|
|
74
72
|
</div>
|
|
75
73
|
</CodeBlockContext.Provider>
|