devnonla-ui 0.1.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/package.json +66 -0
  4. package/src/alert/Alert.tsx +109 -0
  5. package/src/app/App.tsx +36 -0
  6. package/src/button/Button.tsx +278 -0
  7. package/src/button/ButtonCopy.tsx +60 -0
  8. package/src/calendar/Calendar.tsx +133 -0
  9. package/src/chat/AgentPanel.tsx +203 -0
  10. package/src/chat/common/bgTasks.ts +41 -0
  11. package/src/chat/common/sse.ts +156 -0
  12. package/src/chat/common/types.ts +43 -0
  13. package/src/chat/common/useAgentStream.ts +351 -0
  14. package/src/chat/common/utils.ts +73 -0
  15. package/src/chat/index.ts +56 -0
  16. package/src/chat/message-ui/ChatAgentMessage.tsx +29 -0
  17. package/src/chat/message-ui/ChatError.tsx +24 -0
  18. package/src/chat/message-ui/ChatInput.tsx +162 -0
  19. package/src/chat/message-ui/ChatMarkdown.tsx +128 -0
  20. package/src/chat/message-ui/ChatMarkdownTable.tsx +45 -0
  21. package/src/chat/message-ui/ChatSpinner.tsx +5 -0
  22. package/src/chat/message-ui/ChatThinking.tsx +61 -0
  23. package/src/chat/message-ui/ChatUserMessage.tsx +72 -0
  24. package/src/chat/message-ui/ChatWelcome.tsx +70 -0
  25. package/src/chat/message-ui/MermaidBlock.tsx +266 -0
  26. package/src/chat/message-ui/mermaidFence.ts +55 -0
  27. package/src/chat/message-ui/sanitizeMermaid.ts +135 -0
  28. package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +36 -0
  29. package/src/chat/tool-ui/BackgroundTasksBar.tsx +152 -0
  30. package/src/chat/tool-ui/CallAgentToolUI.tsx +168 -0
  31. package/src/chat/tool-ui/ChatToolCall.tsx +77 -0
  32. package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +49 -0
  33. package/src/chat/tool-ui/ReadSkillToolUI.tsx +80 -0
  34. package/src/chat/tool-ui/RunJsToolUI.tsx +106 -0
  35. package/src/chat/tool-ui/ToolUiBadge.tsx +10 -0
  36. package/src/chat/tool-ui/ToolUiTrailing.tsx +24 -0
  37. package/src/chat/tool-ui/WebFetchToolUI.tsx +53 -0
  38. package/src/chat/tool-ui/registry.ts +29 -0
  39. package/src/chat/tool-ui/types.ts +22 -0
  40. package/src/checkbox/Checkbox.tsx +50 -0
  41. package/src/codeblock/CodeBlock.tsx +76 -0
  42. package/src/codeblock/highlight.ts +159 -0
  43. package/src/colorpicker/ColorPicker.tsx +45 -0
  44. package/src/datepicker/DatePicker.tsx +481 -0
  45. package/src/desktop/DesktopHeader.tsx +92 -0
  46. package/src/desktop/DesktopIcon.tsx +34 -0
  47. package/src/desktop/DesktopStage.tsx +6 -0
  48. package/src/desktop/DesktopWindow.tsx +504 -0
  49. package/src/desktop/MeadowDesktop.tsx +15 -0
  50. package/src/desktop/MeadowShell.tsx +12 -0
  51. package/src/desktop/MeadowWallpaper.tsx +5 -0
  52. package/src/desktop/assets/desktop-meadow.jpg +0 -0
  53. package/src/desktop/assets.d.ts +9 -0
  54. package/src/desktop/surface.ts +1 -0
  55. package/src/drawer/Drawer.tsx +125 -0
  56. package/src/dropdown/ContextMenu.tsx +97 -0
  57. package/src/dropdown/Dropdown.tsx +139 -0
  58. package/src/dropdown/menuClasses.ts +9 -0
  59. package/src/empty/Empty.tsx +47 -0
  60. package/src/form/Form.tsx +95 -0
  61. package/src/form/FormItem.tsx +145 -0
  62. package/src/form/common/context.ts +13 -0
  63. package/src/form/common/enum.ts +20 -0
  64. package/src/form/common/rules.ts +68 -0
  65. package/src/form/common/types.ts +169 -0
  66. package/src/form/index.ts +35 -0
  67. package/src/form/partials/FormItemHelps.tsx +31 -0
  68. package/src/form/variants/FieldCheckbox.tsx +20 -0
  69. package/src/form/variants/FieldColor.tsx +63 -0
  70. package/src/form/variants/FieldDateTime.tsx +25 -0
  71. package/src/form/variants/FieldHidden.tsx +9 -0
  72. package/src/form/variants/FieldInput.tsx +24 -0
  73. package/src/form/variants/FieldNumber.tsx +28 -0
  74. package/src/form/variants/FieldObject.tsx +29 -0
  75. package/src/form/variants/FieldRadio.tsx +40 -0
  76. package/src/form/variants/FieldRepeater.tsx +62 -0
  77. package/src/form/variants/FieldRepeaterItem.tsx +64 -0
  78. package/src/form/variants/FieldSelect.tsx +27 -0
  79. package/src/form/variants/FieldSelectMultiple.tsx +32 -0
  80. package/src/form/variants/FieldSelectRemote.tsx +66 -0
  81. package/src/form/variants/FieldSwitch.tsx +17 -0
  82. package/src/form/variants/FieldTextarea.tsx +25 -0
  83. package/src/form/variants/FieldTime.tsx +25 -0
  84. package/src/form-layout/FormLayout.tsx +74 -0
  85. package/src/icon/FluentIcon.tsx +28 -0
  86. package/src/icon/fluent.ts +75 -0
  87. package/src/index.ts +214 -0
  88. package/src/input/Input.tsx +371 -0
  89. package/src/input/SearchInput.tsx +73 -0
  90. package/src/lib/cn.ts +6 -0
  91. package/src/lib/placement.ts +44 -0
  92. package/src/lib/sizes.ts +117 -0
  93. package/src/lib/surface.ts +8 -0
  94. package/src/menu/Menu.tsx +98 -0
  95. package/src/message/message.tsx +214 -0
  96. package/src/modal/Modal.tsx +277 -0
  97. package/src/pagination/Pagination.tsx +103 -0
  98. package/src/popconfirm/Popconfirm.tsx +134 -0
  99. package/src/popover/Popover.tsx +116 -0
  100. package/src/scroll/OverlayScroll.tsx +141 -0
  101. package/src/segmented/Segmented.tsx +67 -0
  102. package/src/select/Select.tsx +319 -0
  103. package/src/skeleton/Skeleton.tsx +47 -0
  104. package/src/spin/Spin.tsx +160 -0
  105. package/src/styles.css +897 -0
  106. package/src/switch/Switch.tsx +58 -0
  107. package/src/table/Table.tsx +387 -0
  108. package/src/tag/Tag.tsx +109 -0
  109. package/src/theme.ts +131 -0
  110. package/src/timepicker/TimePicker.tsx +751 -0
  111. package/src/tooltip/Tooltip.tsx +59 -0
