@nibapp/ui 0.0.0-stage → 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 (292) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +124 -2
  4. package/TRADEMARKS.md +22 -0
  5. package/dist/_bundled/money/index.d.ts +98 -0
  6. package/dist/_bundled/money/index.js +152 -0
  7. package/dist/_bundled/tokens/index.d.ts +2 -0
  8. package/dist/_bundled/tokens/index.js +2 -0
  9. package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
  10. package/dist/_bundled/tokens/palette.gen.js +206 -0
  11. package/dist/_bundled/tokens/spec.d.ts +291 -0
  12. package/dist/_bundled/tokens/spec.js +221 -0
  13. package/dist/components/accordion.d.ts +27 -0
  14. package/dist/components/accordion.js +66 -0
  15. package/dist/components/alert-dialog.d.ts +59 -0
  16. package/dist/components/alert-dialog.js +82 -0
  17. package/dist/components/alert.d.ts +141 -0
  18. package/dist/components/alert.js +55 -0
  19. package/dist/components/appear.d.ts +21 -0
  20. package/dist/components/appear.js +42 -0
  21. package/dist/components/avatar.d.ts +196 -0
  22. package/dist/components/avatar.js +135 -0
  23. package/dist/components/badge.d.ts +183 -0
  24. package/dist/components/badge.js +68 -0
  25. package/dist/components/block-stack.d.ts +33 -0
  26. package/dist/components/block-stack.js +197 -0
  27. package/dist/components/button.d.ts +327 -0
  28. package/dist/components/button.js +101 -0
  29. package/dist/components/card.d.ts +131 -0
  30. package/dist/components/card.js +100 -0
  31. package/dist/components/checkbox.d.ts +34 -0
  32. package/dist/components/checkbox.js +37 -0
  33. package/dist/components/choice-row.d.ts +104 -0
  34. package/dist/components/choice-row.js +56 -0
  35. package/dist/components/clamped-text.d.ts +17 -0
  36. package/dist/components/clamped-text.js +47 -0
  37. package/dist/components/code-block.d.ts +48 -0
  38. package/dist/components/code-block.js +124 -0
  39. package/dist/components/code-input.d.ts +49 -0
  40. package/dist/components/code-input.js +59 -0
  41. package/dist/components/combobox.d.ts +194 -0
  42. package/dist/components/combobox.js +176 -0
  43. package/dist/components/command-palette.d.ts +57 -0
  44. package/dist/components/command-palette.js +83 -0
  45. package/dist/components/context-menu.d.ts +24 -0
  46. package/dist/components/context-menu.js +30 -0
  47. package/dist/components/copy-button.d.ts +9 -0
  48. package/dist/components/copy-button.js +20 -0
  49. package/dist/components/copy-command.d.ts +18 -0
  50. package/dist/components/copy-command.js +19 -0
  51. package/dist/components/data-table.d.ts +240 -0
  52. package/dist/components/data-table.js +63 -0
  53. package/dist/components/dialog.d.ts +80 -0
  54. package/dist/components/dialog.js +85 -0
  55. package/dist/components/diff-lines.d.ts +20 -0
  56. package/dist/components/diff-lines.js +10 -0
  57. package/dist/components/diff-view.d.ts +128 -0
  58. package/dist/components/diff-view.js +675 -0
  59. package/dist/components/disclosure.d.ts +33 -0
  60. package/dist/components/disclosure.js +51 -0
  61. package/dist/components/drawn-check.d.ts +8 -0
  62. package/dist/components/drawn-check.js +16 -0
  63. package/dist/components/edge-resizer.d.ts +26 -0
  64. package/dist/components/edge-resizer.js +53 -0
  65. package/dist/components/empty-state.d.ts +112 -0
  66. package/dist/components/empty-state.js +56 -0
  67. package/dist/components/expand-toggle.d.ts +19 -0
  68. package/dist/components/expand-toggle.js +10 -0
  69. package/dist/components/field.d.ts +48 -0
  70. package/dist/components/field.js +60 -0
  71. package/dist/components/filter-chip.d.ts +47 -0
  72. package/dist/components/filter-chip.js +33 -0
  73. package/dist/components/group.d.ts +10 -0
  74. package/dist/components/group.js +9 -0
  75. package/dist/components/icon-text.d.ts +11 -0
  76. package/dist/components/icon-text.js +10 -0
  77. package/dist/components/image-sources.d.ts +57 -0
  78. package/dist/components/image-sources.js +58 -0
  79. package/dist/components/image-thumbnail.d.ts +34 -0
  80. package/dist/components/image-thumbnail.js +58 -0
  81. package/dist/components/image-viewer.d.ts +60 -0
  82. package/dist/components/image-viewer.js +411 -0
  83. package/dist/components/inline-edit.d.ts +22 -0
  84. package/dist/components/inline-edit.js +51 -0
  85. package/dist/components/input.d.ts +155 -0
  86. package/dist/components/input.js +109 -0
  87. package/dist/components/item-label.d.ts +11 -0
  88. package/dist/components/item-label.js +5 -0
  89. package/dist/components/item-row.d.ts +23 -0
  90. package/dist/components/item-row.js +19 -0
  91. package/dist/components/kbd.d.ts +55 -0
  92. package/dist/components/kbd.js +86 -0
  93. package/dist/components/link-policy.d.ts +24 -0
  94. package/dist/components/link-policy.js +32 -0
  95. package/dist/components/linked-text.d.ts +15 -0
  96. package/dist/components/linked-text.js +48 -0
  97. package/dist/components/loading-region.d.ts +18 -0
  98. package/dist/components/loading-region.js +17 -0
  99. package/dist/components/markdown.d.ts +34 -0
  100. package/dist/components/markdown.js +307 -0
  101. package/dist/components/menu.d.ts +132 -0
  102. package/dist/components/menu.js +102 -0
  103. package/dist/components/message-bubble.d.ts +57 -0
  104. package/dist/components/message-bubble.js +28 -0
  105. package/dist/components/meta-line.d.ts +28 -0
  106. package/dist/components/meta-line.js +36 -0
  107. package/dist/components/money-input.d.ts +23 -0
  108. package/dist/components/money-input.js +34 -0
  109. package/dist/components/native-select.d.ts +120 -0
  110. package/dist/components/native-select.js +85 -0
  111. package/dist/components/page-trail.d.ts +30 -0
  112. package/dist/components/page-trail.js +43 -0
  113. package/dist/components/page.d.ts +80 -0
  114. package/dist/components/page.js +60 -0
  115. package/dist/components/path-text.d.ts +8 -0
  116. package/dist/components/path-text.js +11 -0
  117. package/dist/components/percent-input.d.ts +20 -0
  118. package/dist/components/percent-input.js +32 -0
  119. package/dist/components/popover.d.ts +52 -0
  120. package/dist/components/popover.js +36 -0
  121. package/dist/components/progress.d.ts +389 -0
  122. package/dist/components/progress.js +142 -0
  123. package/dist/components/radio.d.ts +65 -0
  124. package/dist/components/radio.js +54 -0
  125. package/dist/components/remote-image-gate.d.ts +34 -0
  126. package/dist/components/remote-image-gate.js +60 -0
  127. package/dist/components/resizable.d.ts +23 -0
  128. package/dist/components/resizable.js +30 -0
  129. package/dist/components/rolling-number.d.ts +13 -0
  130. package/dist/components/rolling-number.js +69 -0
  131. package/dist/components/scroll-area.d.ts +17 -0
  132. package/dist/components/scroll-area.js +25 -0
  133. package/dist/components/section-label.d.ts +10 -0
  134. package/dist/components/section-label.js +21 -0
  135. package/dist/components/section-nav.d.ts +22 -0
  136. package/dist/components/section-nav.js +80 -0
  137. package/dist/components/segmented-control.d.ts +125 -0
  138. package/dist/components/segmented-control.js +101 -0
  139. package/dist/components/select.d.ts +120 -0
  140. package/dist/components/select.js +89 -0
  141. package/dist/components/separator.d.ts +10 -0
  142. package/dist/components/separator.js +12 -0
  143. package/dist/components/sheet.d.ts +101 -0
  144. package/dist/components/sheet.js +129 -0
  145. package/dist/components/shortcut-recorder.d.ts +31 -0
  146. package/dist/components/shortcut-recorder.js +67 -0
  147. package/dist/components/skeleton.d.ts +31 -0
  148. package/dist/components/skeleton.js +33 -0
  149. package/dist/components/slider.d.ts +138 -0
  150. package/dist/components/slider.js +98 -0
  151. package/dist/components/spinner.d.ts +37 -0
  152. package/dist/components/spinner.js +16 -0
  153. package/dist/components/switch.d.ts +59 -0
  154. package/dist/components/switch.js +56 -0
  155. package/dist/components/tab-strip.d.ts +29 -0
  156. package/dist/components/tab-strip.js +42 -0
  157. package/dist/components/tabs.d.ts +13 -0
  158. package/dist/components/tabs.js +68 -0
  159. package/dist/components/text-link.d.ts +54 -0
  160. package/dist/components/text-link.js +38 -0
  161. package/dist/components/textarea.d.ts +75 -0
  162. package/dist/components/textarea.js +90 -0
  163. package/dist/components/toast.d.ts +74 -0
  164. package/dist/components/toast.js +128 -0
  165. package/dist/components/tooltip.d.ts +50 -0
  166. package/dist/components/tooltip.js +46 -0
  167. package/dist/components/truncated-text.d.ts +15 -0
  168. package/dist/components/truncated-text.js +19 -0
  169. package/dist/i18n/defaults.d.ts +7 -0
  170. package/dist/i18n/defaults.js +7 -0
  171. package/dist/i18n/format.d.ts +84 -0
  172. package/dist/i18n/format.js +169 -0
  173. package/dist/i18n/index.d.ts +6 -0
  174. package/dist/i18n/index.js +8 -0
  175. package/dist/i18n/locales/ru-formatters.d.ts +3 -0
  176. package/dist/i18n/locales/ru-formatters.js +22 -0
  177. package/dist/i18n/locales/ru.d.ts +5 -0
  178. package/dist/i18n/locales/ru.js +185 -0
  179. package/dist/i18n/plural.d.ts +20 -0
  180. package/dist/i18n/plural.js +26 -0
  181. package/dist/i18n/provider.d.ts +36 -0
  182. package/dist/i18n/provider.js +39 -0
  183. package/dist/i18n/rich.d.ts +14 -0
  184. package/dist/i18n/rich.js +44 -0
  185. package/dist/i18n/ru-plural.d.ts +3 -0
  186. package/dist/i18n/ru-plural.js +4 -0
  187. package/dist/i18n/strings/alert-dialog.d.ts +5 -0
  188. package/dist/i18n/strings/alert-dialog.js +5 -0
  189. package/dist/i18n/strings/block-stack.d.ts +17 -0
  190. package/dist/i18n/strings/block-stack.js +11 -0
  191. package/dist/i18n/strings/code-block.d.ts +18 -0
  192. package/dist/i18n/strings/code-block.js +13 -0
  193. package/dist/i18n/strings/combobox.d.ts +12 -0
  194. package/dist/i18n/strings/combobox.js +10 -0
  195. package/dist/i18n/strings/command-palette.d.ts +17 -0
  196. package/dist/i18n/strings/command-palette.js +11 -0
  197. package/dist/i18n/strings/diff-view.d.ts +92 -0
  198. package/dist/i18n/strings/diff-view.js +68 -0
  199. package/dist/i18n/strings/image-viewer.d.ts +43 -0
  200. package/dist/i18n/strings/image-viewer.js +32 -0
  201. package/dist/i18n/strings/index.d.ts +314 -0
  202. package/dist/i18n/strings/index.js +43 -0
  203. package/dist/i18n/strings/kbd.d.ts +42 -0
  204. package/dist/i18n/strings/kbd.js +39 -0
  205. package/dist/i18n/strings/markdown.d.ts +23 -0
  206. package/dist/i18n/strings/markdown.js +18 -0
  207. package/dist/i18n/strings/money.d.ts +7 -0
  208. package/dist/i18n/strings/money.js +5 -0
  209. package/dist/i18n/strings/primitives.d.ts +25 -0
  210. package/dist/i18n/strings/primitives.js +18 -0
  211. package/dist/i18n/strings/progress.d.ts +4 -0
  212. package/dist/i18n/strings/progress.js +4 -0
  213. package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
  214. package/dist/i18n/strings/shortcut-recorder.js +11 -0
  215. package/dist/i18n/strings/time.d.ts +41 -0
  216. package/dist/i18n/strings/time.js +20 -0
  217. package/dist/i18n/strings/toast.d.ts +6 -0
  218. package/dist/i18n/strings/toast.js +6 -0
  219. package/dist/i18n/time-zones.d.ts +20 -0
  220. package/dist/i18n/time-zones.js +64 -0
  221. package/dist/index.d.ts +84 -0
  222. package/dist/index.js +88 -0
  223. package/dist/lib/ansi.d.ts +55 -0
  224. package/dist/lib/ansi.js +438 -0
  225. package/dist/lib/clock.d.ts +32 -0
  226. package/dist/lib/clock.js +106 -0
  227. package/dist/lib/cn.d.ts +12 -0
  228. package/dist/lib/cn.js +28 -0
  229. package/dist/lib/control-props.d.ts +21 -0
  230. package/dist/lib/control-props.js +14 -0
  231. package/dist/lib/direction.d.ts +13 -0
  232. package/dist/lib/direction.js +25 -0
  233. package/dist/lib/image-size.d.ts +12 -0
  234. package/dist/lib/image-size.js +66 -0
  235. package/dist/lib/keycaps.d.ts +21 -0
  236. package/dist/lib/keycaps.js +62 -0
  237. package/dist/lib/keyed.d.ts +9 -0
  238. package/dist/lib/keyed.js +14 -0
  239. package/dist/lib/keys.d.ts +49 -0
  240. package/dist/lib/keys.js +159 -0
  241. package/dist/lib/kit-motion.d.ts +9 -0
  242. package/dist/lib/kit-motion.js +12 -0
  243. package/dist/lib/link-check-dialog.d.ts +11 -0
  244. package/dist/lib/link-check-dialog.js +18 -0
  245. package/dist/lib/motion-features.d.ts +1 -0
  246. package/dist/lib/motion-features.js +4 -0
  247. package/dist/lib/motion-provider.d.ts +5 -0
  248. package/dist/lib/motion-provider.js +8 -0
  249. package/dist/lib/motion.d.ts +39 -0
  250. package/dist/lib/motion.js +49 -0
  251. package/dist/lib/number-format.d.ts +114 -0
  252. package/dist/lib/number-format.js +313 -0
  253. package/dist/lib/patch.d.ts +72 -0
  254. package/dist/lib/patch.js +344 -0
  255. package/dist/lib/path.d.ts +34 -0
  256. package/dist/lib/path.js +85 -0
  257. package/dist/lib/search.d.ts +10 -0
  258. package/dist/lib/search.js +20 -0
  259. package/dist/lib/shiki-themes.d.ts +7 -0
  260. package/dist/lib/shiki-themes.js +115 -0
  261. package/dist/lib/shiki.d.ts +101 -0
  262. package/dist/lib/shiki.js +353 -0
  263. package/dist/lib/shortcut-context.d.ts +23 -0
  264. package/dist/lib/shortcut-context.js +29 -0
  265. package/dist/lib/text.d.ts +56 -0
  266. package/dist/lib/text.js +149 -0
  267. package/dist/lib/theme-scope.d.ts +18 -0
  268. package/dist/lib/theme-scope.js +24 -0
  269. package/dist/lib/theme.d.ts +40 -0
  270. package/dist/lib/theme.js +96 -0
  271. package/dist/lib/tree-keys.d.ts +72 -0
  272. package/dist/lib/tree-keys.js +126 -0
  273. package/dist/lib/url.d.ts +17 -0
  274. package/dist/lib/url.js +38 -0
  275. package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
  276. package/dist/lib/use-copy-to-clipboard.js +40 -0
  277. package/dist/lib/use-delayed-loading.d.ts +11 -0
  278. package/dist/lib/use-delayed-loading.js +31 -0
  279. package/dist/lib/use-file-picker.d.ts +26 -0
  280. package/dist/lib/use-file-picker.js +43 -0
  281. package/dist/lib/use-hydrated.d.ts +6 -0
  282. package/dist/lib/use-hydrated.js +11 -0
  283. package/dist/lib/use-latest.d.ts +12 -0
  284. package/dist/lib/use-latest.js +22 -0
  285. package/dist/lib/use-resolved-theme.d.ts +12 -0
  286. package/dist/lib/use-resolved-theme.js +25 -0
  287. package/dist/styles/base.css +393 -0
  288. package/dist/styles/code.css +35 -0
  289. package/dist/styles/fonts.css +12 -0
  290. package/dist/styles/index.css +13 -0
  291. package/dist/styles/tokens.gen.css +494 -0
  292. package/package.json +208 -4