@@ -0,0 +1,50 @@
1
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2
+ import { type ComponentPropsWithoutRef, type ReactNode, forwardRef } from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ export type CheckboxProps = Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "onCheckedChange" | "checked" | "onChange"> & {
6
+ checked?: boolean | "indeterminate";
7
+ defaultChecked?: boolean;
8
+ /** antd-style — receives next checked boolean (not a DOM event). */
9
+ onChange?: (checked: boolean) => void;
10
+ children?: ReactNode;
11
+ indeterminate?: boolean;
12
+ };
13
+
14
+ function CheckIcon() {
15
+ return (
16
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden>
17
+ <path d="M2 5.2L4.1 7.2L8 2.8" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
18
+ </svg>
19
+ );
20
+ }
21
+
22
+ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(function Checkbox(
23
+ { className, checked, defaultChecked, onChange, children, indeterminate, disabled, ...rest },
24
+ ref,
25
+ ) {
26
+ const resolved = indeterminate ? "indeterminate" : checked;
27
+ return (
28
+ <label className={cn("inline-flex items-center gap-2 text-sm text-foreground cursor-pointer", disabled && "opacity-45 cursor-not-allowed")}>
29
+ <CheckboxPrimitive.Root
30
+ ref={ref}
31
+ checked={resolved}
32
+ defaultChecked={defaultChecked}
33
+ disabled={disabled}
34
+ onCheckedChange={(v) => onChange?.(v === true)}
35
+ className={cn(
36
+ "size-4 shrink-0 rounded-[4px] border border-input bg-[var(--control-bg)] transition-colors duration-150 ease-[cubic-bezier(0.16,1,0.3,1)]",
37
+ "focus-visible:outline-none data-[state=checked]:bg-brand data-[state=checked]:border-brand data-[state=checked]:text-[var(--nonla-solid-fg)]",
38
+ "data-[state=indeterminate]:bg-brand data-[state=indeterminate]:border-brand data-[state=indeterminate]:text-[var(--nonla-solid-fg)]",
39
+ className,
40
+ )}
41
+ {...rest}
42
+ >
43
+ <CheckboxPrimitive.Indicator className="flex items-center justify-center text-current animate-[nonla-check-in_150ms_cubic-bezier(0.16,1,0.3,1)]">
44
+ <CheckIcon />
45
+ </CheckboxPrimitive.Indicator>
46
+ </CheckboxPrimitive.Root>
47
+ {children != null ? <span>{children}</span> : null}
48
+ </label>
49
+ );
50
+ });
@@ -0,0 +1,76 @@
1
+ import { type ComponentProps, type ReactNode, createContext, useContext, useMemo } from "react";
2
+ import { ButtonCopy } from "../button/ButtonCopy";
3
+ import { cn } from "../lib/cn";
4
+ import { highlightCode, languageLabel, wrapHljsLines } from "./highlight";
5
+
6
+ type CodeBlockContextValue = {
7
+ code: string;
8
+ };
9
+
10
+ const CodeBlockContext = createContext<CodeBlockContextValue | null>(null);
11
+
12
+ export type CodeBlockProps = ComponentProps<"div"> & {
13
+ /** Source code to render. */
14
+ code: string;
15
+ /** Language for highlight.js (`javascript`, `tsx`, `json`, …). */
16
+ language?: string;
17
+ /** Filename / label shown in the header. Falls back to the language name. */
18
+ title?: ReactNode;
19
+ /** Show gutter line numbers. */
20
+ lineNumbers?: boolean;
21
+ /** Wrap long lines instead of horizontal scroll. */
22
+ wordWrap?: boolean;
23
+ };
24
+
25
+ function CodeFileIcon({ size = 14 }: { size?: number }) {
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"> & {
34
+ content?: string;
35
+ };
36
+
37
+ export function CodeBlockCopyButton({ content, className, ...props }: CodeBlockCopyButtonProps) {
38
+ const ctx = useContext(CodeBlockContext);
39
+ return <ButtonCopy text={content ?? ctx?.code ?? ""} label="Copy code" className={className} {...props} />;
40
+ }
41
+
42
+ export function CodeBlock({ code, language, title, lineNumbers = false, wordWrap = false, className, children, ...props }: CodeBlockProps) {
43
+ const html = useMemo(() => {
44
+ const highlighted = highlightCode(code, language);
45
+ return lineNumbers ? wrapHljsLines(highlighted) : highlighted;
46
+ }, [code, language, lineNumbers]);
47
+
48
+ const label = title ?? languageLabel(language);
49
+
50
+ return (
51
+ <CodeBlockContext.Provider value={{ code }}>
52
+ <div className={cn("nonla-codeblock relative flex min-w-0 w-full flex-col overflow-clip rounded-lg border border-border bg-glass-bar text-sm text-foreground backdrop-blur-lg", className)} data-language={language} {...props}>
53
+ <div className="flex h-9 items-center justify-between gap-2 pl-2 pr-1 text-sm text-muted-foreground">
54
+ <div className="flex min-w-0 items-center gap-2">
55
+ {label ? (
56
+ <>
57
+ <CodeFileIcon />
58
+ <span className="truncate font-medium">{label}</span>
59
+ </>
60
+ ) : null}
61
+ </div>
62
+ <CodeBlockCopyButton />
63
+ </div>
64
+
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
+ <div className={cn("w-full", !wordWrap && "overflow-x-auto")}>
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
+ <code className={cn("block px-3 py-2.5", !wordWrap && "w-max min-w-full")} dangerouslySetInnerHTML={{ __html: html }} />
69
+ </pre>
70
+ </div>
71
+ </div>
72
+ {children}
73
+ </div>
74
+ </CodeBlockContext.Provider>
75
+ );
76
+ }
@@ -0,0 +1,159 @@
1
+ import hljs from "highlight.js/lib/common";
2
+ import bash from "highlight.js/lib/languages/bash";
3
+
4
+ /** highlight.js bash only colors shell builtins (`echo`, `ls`) — not `docker run`, flags, or numbers. */
5
+ const CLI_COMMANDS = [
6
+ "docker",
7
+ "git",
8
+ "npm",
9
+ "npx",
10
+ "pnpm",
11
+ "yarn",
12
+ "bun",
13
+ "curl",
14
+ "wget",
15
+ "ssh",
16
+ "scp",
17
+ "rsync",
18
+ "brew",
19
+ "pip",
20
+ "pip3",
21
+ "cargo",
22
+ "make",
23
+ "node",
24
+ "deno",
25
+ "python",
26
+ "python3",
27
+ "kubectl",
28
+ "helm",
29
+ "aws",
30
+ "gh",
31
+ ];
32
+
33
+ const SHELL_KEYWORDS = "if|then|else|elif|fi|for|while|until|in|do|done|case|esac|function|select|time|coproc";
34
+
35
+ hljs.registerLanguage("bash", (api) => {
36
+ const lang = bash(api);
37
+ const keywords = lang.keywords;
38
+ if (keywords && typeof keywords === "object" && !Array.isArray(keywords) && Array.isArray(keywords.built_in)) {
39
+ keywords.built_in = [...keywords.built_in, ...CLI_COMMANDS];
40
+ }
41
+ lang.contains = [
42
+ ...(lang.contains ?? []),
43
+ {
44
+ match: [/(?:^|\n)\s*/, new RegExp(`(?!(?:${SHELL_KEYWORDS})\\b)[a-z][\\w.-]*`)],
45
+ scope: { 2: "built_in" },
46
+ relevance: 0,
47
+ },
48
+ { className: "number", match: /\b\d+\b/, relevance: 0 },
49
+ { className: "attr", match: /(?<=[\s=])-{1,2}[A-Za-z0-9][\w-]*/, relevance: 0 },
50
+ ];
51
+ return lang;
52
+ });
53
+
54
+ const LANG_ALIAS: Record<string, string> = {
55
+ js: "javascript",
56
+ jsx: "javascript",
57
+ mjs: "javascript",
58
+ cjs: "javascript",
59
+ ts: "typescript",
60
+ tsx: "typescript",
61
+ mts: "typescript",
62
+ cts: "typescript",
63
+ py: "python",
64
+ sh: "bash",
65
+ bash: "bash",
66
+ zsh: "bash",
67
+ shell: "bash",
68
+ yml: "yaml",
69
+ md: "markdown",
70
+ rs: "rust",
71
+ cs: "csharp",
72
+ "c++": "cpp",
73
+ hpp: "cpp",
74
+ h: "c",
75
+ ps1: "powershell",
76
+ docker: "dockerfile",
77
+ gql: "graphql",
78
+ };
79
+
80
+ export function normalizeLang(language?: string): string | undefined {
81
+ if (!language) return undefined;
82
+ const key = language.trim().toLowerCase();
83
+ if (!key) return undefined;
84
+ return LANG_ALIAS[key] ?? key;
85
+ }
86
+
87
+ function escapeHtml(text: string): string {
88
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
89
+ }
90
+
91
+ export function highlightCode(code: string, language?: string): string {
92
+ const lang = normalizeLang(language);
93
+ if (lang && hljs.getLanguage(lang)) {
94
+ try {
95
+ return hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;
96
+ } catch {
97
+ /* fall through */
98
+ }
99
+ }
100
+ try {
101
+ return hljs.highlightAuto(code).value;
102
+ } catch {
103
+ return escapeHtml(code);
104
+ }
105
+ }
106
+
107
+ export function wrapHljsLines(html: string): string {
108
+ return html.split("\n").map((line) => `<span class="hljs-line">${line}</span>`).join("\n");
109
+ }
110
+
111
+ const LANG_LABELS: Record<string, string> = {
112
+ js: "JavaScript",
113
+ javascript: "JavaScript",
114
+ jsx: "JSX",
115
+ ts: "TypeScript",
116
+ typescript: "TypeScript",
117
+ tsx: "TSX",
118
+ py: "Python",
119
+ python: "Python",
120
+ sh: "Shell",
121
+ bash: "Bash",
122
+ zsh: "Zsh",
123
+ shell: "Shell",
124
+ json: "JSON",
125
+ yaml: "YAML",
126
+ yml: "YAML",
127
+ toml: "TOML",
128
+ html: "HTML",
129
+ css: "CSS",
130
+ sql: "SQL",
131
+ md: "Markdown",
132
+ markdown: "Markdown",
133
+ rs: "Rust",
134
+ rust: "Rust",
135
+ go: "Go",
136
+ java: "Java",
137
+ cpp: "C++",
138
+ c: "C",
139
+ cs: "C#",
140
+ csharp: "C#",
141
+ php: "PHP",
142
+ rb: "Ruby",
143
+ ruby: "Ruby",
144
+ swift: "Swift",
145
+ kt: "Kotlin",
146
+ kotlin: "Kotlin",
147
+ graphql: "GraphQL",
148
+ dockerfile: "Dockerfile",
149
+ xml: "XML",
150
+ mermaid: "Mermaid",
151
+ };
152
+
153
+ /** Display name for a fenced-code language (`js` → `JavaScript`). */
154
+ export function languageLabel(language?: string): string | undefined {
155
+ if (!language) return undefined;
156
+ const key = language.trim().toLowerCase();
157
+ if (!key) return undefined;
158
+ return LANG_LABELS[key] ?? language.trim();
159
+ }
@@ -0,0 +1,45 @@
1
+ import { cn } from "../lib/cn";
2
+
3
+ export type ColorPickerProps = {
4
+ presets: readonly string[];
5
+ value: string;
6
+ onChange: (color: string) => void;
7
+ size?: number;
8
+ className?: string;
9
+ };
10
+
11
+ const swatchClass =
12
+ "rounded-md cursor-pointer transition-all duration-150 hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
13
+ const activeClass = "ring-2 ring-ring ring-offset-2 ring-offset-background";
14
+
15
+ export function ColorPicker({ presets, value, onChange, size = 28, className }: ColorPickerProps) {
16
+ const isCustom = !presets.includes(value);
17
+
18
+ return (
19
+ <div className={cn("flex items-center gap-1.5 flex-wrap", className)}>
20
+ {presets.map((preset) => {
21
+ const isActive = value === preset;
22
+ return (
23
+ <button
24
+ key={preset}
25
+ type="button"
26
+ onClick={() => onChange(preset)}
27
+ className={cn(swatchClass, isActive && activeClass)}
28
+ style={{ width: size, height: size, backgroundColor: preset }}
29
+ title={preset}
30
+ />
31
+ );
32
+ })}
33
+
34
+ <label
35
+ className={cn("relative overflow-hidden", swatchClass, isCustom && activeClass)}
36
+ style={{ width: size, height: size }}
37
+ title="Custom color"
38
+ >
39
+ <div className="absolute inset-0" style={{ background: "conic-gradient(#e88080, #e8c870, #80d4a0, #70c8d8, #7aaee8, #a888e8, #e88080)" }} />
40
+ {isCustom ? <div className="absolute inset-1 rounded-sm" style={{ backgroundColor: value }} /> : null}
41
+ <input type="color" value={value} onChange={(e) => onChange(e.target.value)} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
42
+ </label>
43
+ </div>
44
+ );
45
+ }