@@ -0,0 +1,307 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ArrowUpRight, Check } from "lucide-react";
4
+ import { createContext, memo, use, useMemo, } from "react";
5
+ import ReactMarkdown, { defaultUrlTransform, } from "react-markdown";
6
+ import remarkGfm from "remark-gfm";
7
+ import remend, { INCOMPLETE_IMAGE_PLACEHOLDER } from "remend";
8
+ import { useUiStrings } from "../i18n/provider.js";
9
+ import { cn } from "../lib/cn.js";
10
+ import { webUrl } from "../lib/url.js";
11
+ import { Code, CodeBlock } from "./code-block.js";
12
+ import { isImagePath, LocalImageThumbnail, localImagePath, useImageSources, useLocalImageScope, } from "./image-sources.js";
13
+ import { ImageThumbnail } from "./image-thumbnail.js";
14
+ import { ExternalLinkCheck, linkShowsAddress, useLinkPolicy } from "./link-policy.js";
15
+ import { RemoteImageCard, RemoteImageGate, remoteImageHost } from "./remote-image-gate.js";
16
+ import { TextLink } from "./text-link.js";
17
+ /* ------------------------------------------------------------------------------------------ */
18
+ /* Block splitting */
19
+ /* ------------------------------------------------------------------------------------------ */
20
+ const FENCE = /^(\s*)(`{3,}|~{3,})/;
21
+ const HEADING = /^ {0,3}#{1,6}(?:\s|$)/;
22
+ const LIST_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
23
+ /** Reference links / footnotes resolve document-wide, so such messages render as one block. */
24
+ const DOCUMENT_SCOPED = /^ {0,3}\[[^\]]+\]:\s*\S|\[\^[^\]]+\]/m;
25
+ /** Line-by-line state machine behind {@link splitMarkdownBlocks}. */
26
+ class BlockSplitter {
27
+ blocks = [];
28
+ current = [];
29
+ isList = false;
30
+ blankRun = 0;
31
+ fence = null;
32
+ push(line) {
33
+ if (this.fence)
34
+ this.inFence(line);
35
+ else if (line.trim() === "") {
36
+ if (this.current.length > 0)
37
+ this.blankRun += 1;
38
+ }
39
+ else
40
+ this.content(line);
41
+ }
42
+ end() {
43
+ this.flush();
44
+ return this.blocks;
45
+ }
46
+ inFence(line) {
47
+ this.current.push(line);
48
+ const marker = FENCE.exec(line)?.[2];
49
+ const fence = this.fence;
50
+ if (fence && marker?.[0] === fence.char && marker.length >= fence.size && line.trim() === marker) {
51
+ this.fence = null;
52
+ // A top-level fence is a block of its own; whatever follows starts fresh.
53
+ if (!fence.nested)
54
+ this.flush();
55
+ }
56
+ }
57
+ content(line) {
58
+ const indented = /^\s/.test(line);
59
+ const continuesList = this.isList && (indented || LIST_ITEM.test(line));
60
+ const marker = FENCE.exec(line)?.[2];
61
+ if (marker !== undefined) {
62
+ const nested = continuesList && indented;
63
+ if (nested)
64
+ this.append(line);
65
+ else
66
+ this.start(line);
67
+ this.fence = { char: marker.charAt(0), size: marker.length, nested };
68
+ }
69
+ else if (HEADING.test(line)) {
70
+ this.start(line);
71
+ this.flush();
72
+ }
73
+ else if (this.current.length === 0 || (this.blankRun > 0 && !continuesList)) {
74
+ this.start(line);
75
+ }
76
+ else {
77
+ this.append(line);
78
+ }
79
+ }
80
+ /** Continue the current block, keeping any blank lines that separated it (loose lists). */
81
+ append(line) {
82
+ for (let i = 0; i < this.blankRun; i++)
83
+ this.current.push("");
84
+ this.current.push(line);
85
+ this.blankRun = 0;
86
+ }
87
+ start(line) {
88
+ this.flush();
89
+ this.isList = LIST_ITEM.test(line);
90
+ this.current.push(line);
91
+ }
92
+ flush() {
93
+ if (this.current.length > 0)
94
+ this.blocks.push(this.current.join("\n"));
95
+ this.current = [];
96
+ this.blankRun = 0;
97
+ }
98
+ }
99
+ /**
100
+ * Splits markdown into top-level blocks (paragraphs, headings, lists, fences, tables, …) so each can
101
+ * be memoized: while streaming only the last block re-parses. Splits at blank lines outside fences,
102
+ * keeps loose lists and indented continuations together, and isolates headings and fences.
103
+ */
104
+ export function splitMarkdownBlocks(source) {
105
+ if (source.trim() === "")
106
+ return [];
107
+ if (DOCUMENT_SCOPED.test(source))
108
+ return [source];
109
+ const splitter = new BlockSplitter();
110
+ for (const line of source.split("\n"))
111
+ splitter.push(line);
112
+ return splitter.end();
113
+ }
114
+ /* ------------------------------------------------------------------------------------------ */
115
+ /* Streaming caret (rehype) */
116
+ /* ------------------------------------------------------------------------------------------ */
117
+ /** Elements whose last child may host the caret; anything else (inline) receives it as a sibling. */
118
+ const CARET_CONTAINERS = new Set(["p", "li", "ul", "ol", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"]);
119
+ /** No caret after these — code blocks show their own streaming state. */
120
+ const CARET_SKIP = new Set(["pre", "table", "hr", "img"]);
121
+ const isElement = (node) => node !== undefined && node.type === "element";
122
+ const isMeaningful = (node) => node.type === "element" || (node.type === "text" && node.value.trim() !== "");
123
+ /** Appends a blinking caret right after the last rendered character. */
124
+ function rehypeCaret() {
125
+ return (tree) => {
126
+ let parent = tree;
127
+ for (;;) {
128
+ const last = parent.children.findLast(isMeaningful);
129
+ if (!isElement(last))
130
+ break;
131
+ if (CARET_SKIP.has(last.tagName))
132
+ return;
133
+ if (!CARET_CONTAINERS.has(last.tagName))
134
+ break;
135
+ parent = last;
136
+ }
137
+ if (parent.type === "root")
138
+ return;
139
+ parent.children.push({
140
+ type: "element",
141
+ tagName: "span",
142
+ properties: { className: ["stream-caret", "ms-0.5"], ariaHidden: "true", dataSlot: "markdown-caret" },
143
+ children: [],
144
+ });
145
+ };
146
+ }
147
+ /* ------------------------------------------------------------------------------------------ */
148
+ /* Element map */
149
+ /* ------------------------------------------------------------------------------------------ */
150
+ const StreamingBlockContext = createContext(false);
151
+ const CodeActionsContext = createContext(undefined);
152
+ const INCOMPLETE_LINK = "streamdown:incomplete-link";
153
+ /** An image inline in the message, safe in `<img>` (no scripts, nothing fetched). */
154
+ const INLINE_IMAGE = /^data:image\/(?:png|jpeg|gif|webp|svg\+xml)[;,]/iu;
155
+ /**
156
+ * Keep remend's placeholders (rendered as inert stand-ins) — sanitize everything else. An image may
157
+ * also be inline data or a `file:` URL: `MarkdownImage` decides how each loads, if at all.
158
+ */
159
+ const urlTransform = (url, key, node) => {
160
+ if (url.startsWith("streamdown:"))
161
+ return url;
162
+ if (key === "src" && node.tagName === "img" && (INLINE_IMAGE.test(url) || /^file:/iu.test(url)))
163
+ return url;
164
+ return defaultUrlTransform(url);
165
+ };
166
+ const isExternal = (href) => webUrl(href) !== undefined;
167
+ function hastText(node) {
168
+ if (node.type === "text")
169
+ return node.value;
170
+ if (node.type === "element")
171
+ return node.children.map(hastText).join("");
172
+ return "";
173
+ }
174
+ function fenceLanguage(code) {
175
+ const classes = code.properties["className"];
176
+ if (!Array.isArray(classes))
177
+ return undefined;
178
+ for (const c of classes) {
179
+ if (typeof c === "string" && c.startsWith("language-"))
180
+ return c.slice("language-".length);
181
+ }
182
+ return undefined;
183
+ }
184
+ function MarkdownPre({ node }) {
185
+ const streaming = use(StreamingBlockContext);
186
+ const codeActions = use(CodeActionsContext);
187
+ const code = node?.children.find(isElement);
188
+ if (!code)
189
+ return null;
190
+ const text = hastText(code);
191
+ const lang = fenceLanguage(code);
192
+ // A block still arriving has no actions: what they act on isn't final yet.
193
+ const actions = streaming || codeActions === undefined
194
+ ? undefined
195
+ : codeActions({ code: text.endsWith("\n") ? text.slice(0, -1) : text, lang });
196
+ const acted = actions !== undefined && actions !== null;
197
+ return _jsx(CodeBlock, { code: text, lang: lang, streaming: streaming, actions: actions, wrap: acted });
198
+ }
199
+ function MarkdownLink({ node, href = "", children, className, ...props }) {
200
+ const policy = useLinkPolicy();
201
+ if (href === INCOMPLETE_LINK) {
202
+ return (_jsx(TextLink, { render: _jsx("span", {}), className: className, children: children }));
203
+ }
204
+ const external = isExternal(href);
205
+ const link = (onCheck) => (_jsxs(TextLink, { href: href, className: className, ...(external && { target: "_blank", rel: "noopener noreferrer" }), ...props, ...(onCheck !== undefined && {
206
+ onClick: (event) => {
207
+ event.preventDefault();
208
+ onCheck();
209
+ },
210
+ }), children: [children, external && (_jsx(ArrowUpRight, { "aria-hidden": true, className: "ms-px inline size-[0.85em] -translate-y-px align-baseline text-fg-subtle" }))] }));
211
+ // Under `check`, a link whose words aren't its address shows the address before it opens.
212
+ const text = node === undefined ? "" : hastText(node);
213
+ if (external && policy === "check" && !linkShowsAddress(text, href)) {
214
+ return _jsx(ExternalLinkCheck, { href: href, trigger: link });
215
+ }
216
+ return link();
217
+ }
218
+ /** Remote hosts whose images load on render (`Markdown.allowImageHosts`); everything else waits for a click. */
219
+ const ImageHostsContext = createContext(new Set());
220
+ const FAILED = { kind: "failed" };
221
+ function MarkdownImage({ node: _node, src, alt = "", className, ...props }) {
222
+ const allowed = use(ImageHostsContext);
223
+ const sources = useImageSources();
224
+ const scope = useLocalImageScope();
225
+ if (src === INCOMPLETE_IMAGE_PLACEHOLDER || src === undefined || src === "") {
226
+ return _jsx("span", { className: "block h-40 w-64 max-w-full animate-pulse-soft rounded-lg bg-bg-muted" });
227
+ }
228
+ const host = remoteImageHost(src);
229
+ if (host !== undefined && sources.remote === "open" && !allowed.has(host)) {
230
+ return _jsx(RemoteImageCard, { src: src, alt: alt, host: host });
231
+ }
232
+ if (host === undefined && INLINE_IMAGE.test(src)) {
233
+ return _jsx(ImageThumbnail, { src: src, alt: alt });
234
+ }
235
+ if (host === undefined && sources.useLocal !== undefined) {
236
+ const path = localImagePath(src);
237
+ // Only a picture's path is read: a message can't make the surface read any other file.
238
+ return isImagePath(path) ? (_jsx(LocalImageThumbnail, { path: path, alt: alt, scope: scope, useLocal: sources.useLocal })) : (_jsx(ImageThumbnail, { src: undefined, alt: alt, problem: FAILED }));
239
+ }
240
+ // A file on this computer, where nothing reads one: it can't show (a page can't load `file:`).
241
+ if (/^file:/iu.test(src))
242
+ return _jsx(ImageThumbnail, { src: undefined, alt: alt, problem: FAILED });
243
+ const image = (_jsx("img", { src: src, alt: alt, loading: "lazy", decoding: "async", className: cn("inline-block max-w-full rounded-lg border border-border-subtle", className), ...props }));
244
+ return (_jsx(RemoteImageGate, { src: src, alt: alt, allowHosts: allowed, children: image }));
245
+ }
246
+ function MarkdownInput({ node: _node, type, checked, className, ...props }) {
247
+ const strings = useUiStrings().markdown;
248
+ if (type !== "checkbox")
249
+ return _jsx("input", { type: type, className: className, ...props });
250
+ return (_jsxs("span", { className: "absolute top-1 start-0 grid size-4 place-items-center", children: [_jsx("input", { type: "checkbox", checked: checked === true, readOnly: true, disabled: true, "aria-label": checked === true ? strings.taskDone : strings.taskTodo, className: cn("peer col-start-1 row-start-1 m-0 size-4 appearance-none rounded-xs border border-border-strong bg-surface shadow-xs", "checked:border-primary checked:bg-primary") }), _jsx(Check, { "aria-hidden": true, strokeWidth: 3, className: "pointer-events-none col-start-1 row-start-1 size-3 text-primary-fg opacity-0 peer-checked:opacity-100" })] }));
251
+ }
252
+ function MarkdownTable({ node: _node, className, ...props }) {
253
+ return (_jsx("div", { dir: "auto", "data-slot": "markdown-table", className: "overflow-x-auto rounded-lg border border-border-subtle", children: _jsx("table", { className: cn("w-full border-collapse text-sm tabular", "[&_tr:last-child>td]:border-b-0 [&_tr>*+*]:border-s [&_tr>*+*]:border-border-subtle", className), ...props }) }));
254
+ }
255
+ const heading = (Tag, size) => function Heading({ node: _node, className, ...props }) {
256
+ return _jsx(Tag, { dir: "auto", className: cn("font-semibold text-fg", size, className), ...props });
257
+ };
258
+ /**
259
+ * Each block takes the direction of the text in it (`dir="auto"`): the text is a model's or a
260
+ * person's, so an English answer reads left to right in a right-to-left interface, and a paragraph
261
+ * in another script beside it reads its own way. Code blocks stay left to right.
262
+ */
263
+ const components = {
264
+ h1: heading("h1", "text-xl"),
265
+ h2: heading("h2", "text-md"),
266
+ h3: heading("h3", "text-md"),
267
+ h4: heading("h4", "text-base"),
268
+ h5: heading("h5", "text-base"),
269
+ h6: heading("h6", "text-base text-fg-muted"),
270
+ p: ({ node: _node, ...props }) => _jsx("p", { dir: "auto", ...props }),
271
+ a: MarkdownLink,
272
+ img: MarkdownImage,
273
+ input: MarkdownInput,
274
+ table: MarkdownTable,
275
+ pre: MarkdownPre,
276
+ code: ({ node: _node, ...props }) => _jsx(Code, { ...props }),
277
+ strong: ({ node: _node, ...props }) => _jsx("strong", { className: "font-semibold text-fg", ...props }),
278
+ del: ({ node: _node, ...props }) => _jsx("del", { className: "text-fg-subtle decoration-fg-faint", ...props }),
279
+ ul: ({ node: _node, className, ...props }) => (_jsx("ul", { dir: "auto", className: cn("space-y-1.5 ps-[1.375em] marker:text-fg-faint", className?.includes("contains-task-list") ? "list-none ps-0" : "list-disc", className), ...props })),
280
+ ol: ({ node: _node, className, ...props }) => (_jsx("ol", { dir: "auto", className: cn("list-decimal space-y-1.5 ps-[1.5em] marker:text-fg-subtle marker:tabular", className), ...props })),
281
+ li: ({ node: _node, className, ...props }) => (_jsx("li", { dir: "auto", className: cn("ps-1 [&>p+p]:mt-2 [&>:is(ul,ol)]:mt-1.5", className?.includes("task-list-item") && "relative ps-6 has-[input:checked]:text-fg-muted", className), ...props })),
282
+ blockquote: ({ node: _node, className, ...props }) => (_jsx("blockquote", { dir: "auto", className: cn("border-s-2 border-border-strong ps-4 text-fg-muted [&>*+*]:mt-2", className), ...props })),
283
+ hr: ({ node: _node, ...props }) => (_jsx("hr", { className: "my-6 border-0 border-t border-border-subtle", ...props })),
284
+ th: ({ node: _node, className, ...props }) => (_jsx("th", { className: cn("border-b border-border-subtle bg-bg-subtle px-3 py-2 text-start font-medium text-fg", className), ...props })),
285
+ td: ({ node: _node, className, ...props }) => (_jsx("td", { className: cn("border-b border-border-subtle px-3 py-2 align-top", className), ...props })),
286
+ };
287
+ /* ------------------------------------------------------------------------------------------ */
288
+ /* Markdown */
289
+ /* ------------------------------------------------------------------------------------------ */
290
+ const REMARK_PLUGINS = [remarkGfm];
291
+ const REHYPE_NONE = [];
292
+ const REHYPE_CARET = [rehypeCaret];
293
+ const MarkdownBlock = memo(function MarkdownBlock({ source, streaming, caret }) {
294
+ return (_jsx(StreamingBlockContext, { value: streaming, children: _jsx(ReactMarkdown, { remarkPlugins: REMARK_PLUGINS, rehypePlugins: caret ? REHYPE_CARET : REHYPE_NONE, components: components, urlTransform: urlTransform, children: source }) }));
295
+ });
296
+ /** Assistant-message renderer: GFM, Shiki code blocks, calm prose rhythm, streaming-safe. */
297
+ export function Markdown({ children, streaming = false, measure = false, allowImageHosts, codeActions, className, ...props }) {
298
+ const blocks = useMemo(() => splitMarkdownBlocks(children), [children]);
299
+ const hostKey = allowImageHosts?.join("\n") ?? "";
300
+ const hosts = useMemo(() => new Set(hostKey === "" ? [] : hostKey.split("\n")), [hostKey]);
301
+ return (_jsxs("div", { "data-slot": "markdown", "data-streaming": streaming || undefined, className: cn("min-w-0 text-md text-fg [overflow-wrap:break-word]", "[&>*+*]:mt-4 [&>*+:is(h1,h2)]:mt-8 [&>*+:is(h3,h4,h5,h6)]:mt-6 [&>:is(h1,h2,h3,h4,h5,h6)+*]:mt-2", measure && "max-w-[72ch]", className), ...props, children: [_jsx(ImageHostsContext, { value: hosts, children: _jsx(CodeActionsContext, { value: codeActions, children: blocks.map((block, index) => {
302
+ const live = streaming && index === blocks.length - 1;
303
+ return (_jsx(MarkdownBlock
304
+ // biome-ignore lint/suspicious/noArrayIndexKey: blocks only append or change in place, so position is identity
305
+ , { source: live ? remend(block) : block, streaming: live, caret: live }, index));
306
+ }) }) }), streaming && blocks.length === 0 && _jsx("span", { "aria-hidden": true, className: "stream-caret" })] }));
307
+ }
@@ -0,0 +1,132 @@
1
+ import { Menu as BaseMenu } from "@base-ui/react/menu";
2
+ import type { ComponentProps, ReactNode } from "react";
3
+ import { type VariantProps } from "../lib/cn.js";
4
+ /** Positioner wrapper: stacking only — the popup owns all visuals. */
5
+ export declare const menuPositionerClass = "z-(--z-overlay) outline-none";
6
+ /** The floating surface for menus and context menus. */
7
+ export declare const menuPopupClass: string;
8
+ export declare const menuItemStyles: import("tailwind-variants").TVReturnType<{
9
+ variant: {
10
+ default: "";
11
+ /** Irreversible or data-losing actions. Tinted only on highlight so menus stay calm. */
12
+ destructive: "text-danger-fg data-highlighted:bg-danger-subtle data-highlighted:text-danger-fg [&_[data-slot=menu-item-icon]]:text-danger-fg";
13
+ };
14
+ /** Reserve the leading indicator column so plain items align with checkbox/radio items. */
15
+ inset: {
16
+ true: "ps-8";
17
+ };
18
+ }, undefined, string[], {
19
+ variant: {
20
+ default: "";
21
+ /** Irreversible or data-losing actions. Tinted only on highlight so menus stay calm. */
22
+ destructive: "text-danger-fg data-highlighted:bg-danger-subtle data-highlighted:text-danger-fg [&_[data-slot=menu-item-icon]]:text-danger-fg";
23
+ };
24
+ /** Reserve the leading indicator column so plain items align with checkbox/radio items. */
25
+ inset: {
26
+ true: "ps-8";
27
+ };
28
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
29
+ variant: {
30
+ default: "";
31
+ /** Irreversible or data-losing actions. Tinted only on highlight so menus stay calm. */
32
+ destructive: "text-danger-fg data-highlighted:bg-danger-subtle data-highlighted:text-danger-fg [&_[data-slot=menu-item-icon]]:text-danger-fg";
33
+ };
34
+ /** Reserve the leading indicator column so plain items align with checkbox/radio items. */
35
+ inset: {
36
+ true: "ps-8";
37
+ };
38
+ }, undefined>>;
39
+ export type MenuItemVariants = VariantProps<typeof menuItemStyles>;
40
+ export declare const menuGroupLabelClass = "flex h-7 select-none items-center px-2 text-xs font-medium text-fg-subtle";
41
+ export declare const menuSeparatorClass = "-mx-1 my-1 h-px bg-border-subtle";
42
+ /** Shared content layout for every kind of item: icon · label/description · shortcut/trailing. */
43
+ export declare function MenuItemContent({ icon, description, shortcut, trailing, children, }: {
44
+ icon?: ReactNode;
45
+ description?: ReactNode;
46
+ shortcut?: readonly string[] | undefined;
47
+ trailing?: ReactNode;
48
+ children?: ReactNode;
49
+ }): import("react").JSX.Element;
50
+ interface ItemSlots {
51
+ /** Leading icon (lucide element). */
52
+ icon?: ReactNode;
53
+ /** Secondary line under the label (e.g. model context window). */
54
+ description?: ReactNode;
55
+ /** The action it triggers: its keys from the host's keymap show as a trailing hint (prefer it). */
56
+ action?: string;
57
+ /** Logical keys rendered as a trailing hint when there is no `action`, e.g. `["mod", "shift", "f"]`. */
58
+ shortcut?: readonly string[];
59
+ /** Any trailing adornment (badge, count…). */
60
+ trailing?: ReactNode;
61
+ }
62
+ export declare const MenuRoot: typeof BaseMenu.Root;
63
+ export declare const MenuTrigger: BaseMenu.Trigger;
64
+ export declare const MenuGroup: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").MenuGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
65
+ export declare const MenuRadioGroup: import("react").NamedExoticComponent<Omit<import("@base-ui/react").MenuRadioGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
66
+ export declare const MenuSubmenuRoot: typeof BaseMenu.SubmenuRoot;
67
+ export interface MenuContentProps extends ComponentProps<typeof BaseMenu.Popup> {
68
+ side?: ComponentProps<typeof BaseMenu.Positioner>["side"];
69
+ align?: ComponentProps<typeof BaseMenu.Positioner>["align"];
70
+ sideOffset?: number;
71
+ alignOffset?: number;
72
+ /** Extra props for the positioner (collision handling, anchor…). */
73
+ positionerProps?: Omit<ComponentProps<typeof BaseMenu.Positioner>, "side" | "align" | "sideOffset" | "alignOffset">;
74
+ /**
75
+ * Where the popup mounts (default: the body). Inside a container-query container, its items can
76
+ * follow that container's width, e.g. an overflow menu showing what its bar hides.
77
+ */
78
+ container?: ComponentProps<typeof BaseMenu.Portal>["container"];
79
+ }
80
+ /** Portal + positioner + popup in one. */
81
+ export declare function MenuContent({ side, align, sideOffset, alignOffset, positionerProps, container, className, ...props }: MenuContentProps): import("react").JSX.Element;
82
+ /**
83
+ * Popup for a nested menu. Overlaps the parent by its padding so the first item lines up with
84
+ * the trigger row.
85
+ */
86
+ export declare function MenuSubmenuContent({ sideOffset, alignOffset, positionerProps, className, ...props }: Omit<MenuContentProps, "side" | "align">): import("react").JSX.Element;
87
+ export interface MenuItemProps extends Omit<ComponentProps<typeof BaseMenu.Item>, "children">, ItemSlots, MenuItemVariants {
88
+ children?: ReactNode;
89
+ }
90
+ export declare function MenuItem({ icon, description, action, shortcut: given, trailing, variant, inset, className, children, ...props }: MenuItemProps): import("react").JSX.Element;
91
+ export interface MenuLinkItemProps extends Omit<ComponentProps<typeof BaseMenu.LinkItem>, "children">, Omit<ItemSlots, "description">, MenuItemVariants {
92
+ children?: ReactNode;
93
+ }
94
+ /** Navigates (renders an `<a>`); otherwise identical to MenuItem. */
95
+ export declare function MenuLinkItem({ icon, action, shortcut: given, trailing, variant, inset, className, children, ...props }: MenuLinkItemProps): import("react").JSX.Element;
96
+ export interface MenuCheckboxItemProps extends Omit<ComponentProps<typeof BaseMenu.CheckboxItem>, "children">, Omit<ItemSlots, "icon"> {
97
+ children?: ReactNode;
98
+ }
99
+ /** Toggle with a leading check. Stays open on click so several options can be flipped. */
100
+ export declare function MenuCheckboxItem({ description, action, shortcut: given, trailing, closeOnClick, className, children, ...props }: MenuCheckboxItemProps): import("react").JSX.Element;
101
+ export interface MenuRadioItemProps extends Omit<ComponentProps<typeof BaseMenu.RadioItem>, "children">, ItemSlots {
102
+ children?: ReactNode;
103
+ }
104
+ /**
105
+ * One choice of a `MenuRadioGroup`. The check sits on the trailing edge when an `icon` is given
106
+ * (model pickers), otherwise in the leading indicator column.
107
+ */
108
+ export declare function MenuRadioItem({ icon, description, action, shortcut: given, trailing, className, children, ...props }: MenuRadioItemProps): import("react").JSX.Element;
109
+ export interface MenuSubmenuTriggerProps extends Omit<ComponentProps<typeof BaseMenu.SubmenuTrigger>, "children">, Pick<ItemSlots, "icon" | "description">, Pick<MenuItemVariants, "inset"> {
110
+ children?: ReactNode;
111
+ }
112
+ export declare function MenuSubmenuTrigger({ icon, description, inset, className, children, ...props }: MenuSubmenuTriggerProps): import("react").JSX.Element;
113
+ export declare function MenuGroupLabel({ className, ...props }: ComponentProps<typeof BaseMenu.GroupLabel>): import("react").JSX.Element;
114
+ export declare function MenuSeparator({ className, ...props }: ComponentProps<typeof BaseMenu.Separator>): import("react").JSX.Element;
115
+ export interface MenuProps {
116
+ /** The trigger element; must accept a ref and DOM props (e.g. <Button>). */
117
+ trigger: ComponentProps<typeof BaseMenu.Trigger>["render"];
118
+ children: ReactNode;
119
+ side?: MenuContentProps["side"];
120
+ align?: MenuContentProps["align"];
121
+ open?: boolean;
122
+ onOpenChange?: ComponentProps<typeof BaseMenu.Root>["onOpenChange"];
123
+ /** Popup class (e.g. a width). */
124
+ className?: string;
125
+ /** Where the popup mounts (`MenuContent`'s `container`). */
126
+ container?: MenuContentProps["container"];
127
+ /** Positioner props (`MenuContent`'s `positionerProps`). */
128
+ positionerProps?: MenuContentProps["positionerProps"];
129
+ }
130
+ /** Convenience wrapper: `<Menu trigger={<Button>Options</Button>}>…items…</Menu>`. */
131
+ export declare function Menu({ trigger, children, side, align, open, onOpenChange, className, container, positionerProps, }: MenuProps): import("react").JSX.Element;
132
+ export {};
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Menu as BaseMenu } from "@base-ui/react/menu";
4
+ import { Check, ChevronRight } from "lucide-react";
5
+ import { cn, tv, withBase } from "../lib/cn.js";
6
+ import { keyShortcutsProps } from "../lib/keys.js";
7
+ import { useShortcut } from "../lib/shortcut-context.js";
8
+ import { usePortalContainer } from "../lib/theme-scope.js";
9
+ import { ItemLabel } from "./item-label.js";
10
+ import { Shortcut } from "./kbd.js";
11
+ /* ------------------------------------------------------------------------------------------ */
12
+ /* Shared styles — reused by ContextMenu (and any other list-of-actions popup). */
13
+ /* ------------------------------------------------------------------------------------------ */
14
+ /** Positioner wrapper: stacking only — the popup owns all visuals. */
15
+ export const menuPositionerClass = "z-(--z-overlay) outline-none";
16
+ /** The floating surface for menus and context menus. */
17
+ export const menuPopupClass = cn("popup-motion surface-raised min-w-48 max-w-80 rounded-xl p-1 text-fg outline-none", "max-h-(--available-height) overflow-y-auto overscroll-contain scrollbar-none");
18
+ export const menuItemStyles = tv({
19
+ base: [
20
+ "group/menu-item relative flex min-h-8 w-full cursor-default select-none items-center gap-2 rounded-md px-2",
21
+ "text-base text-fg outline-none",
22
+ "transition-[background-color,color] duration-(--duration-instant) ease-(--ease-standard)",
23
+ "data-highlighted:bg-hover data-popup-open:bg-hover",
24
+ "data-disabled:pointer-events-none data-disabled:opacity-45",
25
+ "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
26
+ ],
27
+ variants: {
28
+ variant: {
29
+ default: "",
30
+ /** Irreversible or data-losing actions. Tinted only on highlight so menus stay calm. */
31
+ destructive: "text-danger-fg data-highlighted:bg-danger-subtle data-highlighted:text-danger-fg [&_[data-slot=menu-item-icon]]:text-danger-fg",
32
+ },
33
+ /** Reserve the leading indicator column so plain items align with checkbox/radio items. */
34
+ inset: { true: "ps-8" },
35
+ },
36
+ defaultVariants: { variant: "default" },
37
+ });
38
+ export const menuGroupLabelClass = "flex h-7 select-none items-center px-2 text-xs font-medium text-fg-subtle";
39
+ export const menuSeparatorClass = "-mx-1 my-1 h-px bg-border-subtle";
40
+ const iconClass = "flex size-4 shrink-0 items-center justify-center text-fg-subtle transition-colors duration-(--duration-instant) group-data-highlighted/menu-item:text-fg-muted";
41
+ const indicatorClass = cn("flex size-4 items-center justify-center text-fg", "transition-[opacity,scale] duration-(--duration-spring-snappy) ease-(--ease-spring-snappy)", "data-starting-style:scale-50 data-starting-style:opacity-0 data-ending-style:scale-50 data-ending-style:opacity-0", "motion-reduce:transition-none");
42
+ /** Shared content layout for every kind of item: icon · label/description · shortcut/trailing. */
43
+ export function MenuItemContent({ icon, description, shortcut, trailing, children, }) {
44
+ return (_jsxs(_Fragment, { children: [icon !== undefined && (_jsx("span", { "data-slot": "menu-item-icon", "aria-hidden": true, className: iconClass, children: icon })), _jsx(ItemLabel, { description: description, children: children }), shortcut && _jsx(Shortcut, { keys: shortcut, bare: true, size: "md", className: "ms-auto shrink-0 ps-4" }), trailing] }));
45
+ }
46
+ /* ------------------------------------------------------------------------------------------ */
47
+ /* Menu */
48
+ /* ------------------------------------------------------------------------------------------ */
49
+ export const MenuRoot = BaseMenu.Root;
50
+ export const MenuTrigger = BaseMenu.Trigger;
51
+ export const MenuGroup = BaseMenu.Group;
52
+ export const MenuRadioGroup = BaseMenu.RadioGroup;
53
+ export const MenuSubmenuRoot = BaseMenu.SubmenuRoot;
54
+ /** Portal + positioner + popup in one. */
55
+ export function MenuContent({ side = "bottom", align = "start", sideOffset = 6, alignOffset = 0, positionerProps, container, className, ...props }) {
56
+ const scoped = usePortalContainer();
57
+ return (_jsx(BaseMenu.Portal, { container: container ?? scoped, children: _jsx(BaseMenu.Positioner, { side: side, align: align, sideOffset: sideOffset, alignOffset: alignOffset, collisionPadding: 8, className: menuPositionerClass, ...positionerProps, children: _jsx(BaseMenu.Popup, { "data-slot": "menu", className: withBase(menuPopupClass, className), ...props }) }) }));
58
+ }
59
+ /**
60
+ * Popup for a nested menu. Overlaps the parent by its padding so the first item lines up with
61
+ * the trigger row.
62
+ */
63
+ export function MenuSubmenuContent({ sideOffset = -2, alignOffset = -5, positionerProps, className, ...props }) {
64
+ const container = usePortalContainer();
65
+ return (_jsx(BaseMenu.Portal, { container: container, children: _jsx(BaseMenu.Positioner, { sideOffset: sideOffset, alignOffset: alignOffset, collisionPadding: 8, className: menuPositionerClass, ...positionerProps, children: _jsx(BaseMenu.Popup, { "data-slot": "menu-submenu", className: withBase(menuPopupClass, className), ...props }) }) }));
66
+ }
67
+ export function MenuItem({ icon, description, action, shortcut: given, trailing, variant, inset, className, children, ...props }) {
68
+ const shortcut = useShortcut(action) ?? given;
69
+ return (_jsx(BaseMenu.Item, { ...keyShortcutsProps(shortcut), "data-slot": "menu-item", "data-variant": variant ?? "default", className: withBase(menuItemStyles({ variant, inset }), className), ...props, children: _jsx(MenuItemContent, { icon: icon, description: description, shortcut: shortcut, trailing: trailing, children: children }) }));
70
+ }
71
+ /** Navigates (renders an `<a>`); otherwise identical to MenuItem. */
72
+ export function MenuLinkItem({ icon, action, shortcut: given, trailing, variant, inset, className, children, ...props }) {
73
+ const shortcut = useShortcut(action) ?? given;
74
+ return (_jsx(BaseMenu.LinkItem, { ...keyShortcutsProps(shortcut), "data-slot": "menu-item", className: withBase(menuItemStyles({ variant, inset }), className), ...props, children: _jsx(MenuItemContent, { icon: icon, shortcut: shortcut, trailing: trailing, children: children }) }));
75
+ }
76
+ /** Toggle with a leading check. Stays open on click so several options can be flipped. */
77
+ export function MenuCheckboxItem({ description, action, shortcut: given, trailing, closeOnClick = false, className, children, ...props }) {
78
+ const shortcut = useShortcut(action) ?? given;
79
+ return (_jsxs(BaseMenu.CheckboxItem, { ...keyShortcutsProps(shortcut), "data-slot": "menu-checkbox-item", closeOnClick: closeOnClick, className: withBase(menuItemStyles(), className), ...props, children: [_jsx("span", { "aria-hidden": true, className: "flex size-4 shrink-0 items-center justify-center", children: _jsx(BaseMenu.CheckboxItemIndicator, { className: indicatorClass, children: _jsx(Check, { strokeWidth: 2.25, className: "size-3.5" }) }) }), _jsx(MenuItemContent, { description: description, shortcut: shortcut, trailing: trailing, children: children })] }));
80
+ }
81
+ /**
82
+ * One choice of a `MenuRadioGroup`. The check sits on the trailing edge when an `icon` is given
83
+ * (model pickers), otherwise in the leading indicator column.
84
+ */
85
+ export function MenuRadioItem({ icon, description, action, shortcut: given, trailing, className, children, ...props }) {
86
+ const shortcut = useShortcut(action) ?? given;
87
+ const indicator = (_jsx(BaseMenu.RadioItemIndicator, { className: indicatorClass, children: _jsx(Check, { strokeWidth: 2.25, className: "size-3.5" }) }));
88
+ return (_jsxs(BaseMenu.RadioItem, { ...keyShortcutsProps(shortcut), "data-slot": "menu-radio-item", className: withBase(menuItemStyles(), className), ...props, children: [icon === undefined && (_jsx("span", { "aria-hidden": true, className: "flex size-4 shrink-0 items-center justify-center", children: indicator })), _jsx(MenuItemContent, { icon: icon, description: description, shortcut: shortcut, trailing: icon === undefined ? (trailing) : (_jsxs(_Fragment, { children: [trailing, _jsx("span", { "aria-hidden": true, className: "ms-auto flex size-4 shrink-0 items-center justify-center", children: indicator })] })), children: children })] }));
89
+ }
90
+ export function MenuSubmenuTrigger({ icon, description, inset, className, children, ...props }) {
91
+ return (_jsx(BaseMenu.SubmenuTrigger, { "data-slot": "menu-submenu-trigger", className: withBase(menuItemStyles({ inset }), className), ...props, children: _jsx(MenuItemContent, { icon: icon, description: description, trailing: _jsx(ChevronRight, { "aria-hidden": true, className: "ms-auto size-3.5 text-fg-subtle" }), children: children }) }));
92
+ }
93
+ export function MenuGroupLabel({ className, ...props }) {
94
+ return (_jsx(BaseMenu.GroupLabel, { "data-slot": "menu-group-label", className: withBase(menuGroupLabelClass, className), ...props }));
95
+ }
96
+ export function MenuSeparator({ className, ...props }) {
97
+ return (_jsx(BaseMenu.Separator, { "data-slot": "menu-separator", className: withBase(menuSeparatorClass, className), ...props }));
98
+ }
99
+ /** Convenience wrapper: `<Menu trigger={<Button>Options</Button>}>…items…</Menu>`. */
100
+ export function Menu({ trigger, children, side, align, open, onOpenChange, className, container, positionerProps, }) {
101
+ return (_jsxs(BaseMenu.Root, { ...(open !== undefined && { open }), ...(onOpenChange && { onOpenChange }), children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(MenuContent, { ...(side && { side }), ...(align && { align }), ...(container !== undefined && { container }), ...(positionerProps !== undefined && { positionerProps }), className: className, children: children })] }));
102
+ }
@@ -0,0 +1,57 @@
1
+ import type { ComponentProps } from "react";
2
+ import { type VariantProps } from "../lib/cn.js";
3
+ declare const messageBubble: import("tailwind-variants").TVReturnType<{
4
+ /** Who wrote it: the person (a quiet fill), or an automated sender (a hairline). */
5
+ from: {
6
+ person: "bg-bg-muted";
7
+ agent: "border border-border bg-surface";
8
+ };
9
+ size: {
10
+ /** A demo or a preview of a conversation. */
11
+ sm: "px-3.5 py-2 text-sm";
12
+ md: "px-4 py-2.5 text-md";
13
+ };
14
+ /** Not sent yet (queued): faded until it goes. */
15
+ pending: {
16
+ true: "opacity-60";
17
+ };
18
+ }, undefined, "max-w-[85%] whitespace-pre-wrap rounded-2xl rounded-ee-md text-fg [overflow-wrap:anywhere]", {
19
+ /** Who wrote it: the person (a quiet fill), or an automated sender (a hairline). */
20
+ from: {
21
+ person: "bg-bg-muted";
22
+ agent: "border border-border bg-surface";
23
+ };
24
+ size: {
25
+ /** A demo or a preview of a conversation. */
26
+ sm: "px-3.5 py-2 text-sm";
27
+ md: "px-4 py-2.5 text-md";
28
+ };
29
+ /** Not sent yet (queued): faded until it goes. */
30
+ pending: {
31
+ true: "opacity-60";
32
+ };
33
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
34
+ /** Who wrote it: the person (a quiet fill), or an automated sender (a hairline). */
35
+ from: {
36
+ person: "bg-bg-muted";
37
+ agent: "border border-border bg-surface";
38
+ };
39
+ size: {
40
+ /** A demo or a preview of a conversation. */
41
+ sm: "px-3.5 py-2 text-sm";
42
+ md: "px-4 py-2.5 text-md";
43
+ };
44
+ /** Not sent yet (queued): faded until it goes. */
45
+ pending: {
46
+ true: "opacity-60";
47
+ };
48
+ }, undefined>>;
49
+ export interface MessageBubbleProps extends ComponentProps<"div">, VariantProps<typeof messageBubble> {
50
+ }
51
+ /**
52
+ * A message the person sent, as a conversation draws it: a rounded bubble at the end of the line
53
+ * with its tail at the bottom end. The surface alone: the host adds attachments, the time and actions
54
+ * around it.
55
+ */
56
+ export declare function MessageBubble({ from, size, pending, className, ...props }: MessageBubbleProps): import("react").JSX.Element;
57
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { tv } from "../lib/cn.js";
3
+ const messageBubble = tv({
4
+ base: "max-w-[85%] whitespace-pre-wrap rounded-2xl rounded-ee-md text-fg [overflow-wrap:anywhere]",
5
+ variants: {
6
+ /** Who wrote it: the person (a quiet fill), or an automated sender (a hairline). */
7
+ from: {
8
+ person: "bg-bg-muted",
9
+ agent: "border border-border bg-surface",
10
+ },
11
+ size: {
12
+ /** A demo or a preview of a conversation. */
13
+ sm: "px-3.5 py-2 text-sm",
14
+ md: "px-4 py-2.5 text-md",
15
+ },
16
+ /** Not sent yet (queued): faded until it goes. */
17
+ pending: { true: "opacity-60" },
18
+ },
19
+ defaultVariants: { from: "person", size: "md" },
20
+ });
21
+ /**
22
+ * A message the person sent, as a conversation draws it: a rounded bubble at the end of the line
23
+ * with its tail at the bottom end. The surface alone: the host adds attachments, the time and actions
24
+ * around it.
25
+ */
26
+ export function MessageBubble({ from, size, pending, className, ...props }) {
27
+ return (_jsx("div", { "data-slot": "message-bubble", className: messageBubble({ from, size, pending, className }), ...props }));
28
+ }